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,2094 @@
1
+ --- *mini.completion* Completion and signature help
2
+ ---
3
+ --- MIT License Copyright (c) 2021 Evgeni Chasnovski
4
+
5
+ --- Key design ideas:
6
+ --- - Have an async (with customizable "debounce" delay) "two-stage chain
7
+ --- completion": first try to get completion items from LSP client (if set
8
+ --- up) and if no result, fallback to custom action.
9
+ ---
10
+ --- - Managing completion is done as much with Neovim's built-in tools as
11
+ --- possible. |popupmenu-completion| is used to show completion suggestions.
12
+ ---
13
+ --- Features:
14
+ --- - Two-stage chain completion:
15
+ --- - First stage is an LSP completion implemented via
16
+ --- |MiniCompletion.completefunc_lsp()|. It should be set up as either
17
+ --- |'completefunc'| or |'omnifunc'|. It tries to get completion items from
18
+ --- LSP client (via `textDocument/completion` request). Custom
19
+ --- preprocessing of response items is possible (with
20
+ --- `MiniCompletion.config.lsp_completion.process_items`), for example
21
+ --- with fuzzy matching. By default items directly starting with completed
22
+ --- word are kept and are sorted according to LSP specification.
23
+ --- Supports `additionalTextEdits`, like auto-import and others (see `# Notes`),
24
+ --- and snippet items (best results require |mini.snippets| dependency).
25
+ --- - If first stage is not set up or resulted into no candidates, fallback
26
+ --- action is executed. The most tested actions are Neovim's built-in
27
+ --- insert completion (see |ins-completion|).
28
+ ---
29
+ --- - Automatic display in floating window of completion item info (via
30
+ --- `completionItem/resolve` request) and signature help (with highlighting
31
+ --- of active parameter if LSP server provides such information).
32
+ --- Signature help is shown if character to cursor's left is a dedicated trigger
33
+ --- character (configured in `signatureHelpProvider.triggerCharacters` of LSP
34
+ --- server capabilities) and updated without delay if is currently opened.
35
+ --- Already shown window for signature help is fixed and is closed when there
36
+ --- is nothing to show, its text is different, or when leaving Insert mode.
37
+ --- Scroll in either info/signature window with `<C-f>` / `<C-b>` (by default).
38
+ ---
39
+ --- - Automatic actions are done after some configurable amount of delay. This
40
+ --- reduces computational load and allows fast typing (completion and
41
+ --- signature help) and item selection (item info)
42
+ ---
43
+ --- - Force two-stage/fallback completion (`<C-Space>` / `<A-Space>` by default).
44
+ ---
45
+ --- - Customizable highlighting of LSP items. Requires Neovim>=0.11.
46
+ --- Use `config.lsp_completion.process_items` to set dedicated highlight group
47
+ --- in supported fields:
48
+ --- - <abbr_hlgroup> - item label (`abbr` in terms of |complete-items|).
49
+ --- By default only checks if item is marked as deprecated and sets
50
+ --- `MiniCompletionDeprecated` highlight group.
51
+ --- - <kind_hlgroup> - LSP kind ("Function", "Keyword", etc.). By default
52
+ --- uses "lsp" category of |mini.icons| (if enabled).
53
+ ---
54
+ --- What it doesn't do:
55
+ --- - Many configurable sources.
56
+ --- - Automatic mapping of `<CR>`, `<Tab>`, etc. Those tend to have highly
57
+ --- variable user expectations. See `# Helpful mappings` for suggestions or
58
+ --- use |MiniKeymap.map_multistep()| with `"pmenu_*"` built-in steps.
59
+ ---
60
+ --- # Dependencies ~
61
+ ---
62
+ --- Suggested dependencies (provide extra functionality, will work without them):
63
+ ---
64
+ --- - Enabled |mini.icons| module to highlight LSP kind (requires Neovim>=0.11).
65
+ --- If absent, |MiniCompletion.default_process_items()| does not add highlighting.
66
+ --- Also take a look at |MiniIcons.tweak_lsp_kind()|.
67
+ --- - Enabled |mini.snippets| module for better snippet handling (much recommended).
68
+ --- If absent and custom snippet insert is not configured, |vim.snippet.expand()|
69
+ --- is used on Neovim>=0.10 (nothing extra is done on earlier versions).
70
+ --- See |MiniCompletion.default_snippet_insert()|.
71
+ ---
72
+ --- # Setup ~
73
+ ---
74
+ --- This module needs a setup with `require('mini.completion').setup({})`
75
+ --- (replace `{}` with your `config` table). It will create global Lua table
76
+ --- `MiniCompletion` which you can use for scripting or manually (with
77
+ --- `:lua MiniCompletion.*`).
78
+ ---
79
+ --- See |MiniCompletion.config| for `config` structure and default values.
80
+ ---
81
+ --- You can override runtime config settings locally to buffer inside
82
+ --- `vim.b.minicompletion_config` which should have same structure as
83
+ --- `MiniCompletion.config`. See |mini.nvim-buffer-local-config| for more details.
84
+ ---
85
+ --- # Suggested option values ~
86
+ ---
87
+ --- Some options are set automatically (if not set before |MiniCompletion.setup()|):
88
+ --- - |'completeopt'| is set to "menuone,noselect" for less intrusive popup.
89
+ --- To enable fuzzy matching, manually set to "menuone,noselect,fuzzy". Consider
90
+ --- also adding "nosort" flag to preserve initial order when filtering.
91
+ --- - |'shortmess'| is appended with "c" flag for silent <C-n> fallback.
92
+ --- - |'complete'| gets removed "t" flag (if fallback action is default), as it
93
+ --- leads to visible lags.
94
+ ---
95
+ --- # Snippets ~
96
+ ---
97
+ --- As per LSP specification, some completion items can be supplied in the form of
98
+ --- snippet - a template with both pre-defined text and places (called "tabstops")
99
+ --- for user to interactively change/add text during snippet session.
100
+ ---
101
+ --- In |mini.completion| items that will insert snippet have "S" symbol shown in
102
+ --- the popup (as part of `menu` in |complete-items|). To actually insert a snippet:
103
+ --- - Select an item via <C-n> / <C-p>. This will insert item's label (usually not
104
+ --- full snippet) first to reduce visual flicker. The full snippet text will be
105
+ --- shown in info window if LSP server doesn't provide its own info for an item.
106
+ --- - Press <C-y> (|complete_CTRL-Y|) or attempt inserting a non-keyword character
107
+ --- (like <CR>; new character will be removed). It will clear text from previous
108
+ --- step, set cursor, and call `lsp_completion.snippet_insert` with snippet text.
109
+ --- - Press <C-e> (|complete_CTRL-E|) to cancel snippet insert and properly end
110
+ --- completion.
111
+ ---
112
+ --- See |MiniCompletion.default_snippet_insert()| for overview of how to work with
113
+ --- inserted snippets.
114
+ ---
115
+ --- Notes:
116
+ --- - To stop LSP server from suggesting snippets, disable (set to `false`) the
117
+ --- following capability during LSP server start:
118
+ --- `textDocument.completion.completionItem.snippetSupport`.
119
+ --- - If snippet body doesn't contain tabstop, variable, tab, or newline,
120
+ --- `lsp_completion.snippet_insert` is not called and text is inserted as-is.
121
+ ---
122
+ --- # Notes ~
123
+ ---
124
+ --- - A more appropriate (albeit slightly advanced) LSP completion setup is to set
125
+ --- it not on every |BufEnter| event (default), but on every attach of LSP client.
126
+ --- To do that:
127
+ --- - Use in |MiniCompletion.setup()| config: >lua
128
+ ---
129
+ --- lsp_completion = { source_func = 'omnifunc', auto_setup = false }
130
+ --- <
131
+ --- - Set |'omnifunc'| option to exactly `v:lua.MiniCompletion.completefunc_lsp`
132
+ --- for every client attach in an |LspAttach| event. Like this: >lua
133
+ ---
134
+ --- local on_attach = function(args)
135
+ --- vim.bo[args.buf].omnifunc = 'v:lua.MiniCompletion.completefunc_lsp'
136
+ --- end
137
+ --- vim.api.nvim_create_autocmd('LspAttach', { callback = on_attach })
138
+ --- <
139
+ --- This setup is not default to allow simultaneous usage of filetype-specific
140
+ --- |'omnifunc'| (with manual |i_CTRL-X_CTRL-O|) and automated LSP completion.
141
+ ---
142
+ --- - Use |MiniCompletion.get_lsp_capabilities()| to get/set information about part
143
+ --- of LSP specification supported by module. See its help for usability notes.
144
+ ---
145
+ --- - Uses `vim.lsp.protocol.CompletionItemKind` map in LSP step to show a readable
146
+ --- version of item's kind. Modify it directly to change what is displayed.
147
+ --- If you have |mini.icons| enabled, take a look at |MiniIcons.tweak_lsp_kind()|.
148
+ ---
149
+ --- - If you have trouble using custom (overridden) |vim.ui.input()|, disable
150
+ --- |mini.completion| for input buffer (usually based on its |'filetype'|).
151
+ ---
152
+ --- # Comparisons ~
153
+ ---
154
+ --- - [hrsh7th/nvim-cmp](https://github.com/hrsh7th/nvim-cmp):
155
+ --- - Implements own popup menu to show completion candidates, while this
156
+ --- module reuses |ins-completion-menu|.
157
+ --- - Has more complex design which allows multiple sources, each in a form of
158
+ --- a separate plugin. This module has two built-in: LSP and fallback.
159
+ --- - Requires separate plugin for automated signature help.
160
+ --- - Implements own "ghost text" feature, while this module does not.
161
+ ---
162
+ --- - [Saghen/blink.cmp](https://github.com/Saghen/blink.cmp):
163
+ --- - Mostly similar to `nvim-cmp` comparison: provides more features at the
164
+ --- cost of more code and config complexity, while this module is designed
165
+ --- to provide only a handful of "enough" features while relying on Neovim's
166
+ --- built-in capabilities as much as possible.
167
+ --- - Both provide automated signature help out of the box.
168
+ ---
169
+ --- # Helpful mappings ~
170
+ ---
171
+ --- If there is |mini.keymap| available, prefer using |MiniKeymap.map_multistep()|
172
+ --- with `"pmenu_*"` built-in steps. See |MiniKeymap-examples| for examples.
173
+ ---
174
+ --- To use `<Tab>` and `<S-Tab>` for navigation through completion list, make
175
+ --- these mappings: >lua
176
+ ---
177
+ --- local imap_expr = function(lhs, rhs)
178
+ --- vim.keymap.set('i', lhs, rhs, { expr = true })
179
+ --- end
180
+ --- imap_expr('<Tab>', [[pumvisible() ? "\<C-n>" : "\<Tab>"]])
181
+ --- imap_expr('<S-Tab>', [[pumvisible() ? "\<C-p>" : "\<S-Tab>"]])
182
+ --- <
183
+ --- To get more consistent behavior of `<CR>`, you can use this template in
184
+ --- your `init.lua` to make customized mapping: >lua
185
+ ---
186
+ --- _G.cr_action = function()
187
+ --- -- If there is selected item in popup, accept it with <C-y>
188
+ --- if vim.fn.complete_info()['selected'] ~= -1 then return '\25' end
189
+ --- -- Fall back to plain `<CR>`. You might want to customize according
190
+ --- -- to other plugins. For example if 'mini.pairs' is set up, replace
191
+ --- -- next line with `return MiniPairs.cr()`
192
+ --- return '\r'
193
+ --- end
194
+ ---
195
+ --- vim.keymap.set('i', '<CR>', 'v:lua.cr_action()', { expr = true })
196
+ --- <
197
+ --- # Highlight groups ~
198
+ --- *MiniCompletion-hl-groups*
199
+ ---
200
+ --- - `MiniCompletionActiveParameter` - signature active parameter.
201
+ --- - `MiniCompletionDeprecated` - candidates that marked as deprecated.
202
+ --- - `MiniCompletionInfoBorderOutdated` - info window border when text is outdated
203
+ --- due to explicit delay during fast movement through candidates.
204
+ ---
205
+ --- To change any highlight group, set it directly with |nvim_set_hl()|.
206
+ ---
207
+ --- # Disabling ~
208
+ ---
209
+ --- To disable, set `vim.g.minicompletion_disable` (globally) or
210
+ --- `vim.b.minicompletion_disable` (for a buffer) to `true`. Considering high
211
+ --- number of different scenarios and customization intentions, writing exact
212
+ --- rules for disabling module's functionality is left to user. See
213
+ --- |mini.nvim-disabling-recipes| for common recipes.
214
+ ---@tag MiniCompletion
215
+
216
+ --- To allow user customization, certain |User| autocommand events are
217
+ --- triggered under common circumstances:
218
+ ---
219
+ --- - Info and signature help window:
220
+ --- - `MiniCompletionWindowOpen` - after opening new window.
221
+ --- - `MiniCompletionWindowUpdate` - after updating existing window.
222
+ ---
223
+ --- Each event's |event-data| table contains `kind` (one of "info" or "signature")
224
+ --- and `win_id` (affected window identifier) fields.
225
+ ---@tag MiniCompletion-events
226
+
227
+ -- Overall implementation design:
228
+ -- - Completion:
229
+ -- - On `InsertCharPre` event try to start auto completion. If needed,
230
+ -- start timer which after delay will start completion process. Stop this
231
+ -- timer if it is not needed.
232
+ -- - When timer is activated, first execute LSP source (if set up and there
233
+ -- is an active LSP client) by calling built-in complete function
234
+ -- (`completefunc` or `omnifunc`) which tries LSP completion by
235
+ -- asynchronously sending LSP 'textDocument/completion' request to all
236
+ -- LSP clients. When all are done, execute callback which processes
237
+ -- results, stores them in LSP cache and reruns built-in complete
238
+ -- function which produces completion popup.
239
+ -- - If previous step didn't result into any completion, execute (in Insert
240
+ -- mode and if no popup) fallback action.
241
+ -- - Documentation:
242
+ -- - On `CompleteChanged` start auto info with similar to completion timer
243
+ -- pattern.
244
+ -- - If timer is activated, try these sources of item info:
245
+ -- - 'info' field of completion item (see `:h complete-items`).
246
+ -- - 'documentation' field of LSP's previously returned result.
247
+ -- - 'documentation' field in result of asynchronous
248
+ -- 'completeItem/resolve' LSP request.
249
+ -- - If info doesn't consist only from whitespace, show floating window
250
+ -- with its content. Its dimensions and position are computed based on
251
+ -- current state of Neovim's data and content itself (which will be
252
+ -- displayed wrapped with `linebreak` option).
253
+ -- - Signature help (similar to item info):
254
+ -- - On `CursorMovedI` start auto signature (if there is any active LSP
255
+ -- client) with similar to completion timer pattern. Better event might
256
+ -- be `InsertCharPre` but there are issues with 'autopair-type' plugins.
257
+ -- Update immediately if already shown or after delay if character to the
258
+ -- left is signature help trigger (after delay has passed).
259
+ -- - If timer is activated, send 'textDocument/signatureHelp' request to
260
+ -- all LSP clients. On callback, process their results. Window is opened
261
+ -- if not already with the same text (its characteristics are computed
262
+ -- similar to item info). For every LSP client it shows only active
263
+ -- signature (in case there are many). If LSP response has data about
264
+ -- active parameter, it is highlighted with
265
+ -- `MiniCompletionActiveParameter` highlight group.
266
+
267
+ -- Module definition ==========================================================
268
+ local MiniCompletion = {}
269
+ local H = {}
270
+
271
+ --- Module setup
272
+ ---
273
+ ---@param config table|nil Module config table. See |MiniCompletion.config|.
274
+ ---
275
+ ---@usage >lua
276
+ --- require('mini.completion').setup() -- use default config
277
+ --- -- OR
278
+ --- require('mini.completion').setup({}) -- replace {} with your config table
279
+ --- <
280
+ MiniCompletion.setup = function(config)
281
+ -- TODO: Remove after Neovim=0.9 support is dropped
282
+ if vim.fn.has('nvim-0.10') == 0 then
283
+ vim.notify(
284
+ '(mini.completion) Neovim<0.10 is soft deprecated (module works but is not supported).'
285
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
286
+ .. ' Please update your Neovim version.'
287
+ )
288
+ end
289
+
290
+ -- Export module
291
+ _G.MiniCompletion = MiniCompletion
292
+
293
+ -- Setup config
294
+ config = H.setup_config(config)
295
+
296
+ -- Apply config
297
+ H.apply_config(config)
298
+
299
+ -- Define behavior
300
+ H.create_autocommands(config)
301
+
302
+ -- Create default highlighting
303
+ H.create_default_hl()
304
+ end
305
+
306
+ --- Defaults ~
307
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
308
+ MiniCompletion.config = {
309
+ -- Delay (debounce type, in ms) between certain Neovim event and action.
310
+ -- This can be used to (virtually) disable certain automatic actions by
311
+ -- setting very high delay time (like 10^7).
312
+ delay = { completion = 100, info = 100, signature = 50 },
313
+
314
+ -- Configuration for action windows:
315
+ -- - `height` and `width` are maximum dimensions.
316
+ -- - `border` defines border (as in `nvim_open_win()`; default "single").
317
+ window = {
318
+ info = { height = 25, width = 80, border = nil },
319
+ signature = { height = 25, width = 80, border = nil },
320
+ },
321
+
322
+ -- Way of how module does LSP completion
323
+ lsp_completion = {
324
+ -- `source_func` should be one of 'completefunc' or 'omnifunc'.
325
+ source_func = 'completefunc',
326
+
327
+ -- `auto_setup` should be boolean indicating if LSP completion is set up
328
+ -- on every `BufEnter` event.
329
+ auto_setup = true,
330
+
331
+ -- A function which takes LSP 'textDocument/completion' response items
332
+ -- (each with `client_id` field for item's server) and word to complete.
333
+ -- Output should be a table of the same nature as input. Common use case
334
+ -- is custom filter/sort. Default: `default_process_items`
335
+ process_items = nil,
336
+
337
+ -- A function which takes a snippet as string and inserts it at cursor.
338
+ -- Default: `default_snippet_insert` which tries to use 'mini.snippets'
339
+ -- and falls back to `vim.snippet.expand` (on Neovim>=0.10).
340
+ snippet_insert = nil,
341
+ },
342
+
343
+ -- Fallback action as function/string. Executed in Insert mode.
344
+ -- To use built-in completion (`:h ins-completion`), set its mapping as
345
+ -- string. Example: set '<C-x><C-l>' for 'whole lines' completion.
346
+ fallback_action = '<C-n>',
347
+
348
+ -- Module mappings. Use `''` (empty string) to disable one. Some of them
349
+ -- might conflict with system mappings.
350
+ mappings = {
351
+ -- Force two-step/fallback completions
352
+ force_twostep = '<C-Space>',
353
+ force_fallback = '<A-Space>',
354
+
355
+ -- Scroll info/signature window down/up. When overriding, check for
356
+ -- conflicts with built-in keys for popup menu (like `<C-u>`/`<C-o>`
357
+ -- for 'completefunc'/'omnifunc' source function; or `<C-n>`/`<C-p>`).
358
+ scroll_down = '<C-f>',
359
+ scroll_up = '<C-b>',
360
+ },
361
+ }
362
+ --minidoc_afterlines_end
363
+
364
+ -- Module functionality =======================================================
365
+ --- Run two-stage completion
366
+ ---
367
+ ---@param fallback boolean|nil Whether to use fallback completion. Default: `true`.
368
+ ---@param force boolean|nil Whether to force update of completion popup.
369
+ --- Default: `true`.
370
+ MiniCompletion.complete_twostage = function(fallback, force)
371
+ if H.is_disabled() then return end
372
+ if fallback == nil then fallback = true end
373
+ if force == nil then force = true end
374
+
375
+ H.stop_completion()
376
+ H.completion.fallback, H.completion.force = fallback, force
377
+ H.trigger_twostep()
378
+ end
379
+
380
+ --- Run fallback completion
381
+ MiniCompletion.complete_fallback = function()
382
+ if H.is_disabled() then return end
383
+
384
+ H.stop_completion()
385
+ H.completion.fallback, H.completion.force = true, true
386
+ H.trigger_fallback()
387
+ end
388
+
389
+ --- Scroll in info/signature window
390
+ ---
391
+ --- Designed to be used in |:map-<expr>|.
392
+ --- Scrolling is done as if |CTRL-F| and |CTRL-B| is pressed inside target window.
393
+ --- Used in default `config.mappings.scroll_xxx` mappings.
394
+ ---
395
+ ---@param direction string One of `"down"` or `"up"`.
396
+ ---
397
+ ---@return boolean Whether scroll is scheduled to be done.
398
+ MiniCompletion.scroll = function(direction)
399
+ if not (direction == 'down' or direction == 'up') then H.error('`direction` should be one of "up" or "down"') end
400
+ local win_id = H.is_valid_win(H.info.win_id) and H.info.win_id
401
+ or (H.is_valid_win(H.signature.win_id) and H.signature.win_id or nil)
402
+ if win_id == nil then return false end
403
+
404
+ -- Schedule execution as scrolling is not allowed in expression mappings
405
+ local key = direction == 'down' and '\6' or '\2'
406
+ vim.schedule(function()
407
+ if not H.is_valid_win(win_id) then return end
408
+ vim.api.nvim_win_call(win_id, function() vim.cmd('noautocmd normal! ' .. key) end)
409
+ end)
410
+ return true
411
+ end
412
+
413
+ --- Stop actions
414
+ ---
415
+ --- This stops currently active (because of module delay or LSP answer delay)
416
+ --- actions.
417
+ ---
418
+ --- Designed to be used with |autocmd|. No need to use it directly, everything
419
+ --- is setup in |MiniCompletion.setup()|.
420
+ ---
421
+ ---@param actions table|nil Array containing any of `'completion'`, `'info'`, or
422
+ --- `'signature'` strings. Default: array containing all of them.
423
+ MiniCompletion.stop = function(actions)
424
+ actions = actions or { 'completion', 'info', 'signature' }
425
+ for _, n in ipairs(actions) do
426
+ H.stop_actions[n]()
427
+ end
428
+ end
429
+
430
+ --- Module's |complete-functions|
431
+ ---
432
+ --- This is the main function which enables two-stage completion. It should be
433
+ --- set as one of |'completefunc'| or |'omnifunc'|.
434
+ ---
435
+ --- No need to use it directly, everything is setup in |MiniCompletion.setup()|.
436
+ MiniCompletion.completefunc_lsp = function(findstart, base)
437
+ -- Early return
438
+ if not H.has_lsp_clients('completionProvider') or H.completion.lsp.status == 'sent' then
439
+ return findstart == 1 and -3 or {}
440
+ end
441
+
442
+ -- NOTE: having code for request inside this function enables its use
443
+ -- directly with `<C-x><...>` and as a reaction to `<BS>`.
444
+ if H.completion.lsp.status ~= 'received' then
445
+ -- NOTE: it is CRUCIAL to make LSP request on the first call to
446
+ -- 'complete-function' (as in Vim's help). This is due to the fact that
447
+ -- cursor line and position are different on the first and second calls to
448
+ -- 'complete-function'. For example, when calling this function at the end
449
+ -- of the line ' he', cursor position on the first call will be
450
+ -- (<linenum>, 4) and line will be ' he' but on the second call -
451
+ -- (<linenum>, 2) and ' ' (because 2 is a column of completion start).
452
+ --
453
+ -- This request is not executed on second call because it returns `-3` on
454
+ -- first call (which means cancel and leave completion mode).
455
+ H.make_completion_request()
456
+
457
+ -- End completion and wait for LSP callback to re-trigger this
458
+ return findstart == 1 and -3 or {}
459
+ else
460
+ if findstart == 1 then
461
+ local from, to = H.get_completion_range(H.completion.lsp.result)
462
+ -- Cache initial completion state to revert to it when inserting snippet
463
+ -- NOTE: Track only length of base for performance, since this is enough
464
+ H.completion.init_base = { lnum = from[1], col = from[2], length = math.max(to[2] - from[2], 0) }
465
+ return from[2]
466
+ end
467
+
468
+ local is_incomplete = false
469
+ local all_items = H.process_lsp_response(H.completion.lsp.result, function(response, client_id)
470
+ is_incomplete = is_incomplete or (response.isIncomplete == true)
471
+ -- Response can be `CompletionList` with 'items' field plus their
472
+ -- defaults or `CompletionItem[]`
473
+ local items = H.table_get(response, { 'items' }) or response
474
+ if type(items) ~= 'table' then return {} end
475
+
476
+ items = H.apply_item_defaults(items, response.itemDefaults)
477
+ for _, item in ipairs(items) do
478
+ item.client_id = client_id
479
+ end
480
+ return items
481
+ end)
482
+
483
+ -- Process items
484
+ local process_items = H.get_config().lsp_completion.process_items or MiniCompletion.default_process_items
485
+ all_items = process_items(all_items, base)
486
+ local candidates = H.lsp_completion_response_items_to_complete_items(all_items)
487
+
488
+ H.completion.lsp.status = 'done'
489
+ H.completion.lsp.is_incomplete = is_incomplete
490
+
491
+ -- Maybe trigger fallback action
492
+ if vim.tbl_isempty(candidates) and H.completion.fallback then return H.trigger_fallback() end
493
+
494
+ -- Track from which source is current popup
495
+ H.completion.source = 'lsp'
496
+ return candidates
497
+ end
498
+ end
499
+
500
+ --- Default processing of LSP items
501
+ ---
502
+ --- Steps:
503
+ --- - Filter and sort items according to supplied method.
504
+ --- - Arrange items further by completion item kind according to their priority.
505
+ --- - Add `MiniCompletionDeprecated` <abbr_hlgroup> if item is marked as deprecated.
506
+ --- - If |mini.icons| is enabled, add <kind_hlgroup> based on the "lsp" category.
507
+ ---
508
+ --- Example of forcing fuzzy matching, filtering out `Text` items, and putting
509
+ --- `Snippet` items last: >lua
510
+ ---
511
+ --- local kind_priority = { Text = -1, Snippet = 99 }
512
+ --- local opts = { filtersort = 'fuzzy', kind_priority = kind_priority }
513
+ --- local process_items = function(items, base)
514
+ --- return MiniCompletion.default_process_items(items, base, opts)
515
+ --- end
516
+ --- require('mini.completion').setup({
517
+ --- lsp_completion = { process_items = process_items },
518
+ --- })
519
+ --- <
520
+ ---@param items table Array of items from LSP response.
521
+ ---@param base string Base for which completion is done. See |complete-functions|.
522
+ ---@param opts table|nil Options. Possible fields:
523
+ --- - <filtersort> `(string|function)` - method of filtering and sorting items.
524
+ --- If string, should be one of the following:
525
+ --- - `'prefix'` - filter out items not starting with `base`, sort according
526
+ --- to LSP specification. Use `filterText` and `sortText` respectively with
527
+ --- fallback to `label`.
528
+ --- - `'fuzzy'` - filter and sort with |matchfuzzy()| using `filterText`.
529
+ --- - `'none'` - no filter and no sort.
530
+ --- If callable, should take `items` and `base` arguments and return items array.
531
+ --- Default: `'fuzzy'` if |'completeopt'| contains "fuzzy", `'prefix'` otherwise.
532
+ --- - <kind_priority> `(table)` - map of completion item kinds (like `Variable`,
533
+ --- `Snippet`; see string keys of `vim.lsp.protocol.CompletionItemKind`) to
534
+ --- their numerical priority. It will be used after applying <filtersort> to
535
+ --- arrange by completion item kind: items with negative priority kinds will
536
+ --- be filtered out, the rest are sorted by decreasing priority (preserving
537
+ --- order in case of same priority).
538
+ --- Priorities can be any number, only matters how they compare to each other.
539
+ --- Value 100 is used for missing kinds (i.e. not all can be supplied).
540
+ --- Default: `{}` (all equal priority).
541
+ ---
542
+ ---@return table Array of processed items from LSP response.
543
+ MiniCompletion.default_process_items = function(items, base, opts)
544
+ opts = opts or {}
545
+
546
+ -- Filter+sort (important with frequent `isIncomplete`)
547
+ local fs = opts.filtersort or (vim.o.completeopt:find('fuzzy') ~= nil and 'fuzzy' or 'prefix')
548
+ if type(fs) == 'string' then fs = H.filtersort_methods[fs] end
549
+ if not vim.is_callable(fs) then H.error('`filtersort` should be callable or one of "prefix", "fuzzy", "none"') end
550
+ local res = fs(items, base)
551
+
552
+ -- Arrange by kind
553
+ if opts.kind_priority ~= nil then res = H.lsp_arrange_by_kind(res, opts.kind_priority) end
554
+
555
+ -- Add custom highlighting
556
+ local add_abbr_hlgroup = H.make_add_abbr_hlgroup()
557
+ local add_kind_hlgroup = H.make_add_kind_hlgroup()
558
+ for _, item in ipairs(res) do
559
+ add_abbr_hlgroup(item)
560
+ add_kind_hlgroup(item)
561
+ end
562
+ return res
563
+ end
564
+
565
+ --- Default snippet insert
566
+ ---
567
+ --- Order of preference:
568
+ --- - Use |mini.snippets| if set up (i.e. after `require('mini.snippets').setup()`).
569
+ --- - Use |vim.snippet.expand()| on Neovim>=0.10
570
+ --- - Add snippet text at cursor as is.
571
+ ---
572
+ --- After snippet is inserted, user is expected to navigate/jump between dedicated
573
+ --- places (tabstops) to adjust inserted text as needed:
574
+ --- - |mini.snippets| by default uses <C-l> / <C-h> to jump to next/previous tabstop.
575
+ --- Can be adjusted in `mappings` of |MiniSnippets.config|.
576
+ --- - |vim.snippet| on Neovim=0.10 requires manually created mappings for jumping
577
+ --- between tabstops (see |vim.snippet.jump()|). Neovim>=0.11 sets them up
578
+ --- automatically to <Tab> / <S-Tab> (if not overridden by user).
579
+ ---
580
+ --- End session by navigating all the way to the last tabstop. In |mini.snippets|:
581
+ --- - Also make any text edit or exit Insert mode to end the session. This allows
582
+ --- smoother navigation to previous tabstops in case of a lately spotted typo.
583
+ --- - Press `<C-c>` to force session stop.
584
+ ---
585
+ ---@param snippet string Snippet body to insert at cursor.
586
+ ---
587
+ ---@seealso - |MiniSnippets-session| if |mini.snippets| is set up.
588
+ --- - |vim.snippet| for Neovim's built-in snippet engine.
589
+ MiniCompletion.default_snippet_insert = function(snippet)
590
+ if _G.MiniSnippets then
591
+ local insert = MiniSnippets.config.expand.insert or MiniSnippets.default_insert
592
+ return insert({ body = snippet })
593
+ end
594
+ if vim.fn.has('nvim-0.10') == 1 then return vim.snippet.expand(snippet) end
595
+
596
+ local pos, lines = vim.api.nvim_win_get_cursor(0), vim.split(snippet, '\n')
597
+ vim.api.nvim_buf_set_text(0, pos[1] - 1, pos[2], pos[1] - 1, pos[2], lines)
598
+ local n = #lines
599
+ local new_pos = n == 1 and { pos[1], pos[2] + lines[n]:len() } or { pos[1] + n - 1, lines[n]:len() }
600
+ vim.api.nvim_win_set_cursor(0, new_pos)
601
+ end
602
+
603
+ --- Get client LSP capabilities
604
+ ---
605
+ --- Possible usages:
606
+ --- - On Neovim>=0.11 via |vim.lsp.config()|: >lua
607
+ ---
608
+ --- vim.lsp.config('*', {capabilities = MiniCompletion.get_lsp_capabilities()})
609
+ --- <
610
+ --- - Together with |vim.lsp.protocol.make_client_capabilities()| to get the full
611
+ --- client capabilities (use |vim.tbl_deep_extend()| to merge tables).
612
+ ---
613
+ --- - Manually execute `:=MiniCompletion.get_lsp_capabilities()` to see the info.
614
+ ---
615
+ --- Notes:
616
+ --- - It declares completion resolve support for `'additionalTextEdits'` (usually
617
+ --- used for something like auto-import feature), as it is usually a more robust
618
+ --- choice for various LSP servers. As a consequence, this requires selecting
619
+ --- completion item and waiting for `config.delay.info` milliseconds plus server
620
+ --- response time (i.e. until information window shows relevant text).
621
+ --- To not have to wait after an item selection and if the server handles absent
622
+ --- `'additionalTextEdits'` well, set `opts.resolve_additional_text_edits = false`.
623
+ ---
624
+ ---@param opts table|nil Options. Possible fields:
625
+ --- - <resolve_additional_text_edits> `(boolean)` - whether to declare
626
+ --- `'additionalTextEdits'` as possible to resolve in `'completionitem/resolve'`
627
+ --- request. See above "Notes" section.
628
+ --- Default: `true`.
629
+ ---
630
+ ---@return table Data about LSP capabilities supported by |mini.completion|. Has same
631
+ --- structure as relevant parts of |vim.lsp.protocol.make_client_capabilities()|.
632
+ ---
633
+ ---@seealso Structures of `completionClientCapabilities` and `signatureHelpClientCapabilities`
634
+ --- at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification
635
+ MiniCompletion.get_lsp_capabilities = function(opts)
636
+ opts = vim.tbl_extend('force', { resolve_additional_text_edits = true }, opts or {})
637
+
638
+ local resolve_support = { 'detail', 'documentation' }
639
+ if opts.resolve_additional_text_edits then table.insert(resolve_support, 1, 'additionalTextEdits') end
640
+
641
+ local tag_valueset = vim.fn.has('nvim-0.11') == 1 and { vim.lsp.protocol.CompletionTag.Deprecated } or {}
642
+
643
+ return {
644
+ textDocument = {
645
+ -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionClientCapabilities
646
+ completion = {
647
+ dynamicRegistration = false,
648
+ completionItem = {
649
+ snippetSupport = true,
650
+ commitCharactersSupport = false,
651
+ documentationFormat = { 'markdown', 'plaintext' },
652
+ deprecatedSupport = true,
653
+ preselectSupport = false,
654
+ tagSupport = { valueSet = tag_valueset },
655
+ insertReplaceSupport = true,
656
+ resolveSupport = { properties = resolve_support },
657
+ insertTextModeSupport = { valueSet = { 1 } },
658
+ labelDetailsSupport = true,
659
+ },
660
+ completionItemKind = {
661
+ valueSet = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 },
662
+ },
663
+ contextSupport = true,
664
+ insertTextMode = 1,
665
+ completionList = {
666
+ itemDefaults = { 'commitCharacters', 'editRange', 'insertTextFormat', 'insertTextMode', 'data' },
667
+ },
668
+ },
669
+ -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureHelpClientCapabilities
670
+ signatureHelp = {
671
+ dynamicRegistration = false,
672
+ signatureInformation = {
673
+ documentationFormat = { 'markdown', 'plaintext' },
674
+ parameterInformation = {
675
+ labelOffsetSupport = true,
676
+ },
677
+ activeParameterSupport = true,
678
+ },
679
+ contextSupport = false,
680
+ },
681
+ },
682
+ }
683
+ end
684
+
685
+ -- Helper data ================================================================
686
+ -- Module default config
687
+ H.default_config = vim.deepcopy(MiniCompletion.config)
688
+
689
+ -- Track Insert mode changes
690
+ H.text_changed_id = 0
691
+
692
+ -- Namespace for highlighting
693
+ H.ns_id = vim.api.nvim_create_namespace('MiniCompletion')
694
+
695
+ -- Commonly used key sequences
696
+ H.keys = {
697
+ completefunc = vim.api.nvim_replace_termcodes('<C-x><C-u>', true, false, true),
698
+ omnifunc = vim.api.nvim_replace_termcodes('<C-x><C-o>', true, false, true),
699
+ ctrl_n = vim.api.nvim_replace_termcodes('<C-g><C-g><C-n>', true, false, true),
700
+ }
701
+
702
+ -- Flags for whether there is support for dedicated options
703
+ H.has_no_winborder = vim.fn.has('nvim-0.11') == 0
704
+ H.has_pumborder = vim.fn.exists('+pumborder') == 1 -- Neovim>=0.12
705
+
706
+ -- Caches for different actions -----------------------------------------------
707
+ -- Field `lsp` is a table describing state of all used LSP requests. It has the
708
+ -- following structure:
709
+ -- - id: identifier (consecutive numbers).
710
+ -- - status: one of 'sent', 'received', 'done', 'canceled'
711
+ -- - is_incomplete: whether request was incomplete and require recomputing
712
+ -- - result: result of request.
713
+ -- - cancel_fun: function which cancels current request.
714
+
715
+ -- Cache for completion
716
+ H.completion = {
717
+ fallback = true,
718
+ force = false,
719
+ source = nil,
720
+ text_changed_id = 0,
721
+ timer = vim.loop.new_timer(),
722
+ lsp = { id = 0, status = nil, is_incomplete = false, result = nil, resolved = {}, cancel_fun = nil, context = nil },
723
+ init_base = { lnum = nil, col = nil, length = nil },
724
+ }
725
+
726
+ -- Cache for completion item info
727
+ H.info = {
728
+ bufnr = nil,
729
+ event = nil,
730
+ id = 0,
731
+ timer = vim.loop.new_timer(),
732
+ win_id = nil,
733
+ lsp = { id = 0, status = nil, result = nil, cancel_fun = nil },
734
+ }
735
+
736
+ -- Cache for signature help
737
+ H.signature = {
738
+ bufnr = nil,
739
+ text = nil,
740
+ timer = vim.loop.new_timer(),
741
+ win_id = nil,
742
+ lsp = { id = 0, status = nil, result = nil, cancel_fun = nil },
743
+ }
744
+
745
+ -- Helper functionality =======================================================
746
+ -- Settings -------------------------------------------------------------------
747
+ H.setup_config = function(config)
748
+ H.check_type('config', config, 'table', true)
749
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
750
+
751
+ H.check_type('delay', config.delay, 'table')
752
+ H.check_type('window', config.window, 'table')
753
+ H.check_type('lsp_completion', config.lsp_completion, 'table')
754
+ if not (type(config.fallback_action) == 'function' or type(config.fallback_action) == 'string') then
755
+ H.error('`fallback_action` should be function or string, not ' .. type(config.fallback_action))
756
+ end
757
+ H.check_type('mappings', config.mappings, 'table')
758
+
759
+ H.check_type('delay.completion', config.delay.completion, 'number')
760
+ H.check_type('delay.info', config.delay.info, 'number')
761
+ H.check_type('delay.signature', config.delay.signature, 'number')
762
+
763
+ H.check_type('window.info', config.window.info, 'table')
764
+ H.check_type('window.signature', config.window.signature, 'table')
765
+
766
+ if not (config.lsp_completion.source_func == 'completefunc' or config.lsp_completion.source_func == 'omnifunc') then
767
+ H.error('`lsp_completion.source_func` should be one of "completefunc" or "omnifunc"')
768
+ end
769
+ H.check_type('lsp_completion.auto_setup', config.lsp_completion.auto_setup, 'boolean')
770
+ H.check_type('lsp_completion.process_items', config.lsp_completion.process_items, 'callable', true)
771
+ H.check_type('lsp_completion.snippet_insert', config.lsp_completion.snippet_insert, 'callable', true)
772
+
773
+ H.check_type('mappings.force_twostep', config.mappings.force_twostep, 'string')
774
+ H.check_type('mappings.force_fallback', config.mappings.force_fallback, 'string')
775
+ H.check_type('mappings.scroll_down', config.mappings.scroll_down, 'string')
776
+ H.check_type('mappings.scroll_up', config.mappings.scroll_up, 'string')
777
+
778
+ local is_string_or_array = function(x) return type(x) == 'string' or H.islist(x) end
779
+ H.check_type('window.info.height', config.window.info.height, 'number')
780
+ H.check_type('window.info.width', config.window.info.width, 'number')
781
+ if not is_string_or_array(config.window.info.border or 'single') then
782
+ H.error('`config.window.info.border` should be either string or array, not ' .. type(config.window.info.border))
783
+ end
784
+ H.check_type('window.signature.height', config.window.signature.height, 'number')
785
+ H.check_type('window.signature.width', config.window.signature.width, 'number')
786
+ if not is_string_or_array(config.window.signature.border or 'single') then
787
+ H.error(
788
+ '`config.window.signature.border` should be either string or array, not ' .. type(config.window.signature.border)
789
+ )
790
+ end
791
+
792
+ return config
793
+ end
794
+
795
+ H.apply_config = function(config)
796
+ MiniCompletion.config = config
797
+
798
+ H.map('i', config.mappings.force_twostep, MiniCompletion.complete_twostage, { desc = 'Complete with two-stage' })
799
+ H.map('i', config.mappings.force_fallback, MiniCompletion.complete_fallback, { desc = 'Complete with fallback' })
800
+
801
+ local map_scroll = function(lhs, direction)
802
+ local rhs = function() return MiniCompletion.scroll(direction) and '' or lhs end
803
+ H.map('i', lhs, rhs, { expr = true, desc = 'Scroll info/signature ' .. direction })
804
+ end
805
+ map_scroll(config.mappings.scroll_down, 'down')
806
+ map_scroll(config.mappings.scroll_up, 'up')
807
+
808
+ -- Try setting suggested option values
809
+ -- - More common completion behavior
810
+ local was_set = vim.api.nvim_get_option_info2('completeopt', { scope = 'global' }).was_set
811
+ if not was_set then vim.o.completeopt = 'menuone,noselect' end
812
+
813
+ -- - Don't show ins-completion-menu messages ("C" is default on Neovim>=0.10)
814
+ local shortmess_flags = 'c' .. (vim.fn.has('nvim-0.10') == 0 and 'C' or '')
815
+ was_set = vim.api.nvim_get_option_info2('shortmess', { scope = 'global' }).was_set
816
+ if not was_set then vim.opt.shortmess:append(shortmess_flags) end
817
+
818
+ -- - Remove "t" flag to reduce visible lags
819
+ was_set = vim.api.nvim_get_option_info2('complete', { scope = 'global' }).was_set
820
+ if not was_set and config.fallback_action == '<C-n>' then vim.opt.complete:remove('t') end
821
+ end
822
+
823
+ H.create_autocommands = function(config)
824
+ local gr = vim.api.nvim_create_augroup('MiniCompletion', {})
825
+
826
+ local au = function(event, pattern, callback, desc)
827
+ vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
828
+ end
829
+
830
+ au('InsertCharPre', '*', H.auto_completion, 'Auto show completion')
831
+ au('CompleteChanged', '*', H.auto_info, 'Auto show info')
832
+ au('CursorMovedI', '*', H.auto_signature, 'Auto show signature')
833
+ au('ModeChanged', 'i*:[^i]*', function() MiniCompletion.stop() end, 'Stop completion')
834
+ au('CompleteDonePre', '*', H.on_completedonepre, 'On CompleteDonePre')
835
+ au('TextChangedI', '*', H.on_text_changed_i, 'On TextChangedI')
836
+ au('TextChangedP', '*', H.on_text_changed_p, 'On TextChangedP')
837
+
838
+ if config.lsp_completion.auto_setup then
839
+ local source_func = config.lsp_completion.source_func
840
+ local callback = function() vim.bo[source_func] = 'v:lua.MiniCompletion.completefunc_lsp' end
841
+ au('BufEnter', '*', callback, 'Set completion function')
842
+ end
843
+
844
+ au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
845
+ au('FileType', 'TelescopePrompt', function() vim.b.minicompletion_disable = true end, 'Disable locally')
846
+ end
847
+
848
+ H.create_default_hl = function()
849
+ vim.api.nvim_set_hl(0, 'MiniCompletionActiveParameter', { default = true, link = 'LspSignatureActiveParameter' })
850
+ vim.api.nvim_set_hl(0, 'MiniCompletionDeprecated', { default = true, link = 'DiagnosticDeprecated' })
851
+ vim.api.nvim_set_hl(0, 'MiniCompletionInfoBorderOutdated', { default = true, link = 'DiagnosticFloatingWarn' })
852
+ end
853
+
854
+ H.is_disabled = function() return vim.g.minicompletion_disable == true or vim.b.minicompletion_disable == true end
855
+
856
+ H.get_config = function(config)
857
+ return vim.tbl_deep_extend('force', MiniCompletion.config, vim.b.minicompletion_config or {}, config or {})
858
+ end
859
+
860
+ -- Autocommands ---------------------------------------------------------------
861
+ H.auto_completion = function()
862
+ if H.is_disabled() then return end
863
+
864
+ H.completion.timer:stop()
865
+
866
+ local is_incomplete = H.completion.lsp.is_incomplete
867
+ local is_trigger = H.is_lsp_trigger(vim.v.char, 'completion')
868
+ local force = is_trigger or is_incomplete
869
+ if force then
870
+ -- Force fresh LSP completion if needed. Check before checking pumvisible
871
+ -- because it should be forced even if there are visible candidates.
872
+ -- Keep positive `is_incomplete` to allow fast typing and not "forget" that
873
+ -- list was incomplete after the second fast key press. This will force LSP
874
+ -- completion until `isIncomplete=false` response or general `stop()`.
875
+ H.stop_completion(false, is_incomplete)
876
+ elseif H.pumvisible() then
877
+ -- Do nothing if popup is visible. `H.pumvisible()` might be `true` even if
878
+ -- there is no popup. It is common when manually typing candidate followed
879
+ -- by an LSP trigger (like ".").
880
+ -- Keep completion source as it is needed all time when popup is visible.
881
+ -- Keep resolved candidates because they should be relevant in this route.
882
+ return H.stop_completion(true, false, true)
883
+ elseif not H.is_char_keyword(vim.v.char) then
884
+ -- Stop everything if inserted character is not appropriate. Check this
885
+ -- after popup check to allow completion candidates to have bad characters.
886
+ return H.stop_completion(false)
887
+ end
888
+
889
+ -- Start non-forced completion with fallback or forced LSP source for trigger
890
+ H.completion.fallback, H.completion.force = not force, force
891
+
892
+ -- Cache id of Insert mode "text changed" event for a later tracking (reduces
893
+ -- false positive delayed triggers). The intention is to trigger completion
894
+ -- after the delay only if text wasn't changed during waiting. Using only
895
+ -- `InsertCharPre` is not enough though, as not every Insert mode change
896
+ -- triggers `InsertCharPre` event (notable example - hitting `<CR>`).
897
+ -- Also, using `+ 1` here because it is a `Pre` event and needs to cache
898
+ -- after inserting character.
899
+ H.completion.text_changed_id = H.text_changed_id + 1
900
+
901
+ -- If completion was requested after 'lsp' source exhausted itself (there
902
+ -- were matches on typing start, but they disappeared during filtering), call
903
+ -- fallback immediately.
904
+ if H.completion.source == 'lsp' then return H.trigger_fallback() end
905
+
906
+ -- Set completion context with information about how it was triggered
907
+ -- Prefer manual `TriggerCharacter` over automated `...ForIncomplete...`.
908
+ local trigger_kind_name = is_trigger and 'TriggerCharacter'
909
+ or (is_incomplete and 'TriggerForIncompleteCompletions' or 'Invoked')
910
+ local trigger_kind = vim.lsp.protocol.CompletionTriggerKind[trigger_kind_name]
911
+ local trigger_char = trigger_kind_name == 'TriggerCharacter' and vim.v.char or nil
912
+ H.completion.lsp.context = { triggerKind = trigger_kind, triggerCharacter = trigger_char }
913
+
914
+ -- Debounce delay improves experience (can type fast without many popups)
915
+ -- Request right away if improving incomplete suggestions (less flickering),
916
+ -- but still with `vim.schedule` because line is still not up to date during
917
+ -- `InsertCharPre` event.
918
+ local delay = is_incomplete and 0 or H.get_config().delay.completion
919
+ H.completion.timer:start(delay, 0, vim.schedule_wrap(H.trigger_twostep))
920
+ end
921
+
922
+ H.auto_info = function()
923
+ if H.is_disabled() then return end
924
+
925
+ -- Stop current LSP request that tries to get already not current data
926
+ H.cancel_lsp({ H.info })
927
+
928
+ -- Update metadata before leaving to register a `CompleteChanged` event
929
+ H.info.timer:stop()
930
+ H.info.event = vim.v.event
931
+ H.info.id = H.info.id + 1
932
+
933
+ -- Stop showing window if no candidate is selected
934
+ local completed_item = H.info.event.completed_item or {}
935
+ if completed_item.word == nil then
936
+ return vim.schedule(function() H.close_action_window(H.info) end)
937
+ end
938
+
939
+ -- Show info content without delay for visited and resolved LSP item.
940
+ -- Otherwise delay to not spam LSP requests on up/down navigation.
941
+ local item_id = H.table_get(completed_item, { 'user_data', 'lsp', 'item_id' })
942
+ local is_resolved = item_id == nil or H.completion.lsp.resolved[item_id] ~= nil
943
+ local delay = is_resolved and 0 or H.get_config().delay.info
944
+
945
+ -- Mark visually that currently shown content will be outdated for a while
946
+ local win_id = H.info.win_id
947
+ if H.is_valid_win(win_id) and delay > 0 then
948
+ vim.wo[win_id].winhighlight = vim.wo[win_id].winhighlight .. ',FloatBorder:MiniCompletionInfoBorderOutdated'
949
+ end
950
+ local cur_info_id = H.info.id
951
+ H.info.timer:start(delay, 0, function() H.show_info_window(cur_info_id) end)
952
+ end
953
+
954
+ H.auto_signature = function()
955
+ if H.is_disabled() then return end
956
+
957
+ H.signature.timer:stop()
958
+ if not H.has_lsp_clients('signatureHelpProvider') then return end
959
+
960
+ local is_shown = H.is_valid_win(H.signature.win_id)
961
+ local left_char_is_trigger = H.is_lsp_trigger(H.get_left_char(), 'signature')
962
+ if not (is_shown or left_char_is_trigger) then return end
963
+
964
+ local delay = is_shown and 0 or H.get_config().delay.signature
965
+ H.signature.timer:start(delay, 0, vim.schedule_wrap(H.show_signature_window))
966
+ end
967
+
968
+ H.on_completedonepre = function()
969
+ -- Do nothing if it is triggered inside `trigger_lsp()` as a result of
970
+ -- emulating 'completefunc'/'omnifunc' keys. This can happen if popup is
971
+ -- visible and pressing keys first hides it with 'CompleteDonePre' event.
972
+ if H.completion.lsp.status == 'received' then return end
973
+
974
+ -- Do extra actions for LSP completion items
975
+ local lsp_data = H.table_get(vim.v.completed_item, { 'user_data', 'lsp' })
976
+ if lsp_data ~= nil then H.make_lsp_extra_actions(lsp_data) end
977
+
978
+ -- Stop processes
979
+ MiniCompletion.stop({ 'completion', 'info' })
980
+ end
981
+
982
+ H.on_text_changed_i = function()
983
+ -- Track Insert mode changes
984
+ H.text_changed_id = H.text_changed_id + 1
985
+
986
+ -- Stop 'info' processes in case no completion event is triggered but popup
987
+ -- is not visible. See https://github.com/neovim/neovim/issues/15077
988
+ H.stop_info()
989
+ end
990
+
991
+ H.on_text_changed_p = function()
992
+ -- Track Insert mode changes
993
+ H.text_changed_id = H.text_changed_id + 1
994
+ end
995
+
996
+ -- Completion triggers --------------------------------------------------------
997
+ H.trigger_twostep = function()
998
+ -- Trigger only in Insert mode and if text didn't change after trigger
999
+ -- request, unless completion is forced
1000
+ -- NOTE: check for `text_changed_id` equality is still not 100% solution as
1001
+ -- there are cases when, for example, `<CR>` is hit just before this check.
1002
+ -- Because of asynchronous id update and this function call (called after
1003
+ -- delay), these still match.
1004
+ local allow_trigger = (vim.fn.mode() == 'i')
1005
+ and (H.completion.force or (H.completion.text_changed_id == H.text_changed_id))
1006
+ if not allow_trigger then return end
1007
+
1008
+ if H.has_lsp_clients('completionProvider') and H.has_lsp_completion() then
1009
+ H.trigger_lsp()
1010
+ elseif H.completion.fallback then
1011
+ H.trigger_fallback()
1012
+ end
1013
+ end
1014
+
1015
+ H.trigger_lsp = function()
1016
+ -- Check for popup visibility is needed to reduce flickering.
1017
+ -- Possible issue timeline (with 100ms delay with set up LSP):
1018
+ -- 0ms: Key is pressed.
1019
+ -- 100ms: LSP is triggered from first key press.
1020
+ -- 110ms: Another key is pressed.
1021
+ -- 200ms: LSP callback is processed, triggers complete-function which
1022
+ -- processes "received" LSP request.
1023
+ -- 201ms: LSP request is processed, completion is (should be almost
1024
+ -- immediately) provided, request is marked as "done".
1025
+ -- 210ms: LSP is triggered from second key press. As previous request is
1026
+ -- "done", it will once make whole LSP request. Having check for visible
1027
+ -- popup should prevent here the call to complete-function.
1028
+
1029
+ -- Do not trigger if not needed and/or allowed
1030
+ if vim.fn.mode() ~= 'i' or (H.pumvisible() and not H.completion.force) then return end
1031
+
1032
+ -- Overall idea: first make LSP request and re-trigger this same function
1033
+ -- inside its callback to take the "received" route. This reduces flickering
1034
+ -- in case popup is visible (like for `isIncomplete` and trigger characters)
1035
+ -- as pressing 'completefunc'/'omnifunc' keys first hides completion menu.
1036
+ -- There are still minor visual defects: typing new character reduces number
1037
+ -- of matched items which can visually shrink popup while later increase it
1038
+ -- again after LSP response is received. This is usually fine (especially
1039
+ -- with not huge 'pumheight').
1040
+ if H.completion.lsp.status ~= 'received' then return H.make_completion_request() end
1041
+ local keys = H.keys[H.get_config().lsp_completion.source_func]
1042
+ vim.api.nvim_feedkeys(keys, 'n', false)
1043
+ end
1044
+
1045
+ H.trigger_fallback = function()
1046
+ -- Fallback only in Insert mode when no popup is visible
1047
+ local has_popup = H.pumvisible() and not H.completion.force
1048
+ if has_popup or vim.fn.mode() ~= 'i' then return end
1049
+
1050
+ -- Track from which source is current popup
1051
+ H.completion.source = 'fallback'
1052
+
1053
+ -- Execute fallback action
1054
+ local fallback_action = H.get_config().fallback_action or H.default_fallback_action
1055
+ fallback_action = fallback_action == '<C-n>' and H.default_fallback_action or fallback_action
1056
+ if vim.is_callable(fallback_action) then return fallback_action() end
1057
+ if type(fallback_action) ~= 'string' then return end
1058
+
1059
+ -- Having `<C-g><C-g>` also (for some mysterious reason) helps to avoid
1060
+ -- some weird behavior. For example, if `keys = '<C-x><C-l>'` then Neovim
1061
+ -- starts new line when there is no suggestions.
1062
+ local keys = string.format('<C-g><C-g>%s', fallback_action)
1063
+ local trigger_keys = vim.api.nvim_replace_termcodes(keys, true, false, true)
1064
+ vim.api.nvim_feedkeys(trigger_keys, 'n', false)
1065
+ end
1066
+
1067
+ H.default_fallback_action = function() vim.api.nvim_feedkeys(H.keys.ctrl_n, 'n', false) end
1068
+
1069
+ -- Stop actions ---------------------------------------------------------------
1070
+ H.stop_completion = function(keep_source, keep_lsp_is_incomplete, keep_lsp_resolved)
1071
+ H.completion.timer:stop()
1072
+ H.cancel_lsp({ H.completion })
1073
+ H.completion.lsp.context = nil
1074
+ H.completion.fallback, H.completion.force = true, false
1075
+ if not keep_source then H.completion.source = nil end
1076
+ if not keep_lsp_is_incomplete then H.completion.lsp.is_incomplete = false end
1077
+ if not keep_lsp_resolved then H.completion.lsp.resolved = {} end
1078
+ end
1079
+
1080
+ H.stop_info = function()
1081
+ -- Id update is needed to notify that all previous work is not current
1082
+ H.info.id = H.info.id + 1
1083
+ H.info.timer:stop()
1084
+ H.cancel_lsp({ H.info })
1085
+ H.close_action_window(H.info)
1086
+ end
1087
+
1088
+ H.stop_signature = function()
1089
+ H.signature.text = nil
1090
+ H.signature.timer:stop()
1091
+ H.cancel_lsp({ H.signature })
1092
+ H.close_action_window(H.signature)
1093
+ end
1094
+
1095
+ H.stop_actions = {
1096
+ completion = H.stop_completion,
1097
+ info = H.stop_info,
1098
+ signature = H.stop_signature,
1099
+ }
1100
+
1101
+ -- LSP ------------------------------------------------------------------------
1102
+ ---@param capability string|table|nil Server capability (possibly nested
1103
+ --- supplied via table) to check.
1104
+ ---
1105
+ ---@return boolean Whether at least one LSP client supports `capability`.
1106
+ ---@private
1107
+ H.has_lsp_clients = function(capability)
1108
+ local clients = H.get_buf_lsp_clients()
1109
+ if vim.tbl_isempty(clients) then return false end
1110
+ if not capability then return true end
1111
+
1112
+ for _, c in pairs(clients) do
1113
+ local has_capability = H.table_get(c.server_capabilities, capability)
1114
+ if has_capability then return true end
1115
+ end
1116
+ return false
1117
+ end
1118
+
1119
+ H.has_lsp_completion = function()
1120
+ local source_func = H.get_config().lsp_completion.source_func
1121
+ local func = vim.bo[source_func]
1122
+ return func == 'v:lua.MiniCompletion.completefunc_lsp'
1123
+ end
1124
+
1125
+ H.is_lsp_trigger = function(char, type)
1126
+ local triggers
1127
+ local providers = { completion = 'completionProvider', signature = 'signatureHelpProvider' }
1128
+
1129
+ for _, client in ipairs(H.get_buf_lsp_clients()) do
1130
+ triggers = H.table_get(client, { 'server_capabilities', providers[type], 'triggerCharacters' })
1131
+ if vim.tbl_contains(triggers or {}, char) then return true end
1132
+ end
1133
+ return false
1134
+ end
1135
+
1136
+ H.cancel_lsp = function(caches)
1137
+ caches = caches or { H.completion, H.info, H.signature }
1138
+ for _, c in ipairs(caches) do
1139
+ if vim.tbl_contains({ 'sent', 'received' }, c.lsp.status) then
1140
+ if c.lsp.cancel_fun then c.lsp.cancel_fun() end
1141
+ c.lsp.status = 'canceled'
1142
+ end
1143
+
1144
+ c.lsp.result, c.lsp.cancel_fun = nil, nil
1145
+ end
1146
+ end
1147
+
1148
+ H.process_lsp_response = function(request_result, processor)
1149
+ if not request_result then return {} end
1150
+
1151
+ local res = {}
1152
+ for client_id, item in pairs(request_result) do
1153
+ -- TODO: Use only `.err` after compatibility with Neovim=0.10 is dropped
1154
+ if not (item.err or item.error) and item.result then
1155
+ vim.list_extend(res, processor(item.result, client_id) or {})
1156
+ end
1157
+ end
1158
+
1159
+ return res
1160
+ end
1161
+
1162
+ H.is_lsp_current = function(cache, id) return cache.lsp.id == id and cache.lsp.status == 'sent' end
1163
+
1164
+ H.filtersort_methods = {
1165
+ prefix = function(items, base)
1166
+ local res = vim.tbl_filter(function(x) return vim.startswith(H.lsp_get_filterword(x), base) end, items)
1167
+ res = vim.deepcopy(res)
1168
+ table.sort(res, H.lsp_item_compare)
1169
+ return res
1170
+ end,
1171
+ fuzzy = function(items, base)
1172
+ if base == '' then return vim.deepcopy(items) end
1173
+ return vim.fn.matchfuzzy(items, base, { text_cb = H.lsp_get_filterword })
1174
+ end,
1175
+ none = function(items, _) return vim.deepcopy(items) end,
1176
+ }
1177
+
1178
+ H.lsp_arrange_by_kind = function(items, kind_priority)
1179
+ if type(kind_priority) ~= 'table' then H.error('`kind_priority` should be table') end
1180
+
1181
+ H.ensure_kind_map()
1182
+
1183
+ local res_raw = {}
1184
+ for i, item in ipairs(items) do
1185
+ local priority = kind_priority[H.kind_map[item.kind]] or 100
1186
+ if priority >= 0 then table.insert(res_raw, { priority, i, item }) end
1187
+ end
1188
+
1189
+ local compare = function(a, b) return a[1] > b[1] or (a[1] == b[1] and a[2] < b[2]) end
1190
+ table.sort(res_raw, compare)
1191
+ return vim.tbl_map(function(x) return x[3] end, res_raw)
1192
+ end
1193
+
1194
+ H.lsp_get_filterword = function(x) return x.filterText or x.label end
1195
+
1196
+ H.lsp_item_compare = function(a, b) return (a.sortText or a.label) < (b.sortText or b.label) end
1197
+
1198
+ -- Completion -----------------------------------------------------------------
1199
+ H.make_completion_request = function()
1200
+ local current_id = H.completion.lsp.id + 1
1201
+ H.completion.lsp.id = current_id
1202
+ H.completion.lsp.status = 'sent'
1203
+
1204
+ local context = H.completion.lsp.context or { triggerKind = vim.lsp.protocol.CompletionTriggerKind.Invoked }
1205
+ local buf_id, params = vim.api.nvim_get_current_buf(), H.make_position_params(context)
1206
+ -- NOTE: use `buf_request_all()` (instead of `buf_request()`) to easily
1207
+ -- handle possible fallback and to have all completion suggestions be later
1208
+ -- filtered with one `base`. Anyway, the most common situation is with one
1209
+ -- attached LSP client.
1210
+ local cancel_fun = vim.lsp.buf_request_all(buf_id, 'textDocument/completion', params, function(result)
1211
+ if not H.is_lsp_current(H.completion, current_id) then return end
1212
+
1213
+ H.completion.lsp.status = 'received'
1214
+ H.completion.lsp.result = result
1215
+
1216
+ -- Trigger LSP completion to use completefunc/omnifunc route
1217
+ H.trigger_lsp()
1218
+ end)
1219
+
1220
+ -- Cache cancel function to disable requests when they are not needed
1221
+ H.completion.lsp.cancel_fun = cancel_fun
1222
+ end
1223
+
1224
+ H.apply_item_defaults = function(items, defaults)
1225
+ if type(defaults) ~= 'table' then return items end
1226
+
1227
+ local edit_range, has_edit_range = defaults.editRange, type(defaults.editRange) == 'table'
1228
+ local edit_range_range = (edit_range or {}).start ~= nil and edit_range or nil
1229
+ for _, item in ipairs(items) do
1230
+ item.commitCharacters = item.commitCharacters or defaults.commitCharacters
1231
+ item.data = item.data or defaults.data
1232
+ item.insertTextFormat = item.insertTextFormat or defaults.insertTextFormat
1233
+ item.insertTextMode = item.insertTextMode or defaults.insertTextMode
1234
+ if has_edit_range then
1235
+ item.textEdit = item.textEdit or {}
1236
+ -- Infer new text from `item.textEditText` designed for default edit case
1237
+ item.textEdit.newText = item.textEdit.newText or item.textEditText or item.label
1238
+ -- Default `editRange` is range (start+end) or insert+replace ranges
1239
+ item.textEdit.range = item.textEdit.range or edit_range_range
1240
+ item.textEdit.insert = item.textEdit.insert or edit_range.insert
1241
+ item.textEdit.replace = item.textEdit.replace or edit_range.replace
1242
+ end
1243
+ end
1244
+ return items
1245
+ end
1246
+
1247
+ -- Source:
1248
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_completion
1249
+ H.lsp_completion_response_items_to_complete_items = function(items)
1250
+ if vim.tbl_count(items) == 0 then return {} end
1251
+
1252
+ local res, item_kinds = {}, vim.lsp.protocol.CompletionItemKind
1253
+ local snippet_kind = vim.lsp.protocol.CompletionItemKind.Snippet
1254
+ local snippet_inserttextformat = vim.lsp.protocol.InsertTextFormat.Snippet
1255
+ for i, item in ipairs(items) do
1256
+ local word = H.get_completion_word(item)
1257
+
1258
+ local is_snippet_kind = item.kind == snippet_kind
1259
+ local is_snippet_format = item.insertTextFormat == snippet_inserttextformat
1260
+ -- Treat item as snippet only if it has tabstop, variable, tab, or newline.
1261
+ -- It is important to make "implicit" expand work with LSP servers that
1262
+ -- report even regular words as `InsertTextFormat.Snippet` (like `gopls`).
1263
+ -- Otherwise it will "eat" the next typed non-keyword character.
1264
+ -- Account for tabs and newline to allow `snippet_insert` to deal with
1265
+ -- reindenting and tab expansion.
1266
+ local has_snippet_features = (word:find('[^\\]%${?%w') or word:find('^%${?%w') or word:find('[\n\t]')) ~= nil
1267
+ local needs_snippet_insert = (is_snippet_kind or is_snippet_format) and has_snippet_features
1268
+
1269
+ local details = item.labelDetails or {}
1270
+ -- NOTE: Using `table.concat({}, ' ')` would be cleaner but less performant
1271
+ local snip, detail, desc = needs_snippet_insert and 'S' or '', details.detail or '', details.description or ''
1272
+ local pad = (snip ~= '' and detail ~= '') and ' ' or ''
1273
+ local label_detail = snip .. pad .. detail
1274
+ pad = (label_detail ~= '' and desc ~= '') and ' ' or ''
1275
+ label_detail = label_detail .. pad .. desc
1276
+
1277
+ local lsp_data = { item = item, item_id = i }
1278
+ lsp_data.needs_snippet_insert = needs_snippet_insert
1279
+ table.insert(res, {
1280
+ -- Show less for snippet items (usually less confusion), but preserve
1281
+ -- built-in filtering capabilities (as it uses `word` to filter).
1282
+ word = needs_snippet_insert and H.lsp_get_filterword(item) or word,
1283
+ abbr = item.label,
1284
+ abbr_hlgroup = item.abbr_hlgroup,
1285
+ kind = item_kinds[item.kind] or 'Unknown',
1286
+ kind_hlgroup = item.kind_hlgroup,
1287
+ menu = label_detail,
1288
+ -- NOTE: info will be attempted to resolve, use snippet text as fallback
1289
+ info = needs_snippet_insert and word or nil,
1290
+ icase = 1,
1291
+ dup = 1,
1292
+ empty = 1,
1293
+ user_data = { lsp = lsp_data },
1294
+ })
1295
+ end
1296
+ return res
1297
+ end
1298
+
1299
+ H.make_add_abbr_hlgroup = function()
1300
+ local deprecated_tag = vim.lsp.protocol.CompletionTag.Deprecated
1301
+ local contains = vim.list_contains
1302
+ return function(item)
1303
+ local is_deprecated = item.deprecated or (item.tags and contains(item.tags, deprecated_tag))
1304
+ item.abbr_hlgroup = item.abbr_hlgroup or (is_deprecated and 'MiniCompletionDeprecated' or nil)
1305
+ end
1306
+ end
1307
+ if vim.fn.has('nvim-0.11') == 0 then H.make_add_abbr_hlgroup = function()
1308
+ return function() end
1309
+ end end
1310
+
1311
+ H.make_add_kind_hlgroup = function()
1312
+ -- Account for possible effect of `MiniIcons.tweak_lsp_kind()` which modifies
1313
+ -- only array part of `CompletionItemKind` but not "map" part
1314
+ H.ensure_kind_map()
1315
+
1316
+ if _G.MiniIcons == nil then
1317
+ return function() end
1318
+ end
1319
+
1320
+ return function(item)
1321
+ local _, hl, is_default = _G.MiniIcons.get('lsp', H.kind_map[item.kind] or 'Unknown')
1322
+ item.kind_hlgroup = item.kind_hlgroup or (not is_default and hl or nil)
1323
+ end
1324
+ end
1325
+
1326
+ H.ensure_kind_map = function()
1327
+ if H.kind_map ~= nil then return end
1328
+
1329
+ -- Cache kind map so as to not recompute it each time (as it will be called
1330
+ -- in performance sensitive context). Assumes `tweak_lsp_kind()` is called
1331
+ -- right after `require('mini.icons').setup()`.
1332
+ H.kind_map = {}
1333
+ for k, v in pairs(vim.lsp.protocol.CompletionItemKind) do
1334
+ if type(k) == 'string' and type(v) == 'number' then H.kind_map[v] = k end
1335
+ end
1336
+ end
1337
+
1338
+ H.get_completion_word = function(item)
1339
+ return H.table_get(item, { 'textEdit', 'newText' }) or item.insertText or H.lsp_get_filterword(item) or ''
1340
+ end
1341
+
1342
+ H.make_lsp_extra_actions = function(lsp_data)
1343
+ -- Prefer resolved item over the one from 'textDocument/completion'
1344
+ local item = H.completion.lsp.resolved[lsp_data.item_id] or lsp_data.item
1345
+
1346
+ if item.additionalTextEdits == nil and item.command == nil and not lsp_data.needs_snippet_insert then return end
1347
+ local snippet = lsp_data.needs_snippet_insert and H.get_completion_word(item) or nil
1348
+
1349
+ -- Make extra actions not only after an explicit `<C-y>` (accept completed
1350
+ -- item), but also after implicit non-keyword character. This needs:
1351
+ -- - Keeping track of newly added non-keyword character and cursor move (like
1352
+ -- after 'mini.pairs') for a later undo. Do it via using expanding extmark.
1353
+ -- - Delay actual execution to operate *after* characters are inserted (as it
1354
+ -- is not immediate). Otherwise those characters will get "inserted" after
1355
+ -- snippet is inserted and its session is active.
1356
+ local cur = vim.api.nvim_win_get_cursor(0)
1357
+ local extmark_opts = { end_row = cur[1] - 1, end_col = cur[2], right_gravity = false, end_right_gravity = true }
1358
+ local track_extmark_id = vim.api.nvim_buf_set_extmark(0, H.ns_id, cur[1] - 1, cur[2], extmark_opts)
1359
+
1360
+ vim.schedule(function()
1361
+ -- Do nothing if user exited Insert mode
1362
+ if vim.fn.mode() ~= 'i' then return end
1363
+
1364
+ -- Undo possible non-keyword character(s) and cursor move. Do this before
1365
+ -- text edits to have more proper state (as it was at the time edits were
1366
+ -- created by server), but only if there is snippet (keep new characters
1367
+ -- for *only* text edits).
1368
+ if snippet ~= nil then
1369
+ H.del_extmark(track_extmark_id, true)
1370
+ pcall(vim.api.nvim_win_set_cursor, 0, cur)
1371
+ end
1372
+
1373
+ -- Try to only apply additional text edits for non-snippet items
1374
+ if snippet == nil then
1375
+ H.apply_text_edits(item.client_id, item.additionalTextEdits)
1376
+ H.exec_command(item.client_id, item.command)
1377
+ return
1378
+ end
1379
+
1380
+ -- Revert to initial completion state to respect text edit coordinates
1381
+ local init_base = H.completion.init_base
1382
+ local from, to = { init_base.lnum, init_base.col }, vim.api.nvim_win_get_cursor(0)
1383
+ -- NOTE: actual base string should not be relevant here, only byte count
1384
+ local prefix = string.rep('x', init_base.length)
1385
+ pcall(vim.api.nvim_buf_set_text, 0, from[1] - 1, from[2], to[1] - 1, to[2], { prefix })
1386
+ to = { from[1], from[2] + init_base.length }
1387
+ local prefix_extmark_opts = { end_row = to[1] - 1, end_col = to[2] }
1388
+ local prefix_extmark_id = vim.api.nvim_buf_set_extmark(0, H.ns_id, from[1] - 1, from[2], prefix_extmark_opts)
1389
+
1390
+ -- Possibly adjust tracked range to come from LSP item. Clamp to existing
1391
+ -- text state because some LSP servers update `textEdit` during resolve
1392
+ -- (although the must not to) which can error when setting extmarks.
1393
+ local edit_range = H.get_lsp_edit_range({ result = { item } })
1394
+ if edit_range ~= nil then
1395
+ local n_lines = vim.api.nvim_buf_line_count(0)
1396
+ local start_lnum = math.min(edit_range.start.line + 1, n_lines)
1397
+ local end_lnum = math.min(edit_range['end'].line + 1, n_lines)
1398
+ local start_col = math.min(edit_range.start.character, vim.fn.getline(start_lnum):len())
1399
+ local end_col = math.min(edit_range['end'].character, vim.fn.getline(end_lnum):len())
1400
+ from, to = { start_lnum, start_col }, { end_lnum, end_col }
1401
+ end
1402
+
1403
+ -- Try to apply additional text edits *after* restoring state because their
1404
+ -- data is computed by the server at that state. Keep track of the range
1405
+ -- that needs clearing as it might change during edits.
1406
+ from, to = H.apply_tracked_text_edits(item.client_id, item.additionalTextEdits, from, to)
1407
+
1408
+ -- Expand snippet: remove base and insert at cursor
1409
+ pcall(vim.api.nvim_buf_set_text, 0, from[1] - 1, from[2], to[1] - 1, to[2], { '' })
1410
+ -- - Ensure to work with bad `textEdit`, like not covering cursor position
1411
+ vim.api.nvim_win_set_cursor(0, from)
1412
+ H.del_extmark(prefix_extmark_id, true)
1413
+ local insert = H.get_config().lsp_completion.snippet_insert or MiniCompletion.default_snippet_insert
1414
+ insert(snippet)
1415
+ end)
1416
+ end
1417
+
1418
+ H.apply_text_edits = function(client_id, text_edits)
1419
+ if text_edits == nil then return end
1420
+ local offset_encoding = client_id == nil and 'utf-16' or vim.lsp.get_client_by_id(client_id).offset_encoding
1421
+ vim.lsp.util.apply_text_edits(text_edits, vim.api.nvim_get_current_buf(), offset_encoding)
1422
+ end
1423
+
1424
+ H.exec_command = function(client_id, command)
1425
+ if command == nil then return end
1426
+ vim.lsp.get_client_by_id(client_id):exec_cmd(command, { bufnr = vim.api.nvim_get_current_buf() })
1427
+ end
1428
+ if vim.fn.has('nvim-0.11') == 0 then H.exec_command = function(_, _) end end
1429
+
1430
+ H.apply_tracked_text_edits = function(client_id, text_edits, from, to)
1431
+ if text_edits == nil then return from, to end
1432
+
1433
+ -- Prepare extmarks to track relevant positions
1434
+ local cur_pos = vim.api.nvim_win_get_cursor(0)
1435
+ local cursor_extmark_id = vim.api.nvim_buf_set_extmark(0, H.ns_id, cur_pos[1] - 1, cur_pos[2], {})
1436
+
1437
+ local from_extmark_id = vim.api.nvim_buf_set_extmark(0, H.ns_id, from[1] - 1, from[2], {})
1438
+ local to_extmark_id = vim.api.nvim_buf_set_extmark(0, H.ns_id, to[1] - 1, to[2], {})
1439
+
1440
+ -- Do text edits
1441
+ H.apply_text_edits(client_id, text_edits)
1442
+
1443
+ -- Restore cursor position
1444
+ local cursor_data = H.del_extmark(cursor_extmark_id)
1445
+ pcall(vim.api.nvim_win_set_cursor, 0, { cursor_data[1] + 1, cursor_data[2] })
1446
+
1447
+ -- Update in place tracked range
1448
+ local from_data = H.del_extmark(from_extmark_id)
1449
+ local to_data = H.del_extmark(to_extmark_id)
1450
+ return { from_data[1] + 1, from_data[2] }, { to_data[1] + 1, to_data[2] }
1451
+ end
1452
+
1453
+ -- Completion item info -------------------------------------------------------
1454
+ H.show_info_window = vim.schedule_wrap(function(info_id)
1455
+ -- Do nothing if completion item was changed. For example, after autoinvoked
1456
+ -- in timer with zero delay but after it there is another `CompleteChanged`
1457
+ -- that closes popup. This only stops the timer *but* not actually cancelling
1458
+ -- this function.
1459
+ if H.info.id ~= info_id then return end
1460
+
1461
+ -- Get info lines to show. Wait for resolve if returned `false`.
1462
+ local lines = H.info_window_lines(info_id)
1463
+ if lines == false then return end
1464
+ if lines == nil or H.is_whitespace(lines) then lines = { '-No-info-' } end
1465
+
1466
+ -- Ensure permanent buffer with "markdown" highlighting to display info
1467
+ H.ensure_buffer(H.info, 'item-info')
1468
+ H.ensure_highlight(H.info, 'markdown')
1469
+ H.ensure_no_concealed_lines(H.info.bufnr)
1470
+ vim.api.nvim_buf_set_lines(H.info.bufnr, 0, -1, false, lines)
1471
+
1472
+ -- Compute floating window options
1473
+ local opts = H.info_window_options()
1474
+
1475
+ -- Adjust section separator with better visual alternative
1476
+ lines = vim.tbl_map(function(l) return l:gsub('^%-%-%-%-*$', string.rep('─', opts.width)) end, lines)
1477
+ vim.api.nvim_buf_set_lines(H.info.bufnr, 0, -1, false, lines)
1478
+
1479
+ -- Defer execution because of textlock during `CompleteChanged` event
1480
+ vim.schedule(function()
1481
+ -- Ensure that window doesn't open when it shouldn't be
1482
+ if not (H.pumvisible() and vim.fn.mode() == 'i') then return end
1483
+ H.ensure_action_window('info', opts)
1484
+ local win_id = H.info.win_id
1485
+ if not H.is_valid_win(win_id) then return end
1486
+
1487
+ -- Hide helper syntax elements (like ``` code blocks, etc.)
1488
+ vim.wo[H.info.win_id].conceallevel = 3
1489
+
1490
+ -- Scroll past first line if it is a visible (Neovim<0.11) codeblock start
1491
+ if vim.fn.has('nvim-0.11') == 0 and lines[1]:find('^```%S*$') ~= nil then
1492
+ vim.api.nvim_win_call(win_id, function() vim.fn.winrestview({ topline = 2 }) end)
1493
+ end
1494
+ end)
1495
+ end)
1496
+
1497
+ H.info_window_lines = function(info_id)
1498
+ local completed_item = H.info.event.completed_item
1499
+ local info = completed_item.info or ''
1500
+ local lsp_data = H.table_get(completed_item, { 'user_data', 'lsp' })
1501
+
1502
+ -- If popup is not from a known LSP server, use 'info' field of complete-item
1503
+ if lsp_data == nil or lsp_data.item.client_id == nil then return vim.split(info, '\n') end
1504
+ local client_id = lsp_data.item.client_id
1505
+
1506
+ -- Prefer reusing (without new LSP request) already resolved completion item
1507
+ local item_id, resolved_cache = lsp_data.item_id, H.completion.lsp.resolved
1508
+ if resolved_cache[item_id] ~= nil then return H.normalize_item_doc(resolved_cache[item_id], info) end
1509
+
1510
+ -- Try to get documentation from LSP's latest resolved info
1511
+ if H.info.lsp.status == 'received' then
1512
+ local lines = H.normalize_item_doc(H.info.lsp.result, info)
1513
+ H.info.lsp.status = 'done'
1514
+ return lines
1515
+ end
1516
+
1517
+ -- If server doesn't support resolve or not known, reuse first response
1518
+ local client = vim.lsp.get_client_by_id(client_id) or {}
1519
+ local can_resolve = H.table_get(client.server_capabilities, { 'completionProvider', 'resolveProvider' })
1520
+ if not can_resolve or client.id == nil then
1521
+ resolved_cache[item_id] = lsp_data.item
1522
+ return H.normalize_item_doc(lsp_data.item, info)
1523
+ end
1524
+
1525
+ -- Finally, request to resolve current completion to add more documentation
1526
+ local bufnr = vim.api.nvim_get_current_buf()
1527
+ local current_id = H.info.lsp.id + 1
1528
+ H.info.lsp.id = current_id
1529
+ H.info.lsp.status = 'sent'
1530
+ -- - Remove added `client_id` to comply with LSP spec request params
1531
+ lsp_data.item.client_id = nil
1532
+
1533
+ local cancel_fun = H.client_request(client, 'completionItem/resolve', lsp_data.item, function(err, result, _)
1534
+ -- Don't do anything if there is other LSP request in action
1535
+ if not H.is_lsp_current(H.info, current_id) then return end
1536
+
1537
+ H.info.lsp.status = 'received'
1538
+
1539
+ -- Do nothing if completion item was changed
1540
+ if H.info.id ~= info_id then return end
1541
+
1542
+ -- Still use original item if there was no response (usually due to error)
1543
+ result = result or lsp_data.item
1544
+
1545
+ -- Re-add client id to the resolved item
1546
+ result.client_id = client_id
1547
+
1548
+ H.info.lsp.result = result
1549
+ -- - Cache resolved item to not have to send same request on revisit.
1550
+ -- Do this outside of `H.info.event.completed_item` because it will not
1551
+ -- have persistent effect as it will come fresh from Vimscript `v:event`.
1552
+ resolved_cache[item_id] = result
1553
+ H.show_info_window(info_id)
1554
+ end, bufnr)
1555
+
1556
+ -- Re-add client id to an unresolved item due to in-place modification
1557
+ lsp_data.item.client_id = client_id
1558
+ H.info.lsp.cancel_fun = cancel_fun
1559
+ return false
1560
+ end
1561
+
1562
+ H.info_window_options = function()
1563
+ local win_config = H.get_config().window.info
1564
+ local default_border = (H.has_no_winborder or vim.o.winborder == '') and 'single' or nil
1565
+ local border = win_config.border or default_border
1566
+ local pumborder = H.has_pumborder and vim.o.pumborder or ''
1567
+
1568
+ -- Compute dimensions based on actually visible lines to be displayed
1569
+ local lines = H.compute_visible_md_lines(vim.api.nvim_buf_get_lines(H.info.bufnr, 0, -1, false))
1570
+ local info_height, info_width = H.floating_dimensions(lines, win_config.height, win_config.width)
1571
+
1572
+ -- Compute position
1573
+ local event = H.info.event
1574
+ local left_to_pum = event.col - 1
1575
+ local offset = (pumborder == '' or pumborder == 'none') and (event.scrollbar and 1 or 0) or 2
1576
+ local right_to_pum = event.col + event.width + offset
1577
+
1578
+ local border_offset = border == 'none' and 0 or 2
1579
+ local space_left = left_to_pum - border_offset
1580
+ local space_right = vim.o.columns - right_to_pum - border_offset
1581
+
1582
+ -- Decide side at which info window will be displayed
1583
+ local anchor, col, space
1584
+ if info_width <= space_right or space_left <= space_right then
1585
+ anchor, col, space = 'NW', right_to_pum, space_right
1586
+ else
1587
+ anchor, col, space = 'NE', left_to_pum, space_left
1588
+ end
1589
+
1590
+ -- Possibly adjust floating window dimensions to fit screen
1591
+ if space < info_width then
1592
+ info_height, info_width = H.floating_dimensions(lines, win_config.height, space)
1593
+ end
1594
+
1595
+ return {
1596
+ relative = 'editor',
1597
+ anchor = anchor,
1598
+ row = event.row,
1599
+ col = col,
1600
+ width = info_width,
1601
+ height = info_height,
1602
+ focusable = false,
1603
+ style = 'minimal',
1604
+ border = border,
1605
+ title = H.fit_to_width(' Info ', info_width),
1606
+ }
1607
+ end
1608
+
1609
+ -- Signature help -------------------------------------------------------------
1610
+ H.show_signature_window = function()
1611
+ -- If there is no received LSP result, make request and exit
1612
+ if H.signature.lsp.status ~= 'received' then
1613
+ local current_id = H.signature.lsp.id + 1
1614
+ H.signature.lsp.id = current_id
1615
+ H.signature.lsp.status = 'sent'
1616
+
1617
+ local bufnr = vim.api.nvim_get_current_buf()
1618
+ local params = H.make_position_params()
1619
+
1620
+ local cancel_fun = vim.lsp.buf_request_all(bufnr, 'textDocument/signatureHelp', params, function(result)
1621
+ if not H.is_lsp_current(H.signature, current_id) then return end
1622
+
1623
+ H.signature.lsp.status = 'received'
1624
+ H.signature.lsp.result = result
1625
+
1626
+ -- Trigger `show_signature` again to take 'received' route
1627
+ H.show_signature_window()
1628
+ end)
1629
+
1630
+ -- Cache cancel function to disable requests when they are not needed
1631
+ H.signature.lsp.cancel_fun = cancel_fun
1632
+
1633
+ return
1634
+ end
1635
+
1636
+ -- Make lines to show in floating window
1637
+ local lines, hl_ranges = H.signature_window_lines()
1638
+ H.signature.lsp.status = 'done'
1639
+
1640
+ -- Close window and exit if there is nothing to show
1641
+ if not lines or H.is_whitespace(lines) then return H.close_action_window(H.signature) end
1642
+
1643
+ -- Ensure permanent buffer with current highlighting to display signature
1644
+ H.ensure_buffer(H.signature, 'signature-help')
1645
+ H.ensure_highlight(H.signature, vim.bo.filetype)
1646
+ vim.api.nvim_buf_set_lines(H.signature.bufnr, 0, -1, false, lines)
1647
+
1648
+ -- Add highlighting of active parameter
1649
+ local buf_id = H.signature.bufnr
1650
+ vim.api.nvim_buf_clear_namespace(buf_id, H.ns_id, 0, -1)
1651
+ for i, hl_range in ipairs(hl_ranges) do
1652
+ if hl_range[1] ~= nil and hl_range[2] ~= nil then
1653
+ local opts = { end_row = i - 1, end_col = hl_range[2], hl_group = 'MiniCompletionActiveParameter' }
1654
+ vim.api.nvim_buf_set_extmark(buf_id, H.ns_id, i - 1, hl_range[1], opts)
1655
+ end
1656
+ end
1657
+
1658
+ -- If window is already opened and displays the same text, don't reopen it
1659
+ local cur_text = table.concat(lines, '\n')
1660
+ if H.signature.win_id and cur_text == H.signature.text then return end
1661
+
1662
+ -- Cache lines for later checks if window should be reopened
1663
+ H.signature.text = cur_text
1664
+
1665
+ -- Ensure window is closed
1666
+ H.close_action_window(H.signature)
1667
+
1668
+ -- Compute floating window options
1669
+ local opts = H.signature_window_opts()
1670
+
1671
+ -- Ensure that window doesn't open when it shouldn't
1672
+ if vim.fn.mode() == 'i' then H.ensure_action_window('signature', opts) end
1673
+ end
1674
+
1675
+ H.signature_window_lines = function()
1676
+ local signature_data = H.process_lsp_response(H.signature.lsp.result, H.process_signature_response)
1677
+ -- Each line is a single-line active signature string from one attached LSP
1678
+ -- client. Each highlight range is a table which indicates (if not empty)
1679
+ -- what parameter to highlight for every LSP client's signature string.
1680
+ local lines, hl_ranges = {}, {}
1681
+ for _, t in ipairs(signature_data) do
1682
+ -- `t` is allowed to be an empty table (in which case nothing is added) or
1683
+ -- a table with two entries. This ensures that `hl_range`'s integer index
1684
+ -- points to an actual line in future buffer.
1685
+ table.insert(lines, t.label)
1686
+ table.insert(hl_ranges, t.hl_range)
1687
+ end
1688
+
1689
+ return lines, hl_ranges
1690
+ end
1691
+
1692
+ H.process_signature_response = function(response)
1693
+ if not response.signatures or vim.tbl_isempty(response.signatures) then return {} end
1694
+
1695
+ -- Get active signature (based on textDocument/signatureHelp specification)
1696
+ local signature_id = response.activeSignature or 0
1697
+ -- This is according to specification: "If ... value lies outside ...
1698
+ -- defaults to zero"
1699
+ local n_signatures = vim.tbl_count(response.signatures or {})
1700
+ if signature_id < 0 or signature_id >= n_signatures then signature_id = 0 end
1701
+ local signature = response.signatures[signature_id + 1]
1702
+
1703
+ -- Get displayed signature label
1704
+ local res = { label = signature.label:gsub('\n', ' ') }
1705
+
1706
+ -- Get start and end of active parameter (for highlighting)
1707
+ local n_params = #(signature.parameters or {})
1708
+ local has_params = type(signature.parameters) == 'table' and n_params > 0
1709
+
1710
+ -- Take values in this order because data inside signature takes priority
1711
+ local parameter_id = signature.activeParameter or response.activeParameter or 0
1712
+ local param_id_inrange = 0 <= parameter_id and parameter_id < n_params
1713
+
1714
+ -- Computing active parameter only when parameter id is inside bounds is not
1715
+ -- strictly based on specification, as currently (v3.16) it says to treat
1716
+ -- out-of-bounds value as first parameter. However, some clients seem to use
1717
+ -- those values to indicate that nothing needs to be highlighted.
1718
+ -- Sources:
1719
+ -- https://github.com/microsoft/pyright/pull/1876
1720
+ -- https://github.com/microsoft/language-server-protocol/issues/1271
1721
+ if has_params and param_id_inrange then
1722
+ local param_label = signature.parameters[parameter_id + 1].label
1723
+
1724
+ -- Compute highlight range based on type of supplied parameter label: can
1725
+ -- be string label which should be a part of signature label or direct start
1726
+ -- (inclusive) and end (exclusive) range values
1727
+ local label_is_string = type(param_label) == 'string'
1728
+ res.hl_range = label_is_string and { res.label:find(param_label, 1, true) } or (param_label or {})
1729
+ -- - Make zero-indexed and end-exclusive
1730
+ if res.hl_range[1] ~= nil then res.hl_range[1] = res.hl_range[1] - (label_is_string and 1 or 0) end
1731
+ end
1732
+
1733
+ -- Return nested table because this will be a second argument of
1734
+ -- `vim.list_extend()` and the whole inner table is a target value here.
1735
+ return { res }
1736
+ end
1737
+
1738
+ H.signature_window_opts = function()
1739
+ local win_config = H.get_config().window.signature
1740
+ local default_border = (H.has_no_winborder or vim.o.winborder == '') and 'single' or nil
1741
+ local border = win_config.border or default_border
1742
+ local lines = vim.api.nvim_buf_get_lines(H.signature.bufnr, 0, -1, false)
1743
+ local height, width = H.floating_dimensions(lines, win_config.height, win_config.width)
1744
+
1745
+ -- Compute position
1746
+ local win_line = vim.fn.winline()
1747
+ local border_offset = border == 'none' and 0 or 2
1748
+ local space_above = win_line - 1 - border_offset
1749
+ local space_below = vim.api.nvim_win_get_height(0) - win_line - border_offset
1750
+
1751
+ local anchor, row, space
1752
+ if height <= space_above or space_below <= space_above then
1753
+ anchor, row, space = 'SW', 0, space_above
1754
+ else
1755
+ anchor, row, space = 'NW', 1, space_below
1756
+ end
1757
+
1758
+ -- Possibly adjust floating window dimensions to fit screen
1759
+ if space < height then
1760
+ height, width = H.floating_dimensions(lines, space, win_config.width)
1761
+ end
1762
+
1763
+ -- Get zero-indexed current cursor position
1764
+ local bufpos = vim.api.nvim_win_get_cursor(0)
1765
+ bufpos[1] = bufpos[1] - 1
1766
+
1767
+ return {
1768
+ relative = 'win',
1769
+ bufpos = bufpos,
1770
+ anchor = anchor,
1771
+ row = row,
1772
+ col = 0,
1773
+ width = width,
1774
+ height = height,
1775
+ focusable = false,
1776
+ style = 'minimal',
1777
+ border = border,
1778
+ title = H.fit_to_width(' Signature ', width),
1779
+ }
1780
+ end
1781
+
1782
+ -- Helpers for floating windows -----------------------------------------------
1783
+ H.ensure_buffer = function(cache, name)
1784
+ if H.is_loaded_buf(cache.bufnr) then return end
1785
+
1786
+ pcall(vim.api.nvim_buf_delete, cache.bufnr, { force = true })
1787
+ cache.hl_filetype = nil
1788
+ local buf_id = vim.api.nvim_create_buf(false, true)
1789
+ cache.bufnr = buf_id
1790
+ H.set_buf_name(buf_id, name)
1791
+ vim.bo[buf_id].buftype = 'nofile'
1792
+ end
1793
+
1794
+ H.ensure_highlight = function(cache, filetype)
1795
+ if cache.hl_filetype == filetype then return end
1796
+ cache.hl_filetype = filetype
1797
+ local buf_id = cache.bufnr
1798
+
1799
+ local has_lang, lang = pcall(vim.treesitter.language.get_lang, filetype)
1800
+ lang = has_lang and lang or filetype
1801
+ -- TODO: Remove `opts.error` after compatibility with Neovim=0.11 is dropped
1802
+ local has_parser, parser = pcall(vim.treesitter.get_parser, buf_id, lang, { error = false })
1803
+ has_parser = has_parser and parser ~= nil
1804
+ if has_parser then has_parser = pcall(vim.treesitter.start, buf_id, lang) end
1805
+ if not has_parser then vim.bo[buf_id].syntax = filetype end
1806
+ end
1807
+
1808
+ -- Returns tuple of height and width
1809
+ H.floating_dimensions = function(lines, max_height, max_width)
1810
+ max_height, max_width = math.max(max_height, 1), math.max(max_width, 1)
1811
+
1812
+ -- Simulate how lines will look in window with `wrap` and `linebreak`.
1813
+ -- This is not 100% accurate (mostly because of concealed characters and
1814
+ -- multibyte manifest into empty space at bottom), but does the job
1815
+ local lines_wrap = {}
1816
+ for _, l in ipairs(lines) do
1817
+ vim.list_extend(lines_wrap, H.wrap_line(l, max_width))
1818
+ end
1819
+ -- Height is a number of wrapped lines truncated to maximum height
1820
+ local height = math.min(#lines_wrap, max_height)
1821
+
1822
+ -- Width is a maximum width of the first `height` wrapped lines truncated to
1823
+ -- maximum width
1824
+ local width = 0
1825
+ local l_width
1826
+ for i, l in ipairs(lines_wrap) do
1827
+ -- Use `strdisplaywidth()` to account for 'non-UTF8' characters
1828
+ l_width = vim.fn.strdisplaywidth(l)
1829
+ if i <= height and width < l_width then width = l_width end
1830
+ end
1831
+ -- It should already be less that that because of wrapping, so this is "just
1832
+ -- in case"
1833
+ width = math.min(width, max_width)
1834
+
1835
+ return math.max(height, 1), math.max(width, 1)
1836
+ end
1837
+
1838
+ H.ensure_action_window = function(window_kind, opts)
1839
+ local cache = H[window_kind]
1840
+ local is_shown = H.is_valid_win(cache.win_id)
1841
+ if is_shown then vim.api.nvim_win_set_config(cache.win_id, opts) end
1842
+ if not is_shown then cache.win_id = vim.api.nvim_open_win(cache.bufnr, false, opts) end
1843
+
1844
+ local win_id = cache.win_id
1845
+ vim.wo[win_id].breakindent = false
1846
+ vim.wo[win_id].foldenable = false
1847
+ vim.wo[win_id].foldmethod = 'manual'
1848
+ vim.wo[win_id].linebreak = true
1849
+ vim.wo[win_id].winhighlight = vim.wo[win_id].winhighlight:gsub(',FloatBorder:MiniCompletionInfoBorderOutdated', '')
1850
+ vim.wo[win_id].wrap = true
1851
+
1852
+ local event = 'MiniCompletionWindow' .. (is_shown and 'Update' or 'Open')
1853
+ local data = { kind = window_kind, win_id = win_id }
1854
+ vim.api.nvim_exec_autocmds('User', { pattern = event, data = data })
1855
+ end
1856
+
1857
+ H.close_action_window = function(cache)
1858
+ cache.timer:stop()
1859
+
1860
+ if H.is_valid_win(cache.win_id) then vim.api.nvim_win_close(cache.win_id, true) end
1861
+ cache.win_id = nil
1862
+
1863
+ -- For some reason 'buftype' might be reset. Ensure that buffer is scratch.
1864
+ if H.is_loaded_buf(cache.bufnr) then vim.bo[cache.bufnr].buftype = 'nofile' end
1865
+ end
1866
+
1867
+ -- Utilities ------------------------------------------------------------------
1868
+ H.error = function(msg) error('(mini.completion) ' .. msg, 0) end
1869
+
1870
+ H.check_type = function(name, val, ref, allow_nil)
1871
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1872
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1873
+ end
1874
+
1875
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minicompletion://' .. buf_id .. '/' .. name) end
1876
+
1877
+ H.is_loaded_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_loaded(buf_id) end
1878
+
1879
+ H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
1880
+
1881
+ H.is_char_keyword = function(char)
1882
+ -- Using Vim's `match()` and `keyword` enables respecting Cyrillic letters
1883
+ return vim.fn.match(char, '[[:keyword:]]') >= 0
1884
+ end
1885
+
1886
+ -- NOTE: Might return `true` even if there is no visible completion popup, but
1887
+ -- built-in completion is still "active" (`<BS>` will show previous completion
1888
+ -- immediately).
1889
+ H.pumvisible = function() return vim.fn.pumvisible() > 0 end
1890
+
1891
+ H.get_completion_range = function(lsp_result)
1892
+ local pos = vim.api.nvim_win_get_cursor(0)
1893
+
1894
+ -- Prefer completion start from LSP response(s)
1895
+ for _, response_data in pairs(lsp_result or {}) do
1896
+ local range = H.get_lsp_edit_range(response_data)
1897
+ if range ~= nil then return { range.start.line + 1, range.start.character }, pos end
1898
+ end
1899
+
1900
+ -- Fall back to start position of latest keyword
1901
+ local line = vim.api.nvim_get_current_line()
1902
+ return { pos[1], vim.fn.match(line:sub(1, pos[2]), '\\k*$') }, pos
1903
+ end
1904
+
1905
+ H.get_lsp_edit_range = function(response_data)
1906
+ -- TODO: Use only `.err` after compatibility with Neovim=0.10 is dropped
1907
+ if response_data.err or response_data.error or type(response_data.result) ~= 'table' then return end
1908
+
1909
+ -- Try using item defaults if they contain edit range (which can be either
1910
+ -- `Range` or contain `insert` field of `Range` type)
1911
+ local edit_range = H.table_get(response_data.result, { 'itemDefaults', 'editRange' })
1912
+ if type(edit_range) == 'table' then return edit_range.insert or edit_range end
1913
+
1914
+ -- Try using all items to find the first one with edit range
1915
+ local items = response_data.result.items or response_data.result
1916
+ for _, item in ipairs(items) do
1917
+ -- Account for `textEdit` can be either `TextEdit` or `InsertReplaceEdit`
1918
+ if type(item.textEdit) == 'table' then return item.textEdit.range or item.textEdit.insert end
1919
+ end
1920
+ end
1921
+
1922
+ H.del_extmark = function(extmark_id, with_text)
1923
+ local data = vim.api.nvim_buf_get_extmark_by_id(0, H.ns_id, extmark_id, { details = true })
1924
+ vim.api.nvim_buf_del_extmark(0, H.ns_id, extmark_id)
1925
+ -- Possibly remove extmark's text
1926
+ if not with_text or data[1] == nil or data[3].end_row == nil then return data end
1927
+ local start_row, start_col, end_row, end_col = data[1], data[2], data[3].end_row, data[3].end_col
1928
+ if start_row < end_row or (start_row == end_row and start_col < end_col) then
1929
+ vim.api.nvim_buf_set_text(0, start_row, start_col, end_row, end_col, { '' })
1930
+ end
1931
+ return data
1932
+ end
1933
+
1934
+ H.is_whitespace = function(s)
1935
+ if type(s) == 'string' then return s:find('^%s*$') end
1936
+ if type(s) == 'table' then
1937
+ for _, val in ipairs(s) do
1938
+ if not H.is_whitespace(val) then return false end
1939
+ end
1940
+ return true
1941
+ end
1942
+ return false
1943
+ end
1944
+
1945
+ H.fit_to_width = function(text, width)
1946
+ local t_width = vim.fn.strchars(text)
1947
+ return t_width <= width and text or ('…' .. vim.fn.strcharpart(text, t_width - width + 1, width - 1))
1948
+ end
1949
+
1950
+ H.str_byteindex = function(s, i) return vim.str_byteindex(s, 'utf-32', i) end
1951
+ if vim.fn.has('nvim-0.11') == 0 then H.str_byteindex = function(s, i) return vim.str_byteindex(s, i) end end
1952
+
1953
+ -- Simulate splitting single line `l` like how it would look inside window with
1954
+ -- `wrap` and `linebreak` set to `true`
1955
+ H.wrap_line = function(l, width)
1956
+ local res = {}
1957
+
1958
+ local success, width_id = true, nil
1959
+ -- Use `strdisplaywidth()` to account for multibyte characters
1960
+ while success and vim.fn.strdisplaywidth(l) > width do
1961
+ -- Simulate wrap by looking at breaking character from end of current break
1962
+ -- Use `pcall()` to handle complicated multibyte characters (like Chinese)
1963
+ -- for which even `strdisplaywidth()` seems to return incorrect values.
1964
+ success, width_id = pcall(H.str_byteindex, l, width)
1965
+
1966
+ if success then
1967
+ local break_match = vim.fn.match(l:sub(1, width_id):reverse(), '[- \t.,;:!?]')
1968
+ -- If no breaking character found, wrap at whole width
1969
+ local break_id = width_id - (break_match < 0 and 0 or break_match)
1970
+ table.insert(res, l:sub(1, break_id))
1971
+ l = l:sub(break_id + 1)
1972
+ end
1973
+ end
1974
+ table.insert(res, l)
1975
+
1976
+ return res
1977
+ end
1978
+
1979
+ H.table_get = function(t, id)
1980
+ if type(id) ~= 'table' then return H.table_get(t, { id }) end
1981
+ local success, res = true, t
1982
+ for _, i in ipairs(id) do
1983
+ --stylua: ignore start
1984
+ success, res = pcall(function() return res[i] end)
1985
+ if not success or res == nil then return end
1986
+ --stylua: ignore end
1987
+ end
1988
+ return res
1989
+ end
1990
+
1991
+ H.get_left_char = function()
1992
+ local line = vim.api.nvim_get_current_line()
1993
+ local col = vim.api.nvim_win_get_cursor(0)[2]
1994
+
1995
+ return string.sub(line, col, col)
1996
+ end
1997
+
1998
+ H.map = function(mode, lhs, rhs, opts)
1999
+ if lhs == '' then return end
2000
+ opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
2001
+ vim.keymap.set(mode, lhs, rhs, opts)
2002
+ end
2003
+
2004
+ H.normalize_item_doc = function(lsp_item, fallback_info)
2005
+ local detail, doc = lsp_item.detail, lsp_item.documentation
2006
+ -- Fall back to explicit info only of there is no data in completion item
2007
+ -- Assume that explicit info is a code that needs highlighting
2008
+ detail = (detail == nil and doc == nil) and fallback_info or detail
2009
+ if detail == nil and doc == nil then return {} end
2010
+
2011
+ -- Extract string content. Treat markdown and plain kinds the same.
2012
+ -- Show both `detail` and `documentation` if the first provides new info.
2013
+ detail, doc = detail or '', type(doc) == 'table' and (doc.value or '') or (doc or '')
2014
+ -- Wrap details in language's code block to (usually) improve highlighting
2015
+ -- This approach seems to work in 'hrsh7th/nvim-cmp'
2016
+ detail = (H.is_whitespace(detail) or doc:find(detail, 1, true) ~= nil) and '' or (H.wrap_in_codeblock(detail) .. '\n')
2017
+ local text = detail .. doc
2018
+
2019
+ -- Ensure consistent line separators
2020
+ text = text:gsub('\r\n?', '\n')
2021
+ -- Remove trailing whitespace (converts blank lines to empty)
2022
+ text = text:gsub('[ \t]+\n', '\n'):gsub('[ \t]+$', '\n')
2023
+ -- Collapse multiple empty lines, remove top and bottom padding
2024
+ text = text:gsub('\n\n+', '\n\n'):gsub('^\n+', ''):gsub('\n+$', '')
2025
+ -- Ensure single line pads around code blocks: on Neovim<0.11 top and bottom
2026
+ -- lines just appear empty, on Neovim>=0.11 they disappear (account for that)
2027
+ local pad = vim.fn.has('nvim-0.11') == 1 and '\n' or ''
2028
+ text = text:gsub('\n*(\n```%S+\n)', pad .. '%1'):gsub('(\n```\n)\n*', '%1' .. pad)
2029
+
2030
+ if text == '' and fallback_info ~= '' then text = H.wrap_in_codeblock(fallback_info) end
2031
+ return text == '' and {} or vim.split(text, '\n')
2032
+ end
2033
+
2034
+ -- Neovim>=0.11 has visually impactful issue of TS (markdown) highlighting:
2035
+ -- sometimes concealing extmarks are not removed. Remove after 0.11.1 release.
2036
+ -- See https://github.com/neovim/neovim/issues/33333
2037
+ H.ensure_no_concealed_lines = function(buf_id)
2038
+ local ts_ns_id = vim.api.nvim_get_namespaces()['nvim.treesitter.highlighter']
2039
+ pcall(vim.api.nvim_buf_clear_namespace, buf_id, ts_ns_id, 0, -1)
2040
+ end
2041
+ if vim.fn.has('nvim-0.11') == 0 then H.ensure_no_concealed_lines = function(buf_id) end end
2042
+
2043
+ -- Neovim>=0.11 has markdown codeblock delimiters hidden. Neovim<0.11 shows
2044
+ -- them as empty line (so ignore only top and bottom for more compact view).
2045
+ H.compute_visible_md_lines = function(lines)
2046
+ return vim.tbl_filter(function(l) return l:find('^```%S*$') == nil end, lines)
2047
+ end
2048
+ if vim.fn.has('nvim-0.11') == 0 then
2049
+ H.compute_visible_md_lines = function(lines)
2050
+ if lines[1]:find('^```%S*$') ~= nil then table.remove(lines, 1) end
2051
+ if lines[#lines]:find('^```$') ~= nil then table.remove(lines, #lines) end
2052
+ return lines
2053
+ end
2054
+ end
2055
+
2056
+ H.wrap_in_codeblock = function(x) return string.format('```%s\n%s\n```', vim.bo.filetype:match('^[^%.]*'), vim.trim(x)) end
2057
+
2058
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2059
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2060
+
2061
+ H.get_buf_lsp_clients = function() return vim.lsp.get_clients({ bufnr = 0 }) end
2062
+ if vim.fn.has('nvim-0.10') == 0 then H.get_buf_lsp_clients = function() return vim.lsp.buf_get_clients() end end
2063
+
2064
+ -- TODO: Remove after compatibility with Neovim=0.10 is dropped
2065
+ H.make_position_params = function(context)
2066
+ local res = vim.lsp.util.make_position_params()
2067
+ res.context = context
2068
+ return res
2069
+ end
2070
+ if vim.fn.has('nvim-0.11') == 1 then
2071
+ -- Use callable `params` to workaround mandatory non-nil `offset_encoding` in
2072
+ -- `vim.lsp.util.make_position_params()` on Neovim>=0.11
2073
+ H.make_position_params = function(context)
2074
+ return function(client, _)
2075
+ local res = vim.lsp.util.make_position_params(0, client.offset_encoding)
2076
+ res.context = context
2077
+ return res
2078
+ end
2079
+ end
2080
+ end
2081
+
2082
+ -- TODO: Remove after compatibility with Neovim=0.10 is dropped
2083
+ H.client_request = function(client, method, params, handler, bufnr)
2084
+ local ok, request_id = client:request(method, params, handler, bufnr)
2085
+ return ok and function() pcall(client.cancel_request, client, request_id) end or function() end
2086
+ end
2087
+ if vim.fn.has('nvim-0.11') == 0 then
2088
+ H.client_request = function(client, method, params, handler, bufnr)
2089
+ local ok, request_id = client.request(method, params, handler, bufnr)
2090
+ return ok and function() pcall(client.cancel_request, request_id) end or function() end
2091
+ end
2092
+ end
2093
+
2094
+ return MiniCompletion