retrovim 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. package/zsh/zsh-patina.toml +2 -0
@@ -0,0 +1,1349 @@
1
+ --- *mini.operators* Text edit operators
2
+ ---
3
+ --- MIT License Copyright (c) 2023 Evgeni Chasnovski
4
+
5
+ --- Features:
6
+ --- - Operators:
7
+ --- - Evaluate text and replace with output.
8
+ --- - Exchange text regions.
9
+ --- - Multiply (duplicate) text.
10
+ --- - Replace text with register.
11
+ --- - Sort text.
12
+ ---
13
+ --- - Automated configurable mappings to operate on textobject, line, selection.
14
+ --- Can be disabled in favor of more control with |MiniOperators.make_mappings()|.
15
+ ---
16
+ --- - All operators support |[count]| and dot-repeat.
17
+ ---
18
+ --- See |MiniOperators-overview| and |MiniOperators.config| for more details.
19
+ ---
20
+ --- # Setup ~
21
+ ---
22
+ --- This module needs a setup with `require('mini.operators').setup({})` (replace
23
+ --- `{}` with your `config` table). It will create global Lua table `MiniOperators`
24
+ --- which you can use for scripting or manually (with `:lua MiniOperators.*`).
25
+ ---
26
+ --- See |MiniOperators.config| for available config settings.
27
+ ---
28
+ --- You can override runtime config settings (but not `config.mappings`) locally
29
+ --- to buffer inside `vim.b.minioperators_config` which should have same structure
30
+ --- as `MiniOperators.config`. See |mini.nvim-buffer-local-config| for more details.
31
+ ---
32
+ --- # Comparisons ~
33
+ ---
34
+ --- - [gbprod/substitute.nvim](https://github.com/gbprod/substitute.nvim):
35
+ --- - Has "replace" and "exchange" variants, but not others from this module.
36
+ --- - Has "replace/substitute" over range functionality, while this module
37
+ --- does not by design (it is similar to |:s| functionality while not
38
+ --- offering significantly lower mental complexity).
39
+ --- - "Replace" highlights pasted text, while in this module it doesn't.
40
+ --- - "Exchange" doesn't work across buffers, while in this module it does.
41
+ ---
42
+ --- - [svermeulen/vim-subversive](https://github.com/svermeulen/vim-subversive):
43
+ --- - Main inspiration for "replace" functionality, so they are mostly similar
44
+ --- for this operator.
45
+ --- - Has "replace/substitute" over range functionality, while this module
46
+ --- does not by design.
47
+ ---
48
+ --- - [tommcdo/vim-exchange](https://github.com/tommcdo/vim-exchange):
49
+ --- - Main inspiration for "exchange" functionality, so they are mostly
50
+ --- similar for this operator.
51
+ --- - Doesn't work across buffers, while this module does.
52
+ ---
53
+ --- - [christoomey/vim-sort-motion](https://github.com/christoomey/vim-sort-motion):
54
+ --- - Uses |:sort| for linewise sorting, while this module uses consistent
55
+ --- sorting algorithm (by default, see |MiniOperators.default_sort_func()|).
56
+ --- - Sorting algorithm can't be customized, while this module allows this
57
+ --- (see `sort.func` in |MiniOperators.config|).
58
+ --- - For charwise region uses only commas as separators, while this module
59
+ --- can also separate by semicolon or whitespace (by default,
60
+ --- see |MiniOperators.default_sort_func()|).
61
+ ---
62
+ --- # Highlight groups ~
63
+ --- *MiniOperators-hl-groups*
64
+ ---
65
+ --- - `MiniOperatorsExchangeFrom` - first region to exchange.
66
+ ---
67
+ --- To change any highlight group, set it directly with |nvim_set_hl()|.
68
+ ---
69
+ --- # Disabling ~
70
+ ---
71
+ --- To disable main functionality, set `vim.g.minioperators_disable` (globally) or
72
+ --- `vim.b.minioperators_disable` (for a buffer) to `true`. Considering high number
73
+ --- of different scenarios and customization intentions, writing exact rules
74
+ --- for disabling module's functionality is left to user. See
75
+ --- |mini.nvim-disabling-recipes| for common recipes.
76
+ ---@tag MiniOperators
77
+
78
+ --- Operator defines an action that will be performed on a textobject, motion,
79
+ --- or visual selection (similar to |d|, |c|, etc.). When makes sense, it can also
80
+ --- respect supplied register (like "replace" operator).
81
+ ---
82
+ --- This module implements each operator in a separate dedicated function
83
+ --- (like |MiniOperators.replace()| for "replace" operator). Each such function
84
+ --- takes `mode` as argument and acts depending on it:
85
+ ---
86
+ --- - If `mode` is `nil` (or not explicitly supplied), it sets |'operatorfunc'|
87
+ --- to this dedicated function and returns `g@` assuming being called from
88
+ --- expression mapping. See |:map-operator| and |:map-expression| for more details.
89
+ ---
90
+ --- - If `mode` is "char", "line", or "block", it acts as `operatorfunc` and performs
91
+ --- action for region between |`[| and |`]| marks.
92
+ ---
93
+ --- - If `mode` is "visual", it performs action for region between |`<| and |`>| marks.
94
+ ---
95
+ --- For more details about specific operator, see help for its function:
96
+ ---
97
+ --- - Evaluate: |MiniOperators.evaluate()|
98
+ --- - Exchange: |MiniOperators.exchange()|
99
+ --- - Multiply: |MiniOperators.multiply()|
100
+ --- - Replace: |MiniOperators.replace()|
101
+ --- - Sort: |MiniOperators.sort()|
102
+ ---
103
+ --- # Mappings ~
104
+ --- *MiniOperators-mappings*
105
+ ---
106
+ --- All operators are automatically mapped during |MiniOperators.setup()| execution.
107
+ --- Mappings keys are deduced from `prefix` field of corresponding `config` entry.
108
+ --- All built-in conflicting mappings are removed (like |gra|, |grn| in Neovim>=0.11).
109
+ --- Both |gx| and |v_gx| are remapped to `gX` (if that is not already taken).
110
+ ---
111
+ --- For each operator the following mappings are created:
112
+ ---
113
+ --- - In Normal mode to operate on textobject. Uses `prefix` directly.
114
+ --- - In Normal mode to operate on line. Appends to `prefix` the last character.
115
+ --- This aligns with |operator-doubled| and established patterns for operators
116
+ --- with more than two characters, like |guu|, |gUU|, etc.
117
+ --- - In Visual mode to operate on visual selection. Uses `prefix` directly.
118
+ ---
119
+ --- Example of default mappings for "replace":
120
+ --- - `gr` in Normal mode for operating on textobject.
121
+ --- Example of usage: `griw` replaces "inner word" with default register.
122
+ --- - `grr` in Normal mode for operating on line.
123
+ --- Example of usage: `grr` replaces current line.
124
+ --- - `gr` in Visual mode for operating on visual selection.
125
+ --- Example of usage: `viw` selects "inner word" and `gr` replaces it.
126
+ ---
127
+ --- There are two suggested ways to customize mappings:
128
+ ---
129
+ --- - Change `prefix` in |MiniOperators.setup()| call. For example, doing >lua
130
+ ---
131
+ --- require('mini.operators').setup({ replace = { prefix = 'cr' } })
132
+ --- <
133
+ --- will make mappings for `cr` / `crr` / `cr` instead of `gr` / `grr` / `gr`.
134
+ ---
135
+ --- - Disable automated mapping creation by supplying empty string as prefix and
136
+ --- use |MiniOperators.make_mappings()| directly. For example: >lua
137
+ ---
138
+ --- -- Disable automated creation of "replace"
139
+ --- local operators = require('mini.operators')
140
+ --- operators.setup({ replace = { prefix = '' } })
141
+ ---
142
+ --- -- Make custom mappings
143
+ --- operators.make_mappings(
144
+ --- 'replace',
145
+ --- { textobject = 'cr', line = 'crr', selection = 'cr' }
146
+ --- )
147
+ --- <
148
+ ---@tag MiniOperators-overview
149
+
150
+ ---@alias __operators_mode string|nil One of `nil`, `'char'`, `'line'`, `'block'`, `'visual'`.
151
+ ---@alias __operators_content table Table with the following fields:
152
+ --- - <lines> `(table)` - array with content lines.
153
+ --- - <submode> `(string)` - region submode. One of `'v'`, `'V'`, `'<C-v>'` (escaped).
154
+
155
+ ---@diagnostic disable:undefined-field
156
+ ---@diagnostic disable:discard-returns
157
+ ---@diagnostic disable:unused-local
158
+ ---@diagnostic disable:cast-local-type
159
+
160
+ -- Module definition ==========================================================
161
+ local MiniOperators = {}
162
+ local H = {}
163
+
164
+ --- Module setup
165
+ ---
166
+ ---@param config table|nil Module config table. See |MiniOperators.config|.
167
+ ---
168
+ ---@usage >lua
169
+ --- require('mini.operators').setup() -- use default config
170
+ --- -- OR
171
+ --- require('mini.operators').setup({}) -- replace {} with your config table
172
+ --- <
173
+ MiniOperators.setup = function(config)
174
+ -- TODO: Remove after Neovim=0.9 support is dropped
175
+ if vim.fn.has('nvim-0.10') == 0 then
176
+ vim.notify(
177
+ '(mini.operators) Neovim<0.10 is soft deprecated (module works but is not supported).'
178
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
179
+ .. ' Please update your Neovim version.'
180
+ )
181
+ end
182
+
183
+ -- Export module
184
+ _G.MiniOperators = MiniOperators
185
+
186
+ -- Setup config
187
+ config = H.setup_config(config)
188
+
189
+ -- Apply config
190
+ H.apply_config(config)
191
+
192
+ -- Define behavior
193
+ H.create_autocommands()
194
+
195
+ -- Create default highlighting
196
+ H.create_default_hl()
197
+ end
198
+
199
+ --stylua: ignore
200
+ --- Defaults ~
201
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
202
+ ---@text # Evaluate ~
203
+ ---
204
+ --- `evaluate.prefix` is a string used to automatically infer operator mappings keys
205
+ --- during |MiniOperators.setup()|. See |MiniOperators-mappings|.
206
+ ---
207
+ --- `evaluate.func` is a function used to actually evaluate text region.
208
+ --- If `nil` (default), |MiniOperators.default_evaluate_func()| is used.
209
+ ---
210
+ --- This function will take content table representing selected text as input
211
+ --- and should return array of lines as output (each item per line).
212
+ --- Content table has fields `lines`, array of region lines, and `submode`,
213
+ --- one of `v`, `V`, `\22` (escaped `<C-v>`) for charwise, linewise, and blockwise.
214
+ ---
215
+ --- To customize evaluation per language, set `evaluate.func` in buffer-local
216
+ --- config (`vim.b.minioperators_config`; see |mini.nvim-buffer-local-config|).
217
+ ---
218
+ --- # Exchange ~
219
+ ---
220
+ --- `exchange.prefix` is a string used to automatically infer operator mappings keys
221
+ --- during |MiniOperators.setup()|. See |MiniOperators-mappings|.
222
+ ---
223
+ --- Note: default value "gx" overrides |gx| / |v_gx|. Instead they are remapped
224
+ --- to `gX` (if that is not already taken). To keep using `gx` with built-in
225
+ --- feature (open URL at cursor) choose different `config.prefix`.
226
+ ---
227
+ --- `exchange.reindent_linewise` is a boolean indicating whether newly put linewise
228
+ --- text should preserve indent of replaced text. In other words, if `false`,
229
+ --- regions are exchanged preserving their indents; if `true` - without them.
230
+ ---
231
+ --- # Multiply ~
232
+ ---
233
+ --- `multiply.prefix` is a string used to automatically infer operator mappings keys
234
+ --- during |MiniOperators.setup()|. See |MiniOperators-mappings|.
235
+ ---
236
+ --- `multiply.func` is a function used to optionally update multiplied text.
237
+ --- If `nil` (default), text used as is.
238
+ ---
239
+ --- Takes content table as input (see "Evaluate" section) and should return
240
+ --- array of lines as output.
241
+ ---
242
+ --- # Replace ~
243
+ ---
244
+ --- `replace.prefix` is a string used to automatically infer operator mappings keys
245
+ --- during |MiniOperators.setup()|. See |MiniOperators-mappings|.
246
+ ---
247
+ --- `replace.reindent_linewise` is a boolean indicating whether newly put linewise
248
+ --- text should preserve indent of replaced text.
249
+ ---
250
+ --- # Sort ~
251
+ ---
252
+ --- `sort.prefix` is a string used to automatically infer operator mappings keys
253
+ --- during |MiniOperators.setup()|. See |MiniOperators-mappings|.
254
+ ---
255
+ --- `sort.func` is a function used to actually sort text region.
256
+ --- If `nil` (default), |MiniOperators.default_sort_func()| is used.
257
+ ---
258
+ --- Takes content table as input (see "Evaluate" section) and should return
259
+ --- array of lines as output.
260
+ ---
261
+ --- Example of `sort.func` which asks user for custom delimiter for charwise region: >lua
262
+ ---
263
+ --- local sort_func = function(content)
264
+ --- local opts = {}
265
+ --- if content.submode == 'v' then
266
+ --- -- Ask for delimiter to be treated as is (not as Lua pattern)
267
+ --- local delimiter = vim.fn.input('Sort delimiter: ')
268
+ --- -- Treat surrounding whitespace as part of split
269
+ --- opts.split_patterns = { '%s*' .. vim.pesc(delimiter) .. '%s*' }
270
+ --- end
271
+ --- return MiniOperators.default_sort_func(content, opts)
272
+ --- end
273
+ ---
274
+ --- require('mini.operators').setup({ sort = { func = sort_func } })
275
+ --- <
276
+ MiniOperators.config = {
277
+ -- Each entry configures one operator.
278
+ -- `prefix` defines keys mapped during `setup()`: in Normal mode
279
+ -- to operate on textobject and line, in Visual - on selection.
280
+
281
+ -- Evaluate text and replace with output
282
+ evaluate = {
283
+ prefix = 'g=',
284
+
285
+ -- Function which does the evaluation
286
+ func = nil,
287
+ },
288
+
289
+ -- Exchange text regions
290
+ exchange = {
291
+ -- NOTE: Default `gx` is remapped to `gX`
292
+ prefix = 'gx',
293
+
294
+ -- Whether to reindent new text to match previous indent
295
+ reindent_linewise = true,
296
+ },
297
+
298
+ -- Multiply (duplicate) text
299
+ multiply = {
300
+ prefix = 'gm',
301
+
302
+ -- Function which can modify text before multiplying
303
+ func = nil,
304
+ },
305
+
306
+ -- Replace text with register
307
+ replace = {
308
+ -- NOTE: Default `gr*` LSP mappings are removed
309
+ prefix = 'gr',
310
+
311
+ -- Whether to reindent new text to match previous indent
312
+ reindent_linewise = true,
313
+ },
314
+
315
+ -- Sort text
316
+ sort = {
317
+ prefix = 'gs',
318
+
319
+ -- Function which does the sort
320
+ func = nil,
321
+ }
322
+ }
323
+ --minidoc_afterlines_end
324
+
325
+ --- Evaluate text and replace with output
326
+ ---
327
+ --- It replaces the region with the output of `config.evaluate.func`.
328
+ --- By default it is |MiniOperators.default_evaluate_func()| which evaluates
329
+ --- text as Lua code depending on the region submode.
330
+ ---
331
+ ---@param mode __operators_mode
332
+ MiniOperators.evaluate = function(mode)
333
+ if H.is_disabled() or not vim.bo.modifiable then return '' end
334
+
335
+ -- If used without arguments inside expression mapping, set it as
336
+ -- 'operatorfunc' and call it again as a result of expression mapping.
337
+ if mode == nil then
338
+ vim.o.operatorfunc = 'v:lua.MiniOperators.evaluate'
339
+ return 'g@'
340
+ end
341
+
342
+ local evaluate_func = H.get_config().evaluate.func or MiniOperators.default_evaluate_func
343
+ local data = H.get_region_data(mode)
344
+ if data == nil then return end
345
+ data.reindent_linewise = true
346
+ H.apply_content_func(evaluate_func, data)
347
+ end
348
+
349
+ --- Exchange text regions
350
+ ---
351
+ --- Has two-step logic:
352
+ --- - First call remembers the region as the one to be exchanged and highlights it
353
+ --- with `MiniOperatorsExchangeFrom` highlight group.
354
+ --- - Second call performs the exchange. Basically, a two substeps action:
355
+ --- "yank both regions" and replace each one with another.
356
+ ---
357
+ --- Notes:
358
+ --- - Use `<C-c>` to stop exchanging after the first step.
359
+ ---
360
+ --- - Exchanged regions can have different (char,line,block)-wise submodes.
361
+ ---
362
+ --- - Works with most cases of intersecting regions, but not officially supported.
363
+ ---
364
+ ---@param mode __operators_mode
365
+ MiniOperators.exchange = function(mode)
366
+ if H.is_disabled() or not vim.bo.modifiable then return '' end
367
+
368
+ -- If used without arguments inside expression mapping, set it as
369
+ -- 'operatorfunc' and call it again as a result of expression mapping.
370
+ if mode == nil then
371
+ vim.o.operatorfunc = 'v:lua.MiniOperators.exchange'
372
+ return 'g@'
373
+ end
374
+
375
+ -- Depending on present cache data, perform exchange step
376
+ if not H.exchange_has_step_one() then
377
+ -- Store data about first region
378
+ H.cache.exchange.step_one = H.exchange_set_region_extmark(mode, true)
379
+ if H.cache.exchange.step_one == nil then return end
380
+
381
+ -- Temporarily remap `<C-c>` to stop the exchange
382
+ H.exchange_set_stop_mapping()
383
+ else
384
+ -- Store data about second region
385
+ H.cache.exchange.step_two = H.exchange_set_region_extmark(mode, false)
386
+ if H.cache.exchange.step_two == nil then return end
387
+
388
+ -- Do exchange
389
+ H.exchange_do()
390
+
391
+ -- Stop exchange
392
+ H.exchange_stop()
393
+ end
394
+ end
395
+
396
+ --- Multiply (duplicate) text
397
+ ---
398
+ --- Copies a region (without affecting registers) and puts it directly after.
399
+ ---
400
+ --- Notes:
401
+ --- - Supports two types of |[count]|: `[count1]gm[count2][textobject]` with default
402
+ --- `config.multiply.prefix` makes `[count1]` copies of region defined by
403
+ --- `[count2][textobject]`. Example: `2gm3aw` - 2 copies of `3aw`.
404
+ ---
405
+ --- - |[count]| for "line" mapping (`gmm` by default) is treated as `[count1]` from
406
+ --- previous note.
407
+ ---
408
+ --- - Advantages of using this instead of "yank" + "paste":
409
+ --- - Doesn't modify any register, while separate steps need some register to
410
+ --- hold multiplied text.
411
+ --- - In most cases separate steps would be "yank" + "move cursor" + "paste",
412
+ --- while "multiply" makes it at once.
413
+ ---
414
+ ---@param mode __operators_mode
415
+ MiniOperators.multiply = function(mode)
416
+ if H.is_disabled() or not vim.bo.modifiable then return '' end
417
+
418
+ -- If used without arguments inside expression mapping, set it as
419
+ -- 'operatorfunc' and call it again as a result of expression mapping.
420
+ if mode == nil then
421
+ vim.o.operatorfunc = 'v:lua.MiniOperators.multiply'
422
+ H.cache.multiply = { count = vim.v.count1 }
423
+
424
+ -- Reset count to allow two counts: first for paste, second for textobject
425
+ return vim.api.nvim_replace_termcodes('<Cmd>redraw<CR>g@', true, true, true)
426
+ end
427
+
428
+ local count = mode == 'visual' and vim.v.count1 or H.cache.multiply.count
429
+ local data = H.get_region_data(mode)
430
+ if data == nil then return end
431
+ local mark_from, mark_to, submode = data.mark_from, data.mark_to, data.submode
432
+
433
+ H.with_temp_context({ registers = { 'x', '"' } }, function()
434
+ -- Yank to temporary "x" register
435
+ local yank_data = { mark_from = mark_from, mark_to = mark_to, submode = submode, mode = mode, register = 'x' }
436
+ H.do_between_marks('y', yank_data)
437
+
438
+ -- Modify lines in "x" register
439
+ local func = H.get_config().multiply.func or function(content) return content.lines end
440
+ local x_reginfo = vim.fn.getreginfo('x')
441
+ x_reginfo.regcontents = func({ lines = x_reginfo.regcontents, submode = submode })
442
+ vim.fn.setreg('x', x_reginfo)
443
+
444
+ -- Adjust cursor for a proper paste
445
+ local ref_coords = H.multiply_get_ref_coords(mark_from, mark_to, submode)
446
+ vim.api.nvim_win_set_cursor(0, ref_coords)
447
+
448
+ -- Paste after textobject from temporary register
449
+ H.cmd_normal(count .. '"xp')
450
+
451
+ -- Adjust cursor to be at start of pasted text. Not in linewise mode as it
452
+ -- already is at first non-blank, while this moves to first column.
453
+ if submode ~= 'V' then vim.cmd('normal! `[') end
454
+ end)
455
+ end
456
+
457
+ --- Replace text with register
458
+ ---
459
+ --- Notes:
460
+ --- - Supports two types of |[count]|: `[count1]gr[count2][textobject]` with default
461
+ --- `config.replace.prefix` puts `[count1]` contents of register over region defined
462
+ --- by `[count2][textobject]`. Example: `2gr3aw` - 2 register contents over `3aw`.
463
+ ---
464
+ --- - |[count]| for "line" mapping (`grr` by default) is treated as `[count1]` from
465
+ --- previous note.
466
+ ---
467
+ --- - Advantages of using this instead of "visually select" + "paste with |v_P|":
468
+ --- - As operator it is dot-repeatable which has cumulative gain in case of
469
+ --- multiple replacing is needed.
470
+ --- - Can automatically reindent.
471
+ ---
472
+ ---@param mode __operators_mode
473
+ MiniOperators.replace = function(mode)
474
+ if H.is_disabled() or not vim.bo.modifiable then return '' end
475
+
476
+ -- If used without arguments inside expression mapping, set it as
477
+ -- 'operatorfunc' and call it again as a result of expression mapping.
478
+ if mode == nil then
479
+ vim.o.operatorfunc = 'v:lua.MiniOperators.replace'
480
+ H.cache.replace = { count = vim.v.count1, register = vim.v.register }
481
+
482
+ -- Reset count to allow two counts: first for paste, second for textobject
483
+ return vim.api.nvim_replace_termcodes('<Cmd>redraw<CR>g@', true, true, true)
484
+ end
485
+
486
+ -- Do replace
487
+ -- - Compute `count` and `register` prior getting region data because it
488
+ -- invalidates them for active Visual mode
489
+ local count = mode == 'visual' and vim.v.count1 or H.cache.replace.count
490
+ local register = mode == 'visual' and vim.v.register or H.cache.replace.register
491
+ local data = H.get_region_data(mode)
492
+ if data == nil then return '' end
493
+ data.count = count
494
+ data.register = register
495
+ data.reindent_linewise = H.get_config().replace.reindent_linewise
496
+
497
+ H.replace_do(data)
498
+
499
+ return ''
500
+ end
501
+
502
+ --- Sort text
503
+ ---
504
+ --- It replaces the region with the output of `config.sort.func`.
505
+ --- By default it is |MiniOperators.default_sort_func()| which sorts the text
506
+ --- depending on submode.
507
+ ---
508
+ --- Notes:
509
+ --- - "line" mapping is charwise (as there is not much sense in sorting
510
+ --- linewise a single line). This also results into no |[count]| support.
511
+ ---
512
+ ---@param mode __operators_mode
513
+ MiniOperators.sort = function(mode)
514
+ if H.is_disabled() or not vim.bo.modifiable then return '' end
515
+
516
+ -- If used without arguments inside expression mapping, set it as
517
+ -- 'operatorfunc' and call it again as a result of expression mapping.
518
+ if mode == nil then
519
+ vim.o.operatorfunc = 'v:lua.MiniOperators.sort'
520
+ return 'g@'
521
+ end
522
+
523
+ local sort_func = H.get_config().sort.func or MiniOperators.default_sort_func
524
+ H.apply_content_func(sort_func, H.get_region_data(mode))
525
+ end
526
+
527
+ --- Make operator mappings
528
+ ---
529
+ ---@param operator_name string Name of existing operator from this module.
530
+ ---@param lhs_tbl table Table with mappings keys. Should have these fields:
531
+ --- - <textobject> `(string)` - Normal mode mapping to operate on textobject.
532
+ --- - <line> `(string)` - Normal mode mapping to operate on line.
533
+ --- Usually an alias for textobject mapping followed by |_|.
534
+ --- For "sort" it operates charwise on whole line without left and right
535
+ --- whitespace (as there is not much sense in sorting linewise a single line).
536
+ --- - <selection> `(string)` - Visual mode mapping to operate on selection.
537
+ ---
538
+ --- Supply empty string to not create particular mapping. Note: creating `line`
539
+ --- mapping needs `textobject` mapping to be set.
540
+ ---
541
+ ---@usage >lua
542
+ --- require('mini.operators').make_mappings(
543
+ --- 'replace',
544
+ --- { textobject = 'cr', line = 'crr', selection = 'cr' }
545
+ --- )
546
+ --- <
547
+ MiniOperators.make_mappings = function(operator_name, lhs_tbl)
548
+ -- Validate arguments
549
+ if not (type(operator_name) == 'string' and MiniOperators[operator_name] ~= nil) then
550
+ H.error('`operator_name` should be a valid operator name.')
551
+ end
552
+ local is_keys_tbl = type(lhs_tbl) == 'table'
553
+ and type(lhs_tbl.textobject) == 'string'
554
+ and type(lhs_tbl.line) == 'string'
555
+ and type(lhs_tbl.selection) == 'string'
556
+ if not is_keys_tbl then H.error('`lhs_tbl` should be a valid table of keys.') end
557
+
558
+ if lhs_tbl.line ~= '' and lhs_tbl.textobject == '' then
559
+ H.error('Creating mapping for `line` needs mapping for `textobject`.')
560
+ end
561
+
562
+ -- Make mappings
563
+ local operator_desc = operator_name:sub(1, 1):upper() .. operator_name:sub(2)
564
+
565
+ local expr_opts = { expr = true, replace_keycodes = false, desc = operator_desc }
566
+ H.map('n', lhs_tbl.textobject, string.format('v:lua.MiniOperators.%s()', operator_name), expr_opts)
567
+
568
+ local rhs = lhs_tbl.textobject .. '_'
569
+ -- - Make `sort()` line mapping to be charwise
570
+ if operator_name == 'sort' then rhs = '^' .. lhs_tbl.textobject .. 'g_' end
571
+ H.map('n', lhs_tbl.line, rhs, { remap = true, desc = operator_desc .. ' line' })
572
+
573
+ local visual_rhs = string.format([[<Cmd>lua MiniOperators.%s('visual')<CR>]], operator_name)
574
+ H.map('x', lhs_tbl.selection, visual_rhs, { desc = operator_desc .. ' selection' })
575
+ end
576
+
577
+ --- Default evaluate function
578
+ ---
579
+ --- Evaluate text as Lua code and return object from last line (like if last
580
+ --- line is prepended with `return` if it is not already).
581
+ ---
582
+ --- Behavior depends on region submode:
583
+ ---
584
+ --- - For charwise and linewise regions, text evaluated as is.
585
+ ---
586
+ --- - For blockwise region, lines are evaluated per line using only first lines
587
+ --- of outputs. This allows separate execution of lines in order to provide
588
+ --- something different compared to linewise region.
589
+ ---
590
+ ---@param content __operators_content
591
+ MiniOperators.default_evaluate_func = function(content)
592
+ if not H.is_content(content) then H.error('`content` should be a content table.') end
593
+
594
+ local lines, submode = content.lines, content.submode
595
+
596
+ -- In non-blockwise mode return the result of the last line
597
+ if submode ~= H.submode_keys.block then return H.eval_lua_lines(lines) end
598
+
599
+ -- In blockwise selection evaluate and return each line separately
600
+ return vim.tbl_map(function(l) return H.eval_lua_lines({ l })[1] end, lines)
601
+ end
602
+
603
+ --- Default sort function
604
+ ---
605
+ --- Sort text based on region submode:
606
+ ---
607
+ --- - For charwise region, split by separator pattern, sort parts, merge back
608
+ --- with separators. Actual pattern is inferred based on the array of patterns
609
+ --- from `opts.split_patterns`: whichever element is present in the text is
610
+ --- used, preferring the earlier one if several are present.
611
+ --- Example: sorting "c, b; a" line with default `opts.split_patterns` results
612
+ --- into "b; a, c" as it is split only by comma.
613
+ ---
614
+ --- - For linewise and blockwise regions sort lines as is.
615
+ ---
616
+ --- Notes:
617
+ --- - Sort is done with |table.sort()| on an array of lines, which doesn't treat
618
+ --- whitespace or digits specially. Use |:sort| for more complicated tasks.
619
+ ---
620
+ --- - Pattern is allowed to be an empty string in which case split results into
621
+ --- all characters as parts.
622
+ ---
623
+ --- - Pad pattern in `split_patterns` with `%s*` to include whitespace into separator.
624
+ --- Example: line "b _ a" with "_" pattern will be sorted as " a_b " (because
625
+ --- it is split as "b ", "_", " a" ) while with `%s*_%s*` pattern it results
626
+ --- into "a _ b" (split as "b", " _ ", "a").
627
+ ---
628
+ ---@param content __operators_content
629
+ ---@param opts table|nil Options. Possible fields:
630
+ --- - <compare_fun> `(function)` - compare function compatible with |table.sort()|.
631
+ --- Default: direct compare with `<`.
632
+ --- - <split_patterns> `(table)` - array of split Lua patterns to be used for
633
+ --- charwise submode. Order is important.
634
+ --- Default: `{ '%s*,%s*', '%s*;%s*', '%s+', '' }`.
635
+ MiniOperators.default_sort_func = function(content, opts)
636
+ if not H.is_content(content) then H.error('`content` should be a content table.') end
637
+
638
+ opts = vim.tbl_deep_extend('force', { compare_fun = nil, split_patterns = nil }, opts or {})
639
+
640
+ local compare_fun = opts.compare_fun or function(a, b) return a < b end
641
+ if not vim.is_callable(compare_fun) then H.error('`opts.compare_fun` should be callable.') end
642
+
643
+ local split_patterns = opts.split_patterns or { '%s*,%s*', '%s*;%s*', '%s+', '' }
644
+ if not H.islist(split_patterns) then H.error('`opts.split_patterns` should be array.') end
645
+
646
+ -- Prepare lines to sort
647
+ local lines, submode = content.lines, content.submode
648
+
649
+ if submode ~= 'v' then
650
+ table.sort(lines, compare_fun)
651
+ return lines
652
+ end
653
+
654
+ local parts, seps = H.sort_charwise_split(lines, split_patterns)
655
+ table.sort(parts, compare_fun)
656
+ return H.sort_charwise_unsplit(parts, seps)
657
+ end
658
+
659
+ -- Helper data ================================================================
660
+ -- Module default config
661
+ H.default_config = vim.deepcopy(MiniOperators.config)
662
+
663
+ -- Namespaces
664
+ H.ns_id = {
665
+ exchange = vim.api.nvim_create_namespace('MiniOperatorsExchange'),
666
+ }
667
+
668
+ -- Cache for all operators
669
+ H.cache = {
670
+ exchange = {},
671
+ multiply = {},
672
+ replace = {},
673
+ }
674
+
675
+ -- Submode keys for
676
+ H.submode_keys = {
677
+ char = 'v',
678
+ line = 'V',
679
+ block = vim.api.nvim_replace_termcodes('<C-v>', true, true, true),
680
+ }
681
+
682
+ -- Helper functionality =======================================================
683
+ -- Settings -------------------------------------------------------------------
684
+ H.setup_config = function(config)
685
+ H.check_type('config', config, 'table', true)
686
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
687
+
688
+ H.check_type('evaluate', config.evaluate, 'table')
689
+ H.check_type('evaluate.prefix', config.evaluate.prefix, 'string')
690
+ H.check_type('evaluate.func', config.evaluate.func, 'function', true)
691
+
692
+ H.check_type('exchange', config.exchange, 'table')
693
+ H.check_type('exchange.prefix', config.exchange.prefix, 'string')
694
+ H.check_type('exchange.reindent_linewise', config.exchange.reindent_linewise, 'boolean')
695
+
696
+ H.check_type('multiply', config.multiply, 'table')
697
+ H.check_type('multiply.prefix', config.multiply.prefix, 'string')
698
+ H.check_type('multiply.func', config.multiply.func, 'function', true)
699
+
700
+ H.check_type('replace', config.replace, 'table')
701
+ H.check_type('replace.prefix', config.replace.prefix, 'string')
702
+ H.check_type('replace.reindent_linewise', config.replace.reindent_linewise, 'boolean')
703
+
704
+ H.check_type('sort', config.sort, 'table')
705
+ H.check_type('sort.prefix', config.sort.prefix, 'string')
706
+ H.check_type('sort.func', config.sort.func, 'function', true)
707
+
708
+ return config
709
+ end
710
+
711
+ H.apply_config = function(config)
712
+ MiniOperators.config = config
713
+
714
+ local remove_lsp_mapping = function(mode, lhs)
715
+ local map_desc = vim.fn.maparg(lhs, mode, false, true).desc
716
+ if map_desc == nil or string.find(map_desc, 'vim%.lsp') == nil then return end
717
+ vim.keymap.del(mode, lhs)
718
+ end
719
+
720
+ local remap_builtin_gx = function(mode)
721
+ if vim.fn.maparg('gX', mode) ~= '' then return end
722
+ local keymap = vim.fn.maparg('gx', mode, false, true)
723
+ local rhs = keymap.callback or keymap.rhs
724
+ if rhs == nil or (keymap.desc or ''):find('URI under cursor') == nil then return end
725
+ vim.keymap.set(mode, 'gX', rhs, { desc = keymap.desc })
726
+ end
727
+
728
+ -- Make mappings
729
+ local map_all = function(operator_name)
730
+ -- Map only valid LHS
731
+ local prefix = config[operator_name].prefix
732
+ if type(prefix) ~= 'string' or prefix == '' then return end
733
+
734
+ -- Remove conflicting built-in mappings
735
+ if prefix == 'gr' and vim.fn.has('nvim-0.11') == 1 then
736
+ remove_lsp_mapping('n', 'gra')
737
+ remove_lsp_mapping('x', 'gra')
738
+ remove_lsp_mapping('n', 'gri')
739
+ remove_lsp_mapping('n', 'grn')
740
+ remove_lsp_mapping('n', 'grr')
741
+ remove_lsp_mapping('n', 'grt')
742
+ remove_lsp_mapping('n', 'grx')
743
+ end
744
+
745
+ if prefix == 'gx' and vim.fn.has('nvim-0.10') == 1 then
746
+ remap_builtin_gx('n')
747
+ remap_builtin_gx('x')
748
+ end
749
+
750
+ local lhs_tbl = {
751
+ textobject = prefix,
752
+ line = prefix .. vim.fn.strcharpart(prefix, vim.fn.strchars(prefix) - 1, 1),
753
+ selection = prefix,
754
+ }
755
+ MiniOperators.make_mappings(operator_name, lhs_tbl)
756
+ end
757
+
758
+ map_all('evaluate')
759
+ map_all('exchange')
760
+ map_all('multiply')
761
+ map_all('replace')
762
+ map_all('sort')
763
+ end
764
+
765
+ H.is_disabled = function() return vim.g.minioperators_disable == true or vim.b.minioperators_disable == true end
766
+
767
+ H.get_config = function(config)
768
+ return vim.tbl_deep_extend('force', MiniOperators.config, vim.b.minioperators_config or {}, config or {})
769
+ end
770
+
771
+ H.create_autocommands = function()
772
+ local gr = vim.api.nvim_create_augroup('MiniOperators', {})
773
+ vim.api.nvim_create_autocmd('ColorScheme', { group = gr, callback = H.create_default_hl, desc = 'Ensure colors' })
774
+ end
775
+
776
+ H.create_default_hl = function()
777
+ vim.api.nvim_set_hl(0, 'MiniOperatorsExchangeFrom', { default = true, link = 'IncSearch' })
778
+ end
779
+
780
+ -- Evaluate -------------------------------------------------------------------
781
+ H.eval_lua_lines = function(lines)
782
+ -- Copy to not modify input
783
+ local lines_copy, n = vim.deepcopy(lines), #lines
784
+ lines_copy[n] = (lines_copy[n]:find('^%s*return%s+') == nil and 'return ' or '') .. lines_copy[n]
785
+
786
+ local str_to_eval = table.concat(lines_copy, '\n')
787
+
788
+ -- Allow returning tuple with any value(s) being `nil`
789
+ return H.inspect_objects(assert(loadstring(str_to_eval))())
790
+ end
791
+
792
+ H.inspect_objects = function(...)
793
+ local objects = {}
794
+ -- Not using `{...}` because it removes `nil` input
795
+ for i = 1, select('#', ...) do
796
+ local v = select(i, ...)
797
+ table.insert(objects, vim.inspect(v))
798
+ end
799
+
800
+ return vim.split(table.concat(objects, '\n'), '\n')
801
+ end
802
+
803
+ -- Exchange -------------------------------------------------------------------
804
+ H.exchange_do = function()
805
+ local step_one, step_two = H.cache.exchange.step_one, H.cache.exchange.step_two
806
+
807
+ -- Do nothing if regions are the same
808
+ if H.exchange_is_same_steps(step_one, step_two) then return end
809
+
810
+ -- Save temporary registers
811
+ local reg_one, reg_two = vim.fn.getreginfo('a'), vim.fn.getreginfo('b')
812
+
813
+ -- Create step temporary contexts (data that should not change)
814
+ local context_one = { buf_id = step_one.buf_id, marks = { 'x', 'y' }, registers = { '"' } }
815
+ local context_two = { buf_id = step_two.buf_id, marks = { 'x', 'y' }, registers = { '"' } }
816
+
817
+ -- Put regions into registers. NOTE: do it before actual exchange to allow
818
+ -- intersecting regions.
819
+ local populating_register = function(step, register)
820
+ return function()
821
+ H.exchange_set_step_marks(step, { 'x', 'y' })
822
+ local yank_data =
823
+ { mark_from = 'x', mark_to = 'y', submode = step.submode, mode = step.mode, register = register }
824
+ H.do_between_marks('y', yank_data)
825
+ end
826
+ end
827
+
828
+ H.with_temp_context(context_one, populating_register(step_one, 'a'))
829
+ H.with_temp_context(context_two, populating_register(step_two, 'b'))
830
+
831
+ -- Sequentially replace
832
+ local replacing = function(step, register)
833
+ return function()
834
+ H.exchange_set_step_marks(step, { 'x', 'y' })
835
+
836
+ local replace_data = {
837
+ count = 1,
838
+ mark_from = 'x',
839
+ mark_to = 'y',
840
+ mode = step.mode,
841
+ register = register,
842
+ reindent_linewise = H.get_config().exchange.reindent_linewise,
843
+ submode = step.submode,
844
+ }
845
+ H.replace_do(replace_data)
846
+ end
847
+ end
848
+
849
+ H.with_temp_context(context_one, replacing(step_one, 'b'))
850
+ H.with_temp_context(context_two, replacing(step_two, 'a'))
851
+
852
+ -- Restore temporary registers
853
+ vim.fn.setreg('a', reg_one)
854
+ vim.fn.setreg('b', reg_two)
855
+ end
856
+
857
+ H.exchange_has_step_one = function()
858
+ local step_one = H.cache.exchange.step_one
859
+ if type(step_one) ~= 'table' then return false end
860
+
861
+ if not vim.api.nvim_buf_is_valid(step_one.buf_id) then
862
+ H.exchange_stop()
863
+ return false
864
+ end
865
+ return true
866
+ end
867
+
868
+ H.exchange_set_region_extmark = function(mode, add_highlight)
869
+ local ns_id = H.ns_id.exchange
870
+
871
+ -- Compute regular marks for target region
872
+ local region_data = H.get_region_data(mode)
873
+ if region_data == nil then return end
874
+ local submode = region_data.submode
875
+ local markcoords_from, markcoords_to = H.get_mark(region_data.mark_from), H.get_mark(region_data.mark_to)
876
+
877
+ -- Compute extmark's range for target region
878
+ local extmark_from = { markcoords_from[1] - 1, markcoords_from[2] }
879
+ local extmark_to = { markcoords_to[1] - 1, H.get_next_char_bytecol(markcoords_to) }
880
+
881
+ -- Adjust for visual selection in case of 'selection=exclusive'
882
+ if region_data.mark_to == '>' and vim.o.selection == 'exclusive' then extmark_to[2] = extmark_to[2] - 1 end
883
+
884
+ -- - Tweak columns for linewise marks
885
+ if submode == 'V' then
886
+ extmark_from[2] = 0
887
+ extmark_to[2] = vim.fn.col({ extmark_to[1] + 1, '$' }) - 1
888
+ end
889
+
890
+ -- Set extmark to represent region. Add highlighting inside of it only if
891
+ -- needed and not in blockwise submode (can't highlight that way).
892
+ local buf_id = vim.api.nvim_get_current_buf()
893
+
894
+ local extmark_hl_group
895
+ if add_highlight and submode ~= H.submode_keys.block then extmark_hl_group = 'MiniOperatorsExchangeFrom' end
896
+
897
+ local extmark_opts = {
898
+ end_row = extmark_to[1],
899
+ end_col = extmark_to[2],
900
+ hl_group = extmark_hl_group,
901
+ -- Using this gravity is better for handling empty lines in linewise mode
902
+ end_right_gravity = mode == 'line',
903
+ }
904
+ local region_extmark_id = vim.api.nvim_buf_set_extmark(buf_id, ns_id, extmark_from[1], extmark_from[2], extmark_opts)
905
+
906
+ -- - Possibly add highlighting for blockwise mode
907
+ if add_highlight and extmark_hl_group == nil then
908
+ -- Highlighting blockwise region needs full register type with width
909
+ local opts = { regtype = H.exchange_get_blockwise_regtype(markcoords_from, markcoords_to) }
910
+ H.highlight_range(buf_id, ns_id, 'MiniOperatorsExchangeFrom', extmark_from, extmark_to, opts)
911
+ end
912
+
913
+ -- Return data to cache
914
+ return { buf_id = buf_id, mode = mode, submode = submode, extmark_id = region_extmark_id }
915
+ end
916
+
917
+ H.exchange_get_region_extmark = function(step)
918
+ return vim.api.nvim_buf_get_extmark_by_id(step.buf_id, H.ns_id.exchange, step.extmark_id, { details = true })
919
+ end
920
+
921
+ H.exchange_set_step_marks = function(step, mark_names)
922
+ local extmark_details = H.exchange_get_region_extmark(step)
923
+
924
+ H.set_mark(mark_names[1], { extmark_details[1] + 1, extmark_details[2] })
925
+
926
+ -- Unadjust for visual selection in case of 'selection=exclusive'
927
+ local should_unadjust = step.mode == 'visual' and vim.o.selection == 'exclusive'
928
+ local col_offset = should_unadjust and 1 or 0
929
+
930
+ H.set_mark(mark_names[2], { extmark_details[3].end_row + 1, extmark_details[3].end_col - 1 + col_offset })
931
+ end
932
+
933
+ H.exchange_get_blockwise_regtype = function(markcoords_from, markcoords_to)
934
+ local f = function()
935
+ -- Yank into "z" register and return its blockwise type
936
+ H.set_mark('x', markcoords_from)
937
+ H.set_mark('y', markcoords_to)
938
+ local yank_data = { mark_from = 'x', mark_to = 'y', submode = H.submode_keys.block, mode = 'block', register = 't' }
939
+ H.do_between_marks('y', yank_data)
940
+
941
+ return vim.fn.getregtype('t')
942
+ end
943
+
944
+ return H.with_temp_context({ buf_id = 0, marks = { 'x', 'y' }, registers = { 't' } }, f)
945
+ end
946
+
947
+ H.exchange_stop = function()
948
+ H.exchange_del_stop_mapping()
949
+
950
+ local cur, ns_id = H.cache.exchange, H.ns_id.exchange
951
+ if cur.step_one ~= nil then pcall(vim.api.nvim_buf_clear_namespace, cur.step_one.buf_id, ns_id, 0, -1) end
952
+ if cur.step_two ~= nil then pcall(vim.api.nvim_buf_clear_namespace, cur.step_two.buf_id, ns_id, 0, -1) end
953
+ H.cache.exchange = {}
954
+ end
955
+
956
+ H.exchange_set_stop_mapping = function()
957
+ local lhs = '<C-c>'
958
+ H.cache.exchange.stop_restore_map_data = vim.fn.maparg(lhs, 'n', false, true)
959
+ vim.keymap.set('n', lhs, H.exchange_stop, { desc = 'Stop exchange' })
960
+ end
961
+
962
+ H.exchange_del_stop_mapping = function()
963
+ local map_data = H.cache.exchange.stop_restore_map_data
964
+ if map_data == nil then return end
965
+
966
+ -- Try restore previous mapping if it was set
967
+ if vim.tbl_count(map_data) > 0 then
968
+ vim.fn.mapset('n', false, map_data)
969
+ else
970
+ vim.keymap.del('n', map_data.lhs or '<C-c>')
971
+ end
972
+ end
973
+
974
+ H.exchange_is_same_steps = function(step_one, step_two)
975
+ if step_one.buf_id ~= step_two.buf_id or step_one.submode ~= step_two.submode then return false end
976
+ -- Region's start and end should be the same
977
+ local one, two = H.exchange_get_region_extmark(step_one), H.exchange_get_region_extmark(step_two)
978
+ return one[1] == two[1] and one[2] == two[2] and one[3].end_row == two[3].end_row and one[3].end_col == two[3].end_col
979
+ end
980
+
981
+ -- Multiply -------------------------------------------------------------------
982
+ H.multiply_get_ref_coords = function(mark_from, mark_to, submode)
983
+ local markcoords_from, markcoords_to = H.get_mark(mark_from), H.get_mark(mark_to)
984
+ if mark_to == '>' and vim.o.selection == 'exclusive' then markcoords_to[2] = markcoords_to[2] - 1 end
985
+
986
+ if submode ~= H.submode_keys.block then return markcoords_to end
987
+
988
+ -- In blockwise selection go to top right corner (allowing for presence of
989
+ -- multibyte characters)
990
+ local row = math.min(markcoords_from[1], markcoords_to[1])
991
+
992
+ -- - "from"/"to" may not only be "top-left"/"bottom-right" but also
993
+ -- "top-right" and "bottom-left"
994
+ local virtcol_from = vim.fn.virtcol({ markcoords_from[1], markcoords_from[2] + 1 })
995
+ local virtcol_to = vim.fn.virtcol({ markcoords_to[1], markcoords_to[2] + 1 })
996
+ local virtcol = math.max(virtcol_from, virtcol_to)
997
+
998
+ local col = vim.fn.virtcol2col(0, row, virtcol)
999
+
1000
+ return { row, col - 1 }
1001
+ end
1002
+
1003
+ -- Replace --------------------------------------------------------------------
1004
+ --- Delete region between two marks and paste from register
1005
+ ---
1006
+ ---@param data table Fields:
1007
+ --- - <count> (optional) - Number of times to paste.
1008
+ --- - <mark_from> - Name of "from" mark.
1009
+ --- - <mark_to> - Name of "to" mark.
1010
+ --- - <mode> - Operator mode. One of 'visual', 'char', 'line', 'block'.
1011
+ --- - <register> - Name of register from which to paste.
1012
+ --- - <submode> - Region submode. One of 'v', 'V', '\22'.
1013
+ ---@private
1014
+ H.replace_do = function(data)
1015
+ -- NOTE: Ideally, implementation would leverage "Visually select - press `P`"
1016
+ -- approach, but it has issues with dot-repeat. The `cancel_redo()` approach
1017
+ -- doesn't work probably because `P` implementation uses more than one
1018
+ -- dot-repeat overwrite.
1019
+ local register, submode = data.register, data.submode
1020
+ local mark_from, mark_to = data.mark_from, data.mark_to
1021
+
1022
+ -- Do nothing with invalid register (don't allow A-Z because they are used to
1023
+ -- append to lowercase register and have no use here)
1024
+ local reg_is_invalid = string.find(register, '^[0-9a-z"%-:.%%#=*+_/]$') == nil
1025
+ if reg_is_invalid then H.error('Register ' .. vim.inspect(register) .. ' is invalid.') end
1026
+
1027
+ -- Get reginfo and infer missing data (can be empty for special registers)
1028
+ local reg_info = vim.fn.getreginfo(register)
1029
+ if reg_info.regcontents == nil then H.error('Register ' .. vim.inspect(register) .. ' is empty.') end
1030
+ reg_info.regtype = reg_info.regtype or 'v'
1031
+
1032
+ -- Determine if region is at edge which is needed for the correct paste key
1033
+ local from_line, from_col = unpack(H.get_mark(mark_from))
1034
+ local to_line, to_col = unpack(H.get_mark(mark_to))
1035
+ local edge_to_col = vim.fn.col({ to_line, '$' }) - 1 - (vim.o.selection == 'exclusive' and 0 or 1)
1036
+
1037
+ local is_edge_line = submode == 'V' and to_line == vim.fn.line('$')
1038
+ local is_edge_col = submode ~= 'V' and to_col == edge_to_col and vim.o.virtualedit ~= 'all'
1039
+ local is_edge = is_edge_line or is_edge_col
1040
+
1041
+ local covers_linewise_all_buffer = is_edge_line and from_line == 1
1042
+
1043
+ -- Compute current indent if needed
1044
+ local init_indent
1045
+ local should_reindent = data.reindent_linewise and data.submode == 'V' and vim.o.equalprg == ''
1046
+ if should_reindent then init_indent = H.get_region_indent(mark_from, mark_to) end
1047
+
1048
+ -- Delete region to black whole register
1049
+ -- - Delete single character in blockwise submode with inclusive motion.
1050
+ -- See https://github.com/neovim/neovim/issues/24613
1051
+ local is_blockwise_single_cell = submode == H.submode_keys.block and from_line == to_line and from_col == to_col
1052
+ local forced_motion = is_blockwise_single_cell and 'v' or submode
1053
+
1054
+ local delete_data =
1055
+ { mark_from = mark_from, mark_to = mark_to, submode = forced_motion, mode = data.mode, register = '_' }
1056
+ H.do_between_marks('d', delete_data)
1057
+
1058
+ -- Set temporary register data to have proper submode and indent
1059
+ -- NOTE: use dedicated temporary register to workaround not being able to
1060
+ -- write register data into readonly registers ('%', '#', '.').
1061
+ local tmp_register, tmp_reg_info = register == '=' and '=' or 'x', vim.deepcopy(reg_info)
1062
+ if tmp_reg_info.regtype:sub(1, 1) ~= submode then tmp_reg_info.regtype = submode end
1063
+ if should_reindent then tmp_reg_info.regcontents = H.update_indent(tmp_reg_info.regcontents, init_indent) end
1064
+
1065
+ local cache_reg_info = vim.fn.getreginfo(tmp_register)
1066
+ vim.fn.setreg(tmp_register, tmp_reg_info)
1067
+
1068
+ -- Paste
1069
+ local expr_reg_keys = tmp_register == '=' and (reg_info.regcontents[1] .. '\r') or ''
1070
+ local paste_keys = (data.count or 1) .. '"' .. tmp_register .. expr_reg_keys .. (is_edge and 'p' or 'P')
1071
+ H.cmd_normal(paste_keys)
1072
+
1073
+ -- Restore temporary register data
1074
+ vim.fn.setreg(tmp_register, cache_reg_info)
1075
+
1076
+ -- Adjust cursor to be at start mark
1077
+ vim.api.nvim_win_set_cursor(0, { from_line, from_col })
1078
+
1079
+ -- Adjust for extra empty line after pasting inside empty buffer
1080
+ if covers_linewise_all_buffer then vim.api.nvim_buf_set_lines(0, 0, 1, true, {}) end
1081
+ end
1082
+
1083
+ -- Sort -----------------------------------------------------------------------
1084
+ H.sort_charwise_split = function(lines, split_patterns)
1085
+ local lines_str = table.concat(lines, '\n')
1086
+
1087
+ local pat
1088
+ for _, pattern in ipairs(split_patterns) do
1089
+ if lines_str:find(pattern) ~= nil then
1090
+ pat = pattern
1091
+ break
1092
+ end
1093
+ end
1094
+
1095
+ if pat == nil then return lines, {} end
1096
+
1097
+ -- Allow pattern to be an empty string to get every character
1098
+ if pat == '' then
1099
+ local parts = vim.split(lines_str, '')
1100
+ local seps = vim.fn['repeat']({ '' }, #parts - 1)
1101
+ return parts, seps
1102
+ end
1103
+
1104
+ -- Split into parts and separators
1105
+ local parts, seps = {}, {}
1106
+ local init, n = 1, lines_str:len()
1107
+ while init < n do
1108
+ local sep_from, sep_to = string.find(lines_str, pat, init)
1109
+ if sep_from == nil then break end
1110
+ table.insert(parts, lines_str:sub(init, sep_from - 1))
1111
+ table.insert(seps, lines_str:sub(sep_from, sep_to))
1112
+ init = sep_to + 1
1113
+ end
1114
+ table.insert(parts, lines_str:sub(init, n))
1115
+
1116
+ return parts, seps
1117
+ end
1118
+
1119
+ H.sort_charwise_unsplit = function(parts, seps)
1120
+ local all = {}
1121
+ for i = 1, #parts do
1122
+ table.insert(all, parts[i])
1123
+ table.insert(all, seps[i] or '')
1124
+ end
1125
+
1126
+ return vim.split(table.concat(all, ''), '\n')
1127
+ end
1128
+
1129
+ -- General --------------------------------------------------------------------
1130
+ H.apply_content_func = function(content_func, data)
1131
+ if data == nil then return end
1132
+ local mark_from, mark_to, submode = data.mark_from, data.mark_to, data.submode
1133
+ local reindent_linewise = data.reindent_linewise
1134
+
1135
+ H.with_temp_context({ marks = { '>' }, registers = { 'x', '"' } }, function()
1136
+ -- Yank effective region content into "x" register.
1137
+ data.register = 'x'
1138
+ H.do_between_marks('y', data)
1139
+
1140
+ -- Apply content function to register content
1141
+ local reg_info = vim.fn.getreginfo('x')
1142
+ local content_init = { lines = reg_info.regcontents, submode = submode }
1143
+ reg_info.regcontents = content_func(content_init)
1144
+ vim.fn.setreg('x', reg_info)
1145
+
1146
+ -- Replace region with new register content
1147
+ local replace_data = {
1148
+ count = 1,
1149
+ mark_from = mark_from,
1150
+ mark_to = mark_to,
1151
+ mode = data.mode,
1152
+ register = 'x',
1153
+ reindent_linewise = reindent_linewise,
1154
+ submode = submode,
1155
+ }
1156
+ H.replace_do(replace_data)
1157
+ end)
1158
+ end
1159
+
1160
+ H.do_between_marks = function(operator, data)
1161
+ -- Force 'inclusive' selection as `<C-v>` submode does not force it (while
1162
+ -- `v` does). This means that in case of 'selection=exclusive' marks should
1163
+ -- be adjusted prior to this.
1164
+ local cache_selection = vim.o.selection
1165
+ if data.mode == 'block' and vim.o.selection == 'exclusive' then vim.o.selection = 'inclusive' end
1166
+
1167
+ -- Allow positioning cursor past line end to work for regions with newline
1168
+ local cache_virtualedit = vim.o.virtualedit
1169
+ vim.o.virtualedit = 'onemore'
1170
+
1171
+ -- Don't trigger `TextYankPost` event as these yanks are not user-facing
1172
+ local is_yank = operator == 'y'
1173
+ local cache_eventignore = vim.o.eventignore
1174
+ if is_yank then vim.o.eventignore = 'TextYankPost' end
1175
+
1176
+ -- Make sure that marks `[` and `]` don't change after `y`
1177
+ local context_marks = { '<', '>' }
1178
+ if is_yank then context_marks = vim.list_extend(context_marks, { '[', ']' }) end
1179
+ H.with_temp_context({ marks = context_marks }, function()
1180
+ local mark_from, mark_to, submode, register = data.mark_from, data.mark_to, data.submode, data.register
1181
+ local keys
1182
+ if data.mode == 'visual' and vim.o.selection == 'exclusive' then
1183
+ keys = ('`' .. mark_from) .. submode .. ('`' .. mark_to) .. ('"' .. register .. operator)
1184
+ else
1185
+ keys = ('`' .. mark_from) .. ('"' .. register .. operator .. submode) .. ('`' .. mark_to)
1186
+ end
1187
+
1188
+ -- Make sure that outer action is dot-repeatable by cancelling effect of
1189
+ -- `d` or dot-repeatable `y`
1190
+ local cancel_redo = operator == 'd' or (operator == 'y' and vim.o.cpoptions:find('y') ~= nil)
1191
+ H.cmd_normal(keys, { cancel_redo = cancel_redo })
1192
+ end)
1193
+
1194
+ vim.o.selection = cache_selection
1195
+ vim.o.virtualedit = cache_virtualedit
1196
+ if is_yank then vim.o.eventignore = cache_eventignore end
1197
+ end
1198
+
1199
+ H.is_content = function(x) return type(x) == 'table' and H.islist(x.lines) and type(x.submode) == 'string' end
1200
+
1201
+ -- Marks ----------------------------------------------------------------------
1202
+ H.get_region_data = function(mode)
1203
+ local submode = H.get_submode(mode)
1204
+ local selection_is_visual = mode == 'visual'
1205
+
1206
+ -- Make sure that visual selection marks are relevant
1207
+ if selection_is_visual and H.is_visual_mode() then vim.cmd('normal! \27') end
1208
+
1209
+ local mark_from = selection_is_visual and '<' or '['
1210
+ local mark_to = selection_is_visual and '>' or ']'
1211
+
1212
+ -- Detect empty region. NOTE: This doesn't work when cursor is on first line
1213
+ -- and first column, but there doesn't seem to be a better way to do that.
1214
+ local pos_from, pos_to = H.get_mark(mark_from), H.get_mark(mark_to)
1215
+ if pos_to[1] < pos_from[1] or (pos_to[1] == pos_from[1] and pos_to[2] < pos_from[2]) then return end
1216
+
1217
+ return { mode = mode, submode = submode, mark_from = mark_from, mark_to = mark_to }
1218
+ end
1219
+
1220
+ H.get_region_indent = function(mark_from, mark_to)
1221
+ local l_from, l_to = H.get_mark(mark_from)[1], H.get_mark(mark_to)[1]
1222
+ local lines = vim.api.nvim_buf_get_lines(0, l_from - 1, l_to, true)
1223
+ return H.compute_indent(lines)
1224
+ end
1225
+
1226
+ H.get_mark = function(mark_name) return vim.api.nvim_buf_get_mark(0, mark_name) end
1227
+
1228
+ H.set_mark = function(mark_name, mark_data) vim.api.nvim_buf_set_mark(0, mark_name, mark_data[1], mark_data[2], {}) end
1229
+
1230
+ H.str_utfindex = function(s, i) return vim.str_utfindex(s, 'utf-32', i) end
1231
+ if vim.fn.has('nvim-0.11') == 0 then H.str_utfindex = function(s, i) return (vim.str_utfindex(s, i)) end end
1232
+
1233
+ H.str_byteindex = function(s, i) return vim.str_byteindex(s, 'utf-32', i) end
1234
+ if vim.fn.has('nvim-0.11') == 0 then H.str_byteindex = function(s, i) return vim.str_byteindex(s, i) end end
1235
+
1236
+ H.get_next_char_bytecol = function(markcoords)
1237
+ local line = vim.fn.getline(markcoords[1])
1238
+ local utf_index = H.str_utfindex(line, math.min(line:len(), markcoords[2] + 1))
1239
+ return H.str_byteindex(line, utf_index)
1240
+ end
1241
+
1242
+ -- Indent ---------------------------------------------------------------------
1243
+ H.compute_indent = function(lines)
1244
+ local res_indent, res_indent_width = nil, math.huge
1245
+ local blank_indent, blank_indent_width = nil, math.huge
1246
+ for _, l in ipairs(lines) do
1247
+ local cur_indent = l:match('^%s*')
1248
+ local cur_indent_width = cur_indent:len()
1249
+ local is_blank = cur_indent_width == l:len()
1250
+ if not is_blank and cur_indent_width < res_indent_width then
1251
+ res_indent, res_indent_width = cur_indent, cur_indent_width
1252
+ elseif is_blank and cur_indent_width < blank_indent_width then
1253
+ blank_indent, blank_indent_width = cur_indent, cur_indent_width
1254
+ end
1255
+ end
1256
+
1257
+ return res_indent or blank_indent or ''
1258
+ end
1259
+
1260
+ H.update_indent = function(lines, new_indent)
1261
+ -- Replace current indent with new indent without affecting blank lines
1262
+ local n_cur_indent = H.compute_indent(lines):len()
1263
+ return vim.tbl_map(function(l)
1264
+ if l:find('^%s*$') ~= nil then return l end
1265
+ return new_indent .. l:sub(n_cur_indent + 1)
1266
+ end, lines)
1267
+ end
1268
+
1269
+ -- Utilities ------------------------------------------------------------------
1270
+ H.error = function(msg) error('(mini.operators) ' .. msg, 0) end
1271
+
1272
+ H.check_type = function(name, val, ref, allow_nil)
1273
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1274
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1275
+ end
1276
+
1277
+ H.map = function(mode, lhs, rhs, opts)
1278
+ if lhs == '' then return end
1279
+ opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
1280
+ vim.keymap.set(mode, lhs, rhs, opts)
1281
+ end
1282
+
1283
+ H.get_submode = function(mode)
1284
+ if mode == 'visual' then return H.is_visual_mode() and vim.fn.mode() or vim.fn.visualmode() end
1285
+ return H.submode_keys[mode]
1286
+ end
1287
+
1288
+ H.is_visual_mode = function()
1289
+ local cur_mode = vim.fn.mode()
1290
+ return cur_mode == 'v' or cur_mode == 'V' or cur_mode == H.submode_keys.block
1291
+ end
1292
+
1293
+ H.with_temp_context = function(context, f)
1294
+ local res
1295
+ vim.api.nvim_buf_call(context.buf_id or 0, function()
1296
+ -- Cache temporary data
1297
+ local marks_data = {}
1298
+ for _, mark_name in ipairs(context.marks or {}) do
1299
+ marks_data[mark_name] = H.get_mark(mark_name)
1300
+ end
1301
+
1302
+ local reg_data = {}
1303
+ for _, reg_name in ipairs(context.registers or {}) do
1304
+ reg_data[reg_name] = vim.fn.getreginfo(reg_name)
1305
+ end
1306
+
1307
+ -- Perform action
1308
+ res = f()
1309
+
1310
+ -- Restore data
1311
+ for mark_name, data in pairs(marks_data) do
1312
+ pcall(H.set_mark, mark_name, data)
1313
+ end
1314
+ for reg_name, data in pairs(reg_data) do
1315
+ pcall(vim.fn.setreg, reg_name, data)
1316
+ end
1317
+ end)
1318
+
1319
+ return res
1320
+ end
1321
+
1322
+ -- A hack to restore previous dot-repeat action
1323
+ H.cancel_redo = function() end
1324
+ (function()
1325
+ local has_ffi, ffi = pcall(require, 'ffi')
1326
+ if not has_ffi then return end
1327
+ local has_cancel_redo = pcall(ffi.cdef, 'void CancelRedo(void)')
1328
+ if not has_cancel_redo then return end
1329
+ H.cancel_redo = function() pcall(ffi.C.CancelRedo) end
1330
+ end)()
1331
+
1332
+ H.cmd_normal = function(command, opts)
1333
+ opts = opts or {}
1334
+ local cancel_redo = opts.cancel_redo
1335
+ if cancel_redo == nil then cancel_redo = true end
1336
+
1337
+ vim.cmd('silent keepjumps normal! ' .. command)
1338
+
1339
+ if cancel_redo then H.cancel_redo() end
1340
+ end
1341
+
1342
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
1343
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
1344
+
1345
+ -- TODO: Remove after compatibility with Neovim=0.10 is dropped
1346
+ H.highlight_range = function(...) vim.hl.range(...) end
1347
+ if vim.fn.has('nvim-0.11') == 0 then H.highlight_range = function(...) vim.highlight.range(...) end end
1348
+
1349
+ return MiniOperators