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,2290 @@
1
+ --- *mini.extra* Extra mini.nvim functionality
2
+ ---
3
+ --- MIT License Copyright (c) 2023 Evgeni Chasnovski
4
+
5
+ --- Extra useful functionality which is not essential enough for other |mini.nvim|
6
+ --- modules to include directly.
7
+ ---
8
+ --- Features:
9
+ ---
10
+ --- - Various pickers for |mini.pick|:
11
+ --- - Built-in diagnostic (|MiniExtra.pickers.diagnostic()|).
12
+ --- - File explorer (|MiniExtra.pickers.explorer()|).
13
+ --- - Git branches/commits/files/hunks (|MiniExtra.pickers.git_hunks()|, etc.).
14
+ --- - Command/search/input history (|MiniExtra.pickers.history()|).
15
+ --- - LSP references/symbols/etc. (|MiniExtra.pickers.lsp()|).
16
+ --- - Tree-sitter nodes (|MiniExtra.pickers.treesitter()|).
17
+ --- - And much more.
18
+ --- See |MiniExtra.pickers| for more.
19
+ ---
20
+ --- - Various textobject specifications for |mini.ai|. See |MiniExtra.gen_ai_spec|.
21
+ ---
22
+ --- - Various highlighters for |mini.hipatterns|. See |MiniExtra.gen_highlighter|.
23
+ ---
24
+ --- Notes:
25
+ --- - This module requires only those |mini.nvim| modules which are needed for
26
+ --- a particular functionality: |mini.pick| for pickers, etc.
27
+ ---
28
+ --- # Setup ~
29
+ ---
30
+ --- This module needs a setup with `require('mini.extra').setup({})` (replace
31
+ --- `{}` with your `config` table). It will create global Lua table `MiniExtra`
32
+ --- which you can use for scripting or manually (with `:lua MiniExtra.*`).
33
+ ---
34
+ --- See |MiniExtra.config| for `config` structure and default values.
35
+ ---
36
+ --- This module doesn't have runtime options, so using `vim.b.miniextra_config`
37
+ --- will have no effect here.
38
+ ---
39
+ --- # Comparisons ~
40
+ ---
41
+ --- - [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim):
42
+ --- - With |MiniExtra.pickers|, |mini.pick| is reasonably on par when it comes
43
+ --- to built-in pickers.
44
+ ---
45
+ --- - [ibhagwan/fzf-lua](https://github.com/ibhagwan/fzf-lua):
46
+ --- - Same as `nvim-telescope/telescope.nvim`.
47
+ ---@tag MiniExtra
48
+
49
+ ---@diagnostic disable:undefined-field
50
+ ---@diagnostic disable:discard-returns
51
+ ---@diagnostic disable:unused-local
52
+ ---@diagnostic disable:cast-local-type
53
+ ---@diagnostic disable:undefined-doc-name
54
+ ---@diagnostic disable:luadoc-miss-type-name
55
+
56
+ ---@alias __extra_ai_spec_return function Function implementing |MiniAi-textobject-specification|.
57
+ ---@alias __extra_pickers_local_opts table|nil Options defining behavior of this particular picker.
58
+ ---@alias __extra_pickers_opts table|nil Options forwarded to |MiniPick.start()|.
59
+ ---@alias __extra_pickers_return any Output of the called picker.
60
+ ---@alias __extra_pickers_git_notes Notes:
61
+ --- - Requires executable `git`.
62
+ --- - Requires target path to be part of git repository.
63
+ --- - Present for exploration and navigation purposes. Doing any Git operations
64
+ --- is suggested to be done in a dedicated Git client and is not planned.
65
+ ---@alias __extra_pickers_preserve_order - <preserve_order> `(boolean)` - whether to preserve original order
66
+ --- during query. Default: `false`.
67
+ ---@alias __extra_pickers_git_path - <path> `(string|nil)` - target path for Git operation (if required). Also
68
+ --- used to find Git repository inside which to construct items.
69
+ --- Default: `nil` for root of Git repository containing |current-directory|.
70
+
71
+ -- Module definition ==========================================================
72
+ local MiniExtra = {}
73
+ local H = {}
74
+
75
+ --- Module setup
76
+ ---
77
+ ---@param config table|nil Module config table. See |MiniExtra.config|.
78
+ ---
79
+ ---@usage >lua
80
+ --- require('mini.extra').setup() -- use default config
81
+ --- -- OR
82
+ --- require('mini.extra').setup({}) -- replace {} with your config table
83
+ --- <
84
+ MiniExtra.setup = function(config)
85
+ -- TODO: Remove after Neovim=0.9 support is dropped
86
+ if vim.fn.has('nvim-0.10') == 0 then
87
+ vim.notify(
88
+ '(mini.extra) Neovim<0.10 is soft deprecated (module works but is not supported).'
89
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
90
+ .. ' Please update your Neovim version.'
91
+ )
92
+ end
93
+
94
+ -- Export module
95
+ _G.MiniExtra = MiniExtra
96
+
97
+ -- Setup config
98
+ config = H.setup_config(config)
99
+
100
+ -- Apply config
101
+ H.apply_config(config)
102
+ end
103
+
104
+ --stylua: ignore
105
+ --- Defaults ~
106
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
107
+ MiniExtra.config = {}
108
+ --minidoc_afterlines_end
109
+
110
+ --- |mini.ai| textobject specification generators
111
+ ---
112
+ --- This is a table with function elements. Call to actually get specification.
113
+ ---
114
+ --- Assumed to be used as part of |MiniAi.setup()|. Example: >lua
115
+ ---
116
+ --- local gen_ai_spec = require('mini.extra').gen_ai_spec
117
+ --- require('mini.ai').setup({
118
+ --- custom_textobjects = {
119
+ --- B = gen_ai_spec.buffer(),
120
+ --- D = gen_ai_spec.diagnostic(),
121
+ --- I = gen_ai_spec.indent(),
122
+ --- L = gen_ai_spec.line(),
123
+ --- N = gen_ai_spec.number(),
124
+ --- },
125
+ --- })
126
+ --- <
127
+ MiniExtra.gen_ai_spec = {}
128
+
129
+ --- Current buffer textobject
130
+ ---
131
+ --- Notes:
132
+ --- - `a` textobject selects all lines in a buffer.
133
+ --- - `i` textobject selects all lines except blank lines at start and end.
134
+ ---
135
+ ---@return __extra_ai_spec_return
136
+ MiniExtra.gen_ai_spec.buffer = function()
137
+ return function(ai_type)
138
+ local start_line, end_line = 1, vim.fn.line('$')
139
+ if ai_type == 'i' then
140
+ -- Skip first and last blank lines for `i` textobject
141
+ local first_nonblank, last_nonblank = vim.fn.nextnonblank(start_line), vim.fn.prevnonblank(end_line)
142
+ -- Do nothing for buffer with all blanks
143
+ if first_nonblank == 0 or last_nonblank == 0 then return { from = { line = start_line, col = 1 } } end
144
+ start_line, end_line = first_nonblank, last_nonblank
145
+ end
146
+
147
+ local to_col = math.max(vim.fn.getline(end_line):len(), 1)
148
+ return { from = { line = start_line, col = 1 }, to = { line = end_line, col = to_col } }
149
+ end
150
+ end
151
+
152
+ --- Current buffer diagnostic textobject
153
+ ---
154
+ --- Notes:
155
+ --- - Both `a` and `i` textobjects return |vim.diagnostic.get()| output for the
156
+ --- current buffer. It is modified to fit |MiniAi-textobject-specification|.
157
+ ---
158
+ ---@param severity any Which severity to use. Forwarded to |vim.diagnostic.get()|.
159
+ --- Default: `nil` to use all diagnostic entries.
160
+ ---
161
+ ---@return __extra_ai_spec_return
162
+ MiniExtra.gen_ai_spec.diagnostic = function(severity)
163
+ return function(ai_type)
164
+ local cur_diag = vim.diagnostic.get(0, { severity = severity })
165
+
166
+ local regions = {}
167
+ for _, diag in ipairs(cur_diag) do
168
+ local from = { line = diag.lnum + 1, col = diag.col + 1 }
169
+ local to = { line = diag.end_lnum + 1, col = diag.end_col }
170
+ if to.line == nil or to.col == nil then to = { line = diag.lnum + 1, col = diag.col + 1 } end
171
+ table.insert(regions, { from = from, to = to })
172
+ end
173
+ return regions
174
+ end
175
+ end
176
+
177
+ --- Current buffer indent scopes textobject
178
+ ---
179
+ --- Indent scope is a set of consecutive lines with the following properties:
180
+ --- - Lines above first and below last are non-blank. They are called borders.
181
+ --- - There is at least one non-blank line in a set.
182
+ --- - All non-blank lines between borders have strictly greater indent
183
+ --- (perceived leading space respecting |'tabstop'|) than either of borders.
184
+ ---
185
+ --- Notes:
186
+ --- - `a` textobject selects scope including borders.
187
+ --- - `i` textobject selects the scope charwise.
188
+ --- - Differences with |MiniIndentscope.textobject()|:
189
+ --- - This textobject always treats blank lines on top and bottom of `i`
190
+ --- textobject as part of it, while |mini.indentscope| can configure that.
191
+ --- - This textobject can select non-covering scopes, while |mini.indentscope|
192
+ --- can not (by design).
193
+ --- - In this textobject scope computation is done only by "casting rays" from
194
+ --- top to bottom and not in both ways as in |mini.indentscope|.
195
+ --- This works in most common scenarios and doesn't work only if indent of
196
+ --- of the bottom border is expected to be larger than the top.
197
+ ---
198
+ ---@return function Function implementing |MiniAi-textobject-specification|.
199
+ --- It returns array of regions representing all indent scopes in the buffer
200
+ --- ordered increasingly by the start line.
201
+ MiniExtra.gen_ai_spec.indent = function() return H.ai_indent_spec end
202
+
203
+ --- Current line textobject
204
+ ---
205
+ --- Notes:
206
+ --- - `a` textobject selects whole line.
207
+ --- - `i` textobject selects line after initial indent.
208
+ ---
209
+ ---@return __extra_ai_spec_return
210
+ MiniExtra.gen_ai_spec.line = function()
211
+ return function(ai_type, _, opts)
212
+ local lnum_from = vim.fn.line('.')
213
+ local lnum_to = lnum_from + opts.n_times - 1
214
+ local line_from, line_to = vim.fn.getline(lnum_from), vim.fn.getline(lnum_to)
215
+ -- Ignore indentation for `i` textobject
216
+ local col_from = ai_type == 'a' and 1 or (line_from:match('^(%s*)'):len() + 1)
217
+ -- Don't select `\n` past the line to operate within a line
218
+ local col_to = line_to:len()
219
+
220
+ return { from = { line = lnum_from, col = col_from }, to = { line = lnum_to, col = col_to } }
221
+ end
222
+ end
223
+
224
+ --- Number textobject
225
+ ---
226
+ --- Notes:
227
+ --- - `a` textobject selects a whole number possibly preceded with "-" and
228
+ --- possibly followed by decimal part (dot and digits).
229
+ --- - `i` textobject selects consecutive digits.
230
+ ---
231
+ ---@return __extra_ai_spec_return
232
+ MiniExtra.gen_ai_spec.number = function()
233
+ local digits_pattern = '%f[%d]%d+%f[%D]'
234
+
235
+ local find_a_number = function(line, init)
236
+ -- First find consecutive digits
237
+ local from, to = line:find(digits_pattern, init)
238
+ if from == nil then return nil, nil end
239
+
240
+ -- Make sure that these digits were not processed before. This can happen
241
+ -- because 'miin.ai' does next with `init = from + 1`, meaning that
242
+ -- "-12.34" was already matched, then it would try to match starting from
243
+ -- "1": we want to avoid matching that right away and avoid matching "34"
244
+ -- from this number.
245
+ if from == init and line:sub(from - 1, from - 1) == '-' then
246
+ init = to + 1
247
+ from, to = line:find(digits_pattern, init)
248
+ end
249
+ if from == nil then return nil, nil end
250
+
251
+ if line:sub(from - 2):find('^%d%.') ~= nil then
252
+ init = to + 1
253
+ from, to = line:find(digits_pattern, init)
254
+ end
255
+ if from == nil then return nil, nil end
256
+
257
+ -- Match the whole number with minus and decimal part
258
+ if line:sub(from - 1, from - 1) == '-' then from = from - 1 end
259
+ local dec_part = line:sub(to + 1):match('^%.%d+()')
260
+ if dec_part ~= nil then to = to + dec_part - 1 end
261
+ return from, to
262
+ end
263
+
264
+ return function(ai_type)
265
+ if ai_type == 'i' then return { digits_pattern } end
266
+ return { find_a_number, { '^().*()$' } }
267
+ end
268
+ end
269
+
270
+ --- |mini.hipatterns| highlighter generators
271
+ ---
272
+ --- This is a table with function elements. Call to actually get specification.
273
+ ---
274
+ --- Assumed to be used as part of |MiniHipatterns.setup()|. Example: >lua
275
+ ---
276
+ --- local hi_words = require('mini.extra').gen_highlighter.words
277
+ --- require('mini.hipatterns').setup({
278
+ --- highlighters = {
279
+ --- todo = hi_words({ 'TODO', 'Todo', 'todo' }, 'MiniHipatternsTodo'),
280
+ --- },
281
+ --- })
282
+ --- <
283
+ MiniExtra.gen_highlighter = {}
284
+
285
+ --- Highlight words
286
+ ---
287
+ --- Notes:
288
+ --- - Words should start and end with alphanumeric symbol (latin letter or digit).
289
+ --- - Words will be highlighted only in full and not if part bigger word, i.e.
290
+ --- there should not be alphanumeric symbol before and after it.
291
+ ---
292
+ ---@param words table Array of words to highlight. Will be matched as is, not
293
+ --- as Lua pattern.
294
+ ---@param group string|function Proper `group` field for `highlighter`.
295
+ --- See |MiniHipatterns.config|.
296
+ ---@param extmark_opts any Proper `extmark_opts` field for `highlighter`.
297
+ --- See |MiniHipatterns.config|.
298
+ MiniExtra.gen_highlighter.words = function(words, group, extmark_opts)
299
+ if not H.islist(words) then H.error('`words` should be an array.') end
300
+ if not (type(group) == 'string' or vim.is_callable(group)) then H.error('`group` should be string or callable.') end
301
+ local pattern = vim.tbl_map(function(x)
302
+ if type(x) ~= 'string' then H.error('All elements of `words` should be strings.') end
303
+ return '%f[%w]()' .. vim.pesc(x) .. '()%f[%W]'
304
+ end, words)
305
+ return { pattern = pattern, group = group, extmark_opts = extmark_opts }
306
+ end
307
+
308
+ --- |mini.pick| pickers
309
+ ---
310
+ --- A table with |mini.pick| pickers (which is a hard dependency).
311
+ --- Notes:
312
+ --- - All have the same signature:
313
+ --- - <local_opts> - optional table with options local to picker.
314
+ --- - <opts> - optional table with options forwarded to |MiniPick.start()|.
315
+ --- - All of them are automatically registered in |MiniPick.registry| inside
316
+ --- both |MiniExtra.setup()| or |MiniPick.setup()| (only one is enough).
317
+ --- - All use default versions of |MiniPick-source.preview|, |MiniPick-source.choose|,
318
+ --- and |MiniPick-source.choose_marked| if not stated otherwise.
319
+ --- Shown text and |MiniPick-source.show| are targeted to the picked items.
320
+ ---
321
+ --- Examples of usage:
322
+ --- - As Lua code: `MiniExtra.pickers.buf_lines()`.
323
+ --- - With |:Pick| command: `:Pick buf_lines scope='current'`
324
+ --- Note: this requires calling |MiniExtra.setup()|.
325
+ MiniExtra.pickers = {}
326
+
327
+ --- Buffer lines picker
328
+ ---
329
+ --- Pick from buffer lines. Notes:
330
+ --- - Loads all target buffers which are currently unloaded.
331
+ ---
332
+ ---@param local_opts __extra_pickers_local_opts
333
+ --- Possible fields:
334
+ --- - <scope> `(string)` - one of "all" (normal listed buffers) or "current".
335
+ --- Default: "all".
336
+ --- __extra_pickers_preserve_order
337
+ ---@param opts __extra_pickers_opts
338
+ ---
339
+ ---@return __extra_pickers_return
340
+ MiniExtra.pickers.buf_lines = function(local_opts, opts)
341
+ local pick = H.validate_pick('buf_lines')
342
+ local_opts = vim.tbl_deep_extend('force', { scope = 'all', preserve_order = false }, local_opts or {})
343
+
344
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'current' }, 'buf_lines')
345
+ local is_scope_all = scope == 'all'
346
+
347
+ -- Define non-blocking callable `items` because getting all lines from all
348
+ -- buffers (plus loading them) may take visibly long time
349
+ local buffers = {}
350
+ if is_scope_all then
351
+ for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
352
+ if vim.bo[buf_id].buflisted and vim.bo[buf_id].buftype == '' then table.insert(buffers, buf_id) end
353
+ end
354
+ else
355
+ buffers = { vim.api.nvim_get_current_buf() }
356
+ end
357
+
358
+ local poke_picker = pick.poke_is_picker_active
359
+ local f = function()
360
+ local items = {}
361
+ for _, buf_id in ipairs(buffers) do
362
+ if not poke_picker() then return end
363
+ H.buf_ensure_loaded(buf_id)
364
+ local buf_name = H.buf_get_name(buf_id) or ''
365
+ local n_digits = math.floor(math.log10(vim.api.nvim_buf_line_count(buf_id))) + 1
366
+ local format_pattern = '%s%' .. n_digits .. 'd\0%s'
367
+ for lnum, l in ipairs(vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)) do
368
+ local prefix = is_scope_all and (buf_name .. '\0') or ''
369
+ table.insert(items, { text = format_pattern:format(prefix, lnum, l), bufnr = buf_id, lnum = lnum })
370
+ end
371
+ end
372
+ pick.set_picker_items(items)
373
+ end
374
+ local items = vim.schedule_wrap(coroutine.wrap(f))
375
+
376
+ local show = H.pick_get_config().source.show
377
+ if is_scope_all and show == nil then show = H.show_with_icons end
378
+ local match_opts = { preserve_order = local_opts.preserve_order }
379
+ local match = function(stritems, inds, query) pick.default_match(stritems, inds, query, match_opts) end
380
+ local default_source = { name = string.format('Buffer lines (%s)', scope), show = show, match = match }
381
+ return H.pick_start(items, { source = default_source }, opts)
382
+ end
383
+
384
+ --- Color scheme picker
385
+ ---
386
+ --- Pick and apply color scheme. Preview temporarily applies item's color scheme
387
+ --- and shows how selected highlight groups look.
388
+ --- Canceling reverts to color scheme before picker start:
389
+ --- - With |MiniColors-colorscheme:apply()| if |mini.colors| was available.
390
+ --- - With |:colorscheme| if |g:colors_name| was available.
391
+ ---
392
+ ---@param local_opts __extra_pickers_local_opts
393
+ --- Possible fields:
394
+ --- - <names> `(table)` - array of color scheme names to pick from.
395
+ --- Default: all available color schemes.
396
+ --- - <preview_hl_groups> `(table)` - array of highlight groups to show in preview
397
+ --- window. Default: all defined highlight groups in alphabetical order.
398
+ ---
399
+ ---@param opts __extra_pickers_opts
400
+ ---
401
+ ---@return __extra_pickers_return
402
+ MiniExtra.pickers.colorschemes = function(local_opts, opts)
403
+ local pick = H.validate_pick('colorschemes')
404
+ local_opts = local_opts or {}
405
+
406
+ -- Infer data to show
407
+ local all_cs = vim.fn.getcompletion('', 'color')
408
+ local items = local_opts.names or all_cs
409
+ if not H.islist(items) then H.error('`names` should be array of color scheme names') end
410
+ for _, item in ipairs(items) do
411
+ if not vim.tbl_contains(all_cs, item) then H.error(vim.inspect(item) .. ' is not a color scheme name') end
412
+ end
413
+
414
+ local hl_groups = local_opts.preview_hl_groups
415
+ if hl_groups ~= nil and not H.islist(hl_groups) then H.error('`preview_hl_groups` should be array') end
416
+
417
+ -- Compute original color scheme to restore
418
+ local bg_orig = vim.o.background
419
+ local has_minicolors, minicolors = pcall(require, 'mini.colors')
420
+ local cs_orig = has_minicolors and minicolors.get_colorscheme() or vim.g.colors_name
421
+ local restore = function()
422
+ vim.o.background = bg_orig
423
+ if cs_orig == nil then return end
424
+ if type(cs_orig) == 'string' then return vim.cmd('colorscheme ' .. cs_orig) end
425
+ cs_orig:apply()
426
+ -- Trigger to indicate actual color scheme application
427
+ vim.api.nvim_exec_autocmds('ColorScheme', {})
428
+ end
429
+
430
+ -- Define source
431
+ local choose = function(item) vim.cmd('colorscheme ' .. item) end
432
+ local choose_marked = function(items_to_mark) choose(items_to_mark[1] or '') end
433
+
434
+ local needs_restore = false
435
+ local preview = function(buf_id, item)
436
+ needs_restore = true
437
+ -- Ensure that previewed color scheme tries to use the initial background.
438
+ -- This can be not the case if previous preview forced new background.
439
+ vim.o.background = bg_orig
440
+ vim.cmd('colorscheme ' .. item)
441
+ H.preview_cs_hl_groups(buf_id, hl_groups)
442
+ end
443
+
444
+ local default_source = { name = 'Colorschemes', preview = preview, choose = choose, choose_marked = choose_marked }
445
+ local result = H.pick_start(items, { source = default_source }, opts)
446
+
447
+ if result == nil and needs_restore then restore() end
448
+ return result
449
+ end
450
+
451
+ --- Neovim commands picker
452
+ ---
453
+ --- Pick from Neovim built-in (|Ex-command|) and |user-commands|.
454
+ --- Notes:
455
+ --- - Preview shows information about the command (if available).
456
+ --- - Choosing either executes command (if reliably known that it doesn't need
457
+ --- arguments) or populates Command line with the command.
458
+ ---
459
+ ---@param local_opts __extra_pickers_local_opts
460
+ --- Not used at the moment.
461
+ ---@param opts __extra_pickers_opts
462
+ ---
463
+ ---@return __extra_pickers_return
464
+ MiniExtra.pickers.commands = function(local_opts, opts)
465
+ local pick = H.validate_pick('commands')
466
+
467
+ local commands = vim.tbl_deep_extend('force', vim.api.nvim_get_commands({}), vim.api.nvim_buf_get_commands(0, {}))
468
+
469
+ local preview = function(buf_id, item)
470
+ local data = commands[item]
471
+ local lines = data == nil and { string.format('No command data for `%s` is yet available.', item) }
472
+ or vim.split(vim.inspect(data), '\n')
473
+ H.set_buflines(buf_id, lines)
474
+ end
475
+
476
+ local choose = function(item)
477
+ local data = commands[item] or {}
478
+ -- If no arguments needed, execute immediately
479
+ local keys = string.format(':%s%s', item, data.nargs == '0' and '\r' or ' ')
480
+ vim.schedule(function() vim.fn.feedkeys(keys) end)
481
+ end
482
+
483
+ local items = vim.fn.getcompletion('', 'command')
484
+ local default_opts = { source = { name = 'Commands', preview = preview, choose = choose } }
485
+ return H.pick_start(items, default_opts, opts)
486
+ end
487
+
488
+ --- Built-in diagnostic picker
489
+ ---
490
+ --- Pick from |vim.diagnostic| using |vim.diagnostic.get()|.
491
+ ---
492
+ ---@param local_opts __extra_pickers_local_opts
493
+ --- Possible fields:
494
+ --- - <get_opts> `(table)` - options for |vim.diagnostic.get()|. Can be used
495
+ --- to limit severity or namespace. Default: `{}`.
496
+ --- - <scope> `(string)` - one of "all" (available) or "current" (buffer).
497
+ --- Default: "all".
498
+ --- - <sort_by> `(string)` - sort priority. One of "severity", "path", "none".
499
+ --- Default: "severity".
500
+ ---@param opts __extra_pickers_opts
501
+ ---
502
+ ---@return __extra_pickers_return
503
+ MiniExtra.pickers.diagnostic = function(local_opts, opts)
504
+ local pick = H.validate_pick('diagnostic')
505
+ local_opts = vim.tbl_deep_extend('force', { get_opts = {}, scope = 'all', sort_by = 'severity' }, local_opts or {})
506
+
507
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'current' }, 'diagnostic')
508
+ local sort_by = H.pick_validate_one_of('sort_by', local_opts, { 'severity', 'path', 'none' }, 'diagnostic')
509
+
510
+ local plus_one = function(x)
511
+ if x == nil then return nil end
512
+ return x + 1
513
+ end
514
+
515
+ local diag_buf_id
516
+ if scope == 'current' then diag_buf_id = vim.api.nvim_get_current_buf() end
517
+ local items = vim.deepcopy(vim.diagnostic.get(diag_buf_id, local_opts.get_opts))
518
+
519
+ -- Compute final path width
520
+ local path_width = 0
521
+ for _, item in ipairs(items) do
522
+ item.path = H.buf_get_name(item.bufnr) or ''
523
+ item.severity = item.severity or 0
524
+ path_width = math.max(path_width, vim.fn.strchars(item.path))
525
+ end
526
+
527
+ -- Sort
528
+ local compare = H.diagnostic_make_compare(sort_by)
529
+ if vim.is_callable(compare) then table.sort(items, compare) end
530
+
531
+ -- Update items
532
+ for _, item in ipairs(items) do
533
+ local severity = vim.diagnostic.severity[item.severity] or ' '
534
+ local text = item.message:gsub('\n', ' ')
535
+ item.text = string.format('%s │ %s │ %s', severity:sub(1, 1), H.ensure_text_width(item.path, path_width), text)
536
+ item.lnum, item.col, item.end_lnum, item.end_col =
537
+ plus_one(item.lnum), plus_one(item.col), plus_one(item.end_lnum), plus_one(item.end_col)
538
+ end
539
+
540
+ local hl_groups_ref = {
541
+ [vim.diagnostic.severity.ERROR] = 'DiagnosticFloatingError',
542
+ [vim.diagnostic.severity.WARN] = 'DiagnosticFloatingWarn',
543
+ [vim.diagnostic.severity.INFO] = 'DiagnosticFloatingInfo',
544
+ [vim.diagnostic.severity.HINT] = 'DiagnosticFloatingHint',
545
+ }
546
+
547
+ -- Define source
548
+ local show = function(buf_id, items_to_show, query)
549
+ pick.default_show(buf_id, items_to_show, query)
550
+
551
+ H.pick_clear_namespace(buf_id, H.ns_id.pickers)
552
+ for i, item in ipairs(items_to_show) do
553
+ H.pick_highlight_line(buf_id, i, hl_groups_ref[item.severity], 199)
554
+ end
555
+ end
556
+
557
+ local name = string.format('Diagnostic (%s)', scope)
558
+ return H.pick_start(items, { source = { name = name, choose = H.choose_with_buflisted, show = show } }, opts)
559
+ end
560
+
561
+ --- File explorer picker
562
+ ---
563
+ --- Explore file system and open file.
564
+ --- Notes:
565
+ --- - Choosing a directory navigates inside it, changing picker's items and
566
+ --- current working directory.
567
+ --- - Query and preview work as usual (not only `move_next`/`move_prev` can be used).
568
+ --- - Preview works for any item.
569
+ ---
570
+ --- Examples:
571
+ ---
572
+ --- - `MiniExtra.pickers.explorer()`
573
+ --- - `:Pick explorer cwd='..'` - open explorer in parent directory.
574
+ ---
575
+ ---@param local_opts __extra_pickers_local_opts
576
+ --- Possible fields:
577
+ --- - <cwd> `(string)` - initial directory to explore. Should be a valid
578
+ --- directory path. Default: `nil` for |current-directory|.
579
+ --- - <filter> `(function)` - callable predicate to filter items to show.
580
+ --- Will be called for every item and should return `true` if it should be
581
+ --- shown. Each item is a table with the following fields:
582
+ --- - <fs_type> `(string)` - path type. One of "directory" or "file".
583
+ --- - <path> `(string)` - item path.
584
+ --- - <text> `(string)` - shown text (path's basename).
585
+ --- - <sort> `(function)` - callable item sorter. Will be called with array
586
+ --- of items (each element with structure as described above) and should
587
+ --- return sorted array of items.
588
+ ---@param opts __extra_pickers_opts
589
+ ---
590
+ ---@return __extra_pickers_return
591
+ MiniExtra.pickers.explorer = function(local_opts, opts)
592
+ local pick = H.validate_pick('explorer')
593
+
594
+ local_opts = vim.tbl_deep_extend('force', { cwd = nil, filter = nil, sort = nil }, local_opts or {})
595
+ local cwd = local_opts.cwd or vim.fn.getcwd()
596
+ if vim.fn.isdirectory(cwd) == 0 then H.error('`local_opts.cwd` should be valid directory path.') end
597
+ -- - Call twice "full path" to make sure that possible '..' are collapsed
598
+ cwd = H.full_path(vim.fn.fnamemodify(cwd, ':p'))
599
+ local filter = local_opts.filter or function() return true end
600
+ if not vim.is_callable(filter) then H.error('`local_opts.filter` should be callable.') end
601
+ local sort = local_opts.sort or H.explorer_default_sort
602
+ if not vim.is_callable(sort) then H.error('`local_opts.sort` should be callable.') end
603
+
604
+ -- Define source
605
+ local choose = function(item)
606
+ local path = item.path
607
+ if vim.fn.filereadable(path) == 1 then return pick.default_choose(path) end
608
+ if vim.fn.isdirectory(path) == 0 then return false end
609
+
610
+ pick.set_picker_items(H.explorer_make_items(path, filter, sort))
611
+ pick.set_picker_opts({ source = { cwd = path } })
612
+ pick.set_picker_query({})
613
+ return true
614
+ end
615
+
616
+ local show = H.pick_get_config().source.show or H.show_with_icons
617
+
618
+ local items = H.explorer_make_items(cwd, filter, sort)
619
+ local source = { items = items, name = 'File explorer', cwd = cwd, show = show, choose = choose }
620
+ opts = vim.tbl_deep_extend('force', { source = source }, opts or {})
621
+ return pick.start(opts)
622
+ end
623
+
624
+ --- Git branches picker
625
+ ---
626
+ --- Pick from Git branches using `git branch`.
627
+ --- __extra_pickers_git_notes
628
+ --- - On choose opens scratch buffer with branch's history.
629
+ ---
630
+ --- Examples:
631
+ ---
632
+ --- - `MiniExtra.pickers.git_branches({ scope = 'local' })` - local branches of
633
+ --- the |current-directory| parent Git repository.
634
+ --- - `:Pick git_branches path='%'` - all branches of the current file parent
635
+ --- Git repository.
636
+ ---
637
+ ---@param local_opts __extra_pickers_local_opts
638
+ --- Possible fields:
639
+ --- __extra_pickers_git_path
640
+ --- - <scope> `(string)` - branch scope to show. One of "all", "local", "remotes".
641
+ --- Default: "all".
642
+ ---@param opts __extra_pickers_opts
643
+ ---
644
+ ---@return __extra_pickers_return
645
+ MiniExtra.pickers.git_branches = function(local_opts, opts)
646
+ local pick = H.validate_pick('git_branches')
647
+ H.validate_git('git_branches')
648
+
649
+ local_opts = vim.tbl_deep_extend('force', { path = nil, scope = 'all' }, local_opts or {})
650
+
651
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'local', 'remotes' }, 'git_branches')
652
+
653
+ -- Compute path to repo with target path (as it might differ from current)
654
+ local path, path_type = H.git_normalize_path(local_opts.path, 'git_branches')
655
+ local repo_dir = H.git_get_repo_dir(path, path_type, 'git_branches')
656
+
657
+ -- Define source
658
+ local preview = function(buf_id, item)
659
+ local branch = item:match('^%*?%s*(%S+)')
660
+ H.cli_show_output(buf_id, { 'git', '-C', repo_dir, 'log', branch, '--format=format:%h %s' })
661
+ end
662
+ local choose = function(item)
663
+ local win_target = (pick.get_picker_state().windows or {}).target
664
+ if win_target == nil or not H.is_valid_win(win_target) then return end
665
+ local buf_id = vim.api.nvim_create_buf(true, true)
666
+ H.set_buf_name(buf_id, item:match('^%*?%s*(%S+)'))
667
+ preview(buf_id, item)
668
+ vim.api.nvim_win_set_buf(win_target, buf_id)
669
+ end
670
+
671
+ local command = { 'git', 'branch', '-v', '--no-color', '--list' }
672
+ if scope == 'all' or scope == 'remotes' then table.insert(command, 3, '--' .. scope) end
673
+
674
+ local name = string.format('Git branches (%s)', scope)
675
+ local default_source = { name = name, cwd = repo_dir, preview = preview, choose = choose }
676
+ opts = vim.tbl_deep_extend('force', { source = default_source }, opts or {})
677
+ return pick.builtin.cli({ command = command }, opts)
678
+ end
679
+
680
+ --- Git commits picker
681
+ ---
682
+ --- Pick from Git commits using `git log`.
683
+ --- __extra_pickers_git_notes
684
+ --- - On choose opens scratch buffer with commit's diff.
685
+ ---
686
+ --- Examples:
687
+ ---
688
+ --- - `MiniExtra.pickers.git_commits()` - all commits from parent Git
689
+ --- repository of |current-directory|.
690
+ --- - `MiniExtra.pickers.git_commits({ path = 'subdir' })` - commits affecting
691
+ --- files from `subdir` subdirectory.
692
+ --- - `:Pick git_commits path='%'` commits affecting current file.
693
+ ---
694
+ ---@param local_opts __extra_pickers_local_opts
695
+ --- Possible fields:
696
+ --- __extra_pickers_git_path
697
+ ---@param opts __extra_pickers_opts
698
+ ---
699
+ ---@return __extra_pickers_return
700
+ MiniExtra.pickers.git_commits = function(local_opts, opts)
701
+ local pick = H.validate_pick('git_commits')
702
+ H.validate_git('git_commits')
703
+
704
+ local_opts = vim.tbl_deep_extend('force', { path = nil }, local_opts or {})
705
+
706
+ -- Compute path to repo with target path (as it might differ from current)
707
+ local path, path_type = H.git_normalize_path(local_opts.path, 'git_commits')
708
+ local repo_dir = H.git_get_repo_dir(path, path_type, 'git_commits')
709
+ if local_opts.path == nil then path = repo_dir end
710
+
711
+ -- Define source
712
+ local preview = function(buf_id, item)
713
+ if type(item) ~= 'string' then return end
714
+ local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, 'git', { error = false })
715
+ has_parser = has_parser and parser ~= nil
716
+ if has_parser then has_parser = pcall(vim.treesitter.start, buf_id, 'git') end
717
+ if not has_parser then vim.bo[buf_id].syntax = 'git' end
718
+ H.cli_show_output(buf_id, { 'git', '-C', repo_dir, '--no-pager', 'show', item:match('^(%S+)') })
719
+ end
720
+ local choose = function(item)
721
+ local win_target = (pick.get_picker_state().windows or {}).target
722
+ if win_target == nil or not H.is_valid_win(win_target) then return end
723
+ local buf_id = vim.api.nvim_create_buf(true, true)
724
+ H.set_buf_name(buf_id, item:match('^(%S+)'))
725
+ preview(buf_id, item)
726
+ -- Set filetype on opened buffer to trigger appropriate `FileType` event
727
+ vim.bo[buf_id].filetype = 'git'
728
+ vim.api.nvim_win_set_buf(win_target, buf_id)
729
+ end
730
+
731
+ local command = { 'git', 'log', [[--format=format:%h %s]], '--', path }
732
+
733
+ local name = string.format('Git commits (%s)', local_opts.path == nil and 'all' or 'for path')
734
+ local default_source = { name = name, cwd = repo_dir, preview = preview, choose = choose }
735
+ opts = vim.tbl_deep_extend('force', { source = default_source }, opts or {})
736
+ return pick.builtin.cli({ command = command }, opts)
737
+ end
738
+
739
+ --- Git files picker
740
+ ---
741
+ --- Pick from Git files using `git ls-files` using standard ignore rules.
742
+ --- __extra_pickers_git_notes
743
+ ---
744
+ --- Examples:
745
+ ---
746
+ --- - `MiniExtra.pickers.git_files({ scope = 'ignored' })` - ignored files from
747
+ --- parent Git repository of |current-directory|.
748
+ --- - `:Pick git_files path='subdir' scope='modified'` - files from `subdir`
749
+ --- subdirectory which differ from Git index.
750
+ ---
751
+ ---@param local_opts __extra_pickers_local_opts
752
+ --- Possible fields:
753
+ --- __extra_pickers_git_path
754
+ --- - <scope> `(string)` - files scope to show. One of:
755
+ --- - "tracked" (`--cached` Git flag).
756
+ --- - "modified" (`--modified` Git flag).
757
+ --- - "untracked" (`--others` Git flag).
758
+ --- - "ignored" (`--ignored` Git flag).
759
+ --- - "deleted" (`--deleted` Git flag).
760
+ --- Default: "tracked".
761
+ ---@param opts __extra_pickers_opts
762
+ ---
763
+ ---@return __extra_pickers_return
764
+ MiniExtra.pickers.git_files = function(local_opts, opts)
765
+ local pick = H.validate_pick('git_files')
766
+ H.validate_git('git_files')
767
+
768
+ local_opts = vim.tbl_deep_extend('force', { path = nil, scope = 'tracked' }, local_opts or {})
769
+ local allowed_scope = { 'tracked', 'modified', 'untracked', 'ignored', 'deleted' }
770
+ local scope = H.pick_validate_scope(local_opts, allowed_scope, 'git_files')
771
+
772
+ -- Compute path to repo with target path (as it might differ from current)
773
+ local path, path_type = H.git_normalize_path(local_opts.path, 'git_files')
774
+ local repo_dir = H.git_get_repo_dir(path, path_type, 'git_files')
775
+ if local_opts.path == nil then path = repo_dir end
776
+ local path_dir = path_type == 'directory' and path or vim.fn.fnamemodify(path, ':h')
777
+
778
+ -- Define source
779
+ local show = H.pick_get_config().source.show or H.show_with_icons
780
+
781
+ local args = ({
782
+ tracked = { '--cached', '--exclude-standard' },
783
+ modified = { '--modified', '--exclude-standard' },
784
+ untracked = { '--others', '--exclude-standard' },
785
+ ignored = { '--others', '--ignored', '--exclude-standard' },
786
+ deleted = { '--deleted', '--exclude-standard' },
787
+ })[local_opts.scope]
788
+ local command = vim.list_extend({ 'git', '-C', path_dir, '-c', 'core.quotepath=false', 'ls-files' }, args)
789
+
790
+ local name = string.format('Git files (%s)', local_opts.scope)
791
+ local default_source = { name = name, cwd = path_dir, show = show }
792
+ opts = vim.tbl_deep_extend('force', { source = default_source }, opts or {})
793
+ return pick.builtin.cli({ command = command }, opts)
794
+ end
795
+
796
+ --- Git hunks picker
797
+ ---
798
+ --- Pick from Git hunks using `git diff`.
799
+ --- __extra_pickers_git_notes
800
+ --- - On choose navigates to hunk's first change.
801
+ ---
802
+ --- Examples:
803
+ ---
804
+ --- - `MiniExtra.pickers.git_hunks({ scope = 'staged' })` - staged hunks from
805
+ --- parent Git repository of |current-directory|.
806
+ --- - `:Pick git_hunks path='%' n_context=0` - hunks from current file computed
807
+ --- with no context.
808
+ ---
809
+ ---@param local_opts __extra_pickers_local_opts
810
+ --- Possible fields:
811
+ --- - <n_context> `(number)` - number of context lines to show in hunk's preview.
812
+ --- Default: 3.
813
+ --- __extra_pickers_git_path
814
+ --- - <scope> `(string)` - hunks scope to show. One of "unstaged" or "staged".
815
+ --- Default: "unstaged".
816
+ ---@param opts __extra_pickers_opts
817
+ ---
818
+ ---@return __extra_pickers_return
819
+ MiniExtra.pickers.git_hunks = function(local_opts, opts)
820
+ local pick = H.validate_pick('git_hunks')
821
+ H.validate_git('git_hunks')
822
+
823
+ local default_local_opts = { n_context = 3, path = nil, scope = 'unstaged' }
824
+ local_opts = vim.tbl_deep_extend('force', default_local_opts, local_opts or {})
825
+
826
+ if not (type(local_opts.n_context) == 'number' and local_opts.n_context >= 0) then
827
+ H.error('`n_context` option in `pickers.git_hunks` picker should be non-negative number.')
828
+ end
829
+ local n_context = math.floor(local_opts.n_context)
830
+ local scope = H.pick_validate_scope(local_opts, { 'unstaged', 'staged' }, 'git_hunks')
831
+
832
+ -- Compute path to repo with target path (as it might differ from current)
833
+ local path, path_type = H.git_normalize_path(local_opts.path, 'git_hunks')
834
+ local repo_dir = H.git_get_repo_dir(path, path_type, 'git_hunks')
835
+ if local_opts.path == nil then path = repo_dir end
836
+
837
+ -- Define source
838
+ local show = H.pick_get_config().source.show or H.show_with_icons
839
+ local preview = function(buf_id, item)
840
+ local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, 'diff', { error = false })
841
+ has_parser = has_parser and parser ~= nil
842
+ if has_parser then has_parser = pcall(vim.treesitter.start, buf_id, 'diff') end
843
+ if not has_parser then vim.bo[buf_id].syntax = 'diff' end
844
+ local lines = vim.deepcopy(item.header)
845
+ vim.list_extend(lines, item.hunk)
846
+ H.set_buflines(buf_id, lines)
847
+ end
848
+
849
+ local command = { 'git', 'diff', '--patch', '--unified=' .. n_context, '--color=never', '--', path }
850
+ if scope == 'staged' then table.insert(command, 4, '--cached') end
851
+
852
+ local postprocess = function(lines) return H.git_difflines_to_hunkitems(lines, n_context) end
853
+
854
+ local name = string.format('Git hunks (%s %s)', scope, local_opts.path == nil and 'all' or 'for path')
855
+ local default_source = { name = name, cwd = repo_dir, show = show, preview = preview }
856
+ opts = vim.tbl_deep_extend('force', { source = default_source }, opts or {})
857
+ return pick.builtin.cli({ command = command, postprocess = postprocess }, opts)
858
+ end
859
+
860
+ --- Matches from |mini.hipatterns| picker
861
+ ---
862
+ --- Pick from |mini.hipatterns| matches using |MiniHipatterns.get_matches()|.
863
+ --- Notes:
864
+ --- - Requires |mini.hipatterns|.
865
+ --- - Highlighter identifier is highlighted with its highlight group.
866
+ ---
867
+ ---@param local_opts __extra_pickers_local_opts
868
+ --- Possible fields:
869
+ --- - <scope> `(string)` - one of "all" (buffers with enabled |mini.hipatterns|)
870
+ --- or "current" (buffer). Default: "all".
871
+ --- - <highlighters> `(table|nil)` - highlighters for which to find matches.
872
+ --- Forwarded to |MiniHipatterns.get_matches()|. Default: `nil`.
873
+ ---@param opts __extra_pickers_opts
874
+ ---
875
+ ---@return __extra_pickers_return
876
+ MiniExtra.pickers.hipatterns = function(local_opts, opts)
877
+ local pick = H.validate_pick('hipatterns')
878
+ local has_hipatterns, hipatterns = pcall(require, 'mini.hipatterns')
879
+ if not has_hipatterns then H.error([[`pickers.hipatterns` requires 'mini.hipatterns' which can not be found.]]) end
880
+
881
+ local_opts = vim.tbl_deep_extend('force', { highlighters = nil, scope = 'all' }, local_opts or {})
882
+ if local_opts.highlighters ~= nil and not H.islist(local_opts.highlighters) then
883
+ H.error('`local_opts.highlighters` should be an array of highlighter identifiers.')
884
+ end
885
+ local highlighters = local_opts.highlighters
886
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'current' }, 'hipatterns')
887
+
888
+ -- Get items
889
+ local buffers = scope == 'all' and hipatterns.get_enabled_buffers() or { vim.api.nvim_get_current_buf() }
890
+ local items, highlighter_width = {}, 0
891
+ for _, buf_id in ipairs(buffers) do
892
+ local lines = vim.api.nvim_buf_get_lines(buf_id, 0, -1, false)
893
+ local buf_name = H.buf_get_name(buf_id)
894
+ if buf_name == '' then buf_name = 'Buffer_' .. buf_id end
895
+
896
+ for _, match in ipairs(hipatterns.get_matches(buf_id, highlighters)) do
897
+ match.highlighter = tostring(match.highlighter)
898
+ match.buf_name, match.line = buf_name, lines[match.lnum]
899
+ table.insert(items, match)
900
+ highlighter_width = math.max(highlighter_width, vim.fn.strchars(match.highlighter))
901
+ end
902
+ end
903
+
904
+ for _, item in ipairs(items) do
905
+ --stylua: ignore
906
+ item.text = string.format(
907
+ '%s │ %s│%d│%d│%s',
908
+ H.ensure_text_width(item.highlighter, highlighter_width),
909
+ item.buf_name, item.lnum, item.col, item.line
910
+ )
911
+ item.buf_name, item.line = nil, nil
912
+ end
913
+
914
+ local show = function(buf_id, items_to_show, query)
915
+ pick.default_show(buf_id, items_to_show, query)
916
+
917
+ H.pick_clear_namespace(buf_id, H.ns_id.pickers)
918
+ for i, item in ipairs(items_to_show) do
919
+ local end_col = string.len(item.highlighter)
920
+ local extmark_opts = { hl_group = item.hl_group, end_row = i - 1, end_col = end_col, priority = 1 }
921
+ vim.api.nvim_buf_set_extmark(buf_id, H.ns_id.pickers, i - 1, 0, extmark_opts)
922
+ end
923
+ end
924
+
925
+ local name = string.format('Mini.hipatterns matches (%s)', scope)
926
+ return H.pick_start(items, { source = { name = name, show = show } }, opts)
927
+ end
928
+
929
+ --- Neovim history picker
930
+ ---
931
+ --- Pick from output of |:history|. Use `<C-e>` to edit current match in
932
+ --- Command line.
933
+ ---
934
+ --- Notes:
935
+ --- - Has no preview.
936
+ --- - Choosing action depends on scope:
937
+ --- - For "cmd" / ":" scopes, the command is executed.
938
+ --- - For "search" / "/" / "?" scopes, search is redone.
939
+ --- - For other scopes nothing is done (but chosen item is still returned).
940
+ --- - `<C-e>` only works for "cmd" / ":" / "search" / "/" / "?" scopes.
941
+ ---
942
+ --- Examples:
943
+ ---
944
+ --- - Command history: `MiniExtra.pickers.history({ scope = ':' })`
945
+ --- - Search history: `:Pick history scope='/'`
946
+ ---
947
+ ---@param local_opts __extra_pickers_local_opts
948
+ --- Possible fields:
949
+ --- - <scope> `(string)` - any allowed {name} flag of |:history| command.
950
+ --- Note: word abbreviations are not allowed. Default: "all".
951
+ ---@param opts __extra_pickers_opts
952
+ ---
953
+ ---@return __extra_pickers_return
954
+ MiniExtra.pickers.history = function(local_opts, opts)
955
+ local pick = H.validate_pick('history')
956
+ local_opts = vim.tbl_deep_extend('force', { scope = 'all' }, local_opts or {})
957
+
958
+ local allowed_scope = { 'all', 'cmd', 'search', 'expr', 'input', 'debug', ':', '/', '?', '=', '@', '>' }
959
+ local scope = H.pick_validate_scope(local_opts, allowed_scope, 'history')
960
+
961
+ --stylua: ignore
962
+ local type_ids = {
963
+ cmd = ':', search = '/', expr = '=', input = '@', debug = '>',
964
+ [':'] = ':', ['/'] = '/', ['='] = '=', ['@'] = '@', ['>'] = '>',
965
+ ['?'] = '?',
966
+ }
967
+
968
+ -- Construct items
969
+ local items = {}
970
+ local names = scope == 'all' and { 'cmd', 'search', 'expr', 'input', 'debug' } or { scope }
971
+ for _, cur_name in ipairs(names) do
972
+ local id = type_ids[cur_name]
973
+ for i = 1, vim.fn.histnr(cur_name) do
974
+ local hist_entry = vim.fn.histget(cur_name, -i)
975
+ if hist_entry == '' then break end
976
+ table.insert(items, string.format('%s %s', id, hist_entry))
977
+ end
978
+ end
979
+
980
+ -- Define source
981
+ local preview = H.pick_make_no_preview('history')
982
+
983
+ local choose = function(item)
984
+ if not (type(item) == 'string' and vim.fn.mode() == 'n') then return end
985
+ local id, entry = item:match('^(.) (.*)$')
986
+ if id == ':' or id == '/' or id == '?' then
987
+ vim.schedule(function() vim.fn.feedkeys(id .. entry .. '\r', 'nxt') end)
988
+ end
989
+ end
990
+
991
+ local edit_command = function()
992
+ local cur_match = MiniPick.get_picker_matches().current
993
+ local cur_scope, cur_item = cur_match:match('^(.) (.*)$')
994
+ if not (cur_scope == ':' or cur_scope == '/' or cur_scope == '?') then return end
995
+ vim.schedule(function() vim.api.nvim_input(cur_scope .. cur_item:gsub('<', '<LT>')) end)
996
+ return true
997
+ end
998
+ local mappings = { edit_command = { char = '<C-e>', func = edit_command } }
999
+
1000
+ opts = vim.tbl_deep_extend('force', opts or {}, { mappings = mappings })
1001
+ local default_source = { name = string.format('History (%s)', scope), preview = preview, choose = choose }
1002
+ return H.pick_start(items, { source = default_source }, opts)
1003
+ end
1004
+
1005
+ --- Highlight groups picker
1006
+ ---
1007
+ --- Pick and preview highlight groups.
1008
+ --- Notes:
1009
+ --- - Item line is colored with same highlight group it represents.
1010
+ --- - Preview shows highlight's definition (as in |:highlight| with {group-name}).
1011
+ --- - Choosing places highlight definition in Command line to update and apply.
1012
+ ---
1013
+ ---@param local_opts __extra_pickers_local_opts
1014
+ --- Not used at the moment.
1015
+ ---@param opts __extra_pickers_opts
1016
+ ---
1017
+ ---@return __extra_pickers_return
1018
+ MiniExtra.pickers.hl_groups = function(local_opts, opts)
1019
+ local pick = H.validate_pick('hl_groups')
1020
+
1021
+ -- Construct items
1022
+ local group_data = vim.split(vim.api.nvim_exec('highlight', true), '\n')
1023
+ local items = {}
1024
+ for _, l in ipairs(group_data) do
1025
+ local group = l:match('^(%S+)')
1026
+ if group ~= nil then table.insert(items, group) end
1027
+ end
1028
+
1029
+ local show = function(buf_id, items_to_show, query)
1030
+ H.set_buflines(buf_id, items_to_show)
1031
+ H.pick_clear_namespace(buf_id, H.ns_id.pickers)
1032
+ -- Highlight line with highlight group of its item
1033
+ for i = 1, #items_to_show do
1034
+ H.pick_highlight_line(buf_id, i, items_to_show[i], 300)
1035
+ end
1036
+ end
1037
+
1038
+ -- Define source
1039
+ local preview = function(buf_id, item)
1040
+ local lines = vim.split(vim.api.nvim_exec('hi ' .. item, true), '\n')
1041
+ H.set_buflines(buf_id, lines)
1042
+ end
1043
+
1044
+ local choose = function(item)
1045
+ local hl_def = vim.split(vim.api.nvim_exec('hi ' .. item, true), '\n')[1]
1046
+ hl_def = hl_def:gsub('^(%S+)%s+xxx%s+', '%1 ')
1047
+ vim.schedule(function() vim.fn.feedkeys(':hi ' .. hl_def, 'n') end)
1048
+ end
1049
+
1050
+ local default_source = { name = 'Highlight groups', show = show, preview = preview, choose = choose }
1051
+ return H.pick_start(items, { source = default_source }, opts)
1052
+ end
1053
+
1054
+ --- Neovim keymaps picker
1055
+ ---
1056
+ --- Pick and preview data about Neovim keymaps.
1057
+ --- Notes:
1058
+ --- - Item line contains data about keymap mode, whether it is buffer local, its
1059
+ --- left hand side, and inferred description.
1060
+ --- - Preview shows keymap data or callback source (if present and reachable).
1061
+ --- - Choosing emulates pressing the left hand side of the keymap.
1062
+ ---
1063
+ ---@param local_opts __extra_pickers_local_opts
1064
+ --- Possible fields:
1065
+ --- - <mode> `(string)` - modes to show. One of "all" or appropriate mode
1066
+ --- for |nvim_set_keymap()|. Default: "all".
1067
+ --- - <scope> `(string)` - scope to show. One of "all", "global", "buf".
1068
+ --- Default: "all".
1069
+ ---@param opts __extra_pickers_opts
1070
+ ---
1071
+ ---@return __extra_pickers_return
1072
+ MiniExtra.pickers.keymaps = function(local_opts, opts)
1073
+ local pick = H.validate_pick('keymaps')
1074
+ local_opts = vim.tbl_deep_extend('force', { mode = 'all', scope = 'all' }, local_opts or {})
1075
+
1076
+ local mode = H.pick_validate_one_of('mode', local_opts, { 'all', 'n', 'x', 's', 'o', 'i', 'l', 'c', 't' }, 'keymaps')
1077
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'global', 'buf' }, 'keymaps')
1078
+
1079
+ -- Create items
1080
+ local items = {}
1081
+ local populate_modes = mode == 'all' and { 'n', 'x', 's', 'o', 'i', 'l', 'c', 't' } or { mode }
1082
+ local max_lhs_width = 0
1083
+ local populate_items = function(source)
1084
+ for _, m in ipairs(populate_modes) do
1085
+ for _, maparg in ipairs(source(m)) do
1086
+ local desc = maparg.desc ~= nil and vim.inspect(maparg.desc) or maparg.rhs
1087
+ local lhs = vim.fn.keytrans(maparg.lhsraw or maparg.lhs)
1088
+ max_lhs_width = math.max(vim.fn.strchars(lhs), max_lhs_width)
1089
+ table.insert(items, { lhs = lhs, desc = desc, maparg = maparg })
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ if scope == 'all' or scope == 'buf' then populate_items(function(m) return vim.api.nvim_buf_get_keymap(0, m) end) end
1095
+ if scope == 'all' or scope == 'global' then populate_items(vim.api.nvim_get_keymap) end
1096
+
1097
+ for _, item in ipairs(items) do
1098
+ local buf_map_indicator = item.maparg.buffer == 0 and ' ' or '@'
1099
+ local lhs_text = H.ensure_text_width(item.lhs, max_lhs_width)
1100
+ item.text = string.format('%s %s │ %s │ %s', item.maparg.mode, buf_map_indicator, lhs_text, item.desc or '')
1101
+ end
1102
+
1103
+ -- Define source
1104
+ local get_callback_pos = function(maparg)
1105
+ if type(maparg.callback) ~= 'function' then return nil, nil end
1106
+ local info = debug.getinfo(maparg.callback)
1107
+ local path = info.source:gsub('^@', '')
1108
+ if vim.fn.filereadable(path) == 0 then return nil, nil end
1109
+ return path, info.linedefined
1110
+ end
1111
+
1112
+ local preview = function(buf_id, item)
1113
+ local path, lnum = get_callback_pos(item.maparg)
1114
+ if path ~= nil then
1115
+ item.path, item.lnum = path, lnum
1116
+ return pick.default_preview(buf_id, item)
1117
+ end
1118
+ local lines = vim.split(vim.inspect(item.maparg), '\n')
1119
+ H.set_buflines(buf_id, lines)
1120
+ end
1121
+
1122
+ local choose = function(item)
1123
+ local keys = vim.api.nvim_replace_termcodes(item.maparg.lhs, true, true, true)
1124
+ -- Restore Visual mode (should be active previously at least once)
1125
+ if item.maparg.mode == 'x' then keys = 'gv' .. keys end
1126
+ vim.schedule(function() vim.api.nvim_input(keys) end)
1127
+ end
1128
+
1129
+ local default_opts = { source = { name = string.format('Keymaps (%s)', scope), preview = preview, choose = choose } }
1130
+ return H.pick_start(items, default_opts, opts)
1131
+ end
1132
+
1133
+ --- Neovim lists picker
1134
+ ---
1135
+ --- Pick and navigate to elements of the following Neovim lists:
1136
+ --- - |quickfix| list.
1137
+ --- - |location-list| of current window.
1138
+ --- - |jumplist|.
1139
+ --- - |changelist|.
1140
+ ---
1141
+ --- Note: it requires explicit `scope`.
1142
+ ---
1143
+ --- Examples:
1144
+ ---
1145
+ --- - `MiniExtra.pickers.list({ scope = 'quickfix' })` - quickfix list.
1146
+ --- - `:Pick list scope='jump'` - jump list.
1147
+ ---
1148
+ ---@param local_opts __extra_pickers_local_opts
1149
+ --- Possible fields:
1150
+ --- - <scope> `(string)` - type of list to show. One of "quickfix", "location",
1151
+ --- "jump", "change". Default: `nil` which means explicit scope is needed.
1152
+ ---@param opts __extra_pickers_opts
1153
+ ---
1154
+ ---@return __extra_pickers_return
1155
+ MiniExtra.pickers.list = function(local_opts, opts)
1156
+ local pick = H.validate_pick('list')
1157
+ local_opts = vim.tbl_deep_extend('force', { scope = nil }, local_opts or {})
1158
+
1159
+ if local_opts.scope == nil then H.error('`pickers.list` needs an explicit scope.') end
1160
+ local allowed_scopes = { 'quickfix', 'location', 'jump', 'change' }
1161
+ local scope = H.pick_validate_scope(local_opts, allowed_scopes, 'list')
1162
+
1163
+ local has_items, items = pcall(H.list_get[scope])
1164
+ if not has_items then items = {} end
1165
+
1166
+ items = vim.tbl_filter(function(x) return H.is_valid_buf(x.bufnr) end, items)
1167
+ items = vim.tbl_map(H.list_enhance_item, items)
1168
+
1169
+ local name = string.format('List (%s)', scope)
1170
+ local show = H.pick_get_config().source.show or H.show_with_icons
1171
+ return H.pick_start(items, { source = { name = name, show = show, choose = H.choose_with_buflisted } }, opts)
1172
+ end
1173
+
1174
+ --- LSP picker
1175
+ ---
1176
+ --- Pick and navigate with LSP methods.
1177
+ --- Notes:
1178
+ --- - Needs an explicit scope from a list of supported ones:
1179
+ --- - "declaration".
1180
+ --- - "definition".
1181
+ --- - "document_symbol".
1182
+ --- - "implementation".
1183
+ --- - "references".
1184
+ --- - "type_definition".
1185
+ --- - "workspace_symbol".
1186
+ --- - "workspace_symbol_live" - same as "workspace_symbol", but with live
1187
+ --- feedback treating picker's prompt as LSP server query. Similar to
1188
+ --- how |MiniPick.builtin.grep_live()| and |MiniPick.builtin.grep()| are
1189
+ --- related. To use regular matching, activate |MiniPick-actions-refine|.
1190
+ --- - Relies on `vim.lsp.buf` methods supporting |vim.lsp.LocationOpts.OnList|.
1191
+ --- In particular, it means that picker is started only if LSP server returns
1192
+ --- list of locations and not a single location.
1193
+ --- - Doesn't return anything due to async nature of `vim.lsp.buf` methods.
1194
+ --- - Requires set up |mini.icons| to show extra icons and highlighting in
1195
+ --- "document_symbol", "workspace_symbol", "workspace_symbol_live" scopes.
1196
+ ---
1197
+ --- Examples:
1198
+ ---
1199
+ --- - `MiniExtra.pickers.lsp({ scope = 'references' })` - references of the symbol
1200
+ --- under cursor.
1201
+ --- - `:Pick lsp scope='document_symbol'` - symbols in current file.
1202
+ ---
1203
+ ---@param local_opts __extra_pickers_local_opts
1204
+ --- Possible fields:
1205
+ --- - <scope> `(string)` - LSP method to use. One of the supported ones (see
1206
+ --- list above). Default: `nil` which means explicit scope is needed.
1207
+ --- - <symbol_query> `(string)` - query for `"workspace_symbol"` scope.
1208
+ --- Default: empty string for all symbols (according to LSP specification).
1209
+ ---@param opts __extra_pickers_opts
1210
+ ---
1211
+ ---@return nil Nothing is returned.
1212
+ MiniExtra.pickers.lsp = function(local_opts, opts)
1213
+ local pick = H.validate_pick('lsp')
1214
+ local_opts = vim.tbl_deep_extend('force', { scope = nil, symbol_query = '' }, local_opts or {})
1215
+
1216
+ if local_opts.scope == nil then H.error('`pickers.lsp` needs an explicit scope.') end
1217
+ --stylua: ignore
1218
+ local allowed_scopes = {
1219
+ 'declaration', 'definition', 'document_symbol', 'implementation',
1220
+ 'references', 'type_definition', 'workspace_symbol', 'workspace_symbol_live'
1221
+ }
1222
+ local scope = H.pick_validate_scope(local_opts, allowed_scopes, 'lsp')
1223
+
1224
+ local buf_lsp_opts, picker_opts = H.lsp_make_opts(scope, opts)
1225
+ if scope == 'references' then return vim.lsp.buf[scope](nil, buf_lsp_opts) end
1226
+ if scope == 'workspace_symbol' then
1227
+ local query = tostring(local_opts.symbol_query)
1228
+ return vim.lsp.buf[scope](query, buf_lsp_opts)
1229
+ end
1230
+ if scope == 'workspace_symbol_live' then
1231
+ picker_opts.source.match = function(_, _, query)
1232
+ if #query == 0 then return MiniPick.set_picker_items({}, { do_match = false }) end
1233
+ local win_id = MiniPick.get_picker_state().windows.target
1234
+ local buf_id = vim.api.nvim_win_get_buf(win_id)
1235
+ vim.api.nvim_buf_call(buf_id, function() vim.lsp.buf.workspace_symbol(table.concat(query), buf_lsp_opts) end)
1236
+ end
1237
+
1238
+ return H.pick_start({}, picker_opts, opts)
1239
+ end
1240
+ vim.lsp.buf[scope](buf_lsp_opts)
1241
+ end
1242
+
1243
+ --- Manual pages
1244
+ ---
1245
+ --- Pick manual page (like described in |ft-man-plugin|).
1246
+ --- Notes:
1247
+ --- - Depends on |:Man| command to preview and choose items.
1248
+ --- - Shows page in the target window. Use |MiniPick-actions-choose| to split.
1249
+ ---
1250
+ ---@param local_opts __extra_pickers_local_opts
1251
+ --- Not used at the moment.
1252
+ ---@param opts __extra_pickers_opts
1253
+ ---
1254
+ ---@return __extra_pickers_return
1255
+ MiniExtra.pickers.manpages = function(local_opts, opts)
1256
+ local pick = H.validate_pick('manpages')
1257
+ if vim.fn.exists(':Man') ~= 2 then H.error('`manpages` picker needs `:Man` command') end
1258
+
1259
+ local latest_man_buf_id
1260
+ local show_man = function(win_id, item, is_preview)
1261
+ -- table.insert(_G.log, { item, name, section })
1262
+ local name, section = (item or ''):match('^(.-)%s-%((.-)%)')
1263
+ if name == nil or section == nil then return end
1264
+ -- - Use first command
1265
+ name = name:gsub(',.*$', '')
1266
+ -- - Extract first valid section. NOTE: using only digits is not enough
1267
+ -- (for example, `man 1 man` and `man 1p man` are different).
1268
+ section = section:match('%w+') or ''
1269
+
1270
+ -- Show man page directly in the target window
1271
+ vim.api.nvim_win_call(win_id, function() vim.cmd('hide Man ' .. section .. ' ' .. name) end)
1272
+
1273
+ -- Ensure proper choose. Possibly undo `buflisted=false` from preview.
1274
+ if not is_preview then
1275
+ vim.bo.buflisted = true
1276
+ return
1277
+ end
1278
+
1279
+ -- Ensure proper preview buffer
1280
+ vim.bo.buflisted, vim.bo.bufhidden, vim.bo.matchpairs = false, 'wipe', ''
1281
+ vim.b.minicursorword_disable = true
1282
+ vim.b.miniindentscope_disable = true
1283
+ -- - Modify after `:Man` to not have "Buffer with this name already exists"
1284
+ vim.api.nvim_buf_set_name(0, 'minipick://' .. vim.api.nvim_get_current_buf() .. '/preview')
1285
+ end
1286
+
1287
+ local preview = function(buf_id, item) show_man(vim.fn.win_findbuf(buf_id)[1], item, true) end
1288
+ local choose = function(item) show_man(MiniPick.get_picker_state().windows.target, item) end
1289
+
1290
+ -- Set necessary environment variables for `vim.loop.spawn` (as it doesn't
1291
+ -- inherit environment variables)
1292
+ local env = { 'MANWIDTH=999' }
1293
+ table.insert(env, vim.env.PATH ~= nil and ('PATH=' .. vim.env.PATH) or nil)
1294
+ table.insert(env, vim.env.MANPATH ~= nil and ('MANPATH=' .. vim.env.MANPATH) or nil)
1295
+ local source = { name = 'Manpages', choose = choose, preview = preview }
1296
+ opts = vim.tbl_deep_extend('force', { source = source }, opts or {})
1297
+ return pick.builtin.cli({ command = { 'man', '-k', '.' }, spawn_opts = { env = env } }, opts)
1298
+ end
1299
+
1300
+ --- Neovim marks picker
1301
+ ---
1302
+ --- Pick and preview position of Neovim |mark|s.
1303
+ ---
1304
+ ---@param local_opts __extra_pickers_local_opts
1305
+ --- Possible fields:
1306
+ --- - <scope> `(string)` - scope to show. One of "all", "global", "buf".
1307
+ --- Default: "all".
1308
+ ---@param opts __extra_pickers_opts
1309
+ ---
1310
+ ---@return __extra_pickers_return
1311
+ MiniExtra.pickers.marks = function(local_opts, opts)
1312
+ local pick = H.validate_pick('marks')
1313
+ local_opts = vim.tbl_deep_extend('force', { scope = 'all' }, local_opts or {})
1314
+
1315
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'global', 'buf' }, 'marks')
1316
+
1317
+ -- Create items
1318
+ local items = {}
1319
+ local populate_items = function(mark_list)
1320
+ for _, info in ipairs(mark_list) do
1321
+ local path
1322
+ if type(info.file) == 'string' then path = vim.fn.fnamemodify(info.file, ':.') end
1323
+ local buf_id
1324
+ if path == nil then buf_id = info.pos[1] end
1325
+
1326
+ local line, col = info.pos[2], math.abs(info.pos[3])
1327
+ local text = string.format('%s │ %s%s│%s', info.mark:sub(2), path == nil and '' or (path .. '│'), line, col)
1328
+ table.insert(items, { text = text, bufnr = buf_id, path = path, lnum = line, col = col })
1329
+ end
1330
+ end
1331
+
1332
+ if scope == 'all' or scope == 'buf' then populate_items(vim.fn.getmarklist(vim.api.nvim_get_current_buf())) end
1333
+ if scope == 'all' or scope == 'global' then populate_items(vim.fn.getmarklist()) end
1334
+
1335
+ local default_opts = { source = { name = string.format('Marks (%s)', scope) } }
1336
+ return H.pick_start(items, default_opts, opts)
1337
+ end
1338
+
1339
+ --- Old files picker
1340
+ ---
1341
+ --- Pick from |v:oldfiles| entries representing readable files.
1342
+ ---
1343
+ ---@param local_opts __extra_pickers_local_opts
1344
+ --- Possible fields:
1345
+ --- - <current_dir> `(boolean)` - whether to return files only from current
1346
+ --- working directory and its subdirectories. Default: `false`.
1347
+ --- __extra_pickers_preserve_order
1348
+ ---@param opts __extra_pickers_opts
1349
+ ---
1350
+ ---@return __extra_pickers_return
1351
+ MiniExtra.pickers.oldfiles = function(local_opts, opts)
1352
+ local pick = H.validate_pick('oldfiles')
1353
+ local_opts = vim.tbl_deep_extend('force', { current_dir = false, preserve_order = false }, local_opts or {})
1354
+ local oldfiles = vim.v.oldfiles
1355
+ if not H.islist(oldfiles) then H.error('`pickers.oldfiles` picker needs valid `v:oldfiles`.') end
1356
+
1357
+ local show_all = not local_opts.current_dir
1358
+ local items = vim.schedule_wrap(function()
1359
+ local cwd = H.normalize_path(pick.get_picker_opts().source.cwd) .. '/'
1360
+ local res = {}
1361
+ for _, path in ipairs(oldfiles) do
1362
+ path = H.normalize_path(path)
1363
+ if vim.fn.filereadable(path) == 1 and (show_all or vim.startswith(path, cwd)) then
1364
+ table.insert(res, H.short_path(path, cwd))
1365
+ end
1366
+ end
1367
+ pick.set_picker_items(res)
1368
+ end)
1369
+
1370
+ local show = H.pick_get_config().source.show or H.show_with_icons
1371
+ local match_opts = { preserve_order = local_opts.preserve_order }
1372
+ local match = function(stritems, inds, query) pick.default_match(stritems, inds, query, match_opts) end
1373
+ return H.pick_start(items, { source = { name = 'Old files', show = show, match = match } }, opts)
1374
+ end
1375
+
1376
+ --- Neovim options picker
1377
+ ---
1378
+ --- Pick and preview data about Neovim options.
1379
+ --- Notes:
1380
+ --- - Item line is colored based on whether it was set (dimmed if wasn't).
1381
+ --- - Preview shows option value in target window and its general information.
1382
+ --- - Choosing places option name in Command line to update and apply.
1383
+ ---
1384
+ ---@param local_opts __extra_pickers_local_opts
1385
+ --- Possible fields:
1386
+ --- - <scope> `(string)` - options to show. One of "all", "global", "win", "buf".
1387
+ --- Default: "all".
1388
+ ---@param opts __extra_pickers_opts
1389
+ ---
1390
+ ---@return __extra_pickers_return
1391
+ MiniExtra.pickers.options = function(local_opts, opts)
1392
+ local pick = H.validate_pick('options')
1393
+ local_opts = vim.tbl_deep_extend('force', { scope = 'all' }, local_opts or {})
1394
+
1395
+ local scope = H.pick_validate_scope(local_opts, { 'all', 'global', 'win', 'buf' }, 'options')
1396
+
1397
+ local items = {}
1398
+ for name, info in pairs(vim.api.nvim_get_all_options_info()) do
1399
+ if scope == 'all' or scope == info.scope then table.insert(items, { text = name, info = info }) end
1400
+ end
1401
+ table.sort(items, function(a, b) return a.text < b.text end)
1402
+
1403
+ local show = function(buf_id, items_to_show, query)
1404
+ pick.default_show(buf_id, items_to_show, query)
1405
+
1406
+ H.pick_clear_namespace(buf_id, H.ns_id.pickers)
1407
+ for i, item in ipairs(items_to_show) do
1408
+ if not item.info.was_set then H.pick_highlight_line(buf_id, i, 'Comment', 199) end
1409
+ end
1410
+ end
1411
+
1412
+ local preview = function(buf_id, item)
1413
+ local pick_windows = pick.get_picker_state().windows
1414
+ local target_win_id = pick_windows.target
1415
+ if not H.is_valid_win(target_win_id) then target_win_id = pick_windows.main end
1416
+ local value_source = ({ global = 'o', win = 'wo', buf = 'bo' })[item.info.scope]
1417
+ local has_value, value = pcall(function()
1418
+ return vim.api.nvim_win_call(target_win_id, function() return vim[value_source][item.info.name] end)
1419
+ end)
1420
+ -- TODO: consider removing after Neovim<=0.10 compatibility is dropped
1421
+ if not has_value then value = '<Option is deprecated (will be removed in later Neovim versions)>' end
1422
+
1423
+ local lines = { 'Value:', unpack(vim.split(vim.inspect(value), '\n')), '', 'Info:' }
1424
+ local hl_lines = { 1, #lines }
1425
+ lines = vim.list_extend(lines, vim.split(vim.inspect(item.info), '\n'))
1426
+
1427
+ H.set_buflines(buf_id, lines)
1428
+ H.pick_highlight_line(buf_id, hl_lines[1], 'MiniPickHeader', 200)
1429
+ H.pick_highlight_line(buf_id, hl_lines[2], 'MiniPickHeader', 200)
1430
+ end
1431
+
1432
+ local choose = function(item)
1433
+ local keys = string.format(':set %s%s', item.info.name, item.info.type == 'boolean' and '' or '=')
1434
+ vim.schedule(function() vim.fn.feedkeys(keys) end)
1435
+ end
1436
+
1437
+ local name = string.format('Options (%s)', scope)
1438
+ local default_source = { name = name, show = show, preview = preview, choose = choose }
1439
+ return H.pick_start(items, { source = default_source }, opts)
1440
+ end
1441
+
1442
+ --- Neovim registers picker
1443
+ ---
1444
+ --- Pick from Neovim |registers|.
1445
+ --- Notes:
1446
+ --- - There is no preview (all information is in the item's text).
1447
+ --- - Choosing pastes content of a register: with |i_CTRL-R| in Insert mode,
1448
+ --- |c_CTRL-R| in Command-line mode, and |P| otherwise.
1449
+ --- Expression register |quote=| is reevaluated (if present) and pasted.
1450
+ ---
1451
+ ---@param local_opts __extra_pickers_local_opts
1452
+ --- Not used at the moment.
1453
+ ---@param opts __extra_pickers_opts
1454
+ ---
1455
+ ---@return __extra_pickers_return
1456
+ MiniExtra.pickers.registers = function(local_opts, opts)
1457
+ local pick = H.validate_pick('registers')
1458
+
1459
+ local describe_register = function(regname)
1460
+ local ok, value = pcall(vim.fn.getreg, regname, 1)
1461
+ if not ok then return '' end
1462
+ return value
1463
+ end
1464
+
1465
+ local all_registers = vim.split('"*+:.%/#=-0123456789abcdefghijklmnopqrstuvwxyz', '')
1466
+
1467
+ local items = {}
1468
+ for _, regname in ipairs(all_registers) do
1469
+ local regcontents = describe_register(regname)
1470
+ local text = string.format('%s │ %s', regname, regcontents)
1471
+ table.insert(items, { regname = regname, regcontents = regcontents, text = text })
1472
+ end
1473
+
1474
+ local choose = vim.schedule_wrap(function(item)
1475
+ local reg, regcontents, mode = item.regname, item.regcontents, vim.fn.mode()
1476
+ if reg == '=' and regcontents ~= '' then reg = reg .. item.regcontents .. '\r' end
1477
+ local keys = string.format('"%s%s', reg, reg == '=' and '' or 'P')
1478
+ -- In Insert and Command-line modes use `<C-r><regname>`
1479
+ if mode == 'i' or mode == 'c' then keys = '\18' .. reg end
1480
+ vim.fn.feedkeys(keys)
1481
+ end)
1482
+
1483
+ local preview = function(buf_id, item) H.set_buflines(buf_id, vim.split(item.regcontents, '\n')) end
1484
+
1485
+ return H.pick_start(items, { source = { name = 'Registers', preview = preview, choose = choose } }, opts)
1486
+ end
1487
+
1488
+ --- Neovim spell suggestions picker
1489
+ ---
1490
+ --- Pick and apply spell suggestions.
1491
+ --- Notes:
1492
+ --- - No preview is available.
1493
+ --- - Choosing replaces current word (|<cword>|) with suggestion.
1494
+ ---
1495
+ ---@param local_opts __extra_pickers_local_opts
1496
+ --- Possible fields:
1497
+ --- - <n_suggestions> `(number)` - number of spell suggestions. Default: 25.
1498
+ ---
1499
+ ---@param opts __extra_pickers_opts
1500
+ ---
1501
+ ---@return __extra_pickers_return
1502
+ MiniExtra.pickers.spellsuggest = function(local_opts, opts)
1503
+ local pick = H.validate_pick('spellsuggest')
1504
+ local_opts = vim.tbl_deep_extend('force', { n_suggestions = 25 }, local_opts or {})
1505
+
1506
+ local n_suggestions = local_opts.n_suggestions
1507
+ if not (type(n_suggestions) == 'number' and n_suggestions > 0) then
1508
+ H.error('`local_opts.n_suggestions` should be a positive number.')
1509
+ end
1510
+
1511
+ local word = vim.fn.expand('<cword>')
1512
+ local suggestions = vim.fn.spellsuggest(word, n_suggestions)
1513
+ local items = {}
1514
+ for i, sugg in ipairs(suggestions) do
1515
+ table.insert(items, { text = sugg, index = i })
1516
+ end
1517
+
1518
+ -- Define scope
1519
+ local preview = H.pick_make_no_preview('spellsuggest')
1520
+ local choose = vim.schedule_wrap(function(item) vim.cmd('normal! ' .. item.index .. 'z=') end)
1521
+
1522
+ local name = 'Spell suggestions for ' .. vim.inspect(word)
1523
+ return H.pick_start(items, { source = { name = name, preview = preview, choose = choose } }, opts)
1524
+ end
1525
+
1526
+ --- Tree-sitter nodes picker
1527
+ ---
1528
+ --- Pick and navigate to |treesitter| nodes of current buffer.
1529
+ --- Notes:
1530
+ --- - Requires active tree-sitter parser in the current buffer.
1531
+ ---
1532
+ ---@param local_opts __extra_pickers_local_opts
1533
+ --- Not used at the moment.
1534
+ ---@param opts __extra_pickers_opts
1535
+ ---
1536
+ ---@return __extra_pickers_return
1537
+ MiniExtra.pickers.treesitter = function(local_opts, opts)
1538
+ local pick = H.validate_pick('treesitter')
1539
+
1540
+ local buf_id = vim.api.nvim_get_current_buf()
1541
+ -- TODO: Remove `opts.error` after compatibility with Neovim=0.11 is dropped
1542
+ local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, nil, { error = false })
1543
+ if not has_parser or parser == nil then H.error('`pickers.treesitter` requires active tree-sitter parser.') end
1544
+
1545
+ -- Make items by traversing roots of all trees (including injections)
1546
+ local items, traverse = {}, nil
1547
+ traverse = function(node, depth)
1548
+ if depth >= 1000 then return end
1549
+ for child in node:iter_children() do
1550
+ if child:named() then
1551
+ local lnum, col, end_lnum, end_col = child:range()
1552
+ lnum, col, end_lnum, end_col = lnum + 1, col + 1, end_lnum + 1, end_col + 1
1553
+ local indent = string.rep(' ', depth)
1554
+ local text = string.format('%s%s (%s│%s - %s│%s)', indent, child:type() or '', lnum, col, end_lnum, end_col)
1555
+ local item = { text = text, bufnr = buf_id, lnum = lnum, col = col, end_lnum = end_lnum, end_col = end_col }
1556
+ table.insert(items, item)
1557
+
1558
+ traverse(child, depth + 1)
1559
+ end
1560
+ end
1561
+ end
1562
+
1563
+ parser:for_each_tree(function(ts_tree, _) traverse(ts_tree:root(), 0) end)
1564
+
1565
+ return H.pick_start(items, { source = { name = 'Tree-sitter nodes' } }, opts)
1566
+ end
1567
+
1568
+ --- Visit paths from |mini.visits| picker
1569
+ ---
1570
+ --- Pick paths from |mini.visits| using |MiniVisits.list_paths()|.
1571
+ --- Notes:
1572
+ --- - Requires |mini.visits|.
1573
+ ---
1574
+ --- Examples:
1575
+ ---
1576
+ --- - `MiniExtra.pickers.visit_paths()` - visits registered for |current-directory|
1577
+ --- and ordered by "robust frecency".
1578
+ --- - `:Pick visit_paths cwd='' recency_weight=1 filter='core'` - all visits with
1579
+ --- "core" label ordered from most to least recent.
1580
+ ---
1581
+ ---@param local_opts __extra_pickers_local_opts
1582
+ --- Possible fields:
1583
+ --- - <cwd> `(string)` - forwarded to |MiniVisits.list_paths()|.
1584
+ --- Default: `nil` to get paths registered for |current-directory|.
1585
+ --- - <filter> `(function|string)` - forwarded to |MiniVisits.list_paths()|.
1586
+ --- Default: `nil` to use all paths.
1587
+ --- __extra_pickers_preserve_order
1588
+ --- - <recency_weight> `(number)` - forwarded to |MiniVisits.gen_sort.default()|.
1589
+ --- Default: 0.5 to use "robust frecency" sorting.
1590
+ --- - <sort> `(function)` - forwarded to |MiniVisits.list_paths()|.
1591
+ --- Default: `nil` to use "robust frecency".
1592
+ --- Note: if supplied, has precedence over `recency_weight`.
1593
+ ---@param opts __extra_pickers_opts
1594
+ ---
1595
+ ---@return __extra_pickers_return
1596
+ MiniExtra.pickers.visit_paths = function(local_opts, opts)
1597
+ local pick = H.validate_pick('visit_paths')
1598
+ local has_visits, visits = pcall(require, 'mini.visits')
1599
+ if not has_visits then H.error([[`pickers.visit_paths` requires 'mini.visits' which can not be found.]]) end
1600
+
1601
+ local default_local_opts = { cwd = nil, filter = nil, preserve_order = false, recency_weight = 0.5, sort = nil }
1602
+ local_opts = vim.tbl_deep_extend('force', default_local_opts, local_opts or {})
1603
+
1604
+ local cwd = local_opts.cwd or vim.fn.getcwd()
1605
+ -- NOTE: Use separate cwd to allow `cwd = ''` to not mean "current directory"
1606
+ local is_for_cwd = cwd ~= ''
1607
+ local picker_cwd = H.normalize_path(cwd == '' and vim.fn.getcwd() or H.full_path(cwd))
1608
+
1609
+ -- Define source
1610
+ local filter = local_opts.filter or visits.gen_filter.default()
1611
+ local sort = local_opts.sort or visits.gen_sort.default({ recency_weight = local_opts.recency_weight })
1612
+ local items = vim.schedule_wrap(function()
1613
+ local paths = visits.list_paths(cwd, { filter = filter, sort = sort })
1614
+ paths = vim.tbl_map(function(x) return H.normalize_path(H.short_path(x, picker_cwd)) end, paths)
1615
+ pick.set_picker_items(paths)
1616
+ end)
1617
+
1618
+ local show = H.pick_get_config().source.show or H.show_with_icons
1619
+ local match_opts = { preserve_order = local_opts.preserve_order }
1620
+ local match = function(stritems, inds, query) pick.default_match(stritems, inds, query, match_opts) end
1621
+
1622
+ local name = string.format('Visit paths (%s)', is_for_cwd and 'cwd' or 'all')
1623
+ local default_source = { name = name, cwd = picker_cwd, match = match, show = show }
1624
+ return H.pick_start(items, { source = default_source }, opts)
1625
+ end
1626
+
1627
+ --- Visit labels from |mini.visits| picker
1628
+ ---
1629
+ --- Pick labels from |mini.visits| using |MiniVisits.list_labels()|
1630
+ --- and |MiniVisits.list_paths()|.
1631
+ --- Notes:
1632
+ --- - Requires |mini.visits|.
1633
+ --- - Preview shows target visit paths filtered to those having previewed label.
1634
+ --- - Choosing essentially starts |MiniExtra.pickers.visit_paths()| for paths
1635
+ --- with the chosen label.
1636
+ ---
1637
+ --- Examples:
1638
+ ---
1639
+ --- - `MiniExtra.pickers.visit_labels()` - labels from visits registered
1640
+ --- for |current-directory|.
1641
+ --- - `:Pick visit_labels cwd=''` - labels from all visits.
1642
+ ---
1643
+ ---@param local_opts __extra_pickers_local_opts
1644
+ --- Possible fields:
1645
+ --- - <cwd> `(string)` - forwarded to |MiniVisits.list_labels()|.
1646
+ --- Default: `nil` to get labels from visits registered for |current-directory|.
1647
+ --- - <filter> `(function|string)` - forwarded to |MiniVisits.list_labels()|.
1648
+ --- Default: `nil` to use all visits.
1649
+ --- - <path> `(string)` - forwarded to |MiniVisits.list_labels()|.
1650
+ --- Default: `""` to get labels from all visits for target `cwd`.
1651
+ --- - <sort> `(function)` - forwarded to |MiniVisits.list_paths()| for
1652
+ --- preview and choose. Default: `nil` to use "robust frecency".
1653
+ ---@param opts __extra_pickers_opts
1654
+ ---
1655
+ ---@return ... Chosen path.
1656
+ MiniExtra.pickers.visit_labels = function(local_opts, opts)
1657
+ local pick = H.validate_pick('visit_labels')
1658
+ local has_visits, visits = pcall(require, 'mini.visits')
1659
+ if not has_visits then H.error([[`pickers.visit_labels` requires 'mini.visits' which can not be found.]]) end
1660
+
1661
+ local default_local_opts = { cwd = nil, filter = nil, path = '', sort = nil }
1662
+ local_opts = vim.tbl_deep_extend('force', default_local_opts, local_opts or {})
1663
+
1664
+ local cwd = local_opts.cwd or vim.fn.getcwd()
1665
+ -- NOTE: Use separate cwd to allow `cwd = ''` to not mean "current directory"
1666
+ local is_for_cwd = cwd ~= ''
1667
+ local picker_cwd = H.normalize_path(cwd == '' and vim.fn.getcwd() or H.full_path(cwd))
1668
+
1669
+ local filter = local_opts.filter or visits.gen_filter.default()
1670
+ local items = visits.list_labels(local_opts.path, local_opts.cwd, { filter = filter })
1671
+
1672
+ -- Define source
1673
+ local list_label_paths = function(label)
1674
+ local new_filter = function(path_data)
1675
+ return filter(path_data) and type(path_data.labels) == 'table' and path_data.labels[label]
1676
+ end
1677
+ local all_paths = visits.list_paths(local_opts.cwd, { filter = new_filter, sort = local_opts.sort })
1678
+ return vim.tbl_map(function(x) return H.normalize_path(H.short_path(x, picker_cwd)) end, all_paths)
1679
+ end
1680
+
1681
+ local preview = function(buf_id, label) vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, list_label_paths(label)) end
1682
+ local choose = function(label)
1683
+ if label == nil then return end
1684
+
1685
+ pick.set_picker_items(list_label_paths(label), { do_match = false })
1686
+ pick.set_picker_query({})
1687
+ local name = string.format('Paths for %s label', vim.inspect(label))
1688
+ local show = H.pick_get_config().source.show or H.show_with_icons
1689
+ pick.set_picker_opts({ source = { name = name, show = show, choose = pick.default_choose } })
1690
+ return true
1691
+ end
1692
+
1693
+ local name = string.format('Visit labels (%s)', is_for_cwd and 'cwd' or 'all')
1694
+ local default_source = { name = name, cwd = picker_cwd, preview = preview, choose = choose }
1695
+ return H.pick_start(items, { source = default_source }, opts)
1696
+ end
1697
+
1698
+ -- Helper data ================================================================
1699
+ -- Module default config
1700
+ H.default_config = MiniExtra.config
1701
+
1702
+ -- Namespaces
1703
+ H.ns_id = {
1704
+ pickers = vim.api.nvim_create_namespace('MiniExtraPickers'),
1705
+ }
1706
+
1707
+ -- Various cache
1708
+ H.cache = {}
1709
+
1710
+ -- File system information
1711
+ H.is_windows = vim.loop.os_uname().sysname == 'Windows_NT'
1712
+
1713
+ -- Helper functionality =======================================================
1714
+ -- Settings -------------------------------------------------------------------
1715
+ H.setup_config = function(config)
1716
+ H.check_type('config', config, 'table', true)
1717
+ return config
1718
+ end
1719
+
1720
+ H.apply_config = function(config)
1721
+ MiniExtra.config = config
1722
+
1723
+ -- Register pickers in 'mini.pick'
1724
+ if type(_G.MiniPick) == 'table' then
1725
+ for name, f in pairs(MiniExtra.pickers) do
1726
+ _G.MiniPick.registry[name] = _G.MiniPick.registry[name] or function(local_opts) return f(local_opts) end
1727
+ end
1728
+ end
1729
+ end
1730
+
1731
+ -- Mini.ai specifications -----------------------------------------------------
1732
+ H.ai_indent_spec = function(ai_type)
1733
+ -- Compute buffer data
1734
+ local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
1735
+ local tab_spaces = string.rep(' ', vim.bo.tabstop)
1736
+
1737
+ -- Traverse lines from top to bottom casting rays
1738
+ local indents, rays, rays_final = {}, {}, {}
1739
+ for i, l in ipairs(lines) do
1740
+ indents[i] = l:match('^([ \t]*)')
1741
+
1742
+ -- Ray can be updated only on non-blank line
1743
+ local is_blank = indents[i]:len() ~= l:len()
1744
+ if is_blank then H.ai_indent_update_rays(i, indents[i]:gsub('\t', tab_spaces):len(), rays, rays_final) end
1745
+ end
1746
+
1747
+ -- The `rays` stack can be not empty at this point which means that there are
1748
+ -- non-empty lines at buffer end without "closing". Ignore them.
1749
+
1750
+ -- Sort for better output
1751
+ table.sort(rays_final, function(a, b) return a.from_line < b.from_line end)
1752
+
1753
+ -- Compute regions:
1754
+ -- - `a` is as if linewise from start to end.
1755
+ -- - `i` is as if charwise not including edge whitespace on start and end.
1756
+ local from_offset, to_offset, to_col_offset = 0, 0, 1
1757
+ if ai_type == 'i' then
1758
+ from_offset, to_offset, to_col_offset = 1, -1, 0
1759
+ end
1760
+ local res = {}
1761
+ for i, ray in ipairs(rays_final) do
1762
+ local from_line, to_line = ray.from_line + from_offset, ray.to_line + to_offset
1763
+ local from_col = ai_type == 'a' and 1 or (indents[from_line]:len() + 1)
1764
+ local to_col = lines[to_line]:len() + to_col_offset
1765
+ res[i] = { from = { line = from_line, col = from_col }, to = { line = to_line, col = to_col } }
1766
+ end
1767
+ return res
1768
+ end
1769
+
1770
+ H.ai_indent_update_rays = function(line_num, indent, rays, rays_final)
1771
+ -- Update rays with finite indent
1772
+ -- `rays` is a stack of cast rays (sorted by increasing start indent).
1773
+ -- Each ray has `from_line` and `to_line` indicating start of `a` textobject.
1774
+ for i = #rays, 1, -1 do
1775
+ local ray = rays[i]
1776
+ -- If current indent is bigger, then ray is cast over non-blank region.
1777
+ -- This assumes that line at `line_num` is not blank.
1778
+ if ray.indent < indent then
1779
+ ray.is_empty = false
1780
+ -- All previously cast rays are already marked as non-blank if they are
1781
+ break
1782
+ end
1783
+
1784
+ -- If ray was cast from bigger indent then current and spans over
1785
+ -- non-empty region, finalize it as it has hit its limit
1786
+ if not ray.is_empty then
1787
+ ray.to_line = line_num
1788
+ table.insert(rays_final, ray)
1789
+ end
1790
+ rays[i] = nil
1791
+ end
1792
+
1793
+ -- Start new ray
1794
+ table.insert(rays, { indent = indent, from_line = line_num, is_empty = true })
1795
+ end
1796
+
1797
+ -- Pickers --------------------------------------------------------------------
1798
+ H.validate_pick = function(fun_name)
1799
+ local has_pick, pick = pcall(require, 'mini.pick')
1800
+ if not has_pick then
1801
+ H.error(string.format([[`pickers.%s()` requires 'mini.pick' which can not be found.]], fun_name))
1802
+ end
1803
+ return pick
1804
+ end
1805
+
1806
+ H.pick_start = function(items, default_opts, opts)
1807
+ local pick = H.validate_pick()
1808
+ local opts_final = vim.tbl_deep_extend('force', default_opts, opts or {}, { source = { items = items } })
1809
+ return pick.start(opts_final)
1810
+ end
1811
+
1812
+ H.pick_highlight_line = function(buf_id, line, hl_group, priority, start_col)
1813
+ local opts = { end_row = line, end_col = 0, hl_mode = 'blend', hl_group = hl_group, priority = priority }
1814
+ vim.api.nvim_buf_set_extmark(buf_id, H.ns_id.pickers, line - 1, start_col or 0, opts)
1815
+ end
1816
+
1817
+ H.pick_prepend_position = function(item)
1818
+ local path
1819
+ if item.path ~= nil then
1820
+ path = item.path
1821
+ elseif H.is_valid_buf(item.bufnr) then
1822
+ local name = vim.api.nvim_buf_get_name(item.bufnr)
1823
+ path = name == '' and ('Buffer_' .. item.bufnr) or name
1824
+ end
1825
+ if path == nil then return item end
1826
+
1827
+ path = vim.fn.fnamemodify(path, ':p:.')
1828
+ local text = item.text or ''
1829
+ local suffix = text == '' and '' or ('│ ' .. text)
1830
+ item.text = string.format('%s│%s│%s%s', path, item.lnum or 1, item.col or 1, suffix)
1831
+ item.text_start_col = text ~= '' and (item.text:len() - text:len()) or nil
1832
+ return item
1833
+ end
1834
+
1835
+ H.pick_clear_namespace = function(buf_id, ns_id) pcall(vim.api.nvim_buf_clear_namespace, buf_id, ns_id, 0, -1) end
1836
+
1837
+ H.pick_make_no_preview = function(picker_name)
1838
+ local lines = { string.format('No preview available for `%s` picker', picker_name) }
1839
+ return function(buf_id, _) H.set_buflines(buf_id, lines) end
1840
+ end
1841
+
1842
+ H.pick_validate_one_of = function(target, opts, values, picker_name)
1843
+ if vim.tbl_contains(values, opts[target]) then return opts[target] end
1844
+ local msg = string.format(
1845
+ '`pickers.%s` has wrong "%s" local option (%s). Should be one of %s.',
1846
+ picker_name,
1847
+ target,
1848
+ vim.inspect(opts[target]),
1849
+ table.concat(vim.tbl_map(vim.inspect, values), ', ')
1850
+ )
1851
+ H.error(msg)
1852
+ end
1853
+
1854
+ H.pick_validate_scope = function(...) return H.pick_validate_one_of('scope', ...) end
1855
+
1856
+ H.pick_get_config = function()
1857
+ return vim.tbl_deep_extend('force', (require('mini.pick') or {}).config or {}, vim.b.minipick_config or {})
1858
+ end
1859
+
1860
+ H.show_with_icons = function(buf_id, items, query)
1861
+ require('mini.pick').default_show(buf_id, items, query, { show_icons = true })
1862
+ end
1863
+
1864
+ H.choose_with_buflisted = function(item)
1865
+ local pick = require('mini.pick')
1866
+ pick.default_choose(item)
1867
+
1868
+ -- Force 'buflisted' on opened item
1869
+ local win_target = pick.get_picker_state().windows.target
1870
+ local buf_id = vim.api.nvim_win_get_buf(win_target)
1871
+ vim.bo[buf_id].buflisted = true
1872
+ end
1873
+
1874
+ -- Colorscheme picker ----------------------------------------------------------
1875
+ H.preview_cs_hl_groups = function(buf_id, hl_groups)
1876
+ local lines = hl_groups
1877
+ if lines == nil then
1878
+ lines = vim.tbl_keys(vim.api.nvim_get_hl(0, {}))
1879
+ table.sort(lines)
1880
+ end
1881
+ vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
1882
+
1883
+ local ns_id = H.ns_id.pickers
1884
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
1885
+ for i, hl in ipairs(lines) do
1886
+ pcall(vim.api.nvim_buf_set_extmark, buf_id, ns_id, i - 1, 0, { end_row = i, end_col = 0, hl_group = hl })
1887
+ end
1888
+ end
1889
+
1890
+ -- Diagnostic picker ----------------------------------------------------------
1891
+ H.diagnostic_make_compare = function(sort_by)
1892
+ if sort_by == 'severity' then
1893
+ return function(a, b)
1894
+ if a.severity < b.severity then return true end
1895
+ if a.severity > b.severity then return false end
1896
+ if a.path < b.path then return true end
1897
+ if a.path > b.path then return false end
1898
+ if a.lnum < b.lnum then return true end
1899
+ if a.lnum > b.lnum then return false end
1900
+ return a.col < b.col
1901
+ end
1902
+ end
1903
+ if sort_by == 'path' then
1904
+ return function(a, b)
1905
+ if a.path < b.path then return true end
1906
+ if a.path > b.path then return false end
1907
+ if a.severity < b.severity then return true end
1908
+ if a.severity > b.severity then return false end
1909
+ if a.lnum < b.lnum then return true end
1910
+ if a.lnum > b.lnum then return false end
1911
+ return a.col < b.col
1912
+ end
1913
+ end
1914
+
1915
+ return nil
1916
+ end
1917
+
1918
+ -- Git pickers ----------------------------------------------------------------
1919
+ H.validate_git = function(picker_name)
1920
+ if vim.fn.executable('git') == 1 then return true end
1921
+ local msg = string.format('`pickers.%s` requires executable `git`.', picker_name)
1922
+ H.error(msg)
1923
+ end
1924
+
1925
+ H.git_normalize_path = function(path, picker_name)
1926
+ path = type(path) == 'string' and path or vim.fn.getcwd()
1927
+ if path == '' then H.error(string.format('Path in `%s` is empty.', picker_name)) end
1928
+ path = H.full_path(path)
1929
+ local path_is_dir, path_is_file = vim.fn.isdirectory(path) == 1, vim.fn.filereadable(path) == 1
1930
+ if not (path_is_dir or path_is_file) then H.error('Path ' .. path .. ' is not a valid path.') end
1931
+ return path, path_is_dir and 'directory' or 'file'
1932
+ end
1933
+
1934
+ H.git_get_repo_dir = function(path, path_type, picker_name)
1935
+ local path_dir = path_type == 'directory' and path or vim.fn.fnamemodify(path, ':h')
1936
+ local repo_dir = vim.fn.systemlist({ 'git', '-C', path_dir, 'rev-parse', '--show-toplevel' })[1]
1937
+ if vim.v.shell_error ~= 0 then
1938
+ local msg = string.format('`pickers.%s` could not find Git repo for %s.', picker_name, path)
1939
+ H.error(msg)
1940
+ end
1941
+ return repo_dir
1942
+ end
1943
+
1944
+ H.git_difflines_to_hunkitems = function(lines, n_context)
1945
+ local header_pattern = '^diff %-%-git'
1946
+ local hunk_pattern = '^@@ %-%d+,?%d* %+(%d+),?%d* @@'
1947
+ -- NOTE: Account for possible `diff.mnemonicPrefix=true` Git config. In that
1948
+ -- case the destination can also be `w`. If won't work for some reason, more
1949
+ -- robust solution is to modify `git diff`: `--src-prefix=a/ --dst-prefix=b/`
1950
+ local from_path_pattern = '^%-%-%- [ai]/(.*)$'
1951
+ local to_path_pattern = '^%+%+%+ [bw]/(.*)$'
1952
+
1953
+ -- Parse diff lines
1954
+ local cur_header, cur_path, is_in_hunk = {}, nil, false
1955
+ local items = {}
1956
+ for _, l in ipairs(lines) do
1957
+ -- Separate path header and hunk for better granularity
1958
+ if l:find(header_pattern) ~= nil then
1959
+ is_in_hunk = false
1960
+ cur_header = {}
1961
+ end
1962
+
1963
+ local path_match = l:match(to_path_pattern) or l:match(from_path_pattern)
1964
+ if path_match ~= nil and not is_in_hunk then cur_path = path_match end
1965
+
1966
+ local hunk_start = l:match(hunk_pattern)
1967
+ if hunk_start ~= nil then
1968
+ is_in_hunk = true
1969
+ local item = { path = cur_path, lnum = tonumber(hunk_start), header = vim.deepcopy(cur_header), hunk = {} }
1970
+ table.insert(items, item)
1971
+ end
1972
+
1973
+ if is_in_hunk then
1974
+ table.insert(items[#items].hunk, l)
1975
+ else
1976
+ table.insert(cur_header, l)
1977
+ end
1978
+ end
1979
+
1980
+ -- Correct line number to point at the first change
1981
+ local try_correct_lnum = function(item, i)
1982
+ if item.hunk[i]:find('^[+-]') == nil then return false end
1983
+ item.lnum = item.lnum + i - 2
1984
+ return true
1985
+ end
1986
+ for _, item in ipairs(items) do
1987
+ for i = 2, #item.hunk do
1988
+ if try_correct_lnum(item, i) then break end
1989
+ end
1990
+ end
1991
+
1992
+ -- Construct aligned text from path and hunk header
1993
+ local text_parts, path_width, coords_width = {}, 0, 0
1994
+ for i, item in ipairs(items) do
1995
+ local coords, title = item.hunk[1]:match('@@ (.-) @@ ?(.*)$')
1996
+ coords, title = coords or '', title or ''
1997
+ text_parts[i] = { item.path, coords, title }
1998
+ path_width = math.max(path_width, vim.fn.strchars(item.path))
1999
+ coords_width = math.max(coords_width, vim.fn.strchars(coords))
2000
+ end
2001
+
2002
+ for i, item in ipairs(items) do
2003
+ local parts = text_parts[i]
2004
+ local path, coords = H.ensure_text_width(parts[1], path_width), H.ensure_text_width(parts[2], coords_width)
2005
+ item.text = string.format('%s │ %s │ %s', path, coords, parts[3])
2006
+ end
2007
+
2008
+ return items
2009
+ end
2010
+
2011
+ -- LSP picker -----------------------------------------------------------------
2012
+ H.lsp_make_opts = function(source, opts)
2013
+ local is_symbol = source:find('symbol') ~= nil
2014
+
2015
+ -- Prepend file position info to item, add decortion, and sort
2016
+ local add_decor_data = function() end
2017
+ if is_symbol and _G.MiniIcons == nil then
2018
+ -- Try using '@...' style highlight group with same name as "kind"
2019
+ add_decor_data = function(item) item.hl = string.format('@%s', string.lower(item.kind or 'unknown')) end
2020
+ end
2021
+ if is_symbol and _G.MiniIcons ~= nil then
2022
+ add_decor_data = function(item)
2023
+ if type(item.kind) ~= 'string' then return end
2024
+ local icon, hl = MiniIcons.get('lsp', item.kind)
2025
+ -- If kind is not original, assume it already contains an icon
2026
+ local icon_prefix = item.kind_orig == item.kind and (icon .. ' ') or ''
2027
+ item.text, item.hl = icon_prefix .. item.text, hl
2028
+ end
2029
+ end
2030
+
2031
+ local process = function(items)
2032
+ if source ~= 'document_symbol' then items = vim.tbl_map(H.pick_prepend_position, items) end
2033
+ -- Input `item.kind` is a string (resolved before `on_list`). Account for
2034
+ -- possibly tweaked symbol map (like after `MiniIcons.tweak_lsp_kind`).
2035
+ local kind_map = H.get_symbol_kind_map()
2036
+ for _, item in ipairs(items) do
2037
+ item.kind_orig, item.kind = item.kind, kind_map[item.kind]
2038
+ add_decor_data(item)
2039
+ item.kind_orig = nil
2040
+ end
2041
+ -- Preserve order for queried methods as server can return deliberate order
2042
+ if not vim.startswith(source, 'workspace_symbol') then table.sort(items, H.lsp_items_compare) end
2043
+ return items
2044
+ end
2045
+
2046
+ local pick = H.validate_pick()
2047
+ local picker_opts = { source = { name = string.format('LSP (%s)', source) } }
2048
+
2049
+ local show_explicit = H.pick_get_config().source.show
2050
+ picker_opts.source.show = function(buf_id, items_to_show, query)
2051
+ if show_explicit ~= nil then return show_explicit(buf_id, items_to_show, query) end
2052
+ -- Show with icons as the non-symbol scopes should have paths
2053
+ if not is_symbol then return H.show_with_icons(buf_id, items_to_show, query) end
2054
+
2055
+ -- Highlight whole lines with pre-computed symbol kind highlight groups
2056
+ pick.default_show(buf_id, items_to_show, query)
2057
+
2058
+ H.pick_clear_namespace(buf_id, H.ns_id.pickers)
2059
+ for i, item in ipairs(items_to_show) do
2060
+ -- Highlight only after position text to emphasize symbol data
2061
+ H.pick_highlight_line(buf_id, i, item.hl, 199, item.text_start_col)
2062
+ end
2063
+ end
2064
+
2065
+ picker_opts.source.choose = function(item)
2066
+ pick.default_choose(item)
2067
+ -- Ensure relative path in `:buffers` output with hacky workaround.
2068
+ -- `default_choose` ensures it with `bufadd(fnamemodify(path, ':.'))`, but
2069
+ -- somehow that doesn't work inside `on_list` of `vim.lsp.buf` methods.
2070
+ vim.fn.chdir(vim.fn.getcwd())
2071
+ end
2072
+
2073
+ local on_list = function(data)
2074
+ local items = data.items
2075
+ for _, item in ipairs(data.items) do
2076
+ item.text, item.path = item.text or '', item.filename or nil
2077
+ end
2078
+ items = process(items)
2079
+
2080
+ if MiniPick.is_picker_active() and source == 'workspace_symbol_live' then
2081
+ return MiniPick.set_picker_items(items, { do_match = false })
2082
+ end
2083
+
2084
+ return H.pick_start(items, picker_opts, opts)
2085
+ end
2086
+
2087
+ return { on_list = on_list }, picker_opts
2088
+ end
2089
+
2090
+ H.get_symbol_kind_map = function()
2091
+ -- Compute symbol kind map from "resolved" string kind to its "original" (as in
2092
+ -- LSP protocol). Those can be different after `MiniIcons.tweak_lsp_kind()`.
2093
+ local res = {}
2094
+ local double_map = vim.lsp.protocol.SymbolKind
2095
+ for k, v in pairs(double_map) do
2096
+ if type(k) == 'string' and type(v) == 'number' then res[double_map[v]] = k end
2097
+ end
2098
+ return res
2099
+ end
2100
+
2101
+ H.lsp_items_compare = function(a, b)
2102
+ local a_path, b_path = a.path or '', b.path or ''
2103
+ if a_path < b_path then return true end
2104
+ if a_path > b_path then return false end
2105
+
2106
+ local a_lnum, b_lnum = a.lnum or 1, b.lnum or 1
2107
+ if a_lnum < b_lnum then return true end
2108
+ if a_lnum > b_lnum then return false end
2109
+
2110
+ local a_col, b_col = a.col or 1, b.col or 1
2111
+ if a_col < b_col then return true end
2112
+ if a_col > b_col then return false end
2113
+
2114
+ return tostring(a) < tostring(b)
2115
+ end
2116
+
2117
+ -- List picker ----------------------------------------------------------------
2118
+ H.list_get = {
2119
+ quickfix = function() return vim.tbl_map(H.list_enhance_qf_loc, vim.fn.getqflist()) end,
2120
+
2121
+ location = function() return vim.tbl_map(H.list_enhance_qf_loc, vim.fn.getloclist(0)) end,
2122
+
2123
+ jump = function()
2124
+ local raw = vim.fn.getjumplist()[1]
2125
+ -- Tweak output: reverse for more relevance; make 1-based column
2126
+ local res, n = {}, #raw
2127
+ for i, x in ipairs(raw) do
2128
+ x.col = x.col + 1
2129
+ res[n - i + 1] = x
2130
+ end
2131
+ return res
2132
+ end,
2133
+
2134
+ change = function()
2135
+ local cur_buf = vim.api.nvim_get_current_buf()
2136
+ local res = vim.fn.getchangelist(cur_buf)[1]
2137
+ for _, x in ipairs(res) do
2138
+ x.col = x.col + 1
2139
+ x.bufnr = cur_buf
2140
+ end
2141
+ return res
2142
+ end,
2143
+ }
2144
+
2145
+ H.list_enhance_qf_loc = function(item)
2146
+ if item.end_lnum == 0 then item.end_lnum = nil end
2147
+ if item.end_col == 0 then item.end_col = nil end
2148
+ if H.is_valid_buf(item.bufnr) then
2149
+ local filename = vim.api.nvim_buf_get_name(item.bufnr)
2150
+ if filename ~= '' then item.filename = filename end
2151
+ end
2152
+ return item
2153
+ end
2154
+
2155
+ H.list_enhance_item = function(item)
2156
+ if vim.fn.filereadable(item.filename) == 1 then item.path = item.filename end
2157
+ return H.pick_prepend_position(item)
2158
+ end
2159
+
2160
+ -- Explorer picker ------------------------------------------------------------
2161
+ H.explorer_make_items = function(path, filter, sort)
2162
+ if vim.fn.isdirectory(path) == 0 then return {} end
2163
+ local res = { { fs_type = 'directory', path = vim.fn.fnamemodify(path, ':h'), text = '..' } }
2164
+ for _, basename in ipairs(vim.fn.readdir(path)) do
2165
+ local subpath = string.format('%s/%s', path, basename)
2166
+ local fs_type = vim.fn.isdirectory(subpath) == 1 and 'directory' or 'file'
2167
+ table.insert(res, { fs_type = fs_type, path = subpath, text = basename .. (fs_type == 'directory' and '/' or '') })
2168
+ end
2169
+
2170
+ return sort(vim.tbl_filter(filter, res))
2171
+ end
2172
+
2173
+ H.explorer_default_sort = function(items)
2174
+ -- Sort ignoring case
2175
+ local res = vim.tbl_map(function(x)
2176
+ --stylua: ignore
2177
+ return {
2178
+ fs_type = x.fs_type, path = x.path, text = x.text,
2179
+ is_dir = x.fs_type == 'directory', lower_name = x.text:lower(),
2180
+ }
2181
+ end, items)
2182
+
2183
+ local compare = function(a, b)
2184
+ -- Put directory first
2185
+ if a.is_dir and not b.is_dir then return true end
2186
+ if not a.is_dir and b.is_dir then return false end
2187
+
2188
+ -- Otherwise order alphabetically ignoring case
2189
+ return a.lower_name < b.lower_name
2190
+ end
2191
+
2192
+ table.sort(res, compare)
2193
+
2194
+ return vim.tbl_map(function(x) return { fs_type = x.fs_type, path = x.path, text = x.text } end, res)
2195
+ end
2196
+
2197
+ -- CLI ------------------------------------------------------------------------
2198
+ H.cli_run = function(command, stdout_hook)
2199
+ stdout_hook = stdout_hook or function() end
2200
+ local executable, args = command[1], vim.list_slice(command, 2, #command)
2201
+ local process, stdout, stderr = nil, vim.loop.new_pipe(), vim.loop.new_pipe()
2202
+ local spawn_opts = { args = args, stdio = { nil, stdout, stderr } }
2203
+ process = vim.loop.spawn(executable, spawn_opts, function() process:close() end)
2204
+
2205
+ H.cli_read_stream(stdout, stdout_hook)
2206
+ H.cli_read_stream(stderr, function(lines)
2207
+ local msg = table.concat(lines, '\n')
2208
+ if msg == '' then return end
2209
+ H.error(msg)
2210
+ end)
2211
+ end
2212
+
2213
+ H.cli_show_output = function(buf_id, command)
2214
+ local stdout_hook = vim.schedule_wrap(function(lines)
2215
+ if not H.is_valid_buf(buf_id) then return end
2216
+ H.set_buflines(buf_id, lines)
2217
+ end)
2218
+ H.cli_run(command, stdout_hook)
2219
+ end
2220
+
2221
+ H.cli_read_stream = function(stream, post_hook)
2222
+ local data_feed = {}
2223
+ local callback = function(err, data)
2224
+ assert(not err, err)
2225
+ if data ~= nil then return table.insert(data_feed, data) end
2226
+
2227
+ local lines = vim.split(table.concat(data_feed), '\n')
2228
+ data_feed = nil
2229
+ stream:close()
2230
+ post_hook(lines)
2231
+ end
2232
+ stream:read_start(callback)
2233
+ end
2234
+
2235
+ -- Buffers --------------------------------------------------------------------
2236
+ H.is_valid_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_valid(buf_id) end
2237
+
2238
+ H.buf_ensure_loaded = function(buf_id)
2239
+ if type(buf_id) ~= 'number' or vim.api.nvim_buf_is_loaded(buf_id) then return end
2240
+ local cache_eventignore = vim.o.eventignore
2241
+ vim.o.eventignore = 'BufEnter'
2242
+ pcall(vim.fn.bufload, buf_id)
2243
+ vim.o.eventignore = cache_eventignore
2244
+ end
2245
+
2246
+ H.buf_get_name = function(buf_id)
2247
+ if not H.is_valid_buf(buf_id) then return nil end
2248
+ local buf_name = vim.api.nvim_buf_get_name(buf_id)
2249
+ if buf_name ~= '' then buf_name = vim.fn.fnamemodify(buf_name, ':~:.') end
2250
+ return buf_name
2251
+ end
2252
+
2253
+ H.set_buflines = function(buf_id, lines) vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines) end
2254
+
2255
+ -- Utilities ------------------------------------------------------------------
2256
+ H.error = function(msg) error('(mini.extra) ' .. msg, 0) end
2257
+
2258
+ H.check_type = function(name, val, ref, allow_nil)
2259
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
2260
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
2261
+ end
2262
+
2263
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'miniextra://' .. buf_id .. '/' .. name) end
2264
+
2265
+ H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
2266
+
2267
+ H.ensure_text_width = function(text, width)
2268
+ local text_width = vim.fn.strchars(text)
2269
+ if text_width <= width then return text .. string.rep(' ', width - text_width) end
2270
+ return '…' .. vim.fn.strcharpart(text, text_width - width + 1, width - 1)
2271
+ end
2272
+
2273
+ H.full_path = function(path) return (vim.fn.fnamemodify(path, ':p'):gsub('(.)/$', '%1')) end
2274
+ H.normalize_path = function(path) return path end
2275
+ if H.is_windows then
2276
+ H.full_path = function(path) return (vim.fn.fnamemodify(path, ':p'):gsub('(.)[\\/]$', '%1')) end
2277
+ H.normalize_path = function(path) return path:gsub('\\', '/') end
2278
+ end
2279
+
2280
+ H.short_path = function(path, cwd)
2281
+ cwd = cwd or vim.fn.getcwd()
2282
+ -- Ensure `cwd` is treated as directory path (to not match similar prefix)
2283
+ cwd = cwd:sub(-1) == '/' and cwd or (cwd .. '/')
2284
+ return vim.startswith(path, cwd) and path:sub(cwd:len() + 1) or vim.fn.fnamemodify(path, ':~')
2285
+ end
2286
+
2287
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2288
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2289
+
2290
+ return MiniExtra