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,2244 @@
1
+ --- *mini.ai* Extend and create a/i textobjects
2
+ ---
3
+ --- MIT License Copyright (c) 2022 Evgeni Chasnovski
4
+
5
+ --- Enhance some builtin |text-objects| (like |a(|, |a)|, |a'|, and more),
6
+ --- create new ones (like `a*`, `a<Space>`, `af`, `a?`, and more), and allow
7
+ --- user to create their own.
8
+ ---
9
+ --- Features:
10
+ --- - Customizable creation of `a`/`i` textobjects using Lua patterns and functions.
11
+ --- Supports:
12
+ --- - Dot-repeat.
13
+ --- - |v:count|.
14
+ --- - Different search methods (see |MiniAi.config|).
15
+ --- - Consecutive application (update selection without leaving Visual mode).
16
+ --- - Aliases for multiple textobjects.
17
+ ---
18
+ --- - Comprehensive builtin textobjects (see more in |MiniAi-builtin-textobjects|):
19
+ --- - Balanced brackets (with and without whitespace) plus alias.
20
+ --- - Balanced quotes plus alias.
21
+ --- - Function call.
22
+ --- - Argument.
23
+ --- - Tag.
24
+ --- - Derived from user prompt.
25
+ --- - Default for anything but Latin letters (to fall back to |text-objects|).
26
+ ---
27
+ --- For more textobjects see |MiniExtra.gen_ai_spec|.
28
+ ---
29
+ --- - Motions for jumping to left/right edge of textobject.
30
+ ---
31
+ --- - Set of specification generators to tweak some builtin textobjects (see
32
+ --- |MiniAi.gen_spec|).
33
+ ---
34
+ --- - Treesitter textobjects (through |MiniAi.gen_spec.treesitter()| helper).
35
+ ---
36
+ --- This module works by defining mappings for both `a` and `i` in Visual and
37
+ --- Operator-pending mode. After typing, they wait for single character user input
38
+ --- treated as textobject identifier and apply resolved textobject specification
39
+ --- (fall back to other mappings if can't find proper textobject id). For more
40
+ --- information see |MiniAi-textobject-specification| and |MiniAi-algorithm|.
41
+ ---
42
+ --- Known issues which won't be resolved:
43
+ --- - Search for builtin textobjects is done mostly using Lua patterns
44
+ --- (regex-like approach). Certain amount of false positives is to be expected.
45
+ ---
46
+ --- - During search for builtin textobjects there is no distinction if it is
47
+ --- inside string or comment. For example, in the following case there will
48
+ --- be wrong match for a function call: `f(a = ")", b = 1)`.
49
+ ---
50
+ --- General rule of thumb: any instrument using available parser for document
51
+ --- structure (like treesitter) will usually provide more precise results. This
52
+ --- module has builtins mostly for plain text textobjects which are useful
53
+ --- most of the times (like "inside brackets", "around quotes/underscore", etc.).
54
+ --- For advanced use cases define function specification for custom textobjects.
55
+ ---
56
+ --- What it doesn't (and probably won't) do:
57
+ --- - Have special operators to specially handle whitespace (like `I` and `A`
58
+ --- in `wellle/targets.vim`). Whitespace handling is assumed to be done inside
59
+ --- textobject specification (like `i(` and `i)` handle whitespace differently).
60
+ ---
61
+ --- # Setup ~
62
+ ---
63
+ --- This module needs a setup with `require('mini.ai').setup({})` (replace
64
+ --- `{}` with your `config` table). It will create global Lua table `MiniAi`
65
+ --- which you can use for scripting or manually (with `:lua MiniAi.*`).
66
+ ---
67
+ --- See |MiniAi.config| for available config settings.
68
+ ---
69
+ --- You can override runtime config settings (like `config.custom_textobjects`)
70
+ --- locally to buffer inside `vim.b.miniai_config` which should have same structure
71
+ --- as `MiniAi.config`. See |mini.nvim-buffer-local-config| for more details.
72
+ ---
73
+ --- To stop module from showing non-error feedback, set `config.silent = true`.
74
+ ---
75
+ --- # Comparisons ~
76
+ ---
77
+ --- - [wellle/targets.vim](https://github.com/wellle/targets.vim):
78
+ --- - Has limited support for creating own textobjects: it is constrained
79
+ --- to pre-defined detection rules. |mini.ai| allows creating own rules
80
+ --- via Lua patterns and functions (see |MiniAi-textobject-specification|).
81
+ --- - Doesn't provide any programmatical API for getting information about
82
+ --- textobjects. |mini.ai| does it via |MiniAi.find_textobject()|.
83
+ --- - Has no implementation of "moving to edge of textobject". |mini.ai|
84
+ --- does it via |MiniAi.move_cursor()| and `g[` and `g]` default mappings.
85
+ --- - Both implement the notion of manual "next"/"last" search directions.
86
+ --- - Implements `A`, `I` operators. |mini.ai| does not by design: it is
87
+ --- assumed to be a property of textobject, not operator.
88
+ --- - Doesn't implement "function call" and "user prompt" textobjects.
89
+ --- |mini.ai| does (with `f` and `?` identifiers).
90
+ --- - Has limited support for "argument" textobject. Although it works in
91
+ --- most situations, it often misdetects commas as argument separator
92
+ --- (like if it is inside quotes or `{}`). |mini.ai| deals with these cases.
93
+ --- - [nvim-treesitter/nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects):
94
+ --- - Along with textobject functionality provides a curated and maintained
95
+ --- set of popular textobject queries for many languages (which can power
96
+ --- |MiniAi.gen_spec.treesitter()| functionality).
97
+ --- - Both support working with |treesitter-directives| allowing more
98
+ --- fine-tuned textobjects.
99
+ --- - Implements only textobjects based on treesitter.
100
+ --- - Doesn't support |v:count|.
101
+ --- - Doesn't support multiple search method (basically, only `'cover'`).
102
+ --- - Doesn't support consecutive application of target textobject.
103
+ ---
104
+ --- # Disabling ~
105
+ ---
106
+ --- To disable, set `vim.g.miniai_disable` (globally) or `vim.b.miniai_disable`
107
+ --- (for a buffer) to `true`. Considering high number of different scenarios
108
+ --- and customization intentions, writing exact rules for disabling module's
109
+ --- functionality is left to user. See |mini.nvim-disabling-recipes| for common
110
+ --- recipes.
111
+ ---@tag MiniAi
112
+
113
+ --- This table describes all builtin textobjects along with what they
114
+ --- represent. Explanation:
115
+ --- - `Key` represents the textobject identifier: single character which should
116
+ --- be typed after `a`/`i`.
117
+ --- - `Name` is a description of textobject.
118
+ --- - `Example line` contains a string for which examples are constructed. The
119
+ --- `*` denotes the cursor position.
120
+ --- - `a`/`i` describe inclusive region representing `a` and `i` textobjects.
121
+ --- Use numbers in separators for easier navigation.
122
+ --- - `2a`/`2i` describe either `2a`/`2i` (support for |v:count|) textobjects
123
+ --- or `a`/`i` textobject followed by another `a`/`i` textobject (consecutive
124
+ --- application leads to incremental selection).
125
+ ---
126
+ --- Example: typing `va)` with cursor on `*` leads to selection from column 2
127
+ --- to column 12. Another typing `a)` changes selection to [1; 13]. Also, besides
128
+ --- visual selection, any |operator| can be used or `g[`/`g]` motions to move
129
+ --- to left/right edge of `a` textobject.
130
+ --- >
131
+ --- ┌───┬───────────────┬──────────────────┬────────┬────────┬────────┬────────┐
132
+ --- │Key│ Name │ Example line │ a │ i │ 2a │ 2i │
133
+ --- ├───┴───────────────┴──────────────────┴────────┴────────┴────────┴────────┤
134
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
135
+ --- │ ( │ Balanced () │ (( *a (bb) )) │ │ │ │ │
136
+ --- │ [ │ Balanced [] │ [[ *a [bb] ]] │ [2;12] │ [4;10] │ [1;13] │ [2;12] │
137
+ --- │ { │ Balanced {} │ {{ *a {bb} }} │ │ │ │ │
138
+ --- │ < │ Balanced <> │ << *a <bb> >> │ │ │ │ │
139
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
140
+ --- │ ) │ Balanced () │ (( *a (bb) )) │ │ │ │ │
141
+ --- │ ] │ Balanced [] │ [[ *a [bb] ]] │ │ │ │ │
142
+ --- │ } │ Balanced {} │ {{ *a {bb} }} │ [2;12] │ [3;11] │ [1;13] │ [2;12] │
143
+ --- │ > │ Balanced <> │ << *a <bb> >> │ │ │ │ │
144
+ --- │ b │ Alias for │ [( *a {bb} )] │ │ │ │ │
145
+ --- │ │ ), ], or } │ │ │ │ │ │
146
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
147
+ --- │ " │ Balanced " │ "*a" " bb " │ │ │ │ │
148
+ --- │ ' │ Balanced ' │ '*a' ' bb ' │ │ │ │ │
149
+ --- │ ` │ Balanced ` │ `*a` ` bb ` │ [1;4] │ [2;3] │ [6;11] │ [7;10] │
150
+ --- │ q │ Alias for │ '*a' " bb " │ │ │ │ │
151
+ --- │ │ ", ', or ` │ │ │ │ │ │
152
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
153
+ --- │ ? │ User prompt │ e*e o e o o │ [3;5] │ [4;4] │ [7;9] │ [8;8] │
154
+ --- │ │(typed e and o)│ │ │ │ │ │
155
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
156
+ --- │ t │ Tag │ <x><y>*a</y></x> │ [4;12] │ [7;8] │ [1;16] │ [4;12] │
157
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
158
+ --- │ f │ Function call │ f(a, g(*b, c) ) │ [6;13] │ [8;12] │ [1;15] │ [3;14] │
159
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
160
+ --- │ a │ Argument │ f(*a, g(b, c) ) │ [3;5] │ [3;4] │ [5;14] │ [7;13] │
161
+ --- ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈1234567890123456┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
162
+ --- │ │ Default │ aa_*b__cc___ │ [4;7] │ [4;5] │ [8;12] │ [8;9] │
163
+ --- │ │ (typed _) │ │ │ │ │ │
164
+ --- └───┴───────────────┴──────────────────┴────────┴────────┴────────┴────────┘
165
+ --- <
166
+ --- Notes:
167
+ --- - All examples assume default `config.search_method`.
168
+ --- - Open brackets differ from close brackets by how they treat inner edge
169
+ --- whitespace for `i` textobject: open ignores it, close - includes.
170
+ --- - Default textobject is activated for identifiers which are not Latin letters.
171
+ --- They are designed to be treated as separators, so include only right edge
172
+ --- in `a` textobject. To include both edges, use custom textobjects
173
+ --- (see |MiniAi-textobject-specification| and |MiniAi.config|). Note:
174
+ --- - When cursor is exactly on the identifier character while there are
175
+ --- two matching candidates on both left and right, the resulting region
176
+ --- with smaller width is preferred.
177
+ ---@tag MiniAi-builtin-textobjects
178
+
179
+ --- Note: this is similar to |MiniSurround-glossary|.
180
+ ---
181
+ --- REGION ~
182
+ --- Table representing region in a buffer. Fields:
183
+ --- - <from> and <to> for inclusive start and end positions (<to> might be
184
+ --- `nil` to describe empty region). Each position is also a table with
185
+ --- line <line> and column <col> (both start at 1).
186
+ --- - <vis_mode> for which Visual mode will be used to select textobject.
187
+ --- See `opts` argument of |MiniAi.select_textobject()|.
188
+ --- One of `'v'`, `'V'`, `'\22'` (escaped `'<C-v>'`).
189
+ ---
190
+ --- Examples: >lua
191
+ ---
192
+ --- { from = { line = 1, col = 1 }, to = { line = 2, col = 1 } }
193
+ ---
194
+ --- -- Forced linewise mode
195
+ --- {
196
+ --- from = { line = 1, col = 1 }, to = { line = 2, col = 1 },
197
+ --- vis_mode = 'V',
198
+ --- }
199
+ ---
200
+ --- -- Empty region
201
+ --- { from = { line = 10, col = 10 } }
202
+ --- <
203
+ --- PATTERN ~
204
+ --- String describing Lua pattern.
205
+ ---
206
+ --- SPAN ~
207
+ --- Interval inside a string (end-exclusive). Like [1, 5). Equal `from` and `to` edges
208
+ --- describe empty span at that point.
209
+ ---
210
+ --- Span `A = [a1, a2)` covers `B = [b1, b2)` if every element of `B` is within
211
+ --- `A` (`a1 <= b < a2`). It also is described as "B is nested inside A".
212
+ ---
213
+ --- NESTED PATTERN ~
214
+ --- Array of patterns aimed to describe nested spans.
215
+ ---
216
+ --- SPAN MATCHES NESTED PATTERN ~
217
+ --- If there is a sequence of consecutively nested spans each matching
218
+ --- corresponding pattern within substring of previous span (or input string
219
+ --- for first span). Example: >lua
220
+ ---
221
+ --- -- Nested patterns for balanced `()` with inner space
222
+ --- { '%b()', '^. .* .$' }
223
+ ---
224
+ --- -- Example input string (with columns underneath for easier reading):
225
+ --- "( ( () ( ) ) )"
226
+ --- -- 12345678901234
227
+ --- <
228
+ --- Here are all matching spans [1, 15) and [3, 13). Both [5, 7) and [8, 10)
229
+ --- match first pattern but not second. All other combinations of `(` and `)`
230
+ --- don't match first pattern (not balanced).
231
+ ---
232
+ --- COMPOSED PATTERN ~
233
+ --- Array with each element describing possible pattern (or array of them) at
234
+ --- that place. Composed pattern basically defines all possible combinations of
235
+ --- nested pattern (their cartesian product). Examples:
236
+ ---
237
+ --- 1. Either balanced `()` or balanced `[]` but both with inner edge space: >lua
238
+ ---
239
+ --- -- Composed pattern
240
+ --- { { '%b()', '%b[]' }, '^. .* .$' }
241
+ ---
242
+ --- -- Composed pattern expanded into equivalent array of nested patterns
243
+ --- { '%b()', '^. .* .$' } -- and
244
+ --- { '%b[]', '^. .* .$' }
245
+ --- <
246
+ --- 2. Either "balanced `()` with inner edge space" or "balanced `[]` with no
247
+ --- inner edge space", both with 5 or more characters: >lua
248
+ ---
249
+ --- -- Composed pattern
250
+ --- { { { '%b()', '^. .* .$' }, { '%b[]', '^.[^ ].*[^ ].$' } }, '.....' }
251
+ ---
252
+ --- -- Composed pattern expanded into equivalent array of nested patterns
253
+ --- { '%b()', '^. .* .$', '.....' } -- and
254
+ --- { '%b[]', '^.[^ ].*[^ ].$', '.....' }
255
+ --- <
256
+ --- SPAN MATCHES COMPOSED PATTERN ~
257
+ --- If it matches at least one nested pattern from expanded composed pattern.
258
+ ---@tag MiniAi-glossary
259
+
260
+ --- Textobject specification has a structure of composed pattern (see
261
+ --- |MiniAi-glossary|) with two differences:
262
+ --- - Last pattern(s) should have even number of empty capture groups denoting
263
+ --- how the last string should be processed to extract `a` or `i` textobject:
264
+ --- - Zero captures mean that whole string represents both `a` and `i`.
265
+ --- Example: `xxx` will define textobject matching string `xxx` literally.
266
+ --- - Two captures represent `i` textobject inside of them. `a` - whole string.
267
+ --- Example: `x()x()x` defines `a` textobject to be `xxx`, `i` - middle `x`.
268
+ --- - Four captures define `a` textobject inside captures 1 and 4, `i` -
269
+ --- inside captures 2 and 3. Example: `x()()x()x()` defines `a`
270
+ --- textobject to be last `xx`, `i` - middle `x`.
271
+ --- - Allows callable objects (see |vim.is_callable()|) in certain places
272
+ --- (enables more complex textobjects in exchange of increase in configuration
273
+ --- complexity and computations):
274
+ --- - If specification itself is a callable, it will be called with the same
275
+ --- arguments as |MiniAi.find_textobject()| and should return one of:
276
+ --- - Composed pattern. Useful for implementing user input. Example of
277
+ --- simplified variant of textobject for function call with name taken
278
+ --- from user prompt: >lua
279
+ ---
280
+ --- function()
281
+ --- local left_edge = vim.pesc(vim.fn.input('Function name: '))
282
+ --- return { left_edge .. '%b()', '^.-%(().*()%)$' }
283
+ --- end
284
+ --- <
285
+ --- - Single output region. Useful to allow full control over
286
+ --- textobject. Will be taken as is. Example of returning whole buffer: >lua
287
+ ---
288
+ --- function()
289
+ --- local from = { line = 1, col = 1 }
290
+ --- local to = {
291
+ --- line = vim.fn.line('$'),
292
+ --- col = math.max(vim.fn.getline('$'):len(), 1)
293
+ --- }
294
+ --- return { from = from, to = to, vis_mode = 'V' }
295
+ --- end
296
+ --- <
297
+ --- - Array of output region(s). Useful for incorporating other
298
+ --- instruments, like treesitter (see |MiniAi.gen_spec.treesitter()|).
299
+ --- The best region will be picked in the same manner as with composed
300
+ --- pattern (respecting options `n_lines`, `search_method`, etc.).
301
+ --- Example of selecting "best" line with display width more than 80: >lua
302
+ ---
303
+ --- function(_, _, _)
304
+ --- local res = {}
305
+ --- for i = 1, vim.api.nvim_buf_line_count(0) do
306
+ --- local cur_line = vim.fn.getline(i)
307
+ --- if vim.fn.strdisplaywidth(cur_line) > 80 then
308
+ --- local region = {
309
+ --- from = { line = i, col = 1 },
310
+ --- to = { line = i, col = cur_line:len() },
311
+ --- }
312
+ --- table.insert(res, region)
313
+ --- end
314
+ --- end
315
+ --- return res
316
+ --- end
317
+ --- <
318
+ --- - If there is a callable instead of assumed string pattern, it is expected
319
+ --- to have signature `(line, init)` and behave like `pattern:find()`.
320
+ --- It should return two numbers representing span in `line` next after
321
+ --- or at `init` (`nil` if there is no such span).
322
+ --- !IMPORTANT NOTE!: it means that output's `from` shouldn't be strictly
323
+ --- to the left of `init` (it will lead to infinite loop). Not allowed as
324
+ --- last item (as it should be pattern with captures).
325
+ --- Example of matching only balanced parenthesis with big enough width: >lua
326
+ ---
327
+ --- {
328
+ --- '%b()',
329
+ --- function(s, init)
330
+ --- if init > 1 or s:len() < 5 then return end
331
+ --- return 1, s:len()
332
+ --- end,
333
+ --- '^.().*().$'
334
+ --- }
335
+ --- <
336
+ --- More examples: >lua
337
+ ---
338
+ --- -- Pair of balanced brackets from set (used for builtin `b` identifier):
339
+ --- { { '%b()', '%b[]', '%b{}' }, '^.().*().$' }
340
+ ---
341
+ --- -- Imitate word ignoring digits and punctuation (only for Latin alphabet):
342
+ --- { '()()%f[%w]%w+()[ \t]*()' }
343
+ ---
344
+ --- -- Word with camel case support (also supports only Latin alphabet):
345
+ --- {
346
+ --- {
347
+ --- '%u[%l%d]+%f[^%l%d]',
348
+ --- '%f[%S][%l%d]+%f[^%l%d]',
349
+ --- '%f[%P][%l%d]+%f[^%l%d]',
350
+ --- '^[%l%d]+%f[^%l%d]',
351
+ --- },
352
+ --- '^().*()$'
353
+ --- }
354
+ ---
355
+ --- -- Number:
356
+ --- { '%f[%d]%d+' }
357
+ ---
358
+ --- -- Date in 'YYYY-MM-DD' format:
359
+ --- { '()%d%d%d%d%-%d%d%-%d%d()' }
360
+ ---
361
+ --- -- Lua block string:
362
+ --- { '%[%[().-()%]%]' }
363
+ --- <
364
+ --- See |MiniAi.gen_spec| for function wrappers to create commonly used
365
+ --- textobject specifications.
366
+ ---@tag MiniAi-textobject-specification
367
+
368
+ --- Search for the textobjects relies on these principles:
369
+ --- - It uses same input data as described in |MiniAi.find_textobject()|,
370
+ --- i.e. whether it is `a` or `i` textobject, its identifier, reference region, etc.
371
+ --- - Textobject specification is constructed based on textobject identifier
372
+ --- (see |MiniAi-textobject-specification|).
373
+ --- - General search is done by converting some 2d buffer region (neighborhood
374
+ --- of reference region) into 1d string (each line is appended with `\n`).
375
+ --- Then search for a best span matching textobject specification is done
376
+ --- inside string (see |MiniAi-glossary|). After that, span is converted back
377
+ --- into 2d region. Note: first search is done inside reference region lines,
378
+ --- and only after that - inside its neighborhood within `config.n_lines`
379
+ --- (see |MiniAi.config|).
380
+ --- - The best matching span is chosen by iterating over all spans matching
381
+ --- textobject specification and comparing them with "current best".
382
+ --- Comparison also depends on reference region (tighter covering is better,
383
+ --- otherwise closer is better) and search method (if span is even considered).
384
+ --- - Extract span based on extraction pattern (last item in nested pattern).
385
+ --- - If task is to perform a consecutive search (`opts.n_times` is greater than 1),
386
+ --- steps are repeated with current best match becoming reference region.
387
+ --- One such additional step is also done if final region is equal to
388
+ --- reference region (this enables consecutive application).
389
+ ---
390
+ --- Notes:
391
+ --- - Iteration over all matched spans is done in depth-first fashion with
392
+ --- respect to nested pattern.
393
+ --- - It is guaranteed that span is compared only once.
394
+ --- - For the sake of increasing functionality, during iteration over all
395
+ --- matching spans, some Lua patterns in composed pattern are handled
396
+ --- specially.
397
+ --- - `%bxx` (`xx` is two identical characters). It denotes balanced pair
398
+ --- of identical characters and results into "paired" matches. For
399
+ --- example, `%b""` for `"aa" "bb"` would match `"aa"` and `"bb"`, but
400
+ --- not middle `" "`.
401
+ --- - `x.-y` (`x` and `y` are different strings). It results only in matches with
402
+ --- smallest width. For example, `e.-o` for `e e o o` will result only in
403
+ --- middle `e o`. Note: it has some implications for when parts have
404
+ --- quantifiers (like `+`, etc.), which usually can be resolved with
405
+ --- frontier pattern `%f[]` (see examples in |MiniAi-textobject-specification|).
406
+ ---@tag MiniAi-algorithm
407
+
408
+ -- Module definition ==========================================================
409
+ local MiniAi = {}
410
+ local H = {}
411
+
412
+ --- Module setup
413
+ ---
414
+ ---@param config table|nil Module config table. See |MiniAi.config|.
415
+ ---
416
+ ---@usage >lua
417
+ --- require('mini.ai').setup() -- use default config
418
+ --- -- OR
419
+ --- require('mini.ai').setup({}) -- replace {} with your config table
420
+ --- <
421
+ MiniAi.setup = function(config)
422
+ -- TODO: Remove after Neovim=0.9 support is dropped
423
+ if vim.fn.has('nvim-0.10') == 0 then
424
+ vim.notify(
425
+ '(mini.ai) Neovim<0.10 is soft deprecated (module works but is not supported).'
426
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
427
+ .. ' Please update your Neovim version.'
428
+ )
429
+ end
430
+
431
+ -- Export module
432
+ _G.MiniAi = MiniAi
433
+
434
+ -- Setup config
435
+ config = H.setup_config(config)
436
+
437
+ -- Apply config
438
+ H.apply_config(config)
439
+ end
440
+
441
+ --- Defaults ~
442
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
443
+ ---@text # Custom textobjects ~
444
+ ---
445
+ --- User can define own textobjects by supplying `config.custom_textobjects`.
446
+ --- It should be a table with keys being single character textobject identifier
447
+ --- (supported by |getcharstr()|, except <Esc> and <C-c> which are used to cancel)
448
+ --- and values - textobject specification (see |MiniAi-textobject-specification|).
449
+ ---
450
+ --- General recommendations:
451
+ --- - This can be used to override builtin ones (|MiniAi-builtin-textobjects|).
452
+ --- Supply non-valid input (not in specification format) to disable module's
453
+ --- builtin textobject in favor of external or Neovim's builtin mapping.
454
+ --- - Keys should use character representation which can be |getcharstr()| output.
455
+ --- For example, `'\r'` and not `'<CR>'`.
456
+ ---
457
+ --- Examples:
458
+ --- >lua
459
+ --- require('mini.ai').setup({
460
+ --- custom_textobjects = {
461
+ --- -- Tweak argument textobject
462
+ --- a = require('mini.ai').gen_spec.argument({ brackets = { '%b()' } }),
463
+ ---
464
+ --- -- Disable brackets alias in favor of builtin block textobject
465
+ --- b = false,
466
+ ---
467
+ --- -- Now `vax` should select `xxx` and `vix` - middle `x`
468
+ --- x = { 'x()x()x' },
469
+ ---
470
+ --- -- Whole buffer
471
+ --- g = function()
472
+ --- local from = { line = 1, col = 1 }
473
+ --- local to = {
474
+ --- line = vim.fn.line('$'),
475
+ --- col = math.max(vim.fn.getline('$'):len(), 1)
476
+ --- }
477
+ --- return { from = from, to = to }
478
+ --- end
479
+ --- }
480
+ --- })
481
+ ---
482
+ --- -- Use `vim.b.miniai_config` to customize per buffer
483
+ --- -- Example of specification useful for Markdown files:
484
+ --- local spec_pair = require('mini.ai').gen_spec.pair
485
+ --- vim.b.miniai_config = {
486
+ --- custom_textobjects = {
487
+ --- ['*'] = spec_pair('*', '*', { type = 'greedy' }),
488
+ --- ['_'] = spec_pair('_', '_', { type = 'greedy' }),
489
+ --- },
490
+ --- }
491
+ --- <
492
+ --- There are more example specifications in |MiniAi-textobject-specification|.
493
+ ---
494
+ --- # Search method ~
495
+ ---
496
+ --- Value of `config.search_method` defines how best match search is done.
497
+ --- Based on its value, one of the following matches will be selected:
498
+ --- - Covering match. Left/right edge is before/after left/right edge of
499
+ --- reference region.
500
+ --- - Previous match. Left/right edge is before left/right edge of reference
501
+ --- region.
502
+ --- - Next match. Left/right edge is after left/right edge of reference region.
503
+ --- - Nearest match. Whichever is closest among previous and next matches.
504
+ ---
505
+ --- Possible values are:
506
+ --- - `'cover'` - use only covering match. Don't use either previous or
507
+ --- next; report that there is no textobject found.
508
+ --- - `'cover_or_next'` (default) - use covering match. If not found, use next.
509
+ --- - `'cover_or_prev'` - use covering match. If not found, use previous.
510
+ --- - `'cover_or_nearest'` - use covering match. If not found, use nearest.
511
+ --- - `'next'` - use next match.
512
+ --- - `'prev'` - use previous match.
513
+ --- - `'nearest'` - use nearest match.
514
+ ---
515
+ --- Note: search is first performed on the reference region lines and only
516
+ --- after failure - on the whole neighborhood defined by `config.n_lines`. This
517
+ --- means that with `config.search_method` not equal to `'cover'`, "prev" or
518
+ --- "next" textobject will end up as search result if they are found on first
519
+ --- stage although covering match might be found in bigger, whole neighborhood.
520
+ --- This design is based on observation that most of the time operation is done
521
+ --- within reference region lines (usually cursor line).
522
+ ---
523
+ --- Here is an example of what `a)` textobject is based on a value of
524
+ --- `'config.search_method'` when cursor is inside `bbb` word:
525
+ --- - `'cover'`: `(a) bbb (c)` -> none
526
+ --- - `'cover_or_next'`: `(a) bbb (c)` -> `(c)`
527
+ --- - `'cover_or_prev'`: `(a) bbb (c)` -> `(a)`
528
+ --- - `'cover_or_nearest'`: depends on cursor position.
529
+ --- For first and second `b` - as in `cover_or_prev` (as previous match is
530
+ --- nearer), for third - as in `cover_or_next` (as next match is nearer).
531
+ --- - `'next'`: `(a) bbb (c)` -> `(c)`. Same outcome for `(bbb)`.
532
+ --- - `'prev'`: `(a) bbb (c)` -> `(a)`. Same outcome for `(bbb)`.
533
+ --- - `'nearest'`: depends on cursor position (same as in `'cover_or_nearest'`).
534
+ ---
535
+ --- # Mappings ~
536
+ ---
537
+ --- Mappings `around_next` / `inside_next` and `around_last` / `inside_last` are
538
+ --- essentially `around` / `inside` but using search method `'next'` and `'prev'`.
539
+ ---
540
+ --- ## Overriding default `an` and `in` ~
541
+ --- *MiniAi-default-an-in*
542
+ ---
543
+ --- Default `around_next` / `inside_next` mappings override newly added in Neovim=0.12
544
+ --- built-in mappings |v_an| and |v_in|. This is intentional for better usability
545
+ --- and backwards compatibility. There are several ways to work around this:
546
+ ---
547
+ --- - Remap Neovim's `an` / `in` to something else before calling |MiniAi.setup()|: >lua
548
+ ---
549
+ --- -- Use `<Leader>ls` and `<Leader>lS` for incremental selection
550
+ --- local copy_keymap = function(mode, from_lhs, to_lhs)
551
+ --- local keymap = vim.fn.maparg(from_lhs, mode, false, true)
552
+ --- local rhs = keymap.callback or keymap.rhs
553
+ --- vim.keymap.set(mode, to_lhs, rhs, { desc = keymap.desc })
554
+ --- end
555
+ --- copy_keymap('x', 'an', '<Leader>ls')
556
+ --- copy_keymap('x', 'in', '<Leader>lS')
557
+ ---
558
+ --- -- ...
559
+ --- require('mini.ai').setup({...})
560
+ --- <
561
+ --- - Use |MiniAi.gen_spec.treesitter()| with one or many captures for versatile
562
+ --- tree-sitter based textobjects. It also allows consecutive application in
563
+ --- Visual mode: similar to incremental selection, with `next` / `last` variants,
564
+ --- but without "decrease selection".
565
+ ---
566
+ --- - Use other values for "next" / "last" variants. For example: >lua
567
+ ---
568
+ --- require('mini.ai').setup({
569
+ --- mappings = {
570
+ --- around_next = 'aN',
571
+ --- inside_next = 'iN',
572
+ --- around_last = 'aL',
573
+ --- inside_last = 'iL',
574
+ --- }
575
+ --- })
576
+ --- <
577
+ --- ## Overriding default `al` and `il` ~
578
+ --- *MiniAi-default-al-il*
579
+ ---
580
+ --- Default `around_last` / `inside_last` mappings override newly added in Neovim=0.13
581
+ --- built-in mappings |al| and |il|. This is intentional for better usability
582
+ --- and backwards compatibility. There are several ways to work around this:
583
+ ---
584
+ --- - Use |MiniExtra.gen_ai_spec.buffer()| and |MiniExtra.gen_ai_spec.line()| to
585
+ --- create custom textobjects for buffer and line: >lua
586
+ ---
587
+ --- require('mini.extra').setup()
588
+ --- require('mini.ai').setup({
589
+ --- custom_textobjects = {
590
+ --- -- Makes `aB` equivalent to built-in `al`
591
+ --- B = MiniExtra.gen_ai_spec.buffer(),
592
+ --- -- Makes `iL` equivalent to built-in `il`
593
+ --- L = MiniExtra.gen_ai_spec.line(),
594
+ --- },
595
+ --- })
596
+ --- <
597
+ --- - Use other values for "next" / "last" variants. See |MiniAi-default-an-in|.
598
+ MiniAi.config = {
599
+ -- Table with textobject id as fields, textobject specification as values.
600
+ -- Also use this to disable builtin textobjects. See |MiniAi.config|.
601
+ custom_textobjects = nil,
602
+
603
+ -- Module mappings. Use `''` (empty string) to disable one.
604
+ mappings = {
605
+ -- Main textobject prefixes
606
+ around = 'a',
607
+ inside = 'i',
608
+
609
+ -- Next/last textobjects
610
+ -- NOTE: This (deliberately) overrides Neovim>=0.12 built-in incremental
611
+ -- selection mappings. See `:h MiniAi-default-an-in` for more details.
612
+ around_next = 'an',
613
+ inside_next = 'in',
614
+ around_last = 'al',
615
+ inside_last = 'il',
616
+
617
+ -- Move cursor to corresponding edge of `a` textobject
618
+ goto_left = 'g[',
619
+ goto_right = 'g]',
620
+ },
621
+
622
+ -- Number of lines within which textobject is searched
623
+ n_lines = 50,
624
+
625
+ -- How to search for object (first inside current line, then inside
626
+ -- neighborhood). One of 'cover', 'cover_or_next', 'cover_or_prev',
627
+ -- 'cover_or_nearest', 'next', 'prev', 'nearest'.
628
+ search_method = 'cover_or_next',
629
+
630
+ -- Whether to disable showing non-error feedback
631
+ -- This also affects (purely informational) helper messages shown after
632
+ -- idle time if user input is required.
633
+ silent = false,
634
+ }
635
+ --minidoc_afterlines_end
636
+
637
+ -- Module functionality =======================================================
638
+ --- Find textobject region
639
+ ---
640
+ ---@param ai_type string One of `'a'` or `'i'`.
641
+ ---@param id string Single character string representing textobject id. It is
642
+ --- used to get specification which is later used to compute textobject region.
643
+ --- Note: if specification is a function, it is called with all present
644
+ --- arguments (`opts` is populated with default arguments).
645
+ ---@param opts table|nil Options. Possible fields:
646
+ --- - <n_lines> - Number of lines within which textobject is searched.
647
+ --- Default: `config.n_lines` (see |MiniAi.config|).
648
+ --- - <n_times> - Number of times to perform a consecutive search. Each one
649
+ --- is done with reference region being previous found textobject region.
650
+ --- Default: 1.
651
+ --- - <reference_region> - region to try to cover (see |MiniAi-glossary|). It
652
+ --- is guaranteed that output region will not be inside or equal to this one.
653
+ --- Default: empty region at cursor position.
654
+ --- - <search_method> - Search method. Default: `config.search_method`.
655
+ ---
656
+ ---@return table|nil Region of textobject or `nil` if no textobject different
657
+ --- from `opts.reference_region` was consecutively found `opts.n_times` times.
658
+ MiniAi.find_textobject = function(ai_type, id, opts)
659
+ if not (ai_type == 'a' or ai_type == 'i') then H.error([[`ai_type` should be one of 'a' or 'i'.]]) end
660
+ opts = vim.tbl_deep_extend('force', H.get_default_opts(), opts or {})
661
+ H.validate_search_method(opts.search_method)
662
+
663
+ -- Get textobject specification
664
+ local tobj_spec = H.get_textobject_spec(id, { ai_type, id, opts })
665
+ if tobj_spec == nil then return end
666
+ if H.is_region(tobj_spec) then return tobj_spec end
667
+
668
+ -- Find region
669
+ local res = H.find_textobject_region(tobj_spec, ai_type, opts)
670
+
671
+ if res == nil then
672
+ local msg = string.format(
673
+ [[No textobject %s found covering region%s within %d line%s and `search_method = '%s'`.]],
674
+ vim.inspect(ai_type .. id),
675
+ opts.n_times == 1 and '' or (' %s times'):format(opts.n_times),
676
+ opts.n_lines,
677
+ opts.n_lines == 1 and '' or 's',
678
+ opts.search_method
679
+ )
680
+ H.message(msg)
681
+ end
682
+
683
+ return res
684
+ end
685
+
686
+ --- Move cursor to edge of textobject
687
+ ---
688
+ ---@param side string One of `'left'` or `'right'`.
689
+ ---@param ai_type string One of `'a'` or `'i'`.
690
+ ---@param id string Single character string representing textobject id.
691
+ ---@param opts table|nil Same as in |MiniAi.find_textobject()|.
692
+ --- `opts.n_times` means number of actual jumps (important when cursor
693
+ --- already on the potential jump spot).
694
+ MiniAi.move_cursor = function(side, ai_type, id, opts)
695
+ if not (side == 'left' or side == 'right') then H.error([[`side` should be one of 'left' or 'right'.]]) end
696
+ opts = opts or {}
697
+ local init_pos = vim.api.nvim_win_get_cursor(0)
698
+
699
+ -- Compute single textobject first to find out if it would move the cursor.
700
+ -- If not, then eventual `n_times` should be bigger by 1 to imitate `n_times`
701
+ -- *actual* jumps. This implements consecutive jumps and has logic of "If
702
+ -- cursor is strictly inside region, move to its side first".
703
+ local new_opts = vim.tbl_deep_extend('force', opts, { n_times = 1 })
704
+ local tobj_single = MiniAi.find_textobject(ai_type, id, new_opts)
705
+ if tobj_single == nil then return end
706
+ local tobj_side = side == 'left' and 'from' or 'to'
707
+
708
+ -- Allow empty region
709
+ tobj_single.to = tobj_single.to or tobj_single.from
710
+
711
+ new_opts.n_times = opts.n_times or 1
712
+ if (init_pos[1] == tobj_single[tobj_side].line) and (init_pos[2] == tobj_single[tobj_side].col - 1) then
713
+ new_opts.n_times = new_opts.n_times + 1
714
+ end
715
+
716
+ -- Compute actually needed textobject while avoiding unnecessary computation
717
+ -- in a most common usage (`v:count1 == 1`)
718
+ local pos = tobj_single[tobj_side]
719
+ if new_opts.n_times > 1 then
720
+ local tobj = MiniAi.find_textobject(ai_type, id, new_opts)
721
+ if tobj == nil then return end
722
+ tobj.to = tobj.to or tobj.from
723
+ pos = tobj[tobj_side]
724
+ end
725
+
726
+ -- Move cursor and open enough folds
727
+ vim.cmd("normal! m'")
728
+ vim.api.nvim_win_set_cursor(0, { pos.line, pos.col - 1 })
729
+ vim.cmd('normal! zv')
730
+ end
731
+
732
+ --- Generate common textobject specifications
733
+ ---
734
+ --- This is a table with function elements. Call to actually get specification.
735
+ ---
736
+ --- Example: >lua
737
+ ---
738
+ --- local gen_spec = require('mini.ai').gen_spec
739
+ --- require('mini.ai').setup({
740
+ --- custom_textobjects = {
741
+ --- -- Tweak argument to be recognized only inside `()` between `;`
742
+ --- a = gen_spec.argument({ brackets = { '%b()' }, separator = ';' }),
743
+ ---
744
+ --- -- Tweak function call to not detect dot in function name
745
+ --- f = gen_spec.function_call({ name_pattern = '[%w_]' }),
746
+ ---
747
+ --- -- Function definition (needs treesitter queries with these captures)
748
+ --- F = gen_spec.treesitter({ a = '@function.outer', i = '@function.inner' }),
749
+ ---
750
+ --- -- Make `|` select both edges in non-balanced way
751
+ --- ['|'] = gen_spec.pair('|', '|', { type = 'non-balanced' }),
752
+ --- }
753
+ --- })
754
+ --- <
755
+ MiniAi.gen_spec = {}
756
+
757
+ --- Argument specification
758
+ ---
759
+ --- Argument textobject (has default `a` identifier) is a region inside
760
+ --- balanced bracket between allowed not excluded separators. Use this function
761
+ --- to tweak how it works.
762
+ ---
763
+ --- Examples:
764
+ --- - `argument({ brackets = { '%b()' } })` will search for an argument only
765
+ --- inside balanced `()`.
766
+ --- - `argument({ separator = '[,;]' })` will treat both `,` and `;` as separators.
767
+ --- - `argument({ exclude_regions = { '%b()' } })` will exclude separators
768
+ --- which are inside balanced `()` (inside outer brackets).
769
+ ---
770
+ ---@param opts table|nil Options. Allowed fields:
771
+ --- - <brackets> - array of patterns for outer balanced brackets.
772
+ --- Default: `{ '%b()', '%b[]', '%b{}' }` (any `()`, `[]`, or `{}` can
773
+ --- enclose arguments).
774
+ --- - <separator> - separator pattern. Default: `','`.
775
+ --- One of the practical usages of this option is to include whitespace
776
+ --- around character to be a part of separator. For example, `'%s*,%s*'`
777
+ --- will treat as separator not only `,`, but its possible surrounding
778
+ --- whitespace. This has both positive and negative effects. On one hand,
779
+ --- `daa` executed over the first argument will delete whitespace after
780
+ --- first comma, leading to a more expected outcome. On the other hand it
781
+ --- is ambiguous which argument is picked when cursor is over whitespace
782
+ --- near the character separator.
783
+ --- - <exclude_regions> - array with patterns for regions inside which
784
+ --- separators will be ignored.
785
+ --- Default: `{ '%b""', "%b''", '%b()', '%b[]', '%b{}' }` (separators
786
+ --- inside balanced quotes or brackets are ignored).
787
+ MiniAi.gen_spec.argument = function(opts)
788
+ opts = vim.tbl_extend('force', {
789
+ brackets = { '%b()', '%b[]', '%b{}' },
790
+ separator = ',',
791
+ exclude_regions = { '%b""', "%b''", '%b()', '%b[]', '%b{}' },
792
+ }, opts or {})
793
+
794
+ local brackets, separator, exclude_regions = opts.brackets, opts.separator, opts.exclude_regions
795
+
796
+ local res = {}
797
+ -- Match brackets
798
+ res[1] = brackets
799
+
800
+ -- Match argument with both left and right separators/brackets
801
+ res[2] = function(s, init)
802
+ -- Cache string separators per spec as they are used multiple times.
803
+ -- Storing per spec allows coexistence of several argument specifications.
804
+ H.cache.argument_sep_spans = H.cache.argument_sep_spans or {}
805
+ H.cache.argument_sep_spans[res] = H.cache.argument_sep_spans[res] or {}
806
+ local sep_spans = H.cache.argument_sep_spans[res][s] or H.arg_get_separator_spans(s, separator, exclude_regions)
807
+ H.cache.argument_sep_spans[res][s] = sep_spans
808
+
809
+ -- Return span fully on right of `init`, `nil` otherwise
810
+ -- For first argument returns left bracket; for last - right one.
811
+ for i = 1, #sep_spans - 1 do
812
+ if init <= sep_spans[i][1] then return sep_spans[i][1], sep_spans[i + 1][2] end
813
+ end
814
+
815
+ return nil
816
+ end
817
+
818
+ -- Make extraction part
819
+ --
820
+ -- Extraction of `a` type depends on argument number, `i` - as `a` but
821
+ -- without separators and inner whitespace. The reason for this complex
822
+ -- solution are the following requirements:
823
+ -- - Don't match argument region when cursor is on the outer bracket.
824
+ -- Example: `f(xxx)` should select argument only when cursor is on 'x'.
825
+ -- - Don't select edge whitespace for first and last argument BUT MATCH WHEN
826
+ -- CURSOR IS ON THEM which needs to match edge whitespace right until the
827
+ -- extraction part. This is useful when working with padded brackets.
828
+ -- Example for `f( xx , yy )`:
829
+ -- - `a` object should select 'xx ,' when cursor is on all ' xx ';
830
+ -- should select ', yy' when cursor is on all ' yy '.
831
+ -- - `i` object should select 'xx' when cursor is on all ' xx ';
832
+ -- should select 'yy' when cursor is on all ' yy '.
833
+ --
834
+ -- At this stage whether argument is first, middle, last, or single is
835
+ -- determined by presence of matching separator at either left or right edge.
836
+ -- If edge matches separator pattern - it has separator. If not - a bracket.
837
+ local left_edge_separator = '^' .. separator
838
+ local find_after_left_separator = function(s)
839
+ local _, sep_end = s:find(left_edge_separator)
840
+ if sep_end == nil then return nil end
841
+ return sep_end + 1
842
+ end
843
+ local find_after_left_bracket = function(s)
844
+ local left_sep = find_after_left_separator(s)
845
+ if left_sep ~= nil then return nil end
846
+ return 2
847
+ end
848
+
849
+ local right_edge_sep = separator .. '$'
850
+ local find_before_right_separator = function(s)
851
+ local sep_start, _ = s:find(right_edge_sep)
852
+ if sep_start == nil then return nil end
853
+ return sep_start - 1
854
+ end
855
+ local find_before_right_bracket = function(s)
856
+ local right_sep = find_before_right_separator(s)
857
+ if right_sep ~= nil then return nil end
858
+ return s:len() - 1
859
+ end
860
+
861
+ local match_and_include = function(left_type, left_include, right_type, right_include)
862
+ local find_after_left = left_type == 'bracket' and find_after_left_bracket or find_after_left_separator
863
+ local find_before_right = right_type == 'bracket' and find_before_right_bracket or find_before_right_separator
864
+
865
+ return function(s, init)
866
+ -- Match only once
867
+ if init > 1 then return nil end
868
+
869
+ -- Make sure that string matches left and right targets
870
+ local left_after, right_before = find_after_left(s), find_before_right(s)
871
+ if left_after == nil or right_before == nil then return nil end
872
+
873
+ -- Possibly include matched edge targets
874
+ local left = left_include and 1 or left_after
875
+ local right = right_include and s:len() or right_before
876
+
877
+ return left, right
878
+ end
879
+ end
880
+
881
+ local extract_first_arg = '^%s*()().-()%s*' .. separator .. '()$'
882
+ local extract_nonfirst_arg = '^()' .. separator .. '%s*().-()()%s*$'
883
+ local extract_single_arg = '^%s*().-()%s*$'
884
+
885
+ res[3] = {
886
+ -- First argument. Include right separator, exclude left whitespace.
887
+ { match_and_include('bracket', false, 'separator', true), extract_first_arg },
888
+
889
+ -- Middle argument. Include only left separator.
890
+ { match_and_include('separator', true, 'separator', false), extract_nonfirst_arg },
891
+
892
+ -- Last argument. Include left separator, exclude right whitespace.
893
+ -- NOTE: it misbehaves for whitespace argument. It's OK because it's rare.
894
+ { match_and_include('separator', true, 'bracket', false), extract_nonfirst_arg },
895
+
896
+ -- Single argument. Include both whitespace (makes `aa` and `ia` differ).
897
+ { match_and_include('bracket', false, 'bracket', false), extract_single_arg },
898
+ }
899
+
900
+ return res
901
+ end
902
+
903
+ --- Function call specification
904
+ ---
905
+ --- Function call textobject (has default `f` identifier) is a region with some
906
+ --- characters followed by balanced `()`. Use this function to tweak how it works.
907
+ ---
908
+ --- Example:
909
+ --- - `function_call({ name_pattern = '[%w_]' })` will recognize function name with
910
+ --- only alphanumeric or underscore (not dot).
911
+ ---
912
+ ---@param opts table|nil Options. Allowed fields:
913
+ --- - <name_pattern> - string pattern of character set allowed in function name.
914
+ --- Default: `'[%w_%.]'` (alphanumeric, underscore, or dot).
915
+ --- Note: should be enclosed in `[]`.
916
+ MiniAi.gen_spec.function_call = function(opts)
917
+ opts = vim.tbl_deep_extend('force', { name_pattern = '[%w_%.]' }, opts or {})
918
+ -- Use frontier pattern to select widest possible name
919
+ return { '%f' .. opts.name_pattern .. opts.name_pattern .. '+%b()', '^.-%(().*()%)$' }
920
+ end
921
+
922
+ --- Pair specification
923
+ ---
924
+ --- Use it to define textobject for region surrounded with `left` from left and
925
+ --- `right` from right. The `a` textobject includes both edges, `i` - excludes them.
926
+ ---
927
+ --- Region can be one of several types (controlled with `opts.type`). All
928
+ --- examples are for default search method, `a` textobject, and use `'_'` as
929
+ --- both `left` and `right`:
930
+ --- - Non-balanced (`{ type = 'non-balanced' }`), default. Equivalent to using
931
+ --- `x.-y` as first pattern. Example: on line `_a_b_c_` it consecutively
932
+ --- matches `_a_`, `_b_`, `_c_`.
933
+ --- - Balanced (`{ type = 'balanced' }`). Equivalent to using `%bxy` as first
934
+ --- pattern. Example: on line `_a_b_c_` it consecutively matches `_a_`, `_c_`.
935
+ --- Note: both `left` and `right` should be single character.
936
+ --- - Greedy (`{ type = 'greedy' }`). Like non-balanced but will select maximum
937
+ --- consecutive `left` and `right` edges. Example: on line `__a__b_` it
938
+ --- consecutively selects `__a__` and `__b_`. Note: both `left` and `right`
939
+ --- should be single character.
940
+ ---
941
+ ---@param left string Left edge.
942
+ ---@param right string Right edge.
943
+ ---@param opts table|nil Options. Possible fields:
944
+ --- - <type> - Type of a pair. One of `'non-balanced'` (default), `'balanced'`,
945
+ --- `'greedy'`.
946
+ MiniAi.gen_spec.pair = function(left, right, opts)
947
+ if not (type(left) == 'string' and type(right) == 'string') then
948
+ H.error('Both `left` and `right` should be strings.')
949
+ end
950
+ opts = vim.tbl_deep_extend('force', { type = 'non-balanced' }, opts or {})
951
+
952
+ if (opts.type == 'balanced' or opts.type == 'greedy') and not (left:len() == 1 and right:len() == 1) then
953
+ local msg =
954
+ string.format([[Both `left` and `right` should be single character for `opts.type == '%s'`.]], opts.type)
955
+ H.error(msg)
956
+ end
957
+
958
+ local left_esc = vim.pesc(left)
959
+ local right_esc = vim.pesc(right)
960
+
961
+ if opts.type == 'balanced' then return { string.format('%%b%s%s', left, right), '^.().*().$' } end
962
+ if opts.type == 'non-balanced' then return { string.format('%s().-()%s', left_esc, right_esc) } end
963
+ if opts.type == 'greedy' then
964
+ return { string.format('%%f[%s]%s+()[^%s]-()%s+%%f[^%s]', left_esc, left_esc, left_esc, right_esc, right_esc) }
965
+ end
966
+
967
+ H.error([[`opts.type` should be one of 'balanced', 'non-balanced', 'greedy'.]])
968
+ end
969
+
970
+ --- Treesitter specification
971
+ ---
972
+ --- This is a specification in function form. When called with a pair of
973
+ --- treesitter captures, it returns a specification function outputting an
974
+ --- array of regions that match corresponding (`a` or `i`) capture.
975
+ ---
976
+ --- In order for this to work, apart from working treesitter parser for desired
977
+ --- language, user should have a reachable language-specific `'textobjects'`
978
+ --- query (see |vim.treesitter.query.get()|).
979
+ --- The most straightforward way for this is to have `textobjects.scm` query
980
+ --- file with treesitter captures stored in some recognized path. This is
981
+ --- primarily designed to be compatible with plugin
982
+ --- `nvim-treesitter/nvim-treesitter-textobjects`, but can be used without it.
983
+ ---
984
+ --- Two most common approaches for having a query file:
985
+ --- - Install `nvim-treesitter/nvim-treesitter-textobjects`. It has curated and
986
+ --- well maintained builtin query files for many languages with a standardized
987
+ --- capture names, like `function.outer`, `function.inner`, etc.
988
+ --- - Manually create file `after/queries/<language-name>/textobjects.scm` in
989
+ --- your |$XDG_CONFIG_HOME| directory. It should contain queries with
990
+ --- captures (later used to define textobjects). See |lua-treesitter-query|.
991
+ --- To verify that query file is reachable, run (example for "lua" language,
992
+ --- output should have at least an intended file): >vim
993
+ ---
994
+ --- :lua print(vim.inspect(vim.treesitter.query.get_files('lua','textobjects')))
995
+ --- <
996
+ --- Example configuration for function definition textobject with
997
+ --- `nvim-treesitter/nvim-treesitter-textobjects` captures:
998
+ --- >lua
999
+ --- local spec_treesitter = require('mini.ai').gen_spec.treesitter
1000
+ --- require('mini.ai').setup({
1001
+ --- custom_textobjects = {
1002
+ --- F = spec_treesitter({ a = '@function.outer', i = '@function.inner' }),
1003
+ --- o = spec_treesitter({
1004
+ --- a = { '@conditional.outer', '@loop.outer' },
1005
+ --- i = { '@conditional.inner', '@loop.inner' },
1006
+ --- })
1007
+ --- }
1008
+ --- })
1009
+ --- <
1010
+ --- Notes:
1011
+ --- - Be sure that query files don't contain unknown |treesitter-directives|
1012
+ --- (like `#make-range!`, for example). Otherwise textobject for such capture
1013
+ --- might not be found as |lua-treesitter-core| won't treat them as captures.
1014
+ --- Verify with `:=vim.treesitter.query.get('lang', 'textobjects')` and see
1015
+ --- if the target capture is recognized as one.
1016
+ --- - It uses buffer's |filetype| to determine query language.
1017
+ --- - It first searches the language under cursor for matches. If no matches are
1018
+ --- found, it falls back to searching parent languages (up to the buffer's root
1019
+ --- language). If no matches are found again, it falls back to recursively
1020
+ --- searching all children languages (from the language under cursor). If no
1021
+ --- matches again - report no matches.
1022
+ --- - On large files it is slower than pattern-based textobjects. Still very
1023
+ --- fast though (one search should be magnitude of milliseconds or tens of
1024
+ --- milliseconds on really large file).
1025
+ ---
1026
+ ---@param ai_captures table Captures for `a` and `i` textobjects: table with
1027
+ --- <a> and <i> fields with captures for `a` and `i` textobjects respectively.
1028
+ --- Each value can be either a string capture (should start with `'@'`) or an
1029
+ --- array of such captures (best among all matches will be chosen).
1030
+ ---@param opts table|nil Options. Possible values:
1031
+ --- - <use_nvim_treesitter> - whether to try to use `nvim-treesitter` plugin
1032
+ --- (if present) to do the query. It used to implement more advanced behavior
1033
+ --- and more coherent experience if `nvim-treesitter-textobjects` queries are
1034
+ --- used. However, as |lua-treesitter-core| methods are more capable now,
1035
+ --- the option will soon be removed. Only present for backward compatibility.
1036
+ --- Default: `false`.
1037
+ ---
1038
+ ---@return function Function with |MiniAi.find_textobject()| signature which
1039
+ --- returns array of current buffer regions representing matches for
1040
+ --- corresponding (`a` or `i`) treesitter capture.
1041
+ ---
1042
+ ---@seealso - |MiniAi-textobject-specification| for how this type of textobject
1043
+ --- specification is processed.
1044
+ --- - |vim.treesitter.query.get()| for how query is fetched.
1045
+ --- - |Query:iter_captures()| for how all query captures are iterated in case of
1046
+ --- no `nvim-treesitter`.
1047
+ MiniAi.gen_spec.treesitter = function(ai_captures, opts)
1048
+ -- TODO: Remove after releasing 'mini.nvim' 0.17.0
1049
+ opts = vim.tbl_deep_extend('force', { use_nvim_treesitter = false }, opts or {})
1050
+ ai_captures = H.prepare_ai_captures(ai_captures)
1051
+
1052
+ -- Tree-sitter ranges are 0-based, end-exclusive, and usually
1053
+ -- `row1-col1-byte1-row2-col2-byte2` (i.e. "range six") format.
1054
+ local ts_range_to_region = function(r)
1055
+ -- The `master` branch of 'nvim-treesitter' can return "range four" format
1056
+ -- if it uses custom directives, like `#make-range!`. Due to the fact that
1057
+ -- it doesn't fully mock the `TSNode:range()` method to return "range six".
1058
+ -- TODO: Remove after 'nvim-treesitter' `master` branch support is dropped.
1059
+ local offset = #r == 4 and -1 or 0
1060
+ local res = { from = { line = r[1] + 1, col = r[2] + 1 }, to = { line = r[4 + offset] + 1, col = r[5 + offset] } }
1061
+
1062
+ -- NOTE: Adjust "row-exclusive, col-0" range that means "all previous row
1063
+ -- including the newline character"
1064
+ if res.to.col == 0 then
1065
+ res.to.line = res.to.line - 1
1066
+ res.to.col = vim.fn.col({ res.to.line, '$' })
1067
+ end
1068
+
1069
+ return res
1070
+ end
1071
+
1072
+ return function(ai_type, _, _)
1073
+ -- Get array of matched treesitter nodes
1074
+ local target_captures = ai_captures[ai_type]
1075
+ local has_nvim_treesitter = pcall(require, 'nvim-treesitter') and pcall(require, 'nvim-treesitter.query')
1076
+ local range_querier = (has_nvim_treesitter and opts.use_nvim_treesitter) and H.get_matched_ranges_plugin
1077
+ or H.get_matched_ranges_builtin
1078
+ local matched_ranges = range_querier(target_captures)
1079
+ return vim.tbl_map(ts_range_to_region, matched_ranges)
1080
+ end
1081
+ end
1082
+
1083
+ --- Specification from user prompt
1084
+ ---
1085
+ --- - Ask user for left and right textobject edges as raw strings (no pattern).
1086
+ --- It uses |MiniInput.get()| (if enabled) or |input().
1087
+ --- - Construct specification for a textobject that matches from left edge string
1088
+ --- to right edge string: `a` includes both strings, `i` only insides.
1089
+ ---
1090
+ --- Used for |MiniAi-builtin-textobjects| with identifier `?`.
1091
+ ---
1092
+ ---@return function Textobject specification as function.
1093
+ MiniAi.gen_spec.user_prompt = function()
1094
+ return function()
1095
+ -- Using cache allows for a dot-repeat without another user input
1096
+ if H.cache.prompted_textobject ~= nil then return H.cache.prompted_textobject end
1097
+
1098
+ local left = H.user_input('Left edge')
1099
+ if left == nil or left == '' then return end
1100
+ local right = H.user_input('Right edge')
1101
+ if right == nil or right == '' then return end
1102
+
1103
+ -- Clean command line from prompt messages (does not work in Visual mode)
1104
+ vim.cmd([[echo '' | redraw]])
1105
+
1106
+ local left_esc, right_esc = vim.pesc(left), vim.pesc(right)
1107
+ local res = { string.format('%s().-()%s', left_esc, right_esc) }
1108
+ H.cache.prompted_textobject = res
1109
+ return res
1110
+ end
1111
+ end
1112
+
1113
+ --- Visually select textobject region
1114
+ ---
1115
+ --- Does nothing if no region is found.
1116
+ ---
1117
+ ---@param ai_type string One of `'a'` or `'i'`.
1118
+ ---@param id string Single character string representing textobject id.
1119
+ ---@param opts table|nil Same as in |MiniAi.find_textobject()|. Extra fields:
1120
+ --- - <vis_mode> - One of `'v'`, `'V'`, or `'\22'` (escaped version of `'<C-v>'`).
1121
+ --- Default: Latest visual mode.
1122
+ --- - <operator_pending> - Whether selection is for Operator-pending mode.
1123
+ --- Used in that mode's mappings, shouldn't be used directly. Default: `false`.
1124
+ MiniAi.select_textobject = function(ai_type, id, opts)
1125
+ if H.is_disabled() then return end
1126
+
1127
+ opts = opts or {}
1128
+ local operator_pending = opts.operator_pending
1129
+
1130
+ -- Exit to Normal before getting textobject id. This way invalid id doesn't
1131
+ -- result into staying in current mode (which seems to be more convenient).
1132
+ H.exit_to_normal_mode()
1133
+
1134
+ local tobj = MiniAi.find_textobject(ai_type, id, opts)
1135
+ if tobj == nil then return end
1136
+
1137
+ local set_cursor = function(position) vim.api.nvim_win_set_cursor(0, { position.line, position.col - 1 }) end
1138
+
1139
+ -- Allow empty regions
1140
+ local tobj_is_empty = tobj.to == nil
1141
+ tobj.to = tobj.to or tobj.from
1142
+
1143
+ -- Compute selection type preferring the one coming from textobject
1144
+ local vis_mode = tobj.vis_mode
1145
+ if vis_mode == nil or not H.is_visual_mode(vis_mode) then
1146
+ local prev_vis_mode = vim.fn.visualmode()
1147
+ prev_vis_mode = prev_vis_mode == '' and 'v' or prev_vis_mode
1148
+ vis_mode = opts.vis_mode and vim.api.nvim_replace_termcodes(opts.vis_mode, true, true, true) or prev_vis_mode
1149
+ end
1150
+
1151
+ local cache_eventignore = vim.o.eventignore
1152
+ -- Allow going past end of line in order to collapse multiline regions
1153
+ local cache_virtualedit, cache_whichwrap = vim.o.virtualedit, vim.o.whichwrap
1154
+
1155
+ -- Cache window horizontal view data to possibly counter unwanted side scroll
1156
+ local leftcol = vim.fn.winsaveview().leftcol
1157
+
1158
+ pcall(function()
1159
+ -- Do nothing in Operator-pending mode for empty region (except `c`, `d`,
1160
+ -- or selected "replace" operators). These are hand picked because they
1161
+ -- completely remove selected text, which is necessary for currently only
1162
+ -- possible empty region selection implementation.
1163
+ local is_empty_opending = tobj_is_empty and opts.operator_pending
1164
+ if is_empty_opending then
1165
+ local is_allowed_empty_opending = vim.v.operator == 'c'
1166
+ or vim.v.operator == 'd'
1167
+ or (vim.v.operator == 'g@' and vim.o.operatorfunc:find('MiniOperators%.replace') ~= nil)
1168
+ or (vim.v.operator == 'g@' and vim.o.operatorfunc:find('substitute') ~= nil)
1169
+ if not is_allowed_empty_opending then return H.message('Textobject region is empty. Nothing is done.') end
1170
+ end
1171
+
1172
+ -- Allow setting cursor past line end (allows collapsing multiline region)
1173
+ -- NOTE: This doesn't work for 'virtualedit=all' and 'selection=inclusive'
1174
+ -- (default). The reason is that later option restoring is done immediately
1175
+ -- leading to a selection obey 'virtualedit=all' rules and thus won't treat
1176
+ -- end-of-line as '\n' and collapse multiline region. The solution is to
1177
+ -- `vim.schedule()` option restore, but it feels too much for a niche case.
1178
+ vim.o.virtualedit = 'onemore'
1179
+
1180
+ -- Select region:
1181
+ -- - Go from start to end stay at range end in Visual mode (as done in
1182
+ -- built-in visual selection).
1183
+ -- - Open just enough folds to have both ends visible.
1184
+ -- - Respect exclusive selection (including when selecting end of line)
1185
+ set_cursor(tobj.from)
1186
+ vim.cmd('normal! zv')
1187
+ vim.cmd('normal! ' .. vis_mode)
1188
+ set_cursor(tobj.to)
1189
+ if vim.o.selection == 'exclusive' and not tobj_is_empty then vim.cmd('set whichwrap=l | normal! l') end
1190
+ vim.cmd('normal! zv')
1191
+
1192
+ -- Restore horizontal view which was possibly affected by moving cursor
1193
+ -- NOTE: It seems to not affect cursor if it is outside of restored view
1194
+ vim.fn.winrestview({ leftcol = leftcol })
1195
+
1196
+ if is_empty_opending then
1197
+ -- Add single space (without triggering events) and visually select it.
1198
+ -- Seems like the only way to make `ci)` and `di)` move inside empty
1199
+ -- brackets. Original idea is from 'wellle/targets.vim'.
1200
+ vim.o.eventignore = 'all'
1201
+
1202
+ -- First escape from previously started Visual mode
1203
+ vim.cmd([[silent! execute "normal! \<Esc>i \<Esc>v"]])
1204
+ end
1205
+ end)
1206
+
1207
+ -- Restore options
1208
+ vim.o.eventignore = cache_eventignore
1209
+ vim.o.virtualedit, vim.o.whichwrap = cache_virtualedit, cache_whichwrap
1210
+ end
1211
+
1212
+ -- Helper data ================================================================
1213
+ -- Module default config
1214
+ H.default_config = vim.deepcopy(MiniAi.config)
1215
+
1216
+ -- Cache for various operations
1217
+ H.cache = {}
1218
+
1219
+ -- Builtin textobjects
1220
+ H.builtin_textobjects = {
1221
+ -- Use balanced pair for brackets. Use opening ones to possibly remove edge
1222
+ -- whitespace from `i` textobject.
1223
+ ['('] = { '%b()', '^.%s*().-()%s*.$' },
1224
+ [')'] = { '%b()', '^.().*().$' },
1225
+ ['['] = { '%b[]', '^.%s*().-()%s*.$' },
1226
+ [']'] = { '%b[]', '^.().*().$' },
1227
+ ['{'] = { '%b{}', '^.%s*().-()%s*.$' },
1228
+ ['}'] = { '%b{}', '^.().*().$' },
1229
+ ['<'] = { '%b<>', '^.%s*().-()%s*.$' },
1230
+ ['>'] = { '%b<>', '^.().*().$' },
1231
+ -- Use special "same balanced" pattern to select quotes in pairs
1232
+ ["'"] = { "%b''", '^.().*().$' },
1233
+ ['"'] = { '%b""', '^.().*().$' },
1234
+ ['`'] = { '%b``', '^.().*().$' },
1235
+ -- Derived from user prompt
1236
+ ['?'] = MiniAi.gen_spec.user_prompt(),
1237
+ -- Argument
1238
+ ['a'] = MiniAi.gen_spec.argument(),
1239
+ -- Brackets
1240
+ ['b'] = { { '%b()', '%b[]', '%b{}' }, '^.().*().$' },
1241
+ -- Function call
1242
+ ['f'] = MiniAi.gen_spec.function_call(),
1243
+ -- Tag
1244
+ ['t'] = { '<(%w-)%f[^<%w][^<>]->.-</%1>', '^<.->().*()</[^/]->$' },
1245
+ -- Quotes
1246
+ ['q'] = { { "%b''", '%b""', '%b``' }, '^.().*().$' },
1247
+ }
1248
+
1249
+ -- Module's namespaces
1250
+ H.ns_id = {
1251
+ -- Track user input
1252
+ input = vim.api.nvim_create_namespace('MiniAiInput'),
1253
+ }
1254
+
1255
+ -- Helper functionality =======================================================
1256
+ -- Settings -------------------------------------------------------------------
1257
+ H.setup_config = function(config)
1258
+ H.check_type('config', config, 'table', true)
1259
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1260
+
1261
+ H.check_type('custom_textobjects', config.custom_textobjects, 'table', true)
1262
+
1263
+ H.check_type('mappings', config.mappings, 'table')
1264
+ H.check_type('mappings.around', config.mappings.around, 'string')
1265
+ H.check_type('mappings.inside', config.mappings.inside, 'string')
1266
+ H.check_type('mappings.around_next', config.mappings.around_next, 'string')
1267
+ H.check_type('mappings.inside_next', config.mappings.inside_next, 'string')
1268
+ H.check_type('mappings.around_last', config.mappings.around_last, 'string')
1269
+ H.check_type('mappings.inside_last', config.mappings.inside_last, 'string')
1270
+ H.check_type('mappings.goto_left', config.mappings.goto_left, 'string')
1271
+ H.check_type('mappings.goto_right', config.mappings.goto_right, 'string')
1272
+
1273
+ H.check_type('n_lines', config.n_lines, 'number')
1274
+ H.validate_search_method(config.search_method, 'search_method')
1275
+ H.check_type('silent', config.silent, 'boolean')
1276
+
1277
+ return config
1278
+ end
1279
+
1280
+ --stylua: ignore
1281
+ H.apply_config = function(config)
1282
+ MiniAi.config = config
1283
+
1284
+ -- Make mappings
1285
+ local maps = config.mappings
1286
+ local m = function(mode, lhs, rhs, opts)
1287
+ opts.expr = true
1288
+ -- Allow recursive mapping to support falling back on user defined mapping
1289
+ opts.remap = true
1290
+ H.map(mode, lhs, rhs, opts)
1291
+ end
1292
+
1293
+ m({ 'n', 'x', 'o' }, maps.goto_left, function() return H.expr_motion('left') end, { desc = 'Move to left "around"' })
1294
+ m({ 'n', 'x', 'o' }, maps.goto_right, function() return H.expr_motion('right') end, { desc = 'Move to right "around"' })
1295
+
1296
+ local make_tobj = function(mode, ai_type, search_method)
1297
+ return function() return H.expr_textobject(mode, ai_type, { search_method = search_method }) end
1298
+ end
1299
+
1300
+ m('x', maps.around, make_tobj('x', 'a'), { desc = 'Around textobject' })
1301
+ m('x', maps.inside, make_tobj('x', 'i'), { desc = 'Inside textobject' })
1302
+ m('o', maps.around, make_tobj('o', 'a'), { desc = 'Around textobject' })
1303
+ m('o', maps.inside, make_tobj('o', 'i'), { desc = 'Inside textobject' })
1304
+
1305
+ m('x', maps.around_next, make_tobj('x', 'a', 'next'), { desc = 'Around next textobject' })
1306
+ m('x', maps.around_last, make_tobj('x', 'a', 'prev'), { desc = 'Around last textobject' })
1307
+ m('x', maps.inside_next, make_tobj('x', 'i', 'next'), { desc = 'Inside next textobject' })
1308
+ m('x', maps.inside_last, make_tobj('x', 'i', 'prev'), { desc = 'Inside last textobject' })
1309
+ m('o', maps.around_next, make_tobj('o', 'a', 'next'), { desc = 'Around next textobject' })
1310
+ m('o', maps.around_last, make_tobj('o', 'a', 'prev'), { desc = 'Around last textobject' })
1311
+ m('o', maps.inside_next, make_tobj('o', 'i', 'next'), { desc = 'Inside next textobject' })
1312
+ m('o', maps.inside_last, make_tobj('o', 'i', 'prev'), { desc = 'Inside last textobject' })
1313
+ end
1314
+
1315
+ H.is_disabled = function() return vim.g.miniai_disable == true or vim.b.miniai_disable == true end
1316
+
1317
+ H.get_config = function(config)
1318
+ return vim.tbl_deep_extend('force', MiniAi.config, vim.b.miniai_config or {}, config or {})
1319
+ end
1320
+
1321
+ H.is_search_method = function(x, x_name)
1322
+ x = x or H.get_config().search_method
1323
+ x_name = x_name or '`config.search_method`'
1324
+
1325
+ local allowed_methods = vim.tbl_keys(H.span_compare_methods)
1326
+ if vim.tbl_contains(allowed_methods, x) then return true end
1327
+
1328
+ table.sort(allowed_methods)
1329
+ local allowed_methods_string = table.concat(vim.tbl_map(vim.inspect, allowed_methods), ', ')
1330
+ local msg = ([[%s should be one of %s.]]):format(x_name, allowed_methods_string)
1331
+ return false, msg
1332
+ end
1333
+
1334
+ H.validate_search_method = function(x, x_name)
1335
+ local is_valid, msg = H.is_search_method(x, x_name)
1336
+ if not is_valid then H.error(msg) end
1337
+ end
1338
+
1339
+ -- Mappings -------------------------------------------------------------------
1340
+ H.expr_textobject = function(mode, ai_type, opts)
1341
+ local tobj_id = H.user_textobject_id(ai_type)
1342
+
1343
+ if tobj_id == nil then return mode == 'o' and '<Esc>' or '' end
1344
+
1345
+ -- Possibly fall back to builtin `a`/`i` textobjects
1346
+ if H.is_disabled() or not H.is_valid_textobject_id(tobj_id) then
1347
+ local mappings = H.get_config().mappings
1348
+ local main_key = mappings[ai_type == 'a' and 'around' or 'inside']
1349
+ local res = main_key .. tobj_id
1350
+ -- If fallback is an existing user mapping, prepend it with '<Ignore>'.
1351
+ -- This deals with `:h recursive_mapping`. Shouldn't prepend if it is a
1352
+ -- builtin textobject. Also see https://github.com/vim/vim/issues/10907 .
1353
+ if vim.fn.maparg(res, mode) ~= '' then res = '<Ignore>' .. res end
1354
+ return res
1355
+ end
1356
+ opts = vim.tbl_deep_extend('force', H.get_default_opts(), opts or {})
1357
+
1358
+ -- Clear cache
1359
+ H.cache = {}
1360
+
1361
+ -- Construct call options based on mode
1362
+ local reference_region_field, operator_pending_field, vis_mode_field = 'nil', 'nil', 'nil'
1363
+
1364
+ if mode == 'x' then
1365
+ -- Use Visual selection as reference region for Visual mode mappings
1366
+ reference_region_field = vim.inspect(H.get_visual_region(), { newline = '', indent = '' })
1367
+ end
1368
+
1369
+ if mode == 'o' then
1370
+ -- Supply `operator_pending` flag in Operator-pending mode
1371
+ operator_pending_field = 'true'
1372
+
1373
+ -- Take into account forced Operator-pending modes ('nov', 'noV', 'no<C-V>')
1374
+ vis_mode_field = vim.fn.mode(1):gsub('^no', '')
1375
+ vis_mode_field = vim.inspect(vis_mode_field == '' and 'v' or vis_mode_field)
1376
+ end
1377
+
1378
+ -- Make expression
1379
+ return '<Cmd>lua '
1380
+ .. string.format(
1381
+ [[MiniAi.select_textobject('%s', %s, { search_method = %s, n_times = %d, reference_region = %s, operator_pending = %s, vis_mode = %s })]],
1382
+ ai_type,
1383
+ vim.inspect(tobj_id),
1384
+ vim.inspect(opts.search_method),
1385
+ vim.v.count1,
1386
+ reference_region_field,
1387
+ operator_pending_field,
1388
+ vis_mode_field
1389
+ )
1390
+ .. '<CR>'
1391
+ end
1392
+
1393
+ H.expr_motion = function(side)
1394
+ if H.is_disabled() then return '' end
1395
+
1396
+ if not (side == 'left' or side == 'right') then H.error([[`side` should be one of 'left' or 'right'.]]) end
1397
+
1398
+ -- Get user input
1399
+ local tobj_id = H.user_textobject_id('a')
1400
+ if tobj_id == nil then return end
1401
+
1402
+ -- Clear cache
1403
+ H.cache = {}
1404
+
1405
+ -- Make expression for moving cursor
1406
+ return '<Cmd>lua '
1407
+ .. string.format([[MiniAi.move_cursor('%s', 'a', %s, { n_times = %d })]], side, vim.inspect(tobj_id), vim.v.count1)
1408
+ .. '<CR>'
1409
+ end
1410
+
1411
+ -- Work with textobject info --------------------------------------------------
1412
+ H.make_textobject_table = function()
1413
+ -- Extend builtins with data from `config`. Don't use `tbl_deep_extend()`
1414
+ -- because only top level keys should be merged.
1415
+ local textobjects = vim.tbl_extend('force', H.builtin_textobjects, H.get_config().custom_textobjects or {})
1416
+
1417
+ -- Use default textobject pattern for anything excluding Latin characters, as
1418
+ -- they are needed to fall back to Neovim's built-in textobjects (like `aw`)
1419
+ return setmetatable(textobjects, {
1420
+ __index = function(_, key)
1421
+ if type(key) == 'string' and string.find(key, '^%a$') ~= nil then return end
1422
+ local key_esc = vim.pesc(key)
1423
+ -- Use `%f[]` to ensure maximum stretch in both directions. Include only
1424
+ -- right edge in `a` textobject.
1425
+ -- Example output: '_()()[^_]-()_+%f[^_]()'
1426
+ return { string.format('%s()()[^%s]-()%s+%%f[^%s]()', key_esc, key_esc, key_esc, key_esc) }
1427
+ end,
1428
+ })
1429
+ end
1430
+
1431
+ H.get_textobject_spec = function(id, args)
1432
+ local textobject_tbl = H.make_textobject_table()
1433
+ local spec = textobject_tbl[id]
1434
+
1435
+ -- Allow function returning spec or region(s)
1436
+ if vim.is_callable(spec) then spec = spec(unpack(args)) end
1437
+
1438
+ -- Wrap callable tables to be an actual functions. Otherwise they might be
1439
+ -- confused with list of patterns.
1440
+ if H.is_composed_pattern(spec) then return vim.tbl_map(H.wrap_callable_table, spec) end
1441
+
1442
+ if not (H.is_region(spec) or H.is_region_array(spec)) then return nil end
1443
+ return spec
1444
+ end
1445
+
1446
+ H.is_valid_textobject_id = function(id)
1447
+ local spec = H.make_textobject_table()[id]
1448
+ return type(spec) == 'table' or vim.is_callable(spec)
1449
+ end
1450
+
1451
+ H.is_region = function(x)
1452
+ if type(x) ~= 'table' then return false end
1453
+ local from_is_valid = type(x.from) == 'table' and type(x.from.line) == 'number' and type(x.from.col) == 'number'
1454
+ -- Allow `to` to be `nil` to describe empty regions
1455
+ local to_is_valid = true
1456
+ if x.to ~= nil then
1457
+ to_is_valid = type(x.to) == 'table' and type(x.to.line) == 'number' and type(x.to.col) == 'number'
1458
+ end
1459
+ return from_is_valid and to_is_valid
1460
+ end
1461
+
1462
+ H.is_region_array = function(x)
1463
+ if not H.islist(x) then return false end
1464
+ for _, v in ipairs(x) do
1465
+ if not H.is_region(v) then return false end
1466
+ end
1467
+ return true
1468
+ end
1469
+
1470
+ H.is_composed_pattern = function(x)
1471
+ if not (H.islist(x) and #x > 0) then return false end
1472
+ for _, val in ipairs(x) do
1473
+ local val_type = type(val)
1474
+ if not (val_type == 'table' or val_type == 'string' or vim.is_callable(val)) then return false end
1475
+ end
1476
+ return true
1477
+ end
1478
+
1479
+ -- Work with finding textobjects ----------------------------------------------
1480
+ ---@param tobj_spec table Composed pattern. Last item(s) - extraction template.
1481
+ ---@param ai_type string One of `'a'` or `'i'`.
1482
+ ---@param opts table Textobject options with all fields present.
1483
+ ---@private
1484
+ H.find_textobject_region = function(tobj_spec, ai_type, opts)
1485
+ local reference_region, n_times, n_lines = opts.reference_region, opts.n_times, opts.n_lines
1486
+
1487
+ if n_times == 0 then return end
1488
+
1489
+ -- Find `n_times` matching spans evolving from reference region span
1490
+ -- First try to find inside 0-neighborhood
1491
+ local neigh = H.get_neighborhood(reference_region, 0)
1492
+ local reference_span = neigh.region_to_span(reference_region)
1493
+
1494
+ local find_next = function(cur_reference_span)
1495
+ local res = H.find_best_match(neigh, tobj_spec, cur_reference_span, opts)
1496
+
1497
+ -- If didn't find in 0-neighborhood, possibly try extend one
1498
+ if res.span == nil then
1499
+ -- Stop if no need to extend neighborhood
1500
+ if n_lines == 0 or neigh.n_neighbors > 0 then return {} end
1501
+
1502
+ -- Update data with respect to new neighborhood
1503
+ local cur_reference_region = neigh.span_to_region(cur_reference_span)
1504
+ neigh = H.get_neighborhood(reference_region, n_lines)
1505
+ reference_span = neigh.region_to_span(reference_region)
1506
+ cur_reference_span = neigh.region_to_span(cur_reference_region)
1507
+
1508
+ -- Recompute based on new neighborhood
1509
+ res = H.find_best_match(neigh, tobj_spec, cur_reference_span, opts)
1510
+ end
1511
+
1512
+ return res
1513
+ end
1514
+
1515
+ local find_res = { span = reference_span }
1516
+ for _ = 1, n_times do
1517
+ find_res = find_next(find_res.span)
1518
+ if find_res.span == nil then return end
1519
+ end
1520
+
1521
+ -- Extract final span
1522
+ local extract = function(span, extract_pattern)
1523
+ -- Use `nil` extract pattern to allow array of regions as textobject spec
1524
+ if extract_pattern == nil then return span end
1525
+
1526
+ -- First extract local (with respect to best matched span) span
1527
+ local s = neigh['1d']:sub(span.from, span.to - 1)
1528
+ local local_span = H.extract_span(s, extract_pattern, ai_type)
1529
+
1530
+ -- Convert local span to global
1531
+ local offset = span.from - 1
1532
+ return { from = local_span.from + offset, to = local_span.to + offset }
1533
+ end
1534
+
1535
+ local final_span = extract(find_res.span, find_res.extract_pattern)
1536
+
1537
+ -- Ensure that output region is different from reference. This is needed if
1538
+ -- final span was shrunk during extraction and resulted into equal to input
1539
+ -- reference. This powers consecutive application of most `i` textobjects.
1540
+ if H.is_span_covering(reference_span, final_span) then
1541
+ find_res = find_next(find_res.span)
1542
+ if find_res.span == nil then return end
1543
+ final_span = extract(find_res.span, find_res.extract_pattern)
1544
+ if H.is_span_covering(reference_span, final_span) then return end
1545
+ end
1546
+
1547
+ -- Convert to region
1548
+ return neigh.span_to_region(final_span, find_res.vis_mode)
1549
+ end
1550
+
1551
+ H.get_default_opts = function()
1552
+ local config = H.get_config()
1553
+ local cur_pos = vim.api.nvim_win_get_cursor(0)
1554
+ return {
1555
+ n_lines = config.n_lines,
1556
+ n_times = vim.v.count1,
1557
+ -- Empty region at cursor position
1558
+ reference_region = { from = { line = cur_pos[1], col = cur_pos[2] + 1 } },
1559
+ search_method = config.search_method,
1560
+ }
1561
+ end
1562
+
1563
+ -- Work with argument textobject ----------------------------------------------
1564
+ H.arg_get_separator_spans = function(s, sep_pattern, exclude_regions)
1565
+ if s:len() <= 2 then return {} end
1566
+
1567
+ -- Pre-compute edge separator spans (assumes edge characters are brackets)
1568
+ local left_bracket_span = { 1, 1 }
1569
+ local right_bracket_span = { s:len(), s:len() }
1570
+
1571
+ -- Get all separator spans (meaning separator is allowed to match more than
1572
+ -- a single character)
1573
+ local sep_spans = {}
1574
+ s:gsub('()' .. sep_pattern .. '()', function(l, r) table.insert(sep_spans, { l, r - 1 }) end)
1575
+ if #sep_spans == 0 then return { left_bracket_span, right_bracket_span } end
1576
+
1577
+ -- Remove separators that are in "excluded regions": by default, inside
1578
+ -- brackets or quotes
1579
+ local inner_s, forbidden = s:sub(2, -2), {}
1580
+ local add_to_forbidden = function(l, r) table.insert(forbidden, { l + 1, r }) end
1581
+
1582
+ for _, pat in ipairs(exclude_regions) do
1583
+ local capture_pat = string.format('()%s()', pat)
1584
+ inner_s:gsub(capture_pat, add_to_forbidden)
1585
+ end
1586
+
1587
+ local res = vim.tbl_filter(function(x) return not H.is_span_inside_spans(x, forbidden) end, sep_spans)
1588
+
1589
+ -- Append edge separators (assumes first and last characters are from
1590
+ -- brackets). This allows single argument and ensures at least 2 elements.
1591
+ table.insert(res, 1, left_bracket_span)
1592
+ table.insert(res, right_bracket_span)
1593
+ return res
1594
+ end
1595
+
1596
+ -- Work with treesitter textobject --------------------------------------------
1597
+ H.prepare_ai_captures = function(ai_captures)
1598
+ local is_capture = function(x)
1599
+ if type(x) == 'string' then x = { x } end
1600
+ if not H.islist(x) then return false end
1601
+
1602
+ for _, v in ipairs(x) do
1603
+ if not (type(v) == 'string' and v:sub(1, 1) == '@') then return false end
1604
+ end
1605
+ return true
1606
+ end
1607
+
1608
+ if not (type(ai_captures) == 'table' and is_capture(ai_captures.a) and is_capture(ai_captures.i)) then
1609
+ H.error('Wrong format for `ai_captures`. See `MiniAi.gen_spec.treesitter()` for details.')
1610
+ end
1611
+
1612
+ local prepare = function(x)
1613
+ if type(x) == 'string' then return { x } end
1614
+ return x
1615
+ end
1616
+
1617
+ return { a = prepare(ai_captures.a), i = prepare(ai_captures.i) }
1618
+ end
1619
+
1620
+ H.get_matched_ranges_plugin = function(captures)
1621
+ local ts_queries = require('nvim-treesitter.query')
1622
+ local buf_id = vim.api.nvim_get_current_buf()
1623
+ local matches = ts_queries.get_capture_matches_recursively(buf_id, captures, 'textobjects')
1624
+ local res = vim.tbl_map(function(m) return vim.treesitter.get_range(m.node, buf_id, m.metadata) end, matches)
1625
+ return res
1626
+ end
1627
+
1628
+ H.get_matched_ranges_builtin = function(captures)
1629
+ -- Get buffer's parser (LanguageTree)
1630
+ local buf_id = vim.api.nvim_get_current_buf()
1631
+ -- TODO: Remove `opts.error` after compatibility with Neovim=0.11 is dropped
1632
+ local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, nil, { error = false })
1633
+ if not has_parser or parser == nil then H.error_treesitter('parser') end
1634
+
1635
+ -- Get parser (LanguageTree) at cursor (important for injected languages)
1636
+ local pos = vim.api.nvim_win_get_cursor(0)
1637
+ local lang_tree = parser:language_for_range({ pos[1] - 1, pos[2], pos[1] - 1, pos[2] })
1638
+ local init_lang_tree = lang_tree
1639
+
1640
+ local missing_query_langs = {}
1641
+ local res = {}
1642
+ -- Go up parent trees to work with injected languages
1643
+ while vim.tbl_isempty(res) and lang_tree ~= nil do
1644
+ H.append_lang_ranges(res, missing_query_langs, buf_id, captures, lang_tree)
1645
+
1646
+ -- `LanguageTree:parent()` was added in Neovim=0.10
1647
+ -- TODO: Drop extra check after compatibility with Neovim=0.9 is dropped
1648
+ lang_tree = lang_tree.parent and lang_tree:parent() or nil
1649
+ end
1650
+
1651
+ -- Fall back to children trees for injected languages
1652
+ if vim.tbl_isempty(res) then
1653
+ local check_children
1654
+ check_children = function(l_tree)
1655
+ for _, child in pairs(l_tree:children()) do
1656
+ H.append_lang_ranges(res, missing_query_langs, buf_id, captures, child)
1657
+ check_children(child)
1658
+ end
1659
+ end
1660
+ check_children(init_lang_tree)
1661
+ end
1662
+
1663
+ if vim.tbl_isempty(res) and not vim.tbl_isempty(missing_query_langs) then
1664
+ H.error_treesitter('query', vim.tbl_keys(missing_query_langs))
1665
+ end
1666
+
1667
+ return res
1668
+ end
1669
+
1670
+ H.append_lang_ranges = function(res, missing_query_langs, buf_id, captures, lang_tree)
1671
+ local lang = lang_tree:lang()
1672
+ local query = vim.treesitter.query.get(lang, 'textobjects')
1673
+
1674
+ if query ~= nil then H.append_ranges(res, buf_id, query, captures, lang_tree) end
1675
+ if query == nil then missing_query_langs[lang] = true end
1676
+ end
1677
+
1678
+ H.append_ranges = function(res, buf_id, query, captures, lang_tree)
1679
+ -- Compute ranges of matched captures
1680
+ local capture_is_requested = vim.tbl_map(function(c) return vim.tbl_contains(captures, '@' .. c) end, query.captures)
1681
+
1682
+ for _, tree in ipairs(lang_tree:trees()) do
1683
+ -- TODO: Remove `opts.all`after compatibility with Neovim=0.10 is dropped
1684
+ for _, match, metadata in query:iter_matches(tree:root(), buf_id, nil, nil, { all = true }) do
1685
+ for capture_id, nodes in pairs(match) do
1686
+ local mt = metadata[capture_id]
1687
+ if capture_is_requested[capture_id] then table.insert(res, H.get_nodes_range_builtin(nodes, buf_id, mt)) end
1688
+ end
1689
+ end
1690
+ end
1691
+ end
1692
+
1693
+ H.get_nodes_range_builtin = function(nodes, buf_id, metadata)
1694
+ -- In Neovim<0.10 `Query:iter_matches()` has `match` map to single node.
1695
+ -- TODO: Remove `opts.all`after compatibility with Neovim=0.9 is dropped
1696
+ nodes = type(nodes) == 'table' and nodes or { nodes }
1697
+
1698
+ -- Get matched range as spanning from left most node start to right most node
1699
+ -- end. This accounts for several matched nodes that are intentionally there
1700
+ -- to cover complex cases. Approach is named "quantified captures".
1701
+ local left, right
1702
+ for _, node in ipairs(nodes) do
1703
+ local range = vim.treesitter.get_range(node, buf_id, metadata)
1704
+ if left == nil or range[3] < left[3] then left = range end
1705
+ if right == nil or range[6] > right[6] then right = range end
1706
+ end
1707
+ return { left[1], left[2], left[3], right[4], right[5], right[6] }
1708
+ end
1709
+
1710
+ H.error_treesitter = function(failed_get, langs)
1711
+ local buf_id, ft = vim.api.nvim_get_current_buf(), vim.bo.filetype
1712
+ if langs == nil then
1713
+ local has_lang, ft_lang = pcall(vim.treesitter.language.get_lang, ft)
1714
+ -- `vim.treesitter.language.get_lang()` defaults to `ft` on Neovim>0.11
1715
+ -- TODO: Drop check after compatibility with Neovim=0.10 is dropped
1716
+ langs = (has_lang and ft_lang ~= nil) and { ft_lang } or { ft }
1717
+ end
1718
+ table.sort(langs)
1719
+ local langs_str = table.concat(vim.tbl_map(vim.inspect, langs), ', ')
1720
+ local langs_noun = #langs == 1 and 'language' or 'languages'
1721
+ local msg = string.format('Can not get %s for buffer %d and %s %s.', failed_get, buf_id, langs_noun, langs_str)
1722
+ H.error(msg)
1723
+ end
1724
+
1725
+ -- Work with matching spans ---------------------------------------------------
1726
+ ---@param neighborhood table Output of `get_neighborhood()`.
1727
+ ---@param tobj_spec table Textobject specification.
1728
+ ---@param reference_span table Span to cover.
1729
+ ---@param opts table Fields: <search_method>.
1730
+ ---@private
1731
+ H.find_best_match = function(neighborhood, tobj_spec, reference_span, opts)
1732
+ local best_span, best_nested_pattern, best_vis_mode, current_nested_pattern
1733
+ local f = function(span, vis_mode)
1734
+ if H.is_better_span(span, best_span, reference_span, opts) then
1735
+ best_span, best_nested_pattern, best_vis_mode = span, current_nested_pattern, vis_mode
1736
+ end
1737
+ end
1738
+
1739
+ if H.is_region_array(tobj_spec) then
1740
+ -- Iterate over all spans representing regions in array
1741
+ for _, region in ipairs(tobj_spec) do
1742
+ -- Consider region only if it is completely within neighborhood
1743
+ if neighborhood.is_region_inside(region) then f(neighborhood.region_to_span(region), region.vis_mode) end
1744
+ end
1745
+ else
1746
+ -- Iterate over all matched spans
1747
+ for _, nested_pattern in ipairs(H.cartesian_product(tobj_spec)) do
1748
+ current_nested_pattern = nested_pattern
1749
+ H.iterate_matched_spans(neighborhood['1d'], nested_pattern, f)
1750
+ end
1751
+ end
1752
+
1753
+ local extract_pattern
1754
+ if best_nested_pattern ~= nil then extract_pattern = best_nested_pattern[#best_nested_pattern] end
1755
+ return { span = best_span, vis_mode = best_vis_mode, extract_pattern = extract_pattern }
1756
+ end
1757
+
1758
+ H.iterate_matched_spans = function(line, nested_pattern, f)
1759
+ local max_level = #nested_pattern
1760
+ -- Keep track of visited spans to ensure only one call of `f`.
1761
+ -- Example: `((a) (b))`, `{'%b()', '%b()'}`
1762
+ local visited = {}
1763
+
1764
+ local process
1765
+ process = function(level, level_line, level_offset)
1766
+ local pattern = nested_pattern[level]
1767
+ local next_span = function(s, init) return H.string_find(s, pattern, init) end
1768
+ if vim.is_callable(pattern) then next_span = pattern end
1769
+
1770
+ local is_same_balanced = type(pattern) == 'string' and pattern:match('^%%b(.)%1$') ~= nil
1771
+ local init = 1
1772
+ while init <= level_line:len() do
1773
+ local from, to = next_span(level_line, init)
1774
+ if from == nil then break end
1775
+
1776
+ if level == max_level then
1777
+ local found_match = H.new_span(from + level_offset, to + level_offset)
1778
+ local found_match_id = string.format('%s_%s', found_match.from, found_match.to)
1779
+ if not visited[found_match_id] then
1780
+ f(found_match)
1781
+ visited[found_match_id] = true
1782
+ end
1783
+ else
1784
+ local next_level_line = level_line:sub(from, to)
1785
+ local next_level_offset = level_offset + from - 1
1786
+ process(level + 1, next_level_line, next_level_offset)
1787
+ end
1788
+
1789
+ -- Start searching from right end to implement "balanced" pair.
1790
+ -- This doesn't work with regular balanced pattern because it doesn't
1791
+ -- capture nested brackets.
1792
+ init = (is_same_balanced and to or from) + 1
1793
+ end
1794
+ end
1795
+
1796
+ process(1, line, 0)
1797
+ end
1798
+
1799
+ -- NOTE: spans are end-exclusive to allow empty spans via `from == to`
1800
+ H.new_span = function(from, to) return { from = from, to = to == nil and from or (to + 1) } end
1801
+
1802
+ ---@param candidate table Candidate span to test against `current`.
1803
+ ---@param current table|nil Current best span.
1804
+ ---@param reference table Reference span to cover.
1805
+ ---@param opts table Fields: <search_method>.
1806
+ ---@private
1807
+ H.is_better_span = function(candidate, current, reference, opts)
1808
+ -- Candidate should be never equal or nested inside reference
1809
+ if H.is_span_covering(reference, candidate) or H.is_span_equal(candidate, reference) then return false end
1810
+
1811
+ return H.span_compare_methods[opts.search_method](candidate, current, reference)
1812
+ end
1813
+
1814
+ H.span_compare_methods = {
1815
+ cover = function(candidate, current, reference)
1816
+ local res = H.is_better_covering_span(candidate, current, reference)
1817
+ if res ~= nil then return res end
1818
+ -- If both are not covering, `candidate` is not better (as it must cover)
1819
+ return false
1820
+ end,
1821
+
1822
+ cover_or_next = function(candidate, current, reference)
1823
+ local res = H.is_better_covering_span(candidate, current, reference)
1824
+ if res ~= nil then return res end
1825
+
1826
+ -- If not covering, `candidate` must be "next" and closer to reference
1827
+ if not H.is_span_on_left(reference, candidate) then return false end
1828
+ if current == nil then return true end
1829
+
1830
+ local dist = H.span_distance.next
1831
+ return dist(candidate, reference) < dist(current, reference)
1832
+ end,
1833
+
1834
+ cover_or_prev = function(candidate, current, reference)
1835
+ local res = H.is_better_covering_span(candidate, current, reference)
1836
+ if res ~= nil then return res end
1837
+
1838
+ -- If not covering, `candidate` must be "previous" and closer to reference
1839
+ if not H.is_span_on_left(candidate, reference) then return false end
1840
+ if current == nil then return true end
1841
+
1842
+ local dist = H.span_distance.prev
1843
+ return dist(candidate, reference) < dist(current, reference)
1844
+ end,
1845
+
1846
+ cover_or_nearest = function(candidate, current, reference)
1847
+ local res = H.is_better_covering_span(candidate, current, reference)
1848
+ if res ~= nil then return res end
1849
+
1850
+ -- If not covering, `candidate` must be closer to reference
1851
+ if current == nil then return true end
1852
+
1853
+ local dist = H.span_distance.near
1854
+ return dist(candidate, reference) < dist(current, reference)
1855
+ end,
1856
+
1857
+ next = function(candidate, current, reference)
1858
+ if H.is_span_covering(candidate, reference) then return false end
1859
+
1860
+ -- `candidate` must be "next" and closer to reference
1861
+ if not H.is_span_on_left(reference, candidate) then return false end
1862
+ if current == nil then return true end
1863
+
1864
+ local dist = H.span_distance.next
1865
+ return dist(candidate, reference) < dist(current, reference)
1866
+ end,
1867
+
1868
+ prev = function(candidate, current, reference)
1869
+ if H.is_span_covering(candidate, reference) then return false end
1870
+
1871
+ -- `candidate` must be "previous" and closer to reference
1872
+ if not H.is_span_on_left(candidate, reference) then return false end
1873
+ if current == nil then return true end
1874
+
1875
+ local dist = H.span_distance.prev
1876
+ return dist(candidate, reference) < dist(current, reference)
1877
+ end,
1878
+
1879
+ nearest = function(candidate, current, reference)
1880
+ if H.is_span_covering(candidate, reference) then return false end
1881
+
1882
+ -- `candidate` must be closer to reference
1883
+ if current == nil then return true end
1884
+
1885
+ local dist = H.span_distance.near
1886
+ return dist(candidate, reference) < dist(current, reference)
1887
+ end,
1888
+ }
1889
+
1890
+ H.span_distance = {
1891
+ -- Other possible choices of distance between [a1, a2] and [b1, b2]:
1892
+ -- - Hausdorff distance: max(|a1 - b1|, |a2 - b2|).
1893
+ -- Source:
1894
+ -- https://math.stackexchange.com/questions/41269/distance-between-two-ranges
1895
+ -- - Minimum distance: min(|a1 - b1|, |a2 - b2|).
1896
+
1897
+ -- Distance is chosen so that "next span" in certain direction is the closest
1898
+ next = function(span_1, span_2) return math.abs(span_1.from - span_2.from) end,
1899
+ prev = function(span_1, span_2) return math.abs(span_1.to - span_2.to) end,
1900
+ near = function(span_1, span_2) return math.min(math.abs(span_1.from - span_2.from), math.abs(span_1.to - span_2.to)) end,
1901
+ }
1902
+
1903
+ H.is_better_covering_span = function(candidate, current, reference)
1904
+ local candidate_is_covering = H.is_span_covering(candidate, reference)
1905
+ local current_is_covering = H.is_span_covering(current, reference)
1906
+
1907
+ if candidate_is_covering and current_is_covering then
1908
+ -- Covering candidate is better than covering current if it is narrower
1909
+ return (candidate.to - candidate.from) < (current.to - current.from)
1910
+ end
1911
+ if candidate_is_covering and not current_is_covering then return true end
1912
+ if not candidate_is_covering and current_is_covering then return false end
1913
+
1914
+ -- Return `nil` if neither span is covering
1915
+ return nil
1916
+ end
1917
+
1918
+ --stylua: ignore
1919
+ H.is_span_covering = function(span, span_to_cover)
1920
+ if span == nil or span_to_cover == nil then return false end
1921
+ if span.from == span.to then
1922
+ return (span.from == span_to_cover.from) and (span_to_cover.to == span.to)
1923
+ end
1924
+ if span_to_cover.from == span_to_cover.to then
1925
+ return (span.from <= span_to_cover.from) and (span_to_cover.to < span.to)
1926
+ end
1927
+
1928
+ return (span.from <= span_to_cover.from) and (span_to_cover.to <= span.to)
1929
+ end
1930
+
1931
+ H.is_span_equal = function(span_1, span_2)
1932
+ if span_1 == nil or span_2 == nil then return false end
1933
+ return (span_1.from == span_2.from) and (span_1.to == span_2.to)
1934
+ end
1935
+
1936
+ H.is_span_on_left = function(span_1, span_2)
1937
+ if span_1 == nil or span_2 == nil then return false end
1938
+ return (span_1.from <= span_2.from) and (span_1.to <= span_2.to)
1939
+ end
1940
+
1941
+ H.is_span_inside_spans = function(ref_span, spans)
1942
+ for _, span in ipairs(spans) do
1943
+ if span[1] <= ref_span[1] and ref_span[2] <= span[2] then return true end
1944
+ end
1945
+ return false
1946
+ end
1947
+
1948
+ -- Work with Lua patterns -----------------------------------------------------
1949
+ H.extract_span = function(s, extract_pattern, ai_type)
1950
+ local positions = { s:match(extract_pattern) }
1951
+
1952
+ if #positions == 1 and type(positions[1]) == 'string' then
1953
+ if s:len() == 0 then return H.new_span(0, 0) end
1954
+ return H.new_span(1, s:len())
1955
+ end
1956
+
1957
+ local is_all_numbers = true
1958
+ for _, pos in ipairs(positions) do
1959
+ if type(pos) ~= 'number' then is_all_numbers = false end
1960
+ end
1961
+
1962
+ local is_valid_positions = is_all_numbers and (#positions == 2 or #positions == 4)
1963
+ if not is_valid_positions then
1964
+ local msg = 'Could not extract proper positions (two or four empty captures) from '
1965
+ .. string.format([[string '%s' with extraction pattern '%s'.]], s, extract_pattern)
1966
+ H.error(msg)
1967
+ end
1968
+
1969
+ local ai_spans
1970
+ if #positions == 2 then
1971
+ ai_spans = { a = H.new_span(1, s:len()), i = H.new_span(positions[1], positions[2] - 1) }
1972
+ else
1973
+ ai_spans = { a = H.new_span(positions[1], positions[4] - 1), i = H.new_span(positions[2], positions[3] - 1) }
1974
+ end
1975
+
1976
+ return ai_spans[ai_type]
1977
+ end
1978
+
1979
+ -- Work with cursor neighborhood ----------------------------------------------
1980
+ ---@param reference_region table Reference region.
1981
+ ---@param n_neighbors number Maximum number of neighbors to include before
1982
+ --- start line and after end line.
1983
+ ---@private
1984
+ H.get_neighborhood = function(reference_region, n_neighbors)
1985
+ -- Compute '2d neighborhood' of (possibly empty) region
1986
+ local from_line, to_line = reference_region.from.line, (reference_region.to or reference_region.from).line
1987
+ local line_start = math.max(1, from_line - n_neighbors)
1988
+ local line_end = math.min(vim.api.nvim_buf_line_count(0), to_line + n_neighbors)
1989
+ local neigh2d = vim.api.nvim_buf_get_lines(0, line_start - 1, line_end, false)
1990
+ -- Append 'newline' character to distinguish between lines in 1d case
1991
+ for k, v in pairs(neigh2d) do
1992
+ neigh2d[k] = v .. '\n'
1993
+ end
1994
+
1995
+ -- '1d neighborhood': position is determined by offset from start
1996
+ local neigh1d = table.concat(neigh2d, '')
1997
+
1998
+ -- Convert 2d buffer position to 1d offset
1999
+ local pos_to_offset = function(pos)
2000
+ if pos == nil then return nil end
2001
+ local line_num = line_start
2002
+ local offset = 0
2003
+ while line_num < pos.line do
2004
+ offset = offset + neigh2d[line_num - line_start + 1]:len()
2005
+ line_num = line_num + 1
2006
+ end
2007
+
2008
+ return offset + pos.col
2009
+ end
2010
+
2011
+ -- Convert 1d offset to 2d buffer position
2012
+ local offset_to_pos = function(offset)
2013
+ if offset == nil then return nil end
2014
+ local line_num = 1
2015
+ local line_offset = 0
2016
+ while line_num <= #neigh2d and line_offset + neigh2d[line_num]:len() < offset do
2017
+ line_offset = line_offset + neigh2d[line_num]:len()
2018
+ line_num = line_num + 1
2019
+ end
2020
+
2021
+ return { line = line_start + line_num - 1, col = offset - line_offset }
2022
+ end
2023
+
2024
+ -- Convert 2d region to 1d span
2025
+ local region_to_span = function(region)
2026
+ if region == nil then return nil end
2027
+ local is_empty = region.to == nil
2028
+ local to = region.to or region.from
2029
+ return { from = pos_to_offset(region.from), to = pos_to_offset(to) + (is_empty and 0 or 1) }
2030
+ end
2031
+
2032
+ -- Convert 1d span to 2d region
2033
+ local span_to_region = function(span, vis_mode)
2034
+ if span == nil then return nil end
2035
+ -- NOTE: this might lead to outside of line positions due to added `\n` at
2036
+ -- the end of lines in 1d-neighborhood. However, this is crucial for
2037
+ -- allowing `i` textobjects to collapse multiline selections.
2038
+ local res = { from = offset_to_pos(span.from), vis_mode = vis_mode }
2039
+
2040
+ -- Convert empty span to empty region
2041
+ if span.from < span.to then res.to = offset_to_pos(span.to - 1) end
2042
+ return res
2043
+ end
2044
+
2045
+ local is_region_inside = function(region)
2046
+ local res = line_start <= region.from.line
2047
+ if region.to ~= nil then res = res and (region.to.line <= line_end) end
2048
+ return res
2049
+ end
2050
+
2051
+ return {
2052
+ n_neighbors = n_neighbors,
2053
+ region = reference_region,
2054
+ ['1d'] = neigh1d,
2055
+ ['2d'] = neigh2d,
2056
+ pos_to_offset = pos_to_offset,
2057
+ offset_to_pos = offset_to_pos,
2058
+ region_to_span = region_to_span,
2059
+ span_to_region = span_to_region,
2060
+ is_region_inside = is_region_inside,
2061
+ }
2062
+ end
2063
+
2064
+ -- Work with user input -------------------------------------------------------
2065
+ H.user_textobject_id = function(ai_type)
2066
+ -- Get from user single character textobject identifier
2067
+ local needs_reminder = true
2068
+ vim.defer_fn(function()
2069
+ if not needs_reminder then return end
2070
+
2071
+ local msg = string.format('Reminder to press `%s` textobject id ', ai_type)
2072
+ H.echo(msg)
2073
+ H.cache.msg_shown = true
2074
+ end, 1000)
2075
+ local ok, char = pcall(vim.fn.getcharstr)
2076
+ needs_reminder = false
2077
+ H.unecho()
2078
+
2079
+ -- Terminate if couldn't get input (like with <C-c>) or on `<Esc>`
2080
+ if not ok or char == '' or char == '\3' or char == '\27' then return nil end
2081
+ return char
2082
+ end
2083
+
2084
+ H.user_input = function(prompt, text)
2085
+ prompt = '(mini.ai) ' .. prompt
2086
+ if _G.MiniInput ~= nil then return MiniInput.get({ prompt = prompt, scope = 'cursor', init_keys = { text } }) end
2087
+
2088
+ -- Use `on_key` to distinguish cancel with `<Esc>` and immediate `<CR>`
2089
+ local was_cancelled = false
2090
+ vim.on_key(function(key) was_cancelled = was_cancelled or key == '\27' end, H.ns_id.input)
2091
+
2092
+ -- Ask for input. Use `pcall` to allow `<C-c>` to cancel user input
2093
+ vim.cmd('echohl Question')
2094
+ local ok, res = pcall(vim.fn.input, { prompt = prompt .. ': ', default = text or '' })
2095
+ vim.cmd('echohl None | echo "" | redraw')
2096
+
2097
+ vim.on_key(nil, H.ns_id.input)
2098
+ return (ok and not was_cancelled) and res or nil
2099
+ end
2100
+
2101
+ -- Work with Visual mode ------------------------------------------------------
2102
+ H.is_visual_mode = function(mode)
2103
+ mode = mode or vim.fn.mode()
2104
+ -- '\22' is an escaped `<C-v>`
2105
+ return mode == 'v' or mode == 'V' or mode == '\22', mode
2106
+ end
2107
+
2108
+ H.exit_to_normal_mode = function()
2109
+ -- Don't use `<C-\><C-n>` in command-line window as they close it
2110
+ if vim.fn.getcmdwintype() ~= '' then
2111
+ local is_vis, cur_mode = H.is_visual_mode()
2112
+ if is_vis then vim.cmd('normal! ' .. cur_mode) end
2113
+ else
2114
+ -- '\28\14' is an escaped version of `<C-\><C-n>`
2115
+ vim.cmd('normal! \28\14')
2116
+ end
2117
+ end
2118
+
2119
+ H.get_visual_region = function()
2120
+ local is_vis, _ = H.is_visual_mode()
2121
+ if not is_vis then return end
2122
+ local res = {
2123
+ from = { line = vim.fn.line('v'), col = vim.fn.col('v') },
2124
+ to = { line = vim.fn.line('.'), col = vim.fn.col('.') },
2125
+ }
2126
+ if res.from.line > res.to.line or (res.from.line == res.to.line and res.from.col > res.to.col) then
2127
+ res = { from = res.to, to = res.from }
2128
+ end
2129
+ return res
2130
+ end
2131
+
2132
+ -- Utilities ------------------------------------------------------------------
2133
+ H.error = function(msg) error('(mini.ai) ' .. msg, 0) end
2134
+
2135
+ H.check_type = function(name, val, ref, allow_nil)
2136
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
2137
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
2138
+ end
2139
+
2140
+ H.echo = function(msg, is_important)
2141
+ if H.get_config().silent then return end
2142
+
2143
+ -- Construct message chunks
2144
+ msg = type(msg) == 'string' and { { msg } } or msg
2145
+ table.insert(msg, 1, { '(mini.ai) ', 'WarningMsg' })
2146
+
2147
+ -- Avoid hit-enter-prompt
2148
+ local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace
2149
+ local chunks, tot_width = {}, 0
2150
+ for _, ch in ipairs(msg) do
2151
+ local new_ch = { vim.fn.strcharpart(ch[1], 0, max_width - tot_width), ch[2] }
2152
+ table.insert(chunks, new_ch)
2153
+ tot_width = tot_width + vim.fn.strdisplaywidth(new_ch[1])
2154
+ if tot_width >= max_width then break end
2155
+ end
2156
+
2157
+ -- Echo. Force redraw to ensure that it is effective (`:h echo-redraw`)
2158
+ vim.cmd([[echo '' | redraw]])
2159
+ vim.api.nvim_echo(chunks, is_important, {})
2160
+ end
2161
+
2162
+ H.unecho = function()
2163
+ if H.cache.msg_shown then vim.cmd([[echo '' | redraw]]) end
2164
+ end
2165
+
2166
+ H.message = function(msg) H.echo(msg, true) end
2167
+
2168
+ H.map = function(mode, lhs, rhs, opts)
2169
+ if lhs == '' then return end
2170
+ opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
2171
+ vim.keymap.set(mode, lhs, rhs, opts)
2172
+ end
2173
+
2174
+ H.string_find = function(s, pattern, init)
2175
+ init = init or 1
2176
+
2177
+ -- Match only start of full string if pattern says so.
2178
+ -- This is needed because `string.find()` doesn't do this.
2179
+ -- Example: `string.find('(aaa)', '^.*$', 4)` returns `4, 5`
2180
+ if pattern:sub(1, 1) == '^' then
2181
+ if init > 1 then return nil end
2182
+ return string.find(s, pattern)
2183
+ end
2184
+
2185
+ -- Handle patterns `x.-y` differently: make match as small as possible. This
2186
+ -- doesn't allow `x` be present inside `.-` match, just as with `yyy`. Which
2187
+ -- also leads to a behavior similar to punctuation id (like with `va_`): no
2188
+ -- covering is possible, only next, previous, or nearest.
2189
+ local check_left, _, prev = string.find(pattern, '(.)%.%-')
2190
+ local is_pattern_special = check_left ~= nil and prev ~= '%'
2191
+ if not is_pattern_special then return string.find(s, pattern, init) end
2192
+
2193
+ -- Make match as small as possible
2194
+ local from, to = string.find(s, pattern, init)
2195
+ if from == nil then return end
2196
+
2197
+ local cur_from, cur_to = from, to
2198
+ while cur_to == to do
2199
+ from, to = cur_from, cur_to
2200
+ cur_from, cur_to = string.find(s, pattern, cur_from + 1)
2201
+ end
2202
+
2203
+ return from, to
2204
+ end
2205
+
2206
+ ---@param arr table List of items. If item is list, consider as set for
2207
+ --- product. Else - make it single item list.
2208
+ ---@private
2209
+ H.cartesian_product = function(arr)
2210
+ if not (type(arr) == 'table' and #arr > 0) then return {} end
2211
+ arr = vim.tbl_map(function(x) return H.islist(x) and x or { x } end, arr)
2212
+
2213
+ local res, cur_item = {}, {}
2214
+ local process
2215
+ process = function(level)
2216
+ for i = 1, #arr[level] do
2217
+ table.insert(cur_item, arr[level][i])
2218
+ if level == #arr then
2219
+ -- Flatten array to allow tables as elements of step tables
2220
+ table.insert(res, H.tbl_flatten(cur_item))
2221
+ else
2222
+ process(level + 1)
2223
+ end
2224
+ table.remove(cur_item, #cur_item)
2225
+ end
2226
+ end
2227
+
2228
+ process(1)
2229
+ return res
2230
+ end
2231
+
2232
+ H.wrap_callable_table = function(x)
2233
+ if vim.is_callable(x) and type(x) == 'table' then
2234
+ return function(...) return x(...) end
2235
+ end
2236
+ return x
2237
+ end
2238
+
2239
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2240
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2241
+ H.tbl_flatten = vim.fn.has('nvim-0.10') == 1 and function(x) return vim.iter(x):flatten(math.huge):totable() end
2242
+ or vim.tbl_flatten
2243
+
2244
+ return MiniAi