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.
- package/.conda/retrovim.yaml +259 -0
- package/.conda/terax.icns +0 -0
- package/.conda/terax.ico +0 -0
- package/.conda/terax.png +0 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitmodules +3 -0
- package/.npm/kanata_touchcursor.js +28 -0
- package/.npm/nvim.js +26 -0
- package/.npm/yazi.js +26 -0
- package/.npm/zsh.js +17 -0
- package/.pip/kanata_touchcursor/__init__.py +28 -0
- package/.pip/nvim/__init__.py +31 -0
- package/.pip/yazi/__init__.py +31 -0
- package/.pip/zsh/__init__.py +22 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +674 -0
- package/README.md +336 -0
- package/kanata/touchcursor.kbd +164 -0
- package/nvim/init.lua +813 -0
- package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
- package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
- package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
- package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
- package/nvim/plugins/mini.nvim/.styluaignore +1 -0
- package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
- package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
- package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
- package/nvim/plugins/mini.nvim/LICENSE +21 -0
- package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
- package/nvim/plugins/mini.nvim/Makefile +51 -0
- package/nvim/plugins/mini.nvim/README.md +261 -0
- package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
- package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
- package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
- package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
- package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
- package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
- package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
- package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
- package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
- package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
- package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
- package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
- package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
- package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
- package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
- package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
- package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
- package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
- package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
- package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
- package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
- package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
- package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
- package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
- package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
- package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
- package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
- package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
- package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
- package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
- package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
- package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
- package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
- package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
- package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
- package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
- package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
- package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
- package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
- package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
- package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
- package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
- package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
- package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
- package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
- package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
- package/nvim/plugins/mini.nvim/logo.png +0 -0
- package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
- package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
- package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
- package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
- package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
- package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
- package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
- package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
- package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
- package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
- package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
- package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
- package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
- package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
- package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
- package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
- package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
- package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
- package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
- package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
- package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
- package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
- package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
- package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
- package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
- package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
- package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
- package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
- package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
- package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
- package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
- package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
- package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
- package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
- package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
- package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
- package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
- package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
- package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
- package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
- package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
- package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
- package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
- package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
- package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
- package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
- package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
- package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
- package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
- package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
- package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
- package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
- package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
- package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
- package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
- package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
- package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
- package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
- package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
- package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
- package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
- package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
- package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
- package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
- package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
- package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
- package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
- package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
- package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
- package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
- package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
- package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
- package/package.json +16 -0
- package/pyproject.toml +43 -0
- package/yazi/init.lua +12 -0
- package/yazi/keymap.toml +114 -0
- package/yazi/lesskey +5 -0
- package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
- package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
- package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
- package/yazi/theme.toml +26 -0
- package/yazi/yazi.toml +15 -0
- package/zsh/.zshrc +145 -0
- package/zsh/starship.toml +20 -0
- 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
|