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,2183 @@
|
|
|
1
|
+
--- *mini.input* Get user input
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2026 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Features:
|
|
6
|
+
---
|
|
7
|
+
--- - Get user input with fully customizable key and view handling.
|
|
8
|
+
---
|
|
9
|
+
--- - Built-in configurable views as floating window, statusline/tabline/winbar,
|
|
10
|
+
--- virtual line/text.
|
|
11
|
+
---
|
|
12
|
+
--- - Implementation is non-blocking but waits to return the input. It also works
|
|
13
|
+
--- in any mode without requiring mode change. See |MiniInput-lifecycle|.
|
|
14
|
+
---
|
|
15
|
+
--- - |vim.ui.input()| implementation. To adjust, use |MiniInput.ui_input()| or
|
|
16
|
+
--- save-restore `vim.ui.input` manually after calling |MiniInput.setup()|.
|
|
17
|
+
---
|
|
18
|
+
--- Sources with more details:
|
|
19
|
+
--- - |MiniInput.get()|
|
|
20
|
+
--- - |MiniInput.default_key()|
|
|
21
|
+
--- - |MiniInput-state|
|
|
22
|
+
--- - |MiniInput-examples|
|
|
23
|
+
--- - |MiniInput-in-other-plugins| (for plugin authors)
|
|
24
|
+
---
|
|
25
|
+
--- # Setup ~
|
|
26
|
+
---
|
|
27
|
+
--- This module needs a setup with `require('mini.input').setup({})` (replace `{}`
|
|
28
|
+
--- with your `config` table). It will create global Lua table `MiniInput` which
|
|
29
|
+
--- you can use for scripting or manually (with `:lua MiniInput.*`).
|
|
30
|
+
---
|
|
31
|
+
--- See |MiniInput.config| for `config` structure and default values.
|
|
32
|
+
---
|
|
33
|
+
--- You can override runtime config settings locally to buffer inside
|
|
34
|
+
--- `vim.b.miniinput_config` which should have same structure as
|
|
35
|
+
--- `MiniInput.config`. See |mini.nvim-buffer-local-config| for more details.
|
|
36
|
+
---
|
|
37
|
+
--- # Comparisons ~
|
|
38
|
+
---
|
|
39
|
+
--- - [folke/snacks.nvim#input](https://github.com/folke/snacks.nvim):
|
|
40
|
+
--- - Both provide |vim.ui.input()| implementation.
|
|
41
|
+
--- - Has asynchronous implementation (i.e. does not wait for user to finish
|
|
42
|
+
--- input), while this module has synchronous non-blocking implementation.
|
|
43
|
+
--- - Uses floating window and forced Insert mode. This module allows more
|
|
44
|
+
--- view customizations and can be used in any mode without interruptions.
|
|
45
|
+
---
|
|
46
|
+
--- - |input()|:
|
|
47
|
+
--- - Both are synchronous.
|
|
48
|
+
--- - Both allow custom highlight and completion.
|
|
49
|
+
--- - This module also allows supplying input scope and visibility,
|
|
50
|
+
--- customizing keys and view.
|
|
51
|
+
---
|
|
52
|
+
--- # Highlight groups ~
|
|
53
|
+
--- *MiniInput-hl-groups*
|
|
54
|
+
---
|
|
55
|
+
--- - `MiniInputAdded` - added text during completion navigation.
|
|
56
|
+
--- - `MiniInputBorder` - border of a |MiniInput.gen_view.floatwin()| handler.
|
|
57
|
+
--- - `MiniInputCaret` - caret symbol shown in a prompt area.
|
|
58
|
+
--- - `MiniInputHide` - input is hidden, usually used instead of `MiniInputPrompt`.
|
|
59
|
+
--- - `MiniInputHint` - hints shown during completion navigation.
|
|
60
|
+
--- - `MiniInputNormal` - basic foreground/background.
|
|
61
|
+
--- - `MiniInputPrompt` - input prompt (intention of the input).
|
|
62
|
+
--- - `MiniInputSpecial` - special keys (like literal `\t`, `\n`, etc.) in input.
|
|
63
|
+
---
|
|
64
|
+
--- # Using in other plugins ~
|
|
65
|
+
--- *MiniInput-in-other-plugins*
|
|
66
|
+
---
|
|
67
|
+
--- - Prefer using |vim.ui.input()| for more user coverage. Use |MiniInput.get()|
|
|
68
|
+
--- only when getting input synchronously is absolutely necessary.
|
|
69
|
+
---
|
|
70
|
+
--- - Perform a `_G.MiniInput ~= nil` check before using any feature. This ensures
|
|
71
|
+
--- that user explicitly set up the module.
|
|
72
|
+
---@tag MiniInput
|
|
73
|
+
|
|
74
|
+
--- Information about the state of the input. It is passed as a handler argument.
|
|
75
|
+
--- Use |MiniInput.get_state()| to get information about the current state (if any).
|
|
76
|
+
--- A table with the following fields:
|
|
77
|
+
---
|
|
78
|
+
--- - <caret> `(number)` - character (not byte) index at which to modify input.
|
|
79
|
+
--- Should be from 1 (prepend input) to "input width plus 1" (append input).
|
|
80
|
+
---
|
|
81
|
+
--- - <complete> `(table|nil)` - information about active completion navigation.
|
|
82
|
+
--- If present, it means that completion navigation is in action.
|
|
83
|
+
--- Its fields describe the state of navigation:
|
|
84
|
+
--- - <base> `(string)` - reference text to the left of caret at the start
|
|
85
|
+
--- of completion. Used to compute candidates. Can be empty string.
|
|
86
|
+
--- - <id> `(number)` - identifier of current completion item. Can be zero to
|
|
87
|
+
--- mean that the base is shown. If not zero, must mean that a `items[id]`
|
|
88
|
+
--- candidate is now shown to the left of caret as the part of the input.
|
|
89
|
+
--- - <items> `(table)` - string array of completion candidates. May be empty.
|
|
90
|
+
--- Are intended to fully replace <base>, so should contain it in any sense.
|
|
91
|
+
--- - <method> `(string)` - completion method. Like `"default"`, `"history"`, etc.
|
|
92
|
+
--- Can be `""` (empty string) to mean "default method of complete handler".
|
|
93
|
+
---
|
|
94
|
+
--- - <data> `(table)` - any information to be reused within the same input session.
|
|
95
|
+
--- Note: handlers should not change fields that they don't "own".
|
|
96
|
+
---
|
|
97
|
+
--- - <errmsg> `(errmsg)` - first error message caught during input process.
|
|
98
|
+
---
|
|
99
|
+
--- - <highlight> `(table|nil)` - information about current input highlighting.
|
|
100
|
+
--- If absent, the whole input is highlighted using `MiniInputNormal` group.
|
|
101
|
+
--- Should be an array of highlight ranges. They might be not ordered, overlap,
|
|
102
|
+
--- go outside of input width. It is up to the view handler to decide how to
|
|
103
|
+
--- interpret them. See |MiniInput.state_to_chunks()| for a helper.
|
|
104
|
+
--- Fields of a single highlight range:
|
|
105
|
+
--- - <from> `(number)` - character index (one-indexed) of range start.
|
|
106
|
+
--- - <to> `(number)` - character index (one-indexed) of range end (inclusive).
|
|
107
|
+
--- Should not be smaller than <from>. Can be |math.huge|.
|
|
108
|
+
--- - <hl> `(string)` - highlight group to use for highlighting.
|
|
109
|
+
---
|
|
110
|
+
--- - <input> `(string)` - current user input. Returned by |MiniInput.get()|.
|
|
111
|
+
---
|
|
112
|
+
--- - <opts> `(table)` - input options, same as in |MiniInput.get()|.
|
|
113
|
+
---
|
|
114
|
+
--- - <prompt> `(string)` - intention of the input. May be empty.
|
|
115
|
+
---
|
|
116
|
+
--- - <status> `(string)` - one of `"start"`, `"progress"`, `"accept"`, `"cancel"`.
|
|
117
|
+
---@tag MiniInput-state
|
|
118
|
+
|
|
119
|
+
--- # General ~
|
|
120
|
+
---
|
|
121
|
+
--- ## Initial input ~
|
|
122
|
+
---
|
|
123
|
+
--- Use `opts.init_keys` to imitate the initial state of the input: >lua
|
|
124
|
+
---
|
|
125
|
+
--- MiniInput.get({ init_keys = { 'Default' } })
|
|
126
|
+
--- <
|
|
127
|
+
--- ## Custom mappings ~
|
|
128
|
+
---
|
|
129
|
+
--- Override `handlers.key` in |MiniInput.config|: >lua
|
|
130
|
+
---
|
|
131
|
+
--- local key_handler = function(state, key)
|
|
132
|
+
--- -- <C-a> - move caret to start of line
|
|
133
|
+
--- if key == '\1' then
|
|
134
|
+
--- state.caret = 1
|
|
135
|
+
--- -- <S-BS> - clear all input
|
|
136
|
+
--- elseif key == vim.keycode('<S-BS>') then
|
|
137
|
+
--- state.input, state.caret = '', 1
|
|
138
|
+
--- else
|
|
139
|
+
--- -- IMPORTANT: Fall back to processing as usual
|
|
140
|
+
--- return MiniInput.default_key(state, key)
|
|
141
|
+
--- end
|
|
142
|
+
--- end
|
|
143
|
+
---
|
|
144
|
+
--- require('mini.input').setup({ handlers = { key = key_handler } })
|
|
145
|
+
--- <
|
|
146
|
+
--- ## Basic command line ~
|
|
147
|
+
---
|
|
148
|
+
--- An alternative |Command-line| with highlighting and completion: >lua
|
|
149
|
+
---
|
|
150
|
+
--- -- Construct reusable `MiniInput.get()` options
|
|
151
|
+
--- local cmdline_opts = { prompt = 'Command', scope = 'editor' }
|
|
152
|
+
--- -- - Highlight using bundled Vim tree-sitter parser and default handler
|
|
153
|
+
--- local highlight_vim = MiniInput.gen_highlight.treesitter('vim')
|
|
154
|
+
--- local highlight_cmdline = function(state)
|
|
155
|
+
--- state = highlight_vim(state) or state
|
|
156
|
+
--- return MiniInput.default_highlight(state) or state
|
|
157
|
+
--- end
|
|
158
|
+
--- cmdline_opts.handlers = { highlight = highlight_cmdline }
|
|
159
|
+
--- -- - Complete as if it is Command line input
|
|
160
|
+
--- cmdline_opts.completion = 'cmdline'
|
|
161
|
+
---
|
|
162
|
+
--- -- Create a mapping for `:`
|
|
163
|
+
--- local input_cmdline = function()
|
|
164
|
+
--- local cmd = MiniInput.get(cmdline_opts)
|
|
165
|
+
--- if cmd ~= nil then vim.cmd(cmd) end
|
|
166
|
+
--- end
|
|
167
|
+
--- vim.keymap.set('n', ':', input_cmdline)
|
|
168
|
+
--- <
|
|
169
|
+
--- # Handlers ~
|
|
170
|
+
---
|
|
171
|
+
--- ## Key ~
|
|
172
|
+
---
|
|
173
|
+
--- Perform custom actions based on arbitrary conditions: >lua
|
|
174
|
+
---
|
|
175
|
+
--- local key_handler = function(state, key)
|
|
176
|
+
--- -- Adjust prompt
|
|
177
|
+
--- state.opts.prompt = state.opts.prompt:gsub('[?:]%s*$', '')
|
|
178
|
+
---
|
|
179
|
+
--- -- Adjust scope
|
|
180
|
+
--- if state.opts.prompt == 'Editor action' then
|
|
181
|
+
--- state.opts.scope = 'editor'
|
|
182
|
+
--- end
|
|
183
|
+
---
|
|
184
|
+
--- -- Hide from view and history
|
|
185
|
+
--- if state.opts.prompt:find('[Pp]assword') ~= nil then
|
|
186
|
+
--- state.opts.hide = true
|
|
187
|
+
--- end
|
|
188
|
+
---
|
|
189
|
+
--- -- IMPORTANT: Process as usual
|
|
190
|
+
--- state = MiniInput.default_key(state, key) or state
|
|
191
|
+
---
|
|
192
|
+
--- -- Auto fill and accept
|
|
193
|
+
--- if state.input == 'AF' then
|
|
194
|
+
--- state.input, state.status = 'Autofilled input', 'accept'
|
|
195
|
+
--- end
|
|
196
|
+
--- end
|
|
197
|
+
--- require('mini.input').setup({ handlers = { key = key_handler } })
|
|
198
|
+
--- <
|
|
199
|
+
--- ## View ~
|
|
200
|
+
---
|
|
201
|
+
--- Show no view: >lua
|
|
202
|
+
---
|
|
203
|
+
--- require('mini.input').setup({ handlers = { view = function() end } })
|
|
204
|
+
--- <
|
|
205
|
+
--- Compute initial style depending on scope: >lua
|
|
206
|
+
---
|
|
207
|
+
--- local input = require('mini.input')
|
|
208
|
+
--- local view_virtline = input.gen_view.virtual({ style = 'above' })
|
|
209
|
+
--- local view_tabline = input.gen_view.uiline({ style = 'tabline' })
|
|
210
|
+
--- local view_winbar = input.gen_view.uiline({ style = 'winbar' })
|
|
211
|
+
--- local view_handler = function(state)
|
|
212
|
+
--- -- NOTE: does not support interactive scope change
|
|
213
|
+
--- local scope, view = state.opts.scope, view_tabline
|
|
214
|
+
--- if scope == 'buffer' or scope == 'window' then view = view_winbar end
|
|
215
|
+
--- if scope == 'cursor' or scope == 'line' then view = view_virtline end
|
|
216
|
+
--- return view(state)
|
|
217
|
+
--- end
|
|
218
|
+
---
|
|
219
|
+
--- input.setup({ handlers = { view = view_handler } })
|
|
220
|
+
--- <
|
|
221
|
+
--- Change symbols for caret and hidden input: see |MiniInput.gen_view|.
|
|
222
|
+
---@tag MiniInput-examples
|
|
223
|
+
|
|
224
|
+
---@alias __input_to_chunks - <to_chunks> `(function)` - a function that takes |MiniInput-state| and
|
|
225
|
+
--- `max_width` arguments and returns an array of `{ text, hl }` chunks that fit
|
|
226
|
+
--- into `max_width` display width. See |MiniInput.state_to_chunks()|.
|
|
227
|
+
|
|
228
|
+
---@diagnostic disable:undefined-field
|
|
229
|
+
---@diagnostic disable:discard-returns
|
|
230
|
+
---@diagnostic disable:unused-local
|
|
231
|
+
|
|
232
|
+
-- Module definition ==========================================================
|
|
233
|
+
local MiniInput = {}
|
|
234
|
+
local H = {}
|
|
235
|
+
|
|
236
|
+
--- Module setup
|
|
237
|
+
---
|
|
238
|
+
---@param config table|nil Module config table. See |MiniInput.config|.
|
|
239
|
+
---
|
|
240
|
+
---@usage >lua
|
|
241
|
+
--- require('mini.input').setup() -- use default config
|
|
242
|
+
--- -- OR
|
|
243
|
+
--- require('mini.input').setup({}) -- replace {} with your config table
|
|
244
|
+
--- <
|
|
245
|
+
MiniInput.setup = function(config)
|
|
246
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
247
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
248
|
+
vim.notify(
|
|
249
|
+
'(mini.input) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
250
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
251
|
+
.. ' Please update your Neovim version.'
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
-- Export module
|
|
256
|
+
_G.MiniInput = MiniInput
|
|
257
|
+
|
|
258
|
+
-- Setup config
|
|
259
|
+
config = H.setup_config(config)
|
|
260
|
+
|
|
261
|
+
-- Apply config
|
|
262
|
+
H.apply_config(config)
|
|
263
|
+
|
|
264
|
+
-- Define behavior
|
|
265
|
+
H.create_autocommands()
|
|
266
|
+
|
|
267
|
+
-- Create default highlighting
|
|
268
|
+
H.create_default_hl()
|
|
269
|
+
|
|
270
|
+
-- Set custom implementation
|
|
271
|
+
vim.ui.input = MiniInput.ui_input
|
|
272
|
+
|
|
273
|
+
-- Adjust terminal emulator's pasting with active input
|
|
274
|
+
H.adjust_vim_paste()
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
--- Defaults ~
|
|
278
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
279
|
+
---@text # Handlers ~
|
|
280
|
+
--- *MiniInput.config.handlers*
|
|
281
|
+
---
|
|
282
|
+
--- `config.handlers` defines functions that are applied during |MiniInput-lifecycle|.
|
|
283
|
+
--- Every handler takes |MiniInput-state| as the first argument and is expected to
|
|
284
|
+
--- either modify it in place or return a new state table.
|
|
285
|
+
---
|
|
286
|
+
--- Use |MiniInput.apply_handler()| to apply a handler for a given |MiniInput-state|.
|
|
287
|
+
--- They can be set up as part of the config (will be used as default) or passed
|
|
288
|
+
--- directly as a part of |MiniInput.get()| call.
|
|
289
|
+
---
|
|
290
|
+
--- ## Complete ~
|
|
291
|
+
---
|
|
292
|
+
--- `handlers.complete` is a handler intended to compute completion suggestions.
|
|
293
|
+
--- Takes |MiniInput-state| and `method` as arguments and is expected to modify
|
|
294
|
+
--- <complete> field. Default: |MiniInput.default_complete()|.
|
|
295
|
+
---
|
|
296
|
+
--- Only `complete.base` and `complete.items` are expected to be set by a complete
|
|
297
|
+
--- handler. Setting and modifying other fields (`complete.id` and `complete.method`)
|
|
298
|
+
--- is done in other handlers and as part of |MiniInput.apply_handler()|. Actually
|
|
299
|
+
--- showing completion information is up to the view handler.
|
|
300
|
+
---
|
|
301
|
+
--- This handler is usually applied manually inside a key handler via using
|
|
302
|
+
--- `MiniInput.apply_handler(state, 'complete', method)`. Like, for example,
|
|
303
|
+
--- in |MiniInput.default_key()| after <Tab> or <Up>.
|
|
304
|
+
---
|
|
305
|
+
--- Here is an example of a simple demo complete handler: >lua
|
|
306
|
+
---
|
|
307
|
+
--- local complete_handler = function(state, method)
|
|
308
|
+
--- if method == '' or method == 'xy' then
|
|
309
|
+
--- local text = vim.fn.strcharpart(state.input, 0, state.caret - 1)
|
|
310
|
+
--- local base = text:match('%S*$')
|
|
311
|
+
--- state.complete = { base = base, items = { base .. 'x', base .. 'y' } }
|
|
312
|
+
--- return
|
|
313
|
+
--- end
|
|
314
|
+
--- return MiniInput.default_complete(state, method)
|
|
315
|
+
--- end
|
|
316
|
+
---
|
|
317
|
+
--- require('mini.input').setup({ handlers = { complete = complete_handler } })
|
|
318
|
+
--- <
|
|
319
|
+
--- ## Key ~
|
|
320
|
+
---
|
|
321
|
+
--- `handlers.key` is a handler intended to process every user key press.
|
|
322
|
+
--- Takes |MiniInput-state| and `key` as arguments. Argument `key` represents a key
|
|
323
|
+
--- that needs to be processed: a string if from the user input or `nil` if input
|
|
324
|
+
--- needs to be set up, refreshed, or torn down. Default: |MiniInput.default_key()|.
|
|
325
|
+
---
|
|
326
|
+
--- A string `key` can be two kinds:
|
|
327
|
+
--- - Forwarded from |getcharstr()| verbatim as a result of interactive key press.
|
|
328
|
+
--- Meaning it will be in escaped form and not as a |key-notation|: i.e. `"\r"`
|
|
329
|
+
--- and not `"<CR>"`. It also means that all kinds of combos (`<M-...>`, `<C-S-...>`),
|
|
330
|
+
--- mouse clicks, and wheel scrolls are also forwarded to key handler.
|
|
331
|
+
--- - Any string as part of `opts.init_keys` in |MiniInput.get()|. If a string
|
|
332
|
+
--- doesn't look like it came from |getcharstr()|, it is usually a good idea
|
|
333
|
+
--- to insert this string at caret as is.
|
|
334
|
+
---
|
|
335
|
+
--- The suggested overall approach for custom key handler is "if `key` is special -
|
|
336
|
+
--- act on it, if can be used in the input - insert at caret, ignore otherwise".
|
|
337
|
+
--- It is also important to never change the current mode (as in |vim-modes|)
|
|
338
|
+
--- for |MiniInput.get()| to work as expected.
|
|
339
|
+
---
|
|
340
|
+
--- Here is an example of a basic custom key handler: >lua
|
|
341
|
+
---
|
|
342
|
+
--- local custom_actions = {
|
|
343
|
+
--- [vim.keycode('<Left>')] = function(state)
|
|
344
|
+
--- state.caret = math.max(state.caret - 1, 1)
|
|
345
|
+
--- end,
|
|
346
|
+
--- [vim.keycode('<Right>')] = function(state)
|
|
347
|
+
--- local input_width = vim.fn.strchars(state.input)
|
|
348
|
+
--- state.caret = math.min(state.caret + 1, input_width + 1)
|
|
349
|
+
--- end,
|
|
350
|
+
--- [vim.keycode('<CR>')] = function(state) state.status = 'accept' end,
|
|
351
|
+
--- [vim.keycode('<Esc>')] = function(state) state.status = 'cancel' end,
|
|
352
|
+
--- }
|
|
353
|
+
---
|
|
354
|
+
--- local key_handler = function(state, key)
|
|
355
|
+
--- -- No need for special setup or teardown
|
|
356
|
+
--- if key == nil then return end
|
|
357
|
+
---
|
|
358
|
+
--- -- If key is special - act on it
|
|
359
|
+
--- if custom_actions[key] then return custom_actions[key](state) end
|
|
360
|
+
---
|
|
361
|
+
--- -- If key is not printable - do nothing
|
|
362
|
+
--- if vim.fn.match(key, '^[[:print:]]\\+$') < 0 then return end
|
|
363
|
+
---
|
|
364
|
+
--- -- Insert at caret
|
|
365
|
+
--- local caret, input = state.caret, state.input
|
|
366
|
+
--- local before_caret = vim.fn.strcharpart(input, 0, caret - 1)
|
|
367
|
+
--- local after_caret = vim.fn.strcharpart(input, caret - 1)
|
|
368
|
+
--- state.input = before_caret .. key .. after_caret
|
|
369
|
+
--- state.caret = caret + vim.fn.strchars(key)
|
|
370
|
+
---
|
|
371
|
+
--- -- No need to return anything as `state` is modified in place
|
|
372
|
+
--- end
|
|
373
|
+
---
|
|
374
|
+
--- require('mini.input').setup({ handlers = { key = key_handler } })
|
|
375
|
+
--- <
|
|
376
|
+
--- ## Highlight ~
|
|
377
|
+
---
|
|
378
|
+
--- `handlers.highlight` is a handler intended to compute and set highlight info
|
|
379
|
+
--- about the current input. Takes |MiniInput-state| as the only argument and is
|
|
380
|
+
--- expected to modify <highlight> field. Default: |MiniInput.default_highlight()|.
|
|
381
|
+
---
|
|
382
|
+
--- See |MiniInput.gen_highlight| for built-in highlight handler generators.
|
|
383
|
+
---
|
|
384
|
+
--- It is usually a good idea to append to a <highlight> if it already exists.
|
|
385
|
+
--- This makes it work more robustly when combining highlights.
|
|
386
|
+
---
|
|
387
|
+
--- Here is a basic example that highlights all letters `a`: >lua
|
|
388
|
+
---
|
|
389
|
+
--- local hl_handler = function(state)
|
|
390
|
+
--- local highlight = {}
|
|
391
|
+
--- for col in string.gmatch(state.input, '()a') do
|
|
392
|
+
--- -- NOTE: range should use character (not byte) indexes
|
|
393
|
+
--- local char_col = vim.fn.charidx(state.input, col)
|
|
394
|
+
--- local range = { from = char_col, to = char_col, hl = 'Special' }
|
|
395
|
+
--- table.insert(highlight, range)
|
|
396
|
+
--- end
|
|
397
|
+
---
|
|
398
|
+
--- state.highlight = vim.list_extend(state.highlight or {}, highlight)
|
|
399
|
+
---
|
|
400
|
+
--- -- Possibly also apply default handler afterwards
|
|
401
|
+
--- return MiniInput.default_highlight(state)
|
|
402
|
+
--- end
|
|
403
|
+
---
|
|
404
|
+
--- require('mini.input').setup({ handlers = { highlight = hl_handler } })
|
|
405
|
+
--- <
|
|
406
|
+
--- ## View ~
|
|
407
|
+
---
|
|
408
|
+
--- `handlers.view` is a handler intended to show the input state on screen.
|
|
409
|
+
--- Takes |MiniInput-state| as the only argument. Default: |MiniInput.default_view()|.
|
|
410
|
+
---
|
|
411
|
+
--- See |MiniInput.gen_view| for built-in view handler generators.
|
|
412
|
+
---
|
|
413
|
+
--- Example of view that uses |nvim_echo()| to show the input: >lua
|
|
414
|
+
---
|
|
415
|
+
--- local view_handler = function(state)
|
|
416
|
+
--- -- Process start and end of the input lifecycle
|
|
417
|
+
--- local is_start = state.status == 'start'
|
|
418
|
+
--- local is_end = state.status == 'accept' or state.status == 'cancel'
|
|
419
|
+
--- if is_start or is_end then vim.cmd('mode') end
|
|
420
|
+
--- if is_end then return end
|
|
421
|
+
---
|
|
422
|
+
--- -- Compute text-hl chunks that fit and show them
|
|
423
|
+
--- local chunks = MiniInput.state_to_chunks(state, vim.v.echospace)
|
|
424
|
+
--- vim.api.nvim_echo(chunks, false, {})
|
|
425
|
+
--- end
|
|
426
|
+
---
|
|
427
|
+
--- require('mini.input').setup({ handlers = { view = view_handler } })
|
|
428
|
+
--- <
|
|
429
|
+
--- # Scope ~
|
|
430
|
+
---
|
|
431
|
+
--- `config.scope` is a string that defines an input scope. It is meant as an extra
|
|
432
|
+
--- information for handlers to tweak their behavior (`view` style, etc.). Possible
|
|
433
|
+
--- values: `"cursor"`, `"line"`, `"buffer"`, `"window"`, `"tabpage"`, `"editor"`, `"project"`.
|
|
434
|
+
MiniInput.config = {
|
|
435
|
+
-- Functions that control input lifecycle
|
|
436
|
+
handlers = {
|
|
437
|
+
-- Compute completion candidates
|
|
438
|
+
complete = nil,
|
|
439
|
+
|
|
440
|
+
-- Compute highlighting of current input
|
|
441
|
+
highlight = nil,
|
|
442
|
+
|
|
443
|
+
-- Handle input start, every key press, and input end
|
|
444
|
+
key = nil,
|
|
445
|
+
|
|
446
|
+
-- Show current input state
|
|
447
|
+
view = nil,
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
-- Default input scope: cursor/line/buffer/window/tabpage/editor/project
|
|
451
|
+
scope = 'editor',
|
|
452
|
+
}
|
|
453
|
+
--minidoc_afterlines_end
|
|
454
|
+
|
|
455
|
+
--- Get input from the user
|
|
456
|
+
---
|
|
457
|
+
--- # Lifecycle ~
|
|
458
|
+
--- *MiniInput-lifecycle*
|
|
459
|
+
---
|
|
460
|
+
--- This module implements custom lifecycle to interact with the user. It starts
|
|
461
|
+
--- when calling |MiniInput.get()| and ends when a value is returned.
|
|
462
|
+
--- Only one active input is allowed simultaneously.
|
|
463
|
+
---
|
|
464
|
+
--- The basic cycle unit is a step that processes a `key` (string or `nil`). It is
|
|
465
|
+
--- done by calling relevant handlers in order: key handler with `key` as a second
|
|
466
|
+
--- argument, highlight handler, view handler. |MiniInput.apply_handler()| is used
|
|
467
|
+
--- to apply each handler and the output of one is used as the input for the next.
|
|
468
|
+
---
|
|
469
|
+
--- During a step some state fields are automatically removed:
|
|
470
|
+
--- - <highlight> is removed before applying a key handler to always have the most
|
|
471
|
+
--- up to date highlighting.
|
|
472
|
+
--- - <complete> is removed if it was not changed after applying a key handler but
|
|
473
|
+
--- something else in the state besides <highlight> did change. This is meant as
|
|
474
|
+
--- an automatic stop of completion when it is not advancing.
|
|
475
|
+
---
|
|
476
|
+
--- If a step sets an ending state <status> (i.e. `"accept"` or `"cancel"`), the input
|
|
477
|
+
--- is finished by extra finishing step (see below).
|
|
478
|
+
---
|
|
479
|
+
--- The order of operations is as follows:
|
|
480
|
+
--- - Create initial |MiniInput-state| based on the input `opts`, with defaults
|
|
481
|
+
--- inferred from |MiniInput.config|, and `vim.b.miniinput_config`.
|
|
482
|
+
--- - Set <status> to `"start"`.
|
|
483
|
+
--- - Advance one step with `key=nil`. This is meant as a "setup" step for handlers.
|
|
484
|
+
--- - Set <status> to `"progress"`.
|
|
485
|
+
--- - Process `opts.init_keys` one item per step with `key` set to the string item.
|
|
486
|
+
--- - Wait for user to press a key (via |getcharstr()|). The key string (in escaped
|
|
487
|
+
--- form and not as a |key-notation|; i.e. `"\r"` and not `"<CR>"`) is then used
|
|
488
|
+
--- to advance a step. Note: <C-c> is hard coded to cancel the input.
|
|
489
|
+
--- - Repeat previous step until the ending <status> (`"accept"` or `"cancel"`).
|
|
490
|
+
--- - Finish the input:
|
|
491
|
+
--- - Perform a "teardown" step with `key=nil`.
|
|
492
|
+
--- - If <errmsg> is set, throw an |error()|.
|
|
493
|
+
--- - If input is accepted (even if empty) and not hidden, add <input> to
|
|
494
|
+
--- the history. Get the whole history with |MiniInput.get_history()|.
|
|
495
|
+
--- - Return <input> if <status> is `"accept"`, `nil` otherwise.
|
|
496
|
+
---
|
|
497
|
+
---@param opts table|nil Options. Possible fields:
|
|
498
|
+
--- - <completion> `(string)` - completion method. Default: `''` to use default
|
|
499
|
+
--- completion method of the `complete` handler.
|
|
500
|
+
--- - <handlers> `(table)` - same as in |MiniInput.config.handlers|, used only for
|
|
501
|
+
--- the duration of the current input.
|
|
502
|
+
--- - <hide> `(boolean)` - whether input should be hidden. Default: `false`.
|
|
503
|
+
--- Note: this does not guarantee a total security of the input, only that
|
|
504
|
+
--- the typed characters are expected to not be shown on screen and not added
|
|
505
|
+
--- to the history. If set:
|
|
506
|
+
--- - The `view` handler is expected to not directly show current input.
|
|
507
|
+
--- Like replace characters with pre-defined string or fully not show.
|
|
508
|
+
--- - The `complete` and `highlight` handlers are not called.
|
|
509
|
+
--- - Accepted input will not be added to the history.
|
|
510
|
+
--- - <init_keys> `(table)` - array of string keys that are emulated before asking
|
|
511
|
+
--- for the user input. Using values that can be an output of |getcharstr()|
|
|
512
|
+
--- should be preferred, but a key handler should work with any string.
|
|
513
|
+
--- Default: `{}`.
|
|
514
|
+
--- - <prompt> `(string)` - intention of the input, same as in |input()|.
|
|
515
|
+
--- Default: `"Input"`.
|
|
516
|
+
--- - <scope> `(string)` - same as in |MiniInput.config|. Default: the value from
|
|
517
|
+
--- `MiniInput.config` with some hard coded exceptions (on Neovim<0.12.3):
|
|
518
|
+
--- - |vim.lsp.buf.rename()| will use `"cursor"` if no `new_name` is supplied.
|
|
519
|
+
---
|
|
520
|
+
---@return string|nil User input (from the <input> state field) if accepted, even if
|
|
521
|
+
--- empty. `nil` if canceled or there was an active input.
|
|
522
|
+
---
|
|
523
|
+
---@usage >lua
|
|
524
|
+
--- local input = MiniInput.get({
|
|
525
|
+
--- -- Intention of the input
|
|
526
|
+
--- prompt = 'New value',
|
|
527
|
+
--- -- The input is for something at cursor
|
|
528
|
+
--- scope = 'cursor',
|
|
529
|
+
--- -- Emulate pressing `a`, `<BS>`, and `b`
|
|
530
|
+
--- init_keys = { 'a', vim.keycode('<BS>'), 'b' },
|
|
531
|
+
--- })
|
|
532
|
+
--- <
|
|
533
|
+
MiniInput.get = function(opts)
|
|
534
|
+
H.check_type('opts', opts, 'table', true)
|
|
535
|
+
opts = opts or {}
|
|
536
|
+
opts.scope = opts.scope or (_G.MiniInput or {})._temp_default_scope
|
|
537
|
+
|
|
538
|
+
-- Only allow one input at a time
|
|
539
|
+
if H.state ~= nil then return nil end
|
|
540
|
+
|
|
541
|
+
local init_state = H.state_new(H.get_config(opts))
|
|
542
|
+
H.state_validate(init_state)
|
|
543
|
+
H.state = init_state
|
|
544
|
+
|
|
545
|
+
H.handle_step(nil)
|
|
546
|
+
if H.state_is_end() then return H.state_finish() end
|
|
547
|
+
H.state.status = 'progress'
|
|
548
|
+
|
|
549
|
+
for _, k in ipairs(H.state.opts.init_keys) do
|
|
550
|
+
H.handle_step(k, true)
|
|
551
|
+
if H.state_is_end() then return H.state_finish() end
|
|
552
|
+
end
|
|
553
|
+
H.redraw()
|
|
554
|
+
|
|
555
|
+
local lmap = H.get_lmap()
|
|
556
|
+
for _ = 1, 1000000 do
|
|
557
|
+
local key = H.getcharstr(lmap)
|
|
558
|
+
if key == nil and not H.state_is_end() then H.state.status = 'cancel' end
|
|
559
|
+
if H.state_is_end() then break end
|
|
560
|
+
H.handle_step(key)
|
|
561
|
+
if H.state_is_end() then break end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
return H.state_finish()
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
--- A |vim.ui.input()| implementation
|
|
568
|
+
---
|
|
569
|
+
--- Function which can be used to directly override |vim.ui.input()| to use this
|
|
570
|
+
--- module functionality. Set automatically in |MiniInput.setup()|.
|
|
571
|
+
---
|
|
572
|
+
---@usage To preserve original `vim.ui.input()`: >lua
|
|
573
|
+
---
|
|
574
|
+
--- local ui_input_orig = vim.ui.input
|
|
575
|
+
--- require('mini.input').setup()
|
|
576
|
+
--- vim.ui.input = ui_input_orig
|
|
577
|
+
--- <
|
|
578
|
+
MiniInput.ui_input = function(opts, on_confirm)
|
|
579
|
+
opts = opts or {}
|
|
580
|
+
local input_opts = { completion = opts.completion, prompt = opts.prompt, scope = opts.scope }
|
|
581
|
+
input_opts.handlers = { highlight = H.make_ui_select_hl_fun(opts.highlight) }
|
|
582
|
+
input_opts.init_keys = { opts.default }
|
|
583
|
+
|
|
584
|
+
on_confirm(MiniInput.get(input_opts))
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
--- Get current input state
|
|
588
|
+
---
|
|
589
|
+
---@return table|nil Current |MiniInput-state| if input is active, `nil` otherwise. Notes:
|
|
590
|
+
--- - For hidden input (`state.opts.hide=true`), both <caret> and <input> are `nil`
|
|
591
|
+
--- to actually hide the input.
|
|
592
|
+
MiniInput.get_state = function()
|
|
593
|
+
if H.state == nil then return nil end
|
|
594
|
+
local res = H.copy_tables(H.state)
|
|
595
|
+
if res.opts.hide then
|
|
596
|
+
res.input, res.caret = nil, nil
|
|
597
|
+
end
|
|
598
|
+
return res
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
--- Get input history
|
|
602
|
+
---
|
|
603
|
+
---@return table Array with data about all previous non-hidden inputs (from earliest
|
|
604
|
+
--- to latest). Each element is a table with the following fields:
|
|
605
|
+
--- - <cwd> `(string)` - |current-directory| at the time of input's end.
|
|
606
|
+
--- - <input> `(string)` - input result.
|
|
607
|
+
--- - <prompt> `(string)` - `opts.prompt` supplied in |MiniInput.get()|.
|
|
608
|
+
--- - <scope> `(string)` - `opts.scope` supplied in |MiniInput.get()|.
|
|
609
|
+
MiniInput.get_history = function() return vim.deepcopy(H.history) end
|
|
610
|
+
|
|
611
|
+
--- Set input history
|
|
612
|
+
---
|
|
613
|
+
---@param history table Array describing all previous inputs. Same structure
|
|
614
|
+
--- as |MiniInput.get_history()| output.
|
|
615
|
+
MiniInput.set_history = function(history)
|
|
616
|
+
H.check_array_of('history', history, 'table')
|
|
617
|
+
for i, h in ipairs(history) do
|
|
618
|
+
local item = string.format('history[%d]', i)
|
|
619
|
+
H.check_type(item .. '.cwd', h.cwd, 'string')
|
|
620
|
+
H.check_type(item .. '.input', h.input, 'string')
|
|
621
|
+
H.check_type(item .. '.prompt', h.prompt, 'string')
|
|
622
|
+
H.check_one_of(item .. '.scope', h.scope, H.allowed_scopes)
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
H.history = vim.deepcopy(history)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
--- Refresh active input
|
|
629
|
+
---
|
|
630
|
+
--- Performs one step of |MiniInput-lifecycle| with `key=nil`.
|
|
631
|
+
MiniInput.refresh = function()
|
|
632
|
+
if H.state == nil then return end
|
|
633
|
+
H.handle_step(nil)
|
|
634
|
+
if H.state_is_end() then H.state_finish() end
|
|
635
|
+
vim.schedule(H.redraw)
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
--- Highlight generators
|
|
639
|
+
---
|
|
640
|
+
--- This is a table with function elements. Call to actually get a view function.
|
|
641
|
+
MiniInput.gen_highlight = {}
|
|
642
|
+
|
|
643
|
+
--- Highlight with tree-sitter
|
|
644
|
+
---
|
|
645
|
+
---@param lang string A language of tree-sitter parser to use.
|
|
646
|
+
---
|
|
647
|
+
---@return function A highlight handler. Seem |MiniInput.config.handlers|.
|
|
648
|
+
MiniInput.gen_highlight.treesitter = function(lang)
|
|
649
|
+
local append_ts_hl_range = function(arr, line, tstree, tree)
|
|
650
|
+
local query = tstree and vim.treesitter.query.get(tree:lang(), 'highlights')
|
|
651
|
+
if query == nil then return end
|
|
652
|
+
for capture, node in query:iter_captures(tstree:root(), line) do
|
|
653
|
+
-- Ignore private captures
|
|
654
|
+
if query.captures[capture]:sub(1, 1) ~= '_' then
|
|
655
|
+
local _, from, _, to = node:range()
|
|
656
|
+
local hl = string.format('@%s.%s', query.captures[capture], query.lang)
|
|
657
|
+
from, to = vim.fn.charidx(line, from) + 1, vim.fn.charidx(line, to)
|
|
658
|
+
if from <= to then table.insert(arr, { from = from, to = to, hl = hl }) end
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
return function(state)
|
|
664
|
+
local line = state.input
|
|
665
|
+
local ok, parser = pcall(vim.treesitter.get_string_parser, line, lang)
|
|
666
|
+
if not ok or parser == nil then return end
|
|
667
|
+
|
|
668
|
+
-- Traverse all trees and compute highlight ranges
|
|
669
|
+
parser:parse(true)
|
|
670
|
+
local highlight = {}
|
|
671
|
+
parser:for_each_tree(function(tstree, tree) append_ts_hl_range(highlight, line, tstree, tree) end)
|
|
672
|
+
|
|
673
|
+
-- Respect maybe already present <highlight>
|
|
674
|
+
state.highlight = vim.list_extend(state.highlight or {}, highlight)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
--- View generators
|
|
679
|
+
---
|
|
680
|
+
--- This is a table with function elements. Call to actually get a view function.
|
|
681
|
+
---
|
|
682
|
+
--- Each element accepts <style> option which fine tunes the input view.
|
|
683
|
+
--- With default key handler (|MiniInput.default_key()|) it can be adjusted
|
|
684
|
+
--- interactively by pressing <C-s>.
|
|
685
|
+
---
|
|
686
|
+
--- Each element also accepts <to_chunks> option. It is a function that takes
|
|
687
|
+
--- a |MiniInput-state| and `max_width` arguments and returns an array of `{ text, hl }`
|
|
688
|
+
--- chunks that fit into `max_width` display width (as in |strdisplaywidth()|).
|
|
689
|
+
---
|
|
690
|
+
--- This is a way to adjust how input is shown. Like caret/hide symbols, etc.
|
|
691
|
+
--- By default uses |MiniInput.state_to_chunks()|, which also means:
|
|
692
|
+
--- - All control characters (like literal `\t`, `\n`, etc.) will be translated
|
|
693
|
+
--- via |keytrans()|.
|
|
694
|
+
---
|
|
695
|
+
--- Example: >lua
|
|
696
|
+
---
|
|
697
|
+
--- local input = require('mini.input')
|
|
698
|
+
---
|
|
699
|
+
--- -- Adjust how state is converted to text-hl chunks
|
|
700
|
+
--- local to_chunks_opts = {
|
|
701
|
+
--- symbol_caret = '_',
|
|
702
|
+
--- symbol_hide = '*',
|
|
703
|
+
--- -- Do not include prompt and hint in `floatwin` handler
|
|
704
|
+
--- include_prompt = false,
|
|
705
|
+
--- include_hint = false,
|
|
706
|
+
--- }
|
|
707
|
+
--- local to_chunks = function(state, max_width)
|
|
708
|
+
--- return MiniInput.state_to_chunks(state, max_width, to_chunks_opts)
|
|
709
|
+
--- end
|
|
710
|
+
---
|
|
711
|
+
--- -- Supply custom `to_chunks` as an option
|
|
712
|
+
--- local view_opts = { to_chunks = to_chunks }
|
|
713
|
+
--- input.setup({ handlers = { view = input.gen_view.floatwin(view_opts) } })
|
|
714
|
+
--- <
|
|
715
|
+
MiniInput.gen_view = {}
|
|
716
|
+
|
|
717
|
+
--- Floating window view
|
|
718
|
+
---
|
|
719
|
+
--- Show input inside a floating window. Prompt and completion hints are shown
|
|
720
|
+
--- in title and footer.
|
|
721
|
+
---
|
|
722
|
+
--- Window position and dimensions are computed based on state's <scope> and
|
|
723
|
+
--- `opts.style`:
|
|
724
|
+
--- - `scope="cursor"` is shown near the cursor. For example, `style="BL"` will
|
|
725
|
+
--- have bottom left corner at nearest top right cell relative to the cursor.
|
|
726
|
+
--- - `scope="line"` is shown near the current line. For example, `style="BL"` will
|
|
727
|
+
--- have bottom left corner at left side above the line.
|
|
728
|
+
--- - `scope="buffer"` and `scope="window"` will be shown relative to the current
|
|
729
|
+
--- window. For example, `style="BL"` will be shown in the bottom left corner.
|
|
730
|
+
--- - `scope="tabpage"`, `scope="editor"`, `scope="project"` will be shown relative to
|
|
731
|
+
--- the overall Neovim instance. For example, `style="BL"` will be shown in the
|
|
732
|
+
--- bottom left corner.
|
|
733
|
+
---
|
|
734
|
+
--- Identifiers of floating window and its buffer are stored as `floatwin_win_id`
|
|
735
|
+
--- and `floatwin_buf_id` in |MiniInput-state| <data> field. Floating window is
|
|
736
|
+
--- not focused and cursor position is not the same as caret position.
|
|
737
|
+
---
|
|
738
|
+
---@param opts table|nil Options. Possible fields:
|
|
739
|
+
--- - <adjust_config> `(function)` - function to adjust default config. Will be
|
|
740
|
+
--- called with two arguments: current |MiniInput-state| and a window config
|
|
741
|
+
--- (always with `relative="editor"` and `anchor="NW"`) computed based
|
|
742
|
+
--- on `opts.style`. Should return an adjusted window config.
|
|
743
|
+
--- Default: `function(state, config) return config end`.
|
|
744
|
+
---
|
|
745
|
+
--- - <style> `(string)` - a two character description of how to show the window.
|
|
746
|
+
--- Default: `"BL"`.
|
|
747
|
+
---
|
|
748
|
+
--- First character describes vertical position:
|
|
749
|
+
--- - `"T"` - top window border will be at scope's reference top border.
|
|
750
|
+
--- - `"M"` - middle between top and bottom window borders will be at the
|
|
751
|
+
--- middle of scope's reference top and bottom borders.
|
|
752
|
+
--- - `"B"` - bottom window border will be at scope's reference bottom border.
|
|
753
|
+
---
|
|
754
|
+
--- Second character describes horizontal position:
|
|
755
|
+
--- - `"L"` - left window border will be at scope's reference left border.
|
|
756
|
+
--- - `"M"` - middle between left and right window borders will be at the
|
|
757
|
+
--- middle of scope's reference left and right borders.
|
|
758
|
+
--- - `"R"` - right window border will be at scope's reference right border.
|
|
759
|
+
---
|
|
760
|
+
--- __input_to_chunks
|
|
761
|
+
---
|
|
762
|
+
---@usage >lua
|
|
763
|
+
--- local input = require('mini.input')
|
|
764
|
+
---
|
|
765
|
+
--- -- Use border different from 'winborder'
|
|
766
|
+
--- local adjust_config = function(_, config)
|
|
767
|
+
--- config.border = 'double'
|
|
768
|
+
--- return config
|
|
769
|
+
--- end
|
|
770
|
+
---
|
|
771
|
+
--- -- Choose initial style based on the scope
|
|
772
|
+
--- local floatwin = input.gen_view.floatwin
|
|
773
|
+
--- local view_tm = floatwin({ style = 'TM', adjust_config = adjust_config })
|
|
774
|
+
--- local view_bl = floatwin({ style = 'BL', adjust_config = adjust_config })
|
|
775
|
+
--- local view_handler = function(state)
|
|
776
|
+
--- local scope, view = state.opts.scope, view_tm
|
|
777
|
+
--- if scope == 'cursor' or scope == 'line' then view = view_bl end
|
|
778
|
+
--- return view(state)
|
|
779
|
+
--- end
|
|
780
|
+
---
|
|
781
|
+
--- input.setup({ handlers = { view = view_handler } })
|
|
782
|
+
--- <
|
|
783
|
+
MiniInput.gen_view.floatwin = function(opts)
|
|
784
|
+
local default_opts = { style = 'BL' }
|
|
785
|
+
default_opts.adjust_config = function(_, config) return config end
|
|
786
|
+
local default_to_chunks_opts = { include_prompt = false, include_hint = false }
|
|
787
|
+
default_opts.to_chunks = function(state, max_width)
|
|
788
|
+
return MiniInput.state_to_chunks(state, max_width, default_to_chunks_opts)
|
|
789
|
+
end
|
|
790
|
+
opts = vim.tbl_extend('force', default_opts, opts or {})
|
|
791
|
+
H.check_type('opts.adjust_config', opts.adjust_config, 'callable')
|
|
792
|
+
H.check_one_of('opts.style', opts.style, { 'TL', 'TM', 'TR', 'ML', 'MM', 'MR', 'BL', 'BM', 'BR' })
|
|
793
|
+
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
|
|
794
|
+
|
|
795
|
+
-- Change style in vertical-horizontal order
|
|
796
|
+
local all_styles = { 'BL', 'ML', 'TL', 'BM', 'MM', 'TM', 'BR', 'MR', 'TR' }
|
|
797
|
+
|
|
798
|
+
return function(state)
|
|
799
|
+
if H.state_is_end(state) then
|
|
800
|
+
pcall(vim.api.nvim_win_close, state.data.floatwin_win_id, true)
|
|
801
|
+
pcall(vim.api.nvim_buf_delete, state.data.floatwin_buf_id, { force = true })
|
|
802
|
+
return
|
|
803
|
+
end
|
|
804
|
+
local style, _ = H.handle_view_style(state, opts.style, all_styles)
|
|
805
|
+
|
|
806
|
+
-- Try to fit all chunks first, but later still truncate to window width
|
|
807
|
+
local winborder = vim.fn.exists('+winborder') == 0 and '' or vim.o.winborder
|
|
808
|
+
default_to_chunks_opts = { include_prompt = winborder == 'none', include_hint = winborder == 'none' }
|
|
809
|
+
local chunks = H.get_chunks(opts, state)
|
|
810
|
+
local default_config = H.default_floatwin_config(state, style, H.get_chunks_displaywidth(chunks))
|
|
811
|
+
local config = opts.adjust_config(state, default_config)
|
|
812
|
+
chunks = H.get_chunks(opts, state, config.width)
|
|
813
|
+
|
|
814
|
+
H.ensure_floatwin_buf(state, chunks)
|
|
815
|
+
H.ensure_floatwin_win(state, config)
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
--- UI line (statusline, tabline, winbar) view
|
|
820
|
+
---
|
|
821
|
+
---@param opts table|nil Options. Possible fields:
|
|
822
|
+
--- - <style> `(string)` - which UI line to use. One of `"statusline"`,
|
|
823
|
+
--- `"tabline"`, `"winbar"`. Default: `"statusline"`.
|
|
824
|
+
---
|
|
825
|
+
--- __input_to_chunks
|
|
826
|
+
---
|
|
827
|
+
---@usage >lua
|
|
828
|
+
--- local input = require('mini.input')
|
|
829
|
+
---
|
|
830
|
+
--- -- Choose initial style based on the scope
|
|
831
|
+
--- local view_tabline = input.gen_view.uiline({ style = 'tabline' })
|
|
832
|
+
--- local view_winbar = input.gen_view.uiline({ style = 'winbar' })
|
|
833
|
+
--- local view_handler = function(state)
|
|
834
|
+
--- local scope, view = state.opts.scope, view_winbar
|
|
835
|
+
--- if scope == 'tabpage' or scope == 'editor' or scope == 'project' then
|
|
836
|
+
--- view = view_tabline
|
|
837
|
+
--- end
|
|
838
|
+
--- return view(state)
|
|
839
|
+
--- end
|
|
840
|
+
---
|
|
841
|
+
--- input.setup({ handlers = { view = view_handler } })
|
|
842
|
+
--- <
|
|
843
|
+
MiniInput.gen_view.uiline = function(opts)
|
|
844
|
+
opts = vim.tbl_extend('force', { style = 'statusline', to_chunks = MiniInput.state_to_chunks }, opts or {})
|
|
845
|
+
H.check_one_of('opts.style', opts.style, { 'statusline', 'tabline', 'winbar' })
|
|
846
|
+
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
|
|
847
|
+
|
|
848
|
+
local all_styles = { 'statusline', 'winbar', 'tabline' }
|
|
849
|
+
local escape_stl = function(x) return (x:gsub('%%', '%%%%')) end
|
|
850
|
+
|
|
851
|
+
return function(state)
|
|
852
|
+
local style, style_is_new = H.handle_view_style(state, opts.style, all_styles)
|
|
853
|
+
H.uiline_handle_option_values(state, style_is_new)
|
|
854
|
+
if H.state_is_end(state) then return end
|
|
855
|
+
|
|
856
|
+
local max_width = style == 'tabline' and vim.o.columns or vim.fn.winwidth(0)
|
|
857
|
+
local chunks = H.get_chunks(opts, state, max_width)
|
|
858
|
+
local parts = vim.tbl_map(function(c) return '%#' .. escape_stl(c[2]) .. '#' .. escape_stl(c[1]) end, chunks)
|
|
859
|
+
local uiline_value = table.concat(parts) .. '%#MiniInputNormal#'
|
|
860
|
+
|
|
861
|
+
local opt = style == 'tabline' and vim.o or vim.wo
|
|
862
|
+
opt[style] = uiline_value
|
|
863
|
+
if style == 'statusline' and vim.o.laststatus < 2 then vim.o.laststatus = 2 end
|
|
864
|
+
if style == 'tabline' and vim.o.showtabline < 2 then vim.o.showtabline = 2 end
|
|
865
|
+
end
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
--- Virtual (line, text) view
|
|
869
|
+
---
|
|
870
|
+
---@param opts table|nil Options. Possible fields:
|
|
871
|
+
--- - <style> `(string)` - how to display virtual text. One of `"above"`, `"below"`,
|
|
872
|
+
--- `"inline"`. Default: `"above"`.
|
|
873
|
+
---
|
|
874
|
+
--- __input_to_chunks
|
|
875
|
+
---
|
|
876
|
+
---@usage >lua
|
|
877
|
+
--- -- Choose different initial style
|
|
878
|
+
--- local input = require('mini.input')
|
|
879
|
+
--- local view_handler = input.gen_view.virtual({ style = 'inline' })
|
|
880
|
+
--- input.setup({ handlers = { view = view_handler } })
|
|
881
|
+
--- <
|
|
882
|
+
MiniInput.gen_view.virtual = function(opts)
|
|
883
|
+
opts = vim.tbl_extend('force', { style = 'above', to_chunks = MiniInput.state_to_chunks }, opts or {})
|
|
884
|
+
|
|
885
|
+
H.check_one_of('opts.style', opts.style, { 'above', 'below', 'inline' })
|
|
886
|
+
H.check_type('opts.to_chunks', opts.to_chunks, 'callable')
|
|
887
|
+
|
|
888
|
+
local ns_id = H.ns_id.view
|
|
889
|
+
local all_styles = { 'above', 'below', 'inline' }
|
|
890
|
+
|
|
891
|
+
return function(state)
|
|
892
|
+
-- Cleanup
|
|
893
|
+
local buf_id, extmark_id = vim.api.nvim_get_current_buf(), state.data.extmark_id
|
|
894
|
+
if H.state_is_end(state) then
|
|
895
|
+
pcall(vim.api.nvim_buf_del_extmark, state.data.buf_id, ns_id, extmark_id)
|
|
896
|
+
return
|
|
897
|
+
end
|
|
898
|
+
local ok_extmark = pcall(vim.api.nvim_get_extmark_by_id, state.data.buf_id, ns_id, extmark_id, {})
|
|
899
|
+
if not (buf_id == state.data.buf_id and ok_extmark) then
|
|
900
|
+
pcall(vim.api.nvim_buf_del_extmark, state.data.buf_id, ns_id, extmark_id)
|
|
901
|
+
end
|
|
902
|
+
state.data.buf_id = buf_id
|
|
903
|
+
|
|
904
|
+
local style, style_is_new = H.handle_view_style(state, opts.style, all_styles)
|
|
905
|
+
local is_virtline = style == 'above' or style == 'below'
|
|
906
|
+
|
|
907
|
+
-- Get chunks
|
|
908
|
+
local win_info = H.get_curwin_info()
|
|
909
|
+
local max_width = win_info.width - win_info.textoff
|
|
910
|
+
local chunks = H.get_chunks(opts, state, max_width)
|
|
911
|
+
local chunks_width = H.get_chunks_displaywidth(chunks)
|
|
912
|
+
if is_virtline and chunks_width < max_width then
|
|
913
|
+
chunks = vim.deepcopy(chunks)
|
|
914
|
+
table.insert(chunks, { string.rep(' ', max_width - chunks_width), 'MiniInputNormal' })
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
-- Set
|
|
918
|
+
local extmark_opts = { id = state.data.extmark_id, priority = 4096 }
|
|
919
|
+
if is_virtline then
|
|
920
|
+
extmark_opts.virt_lines = { chunks }
|
|
921
|
+
extmark_opts.virt_lines_above = style == 'above'
|
|
922
|
+
else
|
|
923
|
+
extmark_opts.virt_text = chunks
|
|
924
|
+
extmark_opts.virt_text_pos = 'inline'
|
|
925
|
+
end
|
|
926
|
+
local cur_pos = vim.api.nvim_win_get_cursor(0)
|
|
927
|
+
state.data.extmark_id = vim.api.nvim_buf_set_extmark(0, ns_id, cur_pos[1] - 1, cur_pos[2], extmark_opts)
|
|
928
|
+
|
|
929
|
+
-- Ensure that both current and virtual lines are visible
|
|
930
|
+
if is_virtline and (state.status == 'start' or style_is_new) then
|
|
931
|
+
local win_line, win_height = vim.fn.winline(), vim.fn.winheight(0)
|
|
932
|
+
if win_line == 1 and style == 'above' then vim.cmd('normal! \25') end
|
|
933
|
+
-- With 'above' the cursor line is moved down, with 'below' - no move
|
|
934
|
+
-- local is_below_screen = style == 'above' and win_line > win_height or win_line >= win_height
|
|
935
|
+
local is_below_screen = (win_line - (style == 'above' and 1 or 0)) >= win_height
|
|
936
|
+
if is_below_screen then vim.cmd('normal! \5') end
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
|
|
941
|
+
--- Default key handler
|
|
942
|
+
---
|
|
943
|
+
--- Emulates most of |Command-line-mode| editing (|cmdline-editing|):
|
|
944
|
+
---
|
|
945
|
+
--- - Accept: <CR>. To insert literal newline, type `<C-j>`.
|
|
946
|
+
---
|
|
947
|
+
--- - Cancel: <Esc> or <C-c>.
|
|
948
|
+
---
|
|
949
|
+
--- - Move caret:
|
|
950
|
+
--- - <Left>, <Right> - one character to left / right.
|
|
951
|
+
--- - <M-h>, <M-l> - one character to left / right.
|
|
952
|
+
--- - <S-Left>, <S-Right> - one word to left / right.
|
|
953
|
+
--- - <C-b>, <C-e> (if no completion) - to start / end of input.
|
|
954
|
+
--- - <Home>, <End> - to start / end of input.
|
|
955
|
+
---
|
|
956
|
+
--- - Delete:
|
|
957
|
+
--- - <BS> / <C-h> - to caret's left. If `opts.autopair` is enabled, also delete
|
|
958
|
+
--- a character to caret's right if it formed a respected character pair.
|
|
959
|
+
--- See "Autopair".
|
|
960
|
+
--- - <Del> - at caret.
|
|
961
|
+
--- - <C-u> - from start to caret. As |c_CTRL-U|.
|
|
962
|
+
--- - <C-w> - contiguous keyword or non-keyword to caret's left. As |c_CTRL-W|.
|
|
963
|
+
---
|
|
964
|
+
--- - Insert at caret:
|
|
965
|
+
--- - <C-k> - digraph based on the next two pressed keys. As |c_CTRL-K|.
|
|
966
|
+
--- - <C-r> - content of a register. As |c_CTRL-R| including support for
|
|
967
|
+
--- special <C-a>, <C-f>, <C-l>, <C-w> keys for a register.
|
|
968
|
+
--- - <C-v>, <C-q> - next key literally. As |c_CTRL-V| and |i_CTRL-V_digit|
|
|
969
|
+
--- (all digits must be typed in full or stopped with <C-c>).
|
|
970
|
+
--- - Pasting from system |clipboard| is supported for "non-streaming" paste (as
|
|
971
|
+
--- described in |vim.paste()|).
|
|
972
|
+
---
|
|
973
|
+
--- - Autopair (if `opts.autopair` is set) is similar to |mini.pairs|:
|
|
974
|
+
--- - Opening characters `(`, `[`, `{` always insert a `()`, `[]`, `{}` pair and places
|
|
975
|
+
--- caret inside of it.
|
|
976
|
+
--- - Closing characters `)`, `]`, `}` move caret to the right if there is the same
|
|
977
|
+
--- character to the right.
|
|
978
|
+
--- - Closeopen characters single+double quotes and backtick perform "close"
|
|
979
|
+
--- action if possible and "open" action if not.
|
|
980
|
+
--- - In all cases press <C-v> before special character to insert it verbatim.
|
|
981
|
+
---
|
|
982
|
+
--- - Completion:
|
|
983
|
+
--- - <Tab>, <S-Tab> - start with `state.opts.completion` method if not active
|
|
984
|
+
--- and advance through active completion (i.e. replace currently displayed
|
|
985
|
+
--- at caret item with the next one).
|
|
986
|
+
--- Note: type `<C-v><Tab>` to insert literal `\t`.
|
|
987
|
+
--- - <C-n>, <C-p>, <Up>, <Down> - start with `"history"` method if not active
|
|
988
|
+
--- and advance through active completion.
|
|
989
|
+
--- - <C-e> - cancel and return to initial input and caret.
|
|
990
|
+
--- - <C-y> - accept current candidate. Note: it will also be accepted after
|
|
991
|
+
--- any key that doesn't advance completion.
|
|
992
|
+
---
|
|
993
|
+
--- - Miscellaneous:
|
|
994
|
+
--- - <C-o> - change scope of the input. Cycles through all available ones.
|
|
995
|
+
--- - <C-s> - change view style. Works only with |MiniInput.gen_view| view
|
|
996
|
+
--- handlers. Cycles through all available ones.
|
|
997
|
+
--- - <C-x> - toggle hide/unhide of the input.
|
|
998
|
+
---
|
|
999
|
+
--- - Special keys (combo, mouse, but not whitespace) not listed above - ignored.
|
|
1000
|
+
--- Anything else (even more than a single character) - inserted at caret.
|
|
1001
|
+
---
|
|
1002
|
+
---@param state table Current |MiniInput-state|.
|
|
1003
|
+
---@param key string|nil A key to process. Should be escaped (`"\r"`, not `"<CR>"`).
|
|
1004
|
+
--- See |vim.keycode()|.
|
|
1005
|
+
---@param opts table|nil Options. Possible fields:
|
|
1006
|
+
--- - <autopair> `(boolean)` - whether perform add autopair. Default: `false`.
|
|
1007
|
+
MiniInput.default_key = function(state, key, opts)
|
|
1008
|
+
opts = opts or {}
|
|
1009
|
+
if key == nil or H.state_is_end(state) then return end
|
|
1010
|
+
if opts.autopair and H.key_methods_autopair[key] ~= nil then return H.key_methods_autopair[key](state) end
|
|
1011
|
+
if H.key_methods[key] ~= nil then return H.key_methods[key](state, opts) end
|
|
1012
|
+
if H.is_special_char(key) then return end
|
|
1013
|
+
H.insert_at_caret(state, key)
|
|
1014
|
+
end
|
|
1015
|
+
|
|
1016
|
+
--- Default highlight handler
|
|
1017
|
+
---
|
|
1018
|
+
--- During active completion highlights added (computed via |matchfuzzy()|)
|
|
1019
|
+
--- characters with `MiniInputAdded` group.
|
|
1020
|
+
---
|
|
1021
|
+
---@param state table Current |MiniInput-state|.
|
|
1022
|
+
MiniInput.default_highlight = function(state)
|
|
1023
|
+
if H.state_is_end(state) then return end
|
|
1024
|
+
|
|
1025
|
+
-- Only show characters added during completion navigation
|
|
1026
|
+
if state.complete == nil or state.complete.id == 0 then return end
|
|
1027
|
+
|
|
1028
|
+
local cur_item = state.complete.items[state.complete.id]
|
|
1029
|
+
local base = state.complete.base
|
|
1030
|
+
local pos = base == '' and {} or vim.fn.matchfuzzypos({ cur_item }, base, { matchseq = 1 })[2][1]
|
|
1031
|
+
if pos == nil then return end
|
|
1032
|
+
|
|
1033
|
+
-- Matches are increasing character zero-based indexes. Highlight ranges
|
|
1034
|
+
-- between them, including start and end edges.
|
|
1035
|
+
local cur_item_width = vim.fn.strchars(cur_item)
|
|
1036
|
+
table.insert(pos, 1, -1)
|
|
1037
|
+
table.insert(pos, cur_item_width)
|
|
1038
|
+
local offset, ranges = state.caret - cur_item_width - 1, {}
|
|
1039
|
+
for i = 2, #pos do
|
|
1040
|
+
if pos[i] - pos[i - 1] > 1 then
|
|
1041
|
+
local from = offset + pos[i - 1] + 1
|
|
1042
|
+
local to = offset + pos[i] - 1
|
|
1043
|
+
table.insert(ranges, { from = from + 1, to = to + 1, hl = 'MiniInputAdded' })
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
-- Respect maybe already present <highlight>
|
|
1048
|
+
state.highlight = vim.list_extend(state.highlight or {}, ranges)
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
--- Default view handler
|
|
1052
|
+
---
|
|
1053
|
+
--- Same as |MiniInput.gen_view.floatwin()| with default options.
|
|
1054
|
+
---
|
|
1055
|
+
---@param state table Current |MiniInput-state|.
|
|
1056
|
+
MiniInput.default_view = function(state) end -- Generated later
|
|
1057
|
+
|
|
1058
|
+
--- Default complete handler
|
|
1059
|
+
---
|
|
1060
|
+
--- Supported methods:
|
|
1061
|
+
--- - `""` (default) - complete with buffer keywords (|'iskeyword'|) that match
|
|
1062
|
+
--- a keyword at caret's left.
|
|
1063
|
+
--- - `"history"` - complete with |MiniInput.get_history()| inputs that have current
|
|
1064
|
+
--- input to caret's left as a prefix. If `opts.precise_history`, only use history
|
|
1065
|
+
--- entries that have all info fields same as the current state. Orders from
|
|
1066
|
+
--- earliest to latest. To initiate with the latest history match, press
|
|
1067
|
+
--- <Up> or <C-p> with default key handler.
|
|
1068
|
+
--- - `"cmdline"` - treat input as |Command-line| text and show completions at caret.
|
|
1069
|
+
--- Note: as there is no |getcmdcomplpat()| variant to use outside of Command-line
|
|
1070
|
+
--- mode, the base is inferred from |getcompletion()| output as the widest text to
|
|
1071
|
+
--- caret's left that matches all candidates: fuzzily if |'wildoptions'| contains
|
|
1072
|
+
--- `fuzzy`, as a prefix otherwise. If none - empty string is used. This approach
|
|
1073
|
+
--- has limitations, but it is good enough.
|
|
1074
|
+
--- - Every method supported by |getcompletion()|. Base is computed as the keyword
|
|
1075
|
+
--- at caret's left.
|
|
1076
|
+
---
|
|
1077
|
+
---@param state table Current |MiniInput-state|.
|
|
1078
|
+
---@param method string Completion method.
|
|
1079
|
+
---@param opts table|nil Options. Possible fields:
|
|
1080
|
+
--- - <precise_history> `(boolean)` - whether for `method='history'` try to match
|
|
1081
|
+
--- only entries that have <cwd> as |current-directory|, same <scope> and
|
|
1082
|
+
--- <prompt> as in current state. Default: `true`.
|
|
1083
|
+
---
|
|
1084
|
+
---@usage >lua
|
|
1085
|
+
--- -- Do not match history precisely
|
|
1086
|
+
--- local complete_opts = { precise_history = false }
|
|
1087
|
+
--- local complete_handler = function(state, method)
|
|
1088
|
+
--- return MiniInput.default_complete(state, method, complete_opts)
|
|
1089
|
+
--- end
|
|
1090
|
+
--- require('mini.input').setup({ handlers = { complete = complete_handler } })
|
|
1091
|
+
--- <
|
|
1092
|
+
MiniInput.default_complete = function(state, method, opts)
|
|
1093
|
+
opts = vim.tbl_extend('force', { precise_history = true }, opts or {})
|
|
1094
|
+
|
|
1095
|
+
if H.state_is_end(state) then return end
|
|
1096
|
+
if method == 'history' then return H.complete_history(state, opts.precise_history) end
|
|
1097
|
+
method = method or state.opts.completion or ''
|
|
1098
|
+
|
|
1099
|
+
local caret, input = state.caret, state.input
|
|
1100
|
+
local text = vim.fn.strcharpart(input, 0, caret - 1)
|
|
1101
|
+
local is_fs_method = method == 'file' or method == 'file_in_path' or method == 'dir' or method == 'dir_in_path'
|
|
1102
|
+
local base_pattern = is_fs_method and '[[:fname:]]*$' or '[[:keyword:]]*$'
|
|
1103
|
+
local base = vim.fn.strcharpart(text, vim.fn.match(text, base_pattern))
|
|
1104
|
+
|
|
1105
|
+
if method == '' then
|
|
1106
|
+
state.complete = { items = H.complete_buf_words(base), base = base }
|
|
1107
|
+
return
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
local pat = method == 'cmdline' and vim.fn.strcharpart(input, 0, caret - 1) or base
|
|
1111
|
+
local ok, items = pcall(vim.fn.getcompletion, pat, method)
|
|
1112
|
+
if not ok then return end
|
|
1113
|
+
base = method == 'cmdline' and H.get_base_from_items(text, items) or base
|
|
1114
|
+
state.complete = { items = items, base = base }
|
|
1115
|
+
end
|
|
1116
|
+
|
|
1117
|
+
--- Convert state into text-hl chunks
|
|
1118
|
+
---
|
|
1119
|
+
--- - Treat `state.highlight` elements in increasing priority, i.e. later ones
|
|
1120
|
+
--- are placed "on top" of the previous ones if they overlap.
|
|
1121
|
+
--- - Uses |MiniInput-hl-groups|.
|
|
1122
|
+
--- - Multiline input (i.e. with newline characters `\n`) is shown as a single line.
|
|
1123
|
+
--- - Treats `max_width` as display width (|strdisplaywidth()|).
|
|
1124
|
+
--- - Truncates output chunks trying to center the caret.
|
|
1125
|
+
---
|
|
1126
|
+
---@param state table A |MiniInput-state|.
|
|
1127
|
+
---@param max_width number|nil Maximum allowed display width. Can be |math.huge|.
|
|
1128
|
+
---@param opts table|nil Options. Possible fields:
|
|
1129
|
+
--- - <keytrans> `(boolean)` - whether to translate control characters (escaped
|
|
1130
|
+
--- version of `<C-...>`). Default: `true`.
|
|
1131
|
+
--- - <include_prompt> `(boolean)` - whether to start chunks with state prompt.
|
|
1132
|
+
--- Default: `true`.
|
|
1133
|
+
--- - <include_hint> `(boolean)` - whether to show complete hint to caret's right.
|
|
1134
|
+
--- Default: `true`.
|
|
1135
|
+
--- - <symbol_caret> `(string)` - string to use for caret. Default: `"▏"`.
|
|
1136
|
+
--- - <symbol_hide> `(string)` - string to use for each input character when
|
|
1137
|
+
--- input is hidden. Default: `"•"`.
|
|
1138
|
+
---
|
|
1139
|
+
---@return table An array of text-hl chunks that fit `max_width` display width.
|
|
1140
|
+
MiniInput.state_to_chunks = function(state, max_width, opts)
|
|
1141
|
+
H.check_type('max_width', max_width, 'number', true)
|
|
1142
|
+
local default_opts = { keytrans = true, include_prompt = true, include_hint = true }
|
|
1143
|
+
default_opts.symbol_caret = '▏'
|
|
1144
|
+
default_opts.symbol_hide = '•'
|
|
1145
|
+
opts = vim.tbl_extend('force', default_opts, opts or {})
|
|
1146
|
+
H.check_type('opts.keytrans', opts.keytrans, 'boolean')
|
|
1147
|
+
H.check_type('opts.include_prompt', opts.include_prompt, 'boolean')
|
|
1148
|
+
H.check_type('opts.include_hint', opts.include_hint, 'boolean')
|
|
1149
|
+
H.check_type('opts.symbol_caret', opts.symbol_caret, 'string')
|
|
1150
|
+
H.check_type('opts.symbol_hide', opts.symbol_hide, 'string')
|
|
1151
|
+
|
|
1152
|
+
-- Precompute state data
|
|
1153
|
+
local hide, symbol_hide = state.opts.hide, opts.symbol_hide
|
|
1154
|
+
local input = hide and string.rep(symbol_hide, vim.fn.strchars(state.input)) or state.input
|
|
1155
|
+
local caret = (hide and vim.fn.strchars(symbol_hide) or 1) * (state.caret - 1) + 1
|
|
1156
|
+
|
|
1157
|
+
-- Compute input chunks to left and right of caret
|
|
1158
|
+
local max_to = vim.fn.strchars(input)
|
|
1159
|
+
local hl_ranges = { { from = 1, to = max_to, hl = 'MiniInputNormal' } }
|
|
1160
|
+
for _, range in ipairs(state.highlight or {}) do
|
|
1161
|
+
hl_ranges = H.insert_hl_range(hl_ranges, max_to, range.from, range.to, range.hl)
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
local input_chunks = {}
|
|
1165
|
+
for i, r in ipairs(hl_ranges) do
|
|
1166
|
+
input_chunks[i] = { vim.fn.strcharpart(input, r.from - 1, r.to - r.from + 1), r.hl }
|
|
1167
|
+
end
|
|
1168
|
+
local input_chunks_left, input_chunks_right = H.split_chunks_at(input_chunks, caret - 1)
|
|
1169
|
+
|
|
1170
|
+
-- Compute chunks in specific order:
|
|
1171
|
+
-- prompt - input_left - caret - complete_hint - input_right
|
|
1172
|
+
local chunks, keytrans = {}, opts.keytrans
|
|
1173
|
+
if opts.include_prompt and state.opts.prompt ~= '' then
|
|
1174
|
+
local prompt = vim.trim(state.opts.prompt)
|
|
1175
|
+
local prompt_hl = hide and 'MiniInputHide' or 'MiniInputPrompt'
|
|
1176
|
+
local new = { { prompt, prompt_hl }, { ' ', 'MiniInputNormal' } }
|
|
1177
|
+
H.append_chunks(chunks, new, keytrans)
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
H.append_chunks(chunks, input_chunks_left, keytrans)
|
|
1181
|
+
|
|
1182
|
+
if opts.symbol_caret ~= '' then H.append_chunks(chunks, { { opts.symbol_caret, 'MiniInputCaret' } }, keytrans) end
|
|
1183
|
+
local caret_offset = H.get_chunks_displaywidth(chunks)
|
|
1184
|
+
|
|
1185
|
+
if opts.include_hint and state.complete ~= nil then
|
|
1186
|
+
local hint = string.format('(%d/%d)', state.complete.id, #state.complete.items)
|
|
1187
|
+
H.append_chunks(chunks, { { hint, 'MiniInputHint' } }, keytrans)
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
H.append_chunks(chunks, input_chunks_right, keytrans)
|
|
1191
|
+
|
|
1192
|
+
return H.fit_chunks_to_displaywidth(chunks, max_width, caret_offset)
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
--- Apply state's handler
|
|
1196
|
+
---
|
|
1197
|
+
--- Given |MiniInput-state|, apply its handler (from `state.opts.handlers`) and
|
|
1198
|
+
--- return the modified state.
|
|
1199
|
+
---
|
|
1200
|
+
--- Notes:
|
|
1201
|
+
--- - Nothing is done for highlight and complete handlers when input is hidden.
|
|
1202
|
+
--- - Applying complete handler ensures that <complete> state field has <id> set
|
|
1203
|
+
--- to 0 (as it assumes new completion) and <method> to the input argument
|
|
1204
|
+
--- (empty string if there was no argument).
|
|
1205
|
+
--- - Error during handler application is stored as <errmsg> field of output state
|
|
1206
|
+
--- if there is an active input. It is propagated via |error()| otherwise.
|
|
1207
|
+
--- - Handler output is validated to be a valid |MiniInput-state|. If not - return
|
|
1208
|
+
--- input `state`. Note: it still could be changed if handler modified in place.
|
|
1209
|
+
---
|
|
1210
|
+
---@param state table A |MiniInput-state|.
|
|
1211
|
+
---@param name string Valid handler name.
|
|
1212
|
+
---@param arg any Extra handler argument: like `key` for key handler and `method`
|
|
1213
|
+
--- for complete handler.
|
|
1214
|
+
---
|
|
1215
|
+
---@return table A |MiniInput-state| as a result of applied handler. If handler returned
|
|
1216
|
+
--- nothing, the input `state` is returned (assumes it was modified in place).
|
|
1217
|
+
MiniInput.apply_handler = function(state, name, arg)
|
|
1218
|
+
H.check_one_of('name', name, { 'complete', 'highlight', 'key', 'view' })
|
|
1219
|
+
H.check_type('arg', arg, 'string', true)
|
|
1220
|
+
if arg == nil and name == 'complete' then arg = '' end
|
|
1221
|
+
|
|
1222
|
+
if state.opts.hide and (name == 'highlight' or name == 'complete') then return state end
|
|
1223
|
+
local ok_handler, res = pcall(state.opts.handlers[name], state, arg)
|
|
1224
|
+
if not ok_handler then H.cache_error(state, 'Error applying `' .. name .. '` handler: ' .. res) end
|
|
1225
|
+
|
|
1226
|
+
local new_state = (ok_handler and res or nil) or state
|
|
1227
|
+
if name == 'complete' and type(new_state.complete) == 'table' then
|
|
1228
|
+
new_state.complete.id = 0
|
|
1229
|
+
new_state.complete.method = arg
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
local ok_state, msg = pcall(H.state_validate, new_state)
|
|
1233
|
+
if not ok_state then H.cache_error(state, msg) end
|
|
1234
|
+
|
|
1235
|
+
return ok_state and new_state or state
|
|
1236
|
+
end
|
|
1237
|
+
|
|
1238
|
+
-- Helper data ================================================================
|
|
1239
|
+
-- Module default config
|
|
1240
|
+
H.default_config = vim.deepcopy(MiniInput.config)
|
|
1241
|
+
|
|
1242
|
+
-- Current state of user input
|
|
1243
|
+
H.state = nil
|
|
1244
|
+
|
|
1245
|
+
-- History of inputs
|
|
1246
|
+
H.history = {}
|
|
1247
|
+
|
|
1248
|
+
-- Supported scopes
|
|
1249
|
+
H.allowed_scopes = { 'cursor', 'line', 'buffer', 'window', 'tabpage', 'editor', 'project' }
|
|
1250
|
+
|
|
1251
|
+
-- Various cache
|
|
1252
|
+
H.cache = {}
|
|
1253
|
+
|
|
1254
|
+
-- Namespaces
|
|
1255
|
+
H.ns_id = {
|
|
1256
|
+
view = vim.api.nvim_create_namespace('MiniInputView'),
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
-- Helper functionality =======================================================
|
|
1260
|
+
-- Settings -------------------------------------------------------------------
|
|
1261
|
+
H.setup_config = function(config)
|
|
1262
|
+
H.check_type('config', config, 'table', true)
|
|
1263
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
1264
|
+
|
|
1265
|
+
H.check_type('handlers', config.handlers, 'table')
|
|
1266
|
+
H.check_type('handlers.complete', config.handlers.complete, 'function', true)
|
|
1267
|
+
H.check_type('handlers.highlight', config.handlers.highlight, 'function', true)
|
|
1268
|
+
H.check_type('handlers.key', config.handlers.key, 'function', true)
|
|
1269
|
+
H.check_type('handlers.view', config.handlers.view, 'function', true)
|
|
1270
|
+
|
|
1271
|
+
H.check_type('scope', config.scope, 'string')
|
|
1272
|
+
|
|
1273
|
+
return config
|
|
1274
|
+
end
|
|
1275
|
+
|
|
1276
|
+
H.apply_config = function(config)
|
|
1277
|
+
MiniInput.config = config
|
|
1278
|
+
|
|
1279
|
+
-- Ensure clear history
|
|
1280
|
+
H.history = {}
|
|
1281
|
+
|
|
1282
|
+
-- Ensure default scope for some core functions
|
|
1283
|
+
if vim.fn.has('nvim-0.12.3') == 0 then
|
|
1284
|
+
-- NOTE: Use `vim.schedule` to not load `vim.lsp` during startup
|
|
1285
|
+
vim.schedule(function() vim.lsp.buf.rename = H.mock_lsp_buf_rename(vim.lsp.buf.rename) end)
|
|
1286
|
+
end
|
|
1287
|
+
end
|
|
1288
|
+
|
|
1289
|
+
H.create_autocommands = function()
|
|
1290
|
+
local gr = vim.api.nvim_create_augroup('MiniInput', {})
|
|
1291
|
+
|
|
1292
|
+
local au = function(event, pattern, callback, desc)
|
|
1293
|
+
vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
|
|
1294
|
+
end
|
|
1295
|
+
|
|
1296
|
+
au('VimResized', '*', function() MiniInput.refresh() end, 'Refresh on resize')
|
|
1297
|
+
au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
|
|
1298
|
+
end
|
|
1299
|
+
|
|
1300
|
+
H.create_default_hl = function()
|
|
1301
|
+
local hi = function(name, opts)
|
|
1302
|
+
opts.default = true
|
|
1303
|
+
vim.api.nvim_set_hl(0, name, opts)
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
hi('MiniInputAdded', { link = 'DiagnosticFloatingOk' })
|
|
1307
|
+
hi('MiniInputBorder', { link = 'FloatBorder' })
|
|
1308
|
+
hi('MiniInputCaret', { link = 'MiniInputPrompt' })
|
|
1309
|
+
hi('MiniInputHide', { link = 'DiagnosticFloatingWarn' })
|
|
1310
|
+
hi('MiniInputHint', { link = 'DiagnosticFloatingHint' })
|
|
1311
|
+
hi('MiniInputNormal', { link = 'NormalFloat' })
|
|
1312
|
+
hi('MiniInputPrompt', { link = 'DiagnosticFloatingInfo' })
|
|
1313
|
+
hi('MiniInputSpecial', { link = 'DiagnosticFloatingWarn' })
|
|
1314
|
+
end
|
|
1315
|
+
|
|
1316
|
+
H.adjust_vim_paste = function()
|
|
1317
|
+
local paste_orig = vim.paste
|
|
1318
|
+
vim.paste = function(lines, phase)
|
|
1319
|
+
if MiniInput.get_state() == nil then return paste_orig(lines, phase) end
|
|
1320
|
+
if phase ~= -1 then return H.notify('There is no streaming paste support. Use `<C-r>+` or `<C-r>*`.', 'WARN') end
|
|
1321
|
+
H.insert_at_caret(H.state, table.concat(lines, '\n'))
|
|
1322
|
+
H.handle_step(nil)
|
|
1323
|
+
end
|
|
1324
|
+
end
|
|
1325
|
+
|
|
1326
|
+
H.get_config = function(config)
|
|
1327
|
+
return vim.tbl_deep_extend('force', MiniInput.config, vim.b.miniinput_config or {}, config or {})
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
-- State ----------------------------------------------------------------------
|
|
1331
|
+
H.state_new = function(opts)
|
|
1332
|
+
opts.completion = opts.completion or ''
|
|
1333
|
+
H.check_type('state.opts.handlers', opts.handlers, 'table')
|
|
1334
|
+
opts.handlers.complete = opts.handlers.complete or MiniInput.default_complete
|
|
1335
|
+
opts.handlers.highlight = opts.handlers.highlight or MiniInput.default_highlight
|
|
1336
|
+
opts.handlers.key = opts.handlers.key or MiniInput.default_key
|
|
1337
|
+
opts.handlers.view = opts.handlers.view or MiniInput.default_view
|
|
1338
|
+
if opts.hide == nil then opts.hide = false end
|
|
1339
|
+
opts.init_keys = opts.init_keys or {}
|
|
1340
|
+
opts.prompt = opts.prompt or 'Input'
|
|
1341
|
+
-- opts.scope should already be precomputed
|
|
1342
|
+
|
|
1343
|
+
return { caret = 1, data = {}, input = '', opts = opts, status = 'start' }
|
|
1344
|
+
end
|
|
1345
|
+
|
|
1346
|
+
H.state_validate = function(x)
|
|
1347
|
+
H.check_type('state', x, 'table')
|
|
1348
|
+
|
|
1349
|
+
H.check_type('state.caret', x.caret, 'number')
|
|
1350
|
+
H.check_type('state.data', x.data, 'table')
|
|
1351
|
+
H.check_type('state.input', x.input, 'string')
|
|
1352
|
+
H.check_one_of('state.status', x.status, { 'start', 'progress', 'accept', 'cancel' })
|
|
1353
|
+
|
|
1354
|
+
if not (1 <= x.caret and x.caret <= (vim.fn.strchars(x.input) + 1)) then
|
|
1355
|
+
H.error('`state.caret` should be between 1 and input width plus 1')
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
if x.complete ~= nil then
|
|
1359
|
+
H.check_type('state.complete', x.complete, 'table')
|
|
1360
|
+
H.check_type('state.complete.base', x.complete.base, 'string')
|
|
1361
|
+
H.check_array_of('state.complete.items', x.complete.items, 'string')
|
|
1362
|
+
H.check_type('state.complete.id', x.complete.id, 'number')
|
|
1363
|
+
H.check_type('state.complete.method', x.complete.method, 'string')
|
|
1364
|
+
end
|
|
1365
|
+
|
|
1366
|
+
if x.highlight ~= nil then
|
|
1367
|
+
H.check_array_of('state.highlight', x.highlight, 'table')
|
|
1368
|
+
for i, h in ipairs(x.highlight) do
|
|
1369
|
+
local item = string.format('state.highlight[%d]', i)
|
|
1370
|
+
H.check_type(item .. '.from', h.from, 'number')
|
|
1371
|
+
H.check_type(item .. '.to', h.to, 'number')
|
|
1372
|
+
H.check_type(item .. '.hl', h.hl, 'string')
|
|
1373
|
+
if h.from > h.to then H.error(string.format('%s.from is bigger than %s.to', item, item)) end
|
|
1374
|
+
end
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
H.check_type('state.opts.completion', x.opts.completion, 'string')
|
|
1378
|
+
H.check_type('state.opts.handlers.complete', x.opts.handlers.complete, 'function')
|
|
1379
|
+
H.check_type('state.opts.handlers.highlight', x.opts.handlers.highlight, 'function')
|
|
1380
|
+
H.check_type('state.opts.handlers.key', x.opts.handlers.key, 'function')
|
|
1381
|
+
H.check_type('state.opts.handlers.view', x.opts.handlers.view, 'function')
|
|
1382
|
+
H.check_type('state.opts.hide', x.opts.hide, 'boolean')
|
|
1383
|
+
H.check_array_of('state.opts.init_keys', x.opts.init_keys, 'string')
|
|
1384
|
+
H.check_type('state.opts.prompt', x.opts.prompt, 'string')
|
|
1385
|
+
H.check_one_of('state.opts.scope', x.opts.scope, H.allowed_scopes)
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
H.state_finish = function()
|
|
1389
|
+
if H.cache.is_in_getcharstr then return vim.api.nvim_feedkeys('\3', 't', true) end
|
|
1390
|
+
|
|
1391
|
+
H.handle_step(nil)
|
|
1392
|
+
|
|
1393
|
+
local res = H.state.status == 'accept' and H.state.input or nil
|
|
1394
|
+
local errmsg, opts = H.state.errmsg, vim.deepcopy(H.state.opts)
|
|
1395
|
+
H.state, H.cache = nil, {}
|
|
1396
|
+
(_G.MiniInput or {})._temp_default_scope = nil
|
|
1397
|
+
|
|
1398
|
+
if errmsg ~= nil then error(errmsg) end
|
|
1399
|
+
|
|
1400
|
+
if res ~= nil and not opts.hide then
|
|
1401
|
+
local hist = { input = res, prompt = opts.prompt, scope = opts.scope, cwd = vim.fn.getcwd() }
|
|
1402
|
+
table.insert(H.history, hist)
|
|
1403
|
+
end
|
|
1404
|
+
return res
|
|
1405
|
+
end
|
|
1406
|
+
|
|
1407
|
+
H.state_is_end = function(state)
|
|
1408
|
+
state = state or H.state
|
|
1409
|
+
return state.status == 'accept' or state.status == 'cancel'
|
|
1410
|
+
end
|
|
1411
|
+
|
|
1412
|
+
H.mock_lsp_buf_rename = function(f)
|
|
1413
|
+
return function(new_name, opts)
|
|
1414
|
+
if new_name == nil then (_G.MiniInput or {})._temp_default_scope = 'cursor' end
|
|
1415
|
+
return f(new_name, opts)
|
|
1416
|
+
end
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
-- Handlers -------------------------------------------------------------------
|
|
1420
|
+
H.handle_step = function(key, skip_redraw)
|
|
1421
|
+
local state, ref_state = H.copy_tables(H.state), H.copy_tables(H.state)
|
|
1422
|
+
|
|
1423
|
+
-- Stop previous highlighting, as it is not up to date
|
|
1424
|
+
state.highlight, ref_state.highlight = nil, nil
|
|
1425
|
+
|
|
1426
|
+
state = MiniInput.apply_handler(state, 'key', key)
|
|
1427
|
+
|
|
1428
|
+
-- Stop completion if something has changed outside of completion
|
|
1429
|
+
local is_complete_stop = vim.deep_equal(state.complete, ref_state.complete) and not vim.deep_equal(state, ref_state)
|
|
1430
|
+
if is_complete_stop then state.complete = nil end
|
|
1431
|
+
|
|
1432
|
+
state = MiniInput.apply_handler(state, 'highlight')
|
|
1433
|
+
state = MiniInput.apply_handler(state, 'view')
|
|
1434
|
+
|
|
1435
|
+
H.state = state
|
|
1436
|
+
if not skip_redraw then H.redraw() end
|
|
1437
|
+
end
|
|
1438
|
+
|
|
1439
|
+
-- Default key handler --------------------------------------------------------
|
|
1440
|
+
H.key_methods = {}
|
|
1441
|
+
|
|
1442
|
+
H.keycode = vim.fn.has('nvim-0.10') == 1 and vim.keycode
|
|
1443
|
+
or function(s) return vim.api.nvim_replace_termcodes(s, true, true, true) end
|
|
1444
|
+
local kc = H.keycode
|
|
1445
|
+
|
|
1446
|
+
-- General
|
|
1447
|
+
H.key_methods[kc('<CR>')] = function(state, _) state.status = 'accept' end
|
|
1448
|
+
H.key_methods[kc('<Esc>')] = function(state, _) state.status = 'cancel' end
|
|
1449
|
+
H.key_methods[kc('<C-c>')] = H.key_methods[kc('<Esc>')]
|
|
1450
|
+
|
|
1451
|
+
-- Caret movement
|
|
1452
|
+
H.key_methods[kc('<Left>')] = function(state, _)
|
|
1453
|
+
state.caret = H.clamp(state.caret - 1, 1, vim.fn.strchars(state.input) + 1)
|
|
1454
|
+
end
|
|
1455
|
+
H.key_methods[kc('<Right>')] = function(state, _)
|
|
1456
|
+
state.caret = H.clamp(state.caret + 1, 1, vim.fn.strchars(state.input) + 1)
|
|
1457
|
+
end
|
|
1458
|
+
H.key_methods[kc('<M-h>')] = H.key_methods[kc('<Left>')]
|
|
1459
|
+
H.key_methods[kc('<M-l>')] = H.key_methods[kc('<Right>')]
|
|
1460
|
+
H.key_methods[kc('<S-Left>')] = function(state, _)
|
|
1461
|
+
local caret, input = state.caret, state.input
|
|
1462
|
+
local to = H.match_keyword_chars(input, caret, 'left')
|
|
1463
|
+
state.caret = H.clamp(to + 1, 1, vim.fn.strchars(state.input) + 1)
|
|
1464
|
+
end
|
|
1465
|
+
H.key_methods[kc('<S-Right>')] = function(state, _)
|
|
1466
|
+
local caret, input = state.caret, state.input
|
|
1467
|
+
local to = H.match_keyword_chars(input, caret, 'right')
|
|
1468
|
+
state.caret = H.clamp(to + 1, 1, vim.fn.strchars(state.input) + 1)
|
|
1469
|
+
end
|
|
1470
|
+
H.key_methods[kc('<C-b>')] = function(state, _) state.caret = 1 end
|
|
1471
|
+
H.key_methods[kc('<C-e>')] = function(state, _)
|
|
1472
|
+
if state.complete == nil then
|
|
1473
|
+
state.caret = vim.fn.strchars(state.input) + 1
|
|
1474
|
+
return
|
|
1475
|
+
end
|
|
1476
|
+
H.advance_state_complete(state, -state.complete.id)
|
|
1477
|
+
state.complete = nil
|
|
1478
|
+
end
|
|
1479
|
+
H.key_methods[kc('<Home>')] = H.key_methods[kc('<C-b>')]
|
|
1480
|
+
H.key_methods[kc('<End>')] = function(state, _) state.caret = vim.fn.strchars(state.input) + 1 end
|
|
1481
|
+
|
|
1482
|
+
-- Delete
|
|
1483
|
+
H.key_methods[kc('<BS>')] = function(state, opts)
|
|
1484
|
+
local caret, input = state.caret, state.input
|
|
1485
|
+
if caret <= 1 then return end
|
|
1486
|
+
local right_offset = (opts.autopair and H.is_inside_pair(state)) and 1 or 0
|
|
1487
|
+
state.input = vim.fn.strcharpart(input, 0, caret - 2) .. vim.fn.strcharpart(input, caret - 1 + right_offset)
|
|
1488
|
+
state.caret = caret - 1
|
|
1489
|
+
end
|
|
1490
|
+
H.key_methods[kc('<C-h>')] = H.key_methods[kc('<BS>')]
|
|
1491
|
+
H.key_methods[kc('<Del>')] = function(state, _)
|
|
1492
|
+
local caret, input = state.caret, state.input
|
|
1493
|
+
if caret > vim.fn.strchars(input) then return end
|
|
1494
|
+
state.input = vim.fn.strcharpart(input, 0, caret - 1) .. vim.fn.strcharpart(input, caret)
|
|
1495
|
+
end
|
|
1496
|
+
H.key_methods[kc('<C-u>')] = function(state, _)
|
|
1497
|
+
state.input = vim.fn.strcharpart(state.input, state.caret - 1)
|
|
1498
|
+
state.caret = 1
|
|
1499
|
+
end
|
|
1500
|
+
H.key_methods[kc('<C-w>')] = function(state, _)
|
|
1501
|
+
local caret, input = state.caret, state.input
|
|
1502
|
+
local left_to = H.match_keyword_chars(input, caret, 'left')
|
|
1503
|
+
state.input = vim.fn.strcharpart(input, 0, left_to) .. vim.fn.strcharpart(input, caret - 1)
|
|
1504
|
+
state.caret = H.clamp(left_to + 1, 1, vim.fn.strchars(state.input) + 1)
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1507
|
+
-- Special insert
|
|
1508
|
+
H.key_methods[kc('<C-k>')] = function(state, _)
|
|
1509
|
+
local ok, new = pcall(vim.fn.digraph_get, H.getcharstr_many(2, { ['\27'] = true }))
|
|
1510
|
+
if not ok then return end
|
|
1511
|
+
H.insert_at_caret(state, new)
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
H.key_methods[kc('<C-r>')] = function(state, _)
|
|
1515
|
+
-- Get register content
|
|
1516
|
+
local register = H.getcharstr()
|
|
1517
|
+
if register == nil then return end
|
|
1518
|
+
-- - Mimic some "insert object under cursor" behavior of Command-line mode
|
|
1519
|
+
local reg_content
|
|
1520
|
+
local expand_var = ({ ['\1'] = '<cWORD>', ['\6'] = '<cfile>', ['\23'] = '<cword>' })[register]
|
|
1521
|
+
if expand_var then reg_content = vim.fn.expand(expand_var) end
|
|
1522
|
+
if register == '\f' then reg_content = vim.fn.getline('.') end
|
|
1523
|
+
if reg_content == nil then
|
|
1524
|
+
local has_register, r = pcall(vim.fn.getreg, register)
|
|
1525
|
+
reg_content = has_register and r or ''
|
|
1526
|
+
end
|
|
1527
|
+
|
|
1528
|
+
H.insert_at_caret(state, reg_content)
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
H.key_methods[kc('<C-q>')] = function(state, _)
|
|
1532
|
+
local char = H.getcharstr({}, true)
|
|
1533
|
+
if char == nil then return end
|
|
1534
|
+
|
|
1535
|
+
-- See `:h i_CTRL-V_digit`
|
|
1536
|
+
if char:find('^[%doOxXuU]$') ~= nil then
|
|
1537
|
+
local new_text = H.get_ctrl_v_digits(char)
|
|
1538
|
+
if new_text ~= nil then H.insert_at_caret(state, new_text) end
|
|
1539
|
+
return
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
local ok, new_text = pcall(vim.fn.keytrans, char)
|
|
1543
|
+
if not ok or (ok and vim.fn.char2nr(char) <= 31) then new_text = char end
|
|
1544
|
+
H.insert_at_caret(state, new_text)
|
|
1545
|
+
end
|
|
1546
|
+
H.key_methods[kc('<C-v>')] = H.key_methods[kc('<C-q>')]
|
|
1547
|
+
|
|
1548
|
+
-- History navigation
|
|
1549
|
+
H.key_methods[kc('<Up>')] = function(state, _)
|
|
1550
|
+
if state.complete == nil then state = MiniInput.apply_handler(state, 'complete', 'history') end
|
|
1551
|
+
return H.advance_state_complete(state, -1)
|
|
1552
|
+
end
|
|
1553
|
+
H.key_methods[kc('<Down>')] = function(state, _)
|
|
1554
|
+
if state.complete == nil then state = MiniInput.apply_handler(state, 'complete', 'history') end
|
|
1555
|
+
return H.advance_state_complete(state, 1)
|
|
1556
|
+
end
|
|
1557
|
+
H.key_methods[kc('<C-n>')] = H.key_methods[kc('<Down>')]
|
|
1558
|
+
H.key_methods[kc('<C-p>')] = H.key_methods[kc('<Up>')]
|
|
1559
|
+
H.key_methods[kc('<C-y>')] = function(state, _) state.complete = nil end
|
|
1560
|
+
|
|
1561
|
+
-- Completion navigation
|
|
1562
|
+
H.key_methods[kc('<Tab>')] = function(state, _)
|
|
1563
|
+
if state.complete == nil then state = MiniInput.apply_handler(state, 'complete', state.opts.completion) end
|
|
1564
|
+
return H.advance_state_complete(state, 1)
|
|
1565
|
+
end
|
|
1566
|
+
H.key_methods[kc('<S-Tab>')] = function(state, _)
|
|
1567
|
+
if state.complete == nil then state = MiniInput.apply_handler(state, 'complete', state.opts.completion) end
|
|
1568
|
+
return H.advance_state_complete(state, -1)
|
|
1569
|
+
end
|
|
1570
|
+
|
|
1571
|
+
-- Miscellaneous
|
|
1572
|
+
H.key_methods[kc('<C-o>')] = function(state, _)
|
|
1573
|
+
state.opts.scope = H.advance_from_choices(H.allowed_scopes, state.opts.scope, 'scope')
|
|
1574
|
+
end
|
|
1575
|
+
H.key_methods[kc('<C-s>')] = function(state, _)
|
|
1576
|
+
state.data.new_style = H.advance_from_choices(state.data.all_styles, state.data.style, 'style')
|
|
1577
|
+
end
|
|
1578
|
+
H.key_methods[kc('<C-x>')] = function(state, _)
|
|
1579
|
+
state.opts.hide = not state.opts.hide
|
|
1580
|
+
H.notify('Input is ' .. (state.opts.hide and '' or 'not ') .. 'hidden')
|
|
1581
|
+
end
|
|
1582
|
+
|
|
1583
|
+
-- Autopair keys
|
|
1584
|
+
H.key_methods_autopair = {}
|
|
1585
|
+
H.key_methods_autopair['('] = function(state) H.autopair_open(state, '()') end
|
|
1586
|
+
H.key_methods_autopair['['] = function(state) H.autopair_open(state, '[]') end
|
|
1587
|
+
H.key_methods_autopair['{'] = function(state) H.autopair_open(state, '{}') end
|
|
1588
|
+
H.key_methods_autopair[')'] = function(state) H.autopair_close(state, ')') end
|
|
1589
|
+
H.key_methods_autopair[']'] = function(state) H.autopair_close(state, ']') end
|
|
1590
|
+
H.key_methods_autopair['}'] = function(state) H.autopair_close(state, '}') end
|
|
1591
|
+
H.key_methods_autopair['"'] = function(state) H.autopair_closeopen(state, '""', '"') end
|
|
1592
|
+
H.key_methods_autopair["'"] = function(state) H.autopair_closeopen(state, "''", "'") end
|
|
1593
|
+
H.key_methods_autopair['`'] = function(state) H.autopair_closeopen(state, '``', '`') end
|
|
1594
|
+
|
|
1595
|
+
-- Local helpers
|
|
1596
|
+
H.insert_at_caret = function(state, new_text)
|
|
1597
|
+
local caret, input = state.caret, state.input
|
|
1598
|
+
state.input = vim.fn.strcharpart(input, 0, caret - 1) .. new_text .. vim.fn.strcharpart(input, caret - 1)
|
|
1599
|
+
state.caret = caret + vim.fn.strchars(new_text)
|
|
1600
|
+
end
|
|
1601
|
+
|
|
1602
|
+
H.match_keyword_chars = function(input, caret, side)
|
|
1603
|
+
local text = side == 'left' and vim.fn.strcharpart(input, 0, caret - 1) or vim.fn.strcharpart(input, caret - 1)
|
|
1604
|
+
local pat_keyword = side == 'left' and '[[:keyword:]]\\+$' or '^[[:keyword:]]\\+\\zs'
|
|
1605
|
+
local pat_non_keyword = side == 'left' and '[^[:keyword:]]\\+$' or '^[^[:keyword:]]\\+\\zs'
|
|
1606
|
+
|
|
1607
|
+
local res = math.max(vim.fn.match(text, pat_keyword), vim.fn.match(text, pat_non_keyword), 0)
|
|
1608
|
+
return vim.fn.charidx(text, res) + (side == 'left' and 0 or (caret - 1))
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
H.get_ctrl_v_digits = function(submode)
|
|
1612
|
+
local n_extra = ({ o = 3, O = 3, u = 4, U = 8 })[submode] or 2
|
|
1613
|
+
local digits = H.getcharstr_many(n_extra, { ['\27'] = true })
|
|
1614
|
+
if digits == nil then return nil end
|
|
1615
|
+
digits = (submode:find('%d') ~= nil and submode or '') .. digits
|
|
1616
|
+
|
|
1617
|
+
local base = ({ o = 8, O = 8, x = 16, X = 16, u = 16, U = 16 })[submode] or 10
|
|
1618
|
+
local ok, res = pcall(vim.fn.nr2char, tonumber(digits, base))
|
|
1619
|
+
return ok and res or nil
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
H.advance_state_complete = function(state, increment)
|
|
1623
|
+
local no_complete = H.state_is_end(state) or not (type(state.complete) == 'table' and #state.complete.items > 0)
|
|
1624
|
+
if no_complete then return state end
|
|
1625
|
+
|
|
1626
|
+
local old_id, base = state.complete.id, state.complete.base
|
|
1627
|
+
local old = state.complete.items[old_id] or base
|
|
1628
|
+
local new_id = (old_id + increment) % (#state.complete.items + 1)
|
|
1629
|
+
local new = state.complete.items[new_id] or base
|
|
1630
|
+
state.complete.id = new_id
|
|
1631
|
+
|
|
1632
|
+
-- Replace currently shown candidate with the new one
|
|
1633
|
+
local caret, input = state.caret, state.input
|
|
1634
|
+
local old_len = vim.fn.strchars(old)
|
|
1635
|
+
state.input = vim.fn.strcharpart(input, 0, caret - old_len - 1) .. new .. vim.fn.strcharpart(input, caret - 1)
|
|
1636
|
+
state.caret = caret + (vim.fn.strchars(new) - old_len)
|
|
1637
|
+
|
|
1638
|
+
return state
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
H.advance_from_choices = function(all, cur, name)
|
|
1642
|
+
local new, n = nil, #all
|
|
1643
|
+
for i, v in ipairs(all) do
|
|
1644
|
+
if v == cur then new = all[i % n + 1] end
|
|
1645
|
+
end
|
|
1646
|
+
if not (new == nil or new == cur) then H.notify(string.format('Changed %s to %s', name, vim.inspect(new))) end
|
|
1647
|
+
return new
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1650
|
+
H.autopair_open = function(state, pair)
|
|
1651
|
+
H.insert_at_caret(state, pair)
|
|
1652
|
+
state.caret = state.caret - 1
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
H.autopair_close = function(state, close_char)
|
|
1656
|
+
local is_before_close = vim.fn.strcharpart(state.input, state.caret - 1, 1) == close_char
|
|
1657
|
+
if is_before_close then state.caret = state.caret + 1 end
|
|
1658
|
+
if not is_before_close then H.insert_at_caret(state, close_char) end
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1661
|
+
H.autopair_closeopen = function(state, pair, close_char)
|
|
1662
|
+
local is_before_close = vim.fn.strcharpart(state.input, state.caret - 1, 1) == close_char
|
|
1663
|
+
if is_before_close then H.autopair_close(state, close_char) end
|
|
1664
|
+
if not is_before_close then H.autopair_open(state, pair) end
|
|
1665
|
+
end
|
|
1666
|
+
|
|
1667
|
+
H.is_inside_pair = function(state)
|
|
1668
|
+
local neigh = vim.fn.strcharpart(state.input, state.caret - 2, 2)
|
|
1669
|
+
return ({ ['()'] = true, ['[]'] = true, ['{}'] = true, ['""'] = true, ["''"] = true, ['``'] = true })[neigh] ~= nil
|
|
1670
|
+
end
|
|
1671
|
+
|
|
1672
|
+
-- Default complete handler ---------------------------------------------------
|
|
1673
|
+
H.complete_history = function(state, precise_history)
|
|
1674
|
+
local cwd, scope, prompt = vim.fn.getcwd(), state.opts.scope, state.opts.prompt
|
|
1675
|
+
local is_precise = function(h) return h.cwd == cwd and h.scope == scope and h.prompt == prompt end
|
|
1676
|
+
if not precise_history then is_precise = function(x) return true end end
|
|
1677
|
+
|
|
1678
|
+
local base = vim.fn.strcharpart(state.input, 0, state.caret - 1)
|
|
1679
|
+
local is_match = function(x) return vim.startswith(x, base) and x ~= base end
|
|
1680
|
+
|
|
1681
|
+
local raw, matched, seen = MiniInput.get_history(), {}, {}
|
|
1682
|
+
for i = #raw, 1, -1 do
|
|
1683
|
+
local val = raw[i].input
|
|
1684
|
+
if not seen[val] and is_match(val) and is_precise(raw[i]) then table.insert(matched, val) end
|
|
1685
|
+
seen[val] = true
|
|
1686
|
+
end
|
|
1687
|
+
local items, n = {}, #matched
|
|
1688
|
+
for i = 1, #raw do
|
|
1689
|
+
items[n - i + 1] = matched[i]
|
|
1690
|
+
end
|
|
1691
|
+
state.complete = { base = base, items = items }
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
H.complete_buf_words = function(base)
|
|
1695
|
+
-- Do not match all words for performance reasons
|
|
1696
|
+
if base == '' then return {} end
|
|
1697
|
+
|
|
1698
|
+
-- Get keywords in current buffer that fuzzy match base
|
|
1699
|
+
local pattern_parts = {}
|
|
1700
|
+
for i = 1, vim.fn.strchars(base) do
|
|
1701
|
+
table.insert(pattern_parts, vim.fn.strcharpart(base, i - 1, 1))
|
|
1702
|
+
end
|
|
1703
|
+
-- NOTE: This looks hacky, but it is very fast in common cases and already
|
|
1704
|
+
-- respects 'ignorecase' and 'smartcase'
|
|
1705
|
+
local pattern = '\\V\\k\\*' .. table.concat(pattern_parts, '\\k\\*') .. '\\k\\*'
|
|
1706
|
+
vim.g._miniinput_matches = {}
|
|
1707
|
+
|
|
1708
|
+
local cache_hlsearch, cache_cursor = vim.v.hlsearch, vim.api.nvim_win_get_cursor(0)
|
|
1709
|
+
local search_cmd = 'silent! keeppatterns %s/' .. pattern .. '/\\=add(g:_miniinput_matches, submatch(0))/gn'
|
|
1710
|
+
vim.cmd(search_cmd)
|
|
1711
|
+
-- Here `vim.v` doesn't work: https://github.com/neovim/neovim/issues/25294
|
|
1712
|
+
vim.cmd('let v:hlsearch=' .. cache_hlsearch)
|
|
1713
|
+
vim.api.nvim_win_set_cursor(0, cache_cursor)
|
|
1714
|
+
local matches = vim.g._miniinput_matches
|
|
1715
|
+
vim.g._miniinput_matches = nil
|
|
1716
|
+
|
|
1717
|
+
-- Compute final unique matches sorted by how good fuzzy match is
|
|
1718
|
+
local uniq, seen = {}, { [base] = true }
|
|
1719
|
+
for _, m in ipairs(matches) do
|
|
1720
|
+
if not seen[m] then table.insert(uniq, m) end
|
|
1721
|
+
seen[m] = true
|
|
1722
|
+
end
|
|
1723
|
+
local ref = (vim.o.ignorecase and not vim.o.smartcase) and vim.fn.tolower(base) or base
|
|
1724
|
+
return vim.fn.matchfuzzy(uniq, ref, { matchseq = 1 })
|
|
1725
|
+
end
|
|
1726
|
+
|
|
1727
|
+
H.get_base_from_items = function(text, items)
|
|
1728
|
+
if #items == 0 then return '' end
|
|
1729
|
+
|
|
1730
|
+
local is_match = function(base_cur)
|
|
1731
|
+
for _, item in ipairs(items) do
|
|
1732
|
+
if not vim.startswith(item, base_cur) then return false end
|
|
1733
|
+
end
|
|
1734
|
+
return true
|
|
1735
|
+
end
|
|
1736
|
+
if vim.o.wildoptions:find('fuzzy') ~= nil then
|
|
1737
|
+
is_match = function(base_cur) return #vim.fn.matchfuzzy(items, base_cur, { matchseq = 1 }) == #items end
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
local res
|
|
1741
|
+
for i = vim.fn.strchars(text), 1, -1 do
|
|
1742
|
+
local base_cur = vim.fn.strcharpart(text, i - 1)
|
|
1743
|
+
if is_match(base_cur) then res = base_cur end
|
|
1744
|
+
end
|
|
1745
|
+
return res or ''
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
-- Views ----------------------------------------------------------------------
|
|
1749
|
+
H.handle_view_style = function(state, init_style, all_styles)
|
|
1750
|
+
if state.status == 'start' then state.data.all_styles = all_styles end
|
|
1751
|
+
local style_is_new = state.data.new_style ~= nil and state.data.new_style ~= state.data.style
|
|
1752
|
+
state.data.style = state.data.new_style or state.data.style or init_style
|
|
1753
|
+
state.data.new_style = nil
|
|
1754
|
+
return state.data.style, style_is_new
|
|
1755
|
+
end
|
|
1756
|
+
|
|
1757
|
+
H.default_floatwin_config = function(state, style, target_width)
|
|
1758
|
+
local ver, hor = style:sub(1, 1), style:sub(2, 2)
|
|
1759
|
+
local wininfo = H.get_curwin_info()
|
|
1760
|
+
|
|
1761
|
+
local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
|
|
1762
|
+
local has_statusline = vim.o.laststatus > 0
|
|
1763
|
+
local max_height = vim.o.lines - vim.o.cmdheight - (has_tabline and 1 or 0) - (has_statusline and 1 or 0)
|
|
1764
|
+
local max_width = vim.o.columns
|
|
1765
|
+
|
|
1766
|
+
-- Compute window config
|
|
1767
|
+
local winborder = vim.fn.exists('+winborder') == 0 and '' or vim.o.winborder
|
|
1768
|
+
local border = winborder == '' and 'single' or nil
|
|
1769
|
+
local has_border = winborder ~= 'none'
|
|
1770
|
+
|
|
1771
|
+
local prompt = state.opts.prompt:gsub('[\t\n%z]', ' ')
|
|
1772
|
+
local title_text = prompt == '' and '' or (' ' .. vim.trim(prompt) .. ' ')
|
|
1773
|
+
local footer_text, complete = '', state.complete
|
|
1774
|
+
if complete ~= nil then
|
|
1775
|
+
local method_prefix = complete.method == '' and '' or (' ' .. vim.trim(complete.method))
|
|
1776
|
+
footer_text = string.format('%s %d/%d ', (method_prefix:gsub('[\t\n%z]', ' ')), complete.id, #complete.items)
|
|
1777
|
+
end
|
|
1778
|
+
local title_width, footer_width = vim.fn.strdisplaywidth(title_text), vim.fn.strdisplaywidth(footer_text)
|
|
1779
|
+
|
|
1780
|
+
local width = H.clamp(target_width, math.max(title_width, footer_width), max_width)
|
|
1781
|
+
local height = 1
|
|
1782
|
+
local width_offset = has_border and 2 or 0
|
|
1783
|
+
local height_offset = has_border and 2 or 0
|
|
1784
|
+
|
|
1785
|
+
local config = { relative = 'editor', anchor = 'NW', border = border, style = 'minimal', noautocmd = true }
|
|
1786
|
+
-- - Make sure the window is shown on top of other 'mini.nvim' windows
|
|
1787
|
+
config.zindex = 252
|
|
1788
|
+
|
|
1789
|
+
-- Compute position and dimensions based on scope
|
|
1790
|
+
local ref_rect = { row = has_tabline and 1 or 0, col = 0, width = max_width, height = max_height }
|
|
1791
|
+
local scope = state.opts.scope
|
|
1792
|
+
if not (scope == 'editor' or scope == 'tabpage' or scope == 'project') then
|
|
1793
|
+
-- NOTE: use window for "cursor" as it works when called from command line
|
|
1794
|
+
local winwidth, winheight = wininfo.width, wininfo.height
|
|
1795
|
+
local win_config = vim.api.nvim_win_get_config(0)
|
|
1796
|
+
local win_no_border = win_config.relative == '' or win_config.border == 'none' or win_config.border == nil
|
|
1797
|
+
local win_row = wininfo.winrow - (win_no_border and 1 or 0)
|
|
1798
|
+
local win_col = wininfo.wincol - (win_no_border and 1 or 0)
|
|
1799
|
+
ref_rect = { row = win_row, col = win_col, width = winwidth, height = winheight }
|
|
1800
|
+
|
|
1801
|
+
-- - Nudges to not cover cursor if not 'center'
|
|
1802
|
+
local row_nudge = (ver == 'T' and 1 or ver == 'B' and -1 or 0)
|
|
1803
|
+
local col_nudge = (hor == 'L' and 1 or hor == 'R' and -1 or 0)
|
|
1804
|
+
|
|
1805
|
+
if scope == 'cursor' then
|
|
1806
|
+
local cur_row = win_row + vim.fn.winline() - 1 + row_nudge
|
|
1807
|
+
local cur_col = win_col + vim.fn.wincol() - 1 + col_nudge
|
|
1808
|
+
ref_rect = { row = cur_row, col = cur_col, width = 1, height = 1 }
|
|
1809
|
+
end
|
|
1810
|
+
if scope == 'line' then
|
|
1811
|
+
local cur_row = win_row + vim.fn.winline() - 1 + row_nudge
|
|
1812
|
+
local cur_col = win_col + wininfo.textoff
|
|
1813
|
+
ref_rect = { row = cur_row, col = cur_col, width = winwidth - wininfo.textoff, height = 1 }
|
|
1814
|
+
end
|
|
1815
|
+
|
|
1816
|
+
-- Ensure floating window does not go outside of reference rectangle
|
|
1817
|
+
if scope ~= 'cursor' then width = H.clamp(width, 1, ref_rect.width - width_offset) end
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1820
|
+
local ver_coef = ver == 'T' and 0 or (ver == 'M' and 0.5 or 1)
|
|
1821
|
+
local hor_coef = hor == 'L' and 0 or (hor == 'M' and 0.5 or 1)
|
|
1822
|
+
|
|
1823
|
+
config.row = ref_rect.row + math.floor(ver_coef * (ref_rect.height - height - height_offset))
|
|
1824
|
+
config.col = ref_rect.col + math.floor(hor_coef * (ref_rect.width - width - width_offset))
|
|
1825
|
+
config.height = math.min(height, max_height - height_offset)
|
|
1826
|
+
config.width = math.min(width, max_width - width_offset)
|
|
1827
|
+
|
|
1828
|
+
-- Set title and footer (even if empty string, to remove previous one)
|
|
1829
|
+
if has_border then
|
|
1830
|
+
local title_hl = state.opts.hide and 'MiniInputHide' or 'MiniInputPrompt'
|
|
1831
|
+
config.title = { { H.fit_to_displaywidth(title_text, config.width), title_hl } }
|
|
1832
|
+
config.title_pos = 'left'
|
|
1833
|
+
if vim.fn.has('nvim-0.10') == 1 then
|
|
1834
|
+
config.footer = { { H.fit_to_displaywidth(footer_text, config.width), 'MiniInputHint' } }
|
|
1835
|
+
config.footer_pos = 'right'
|
|
1836
|
+
end
|
|
1837
|
+
end
|
|
1838
|
+
|
|
1839
|
+
return config
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
H.ensure_floatwin_buf = function(state, chunks)
|
|
1843
|
+
local buf_id = state.data.floatwin_buf_id
|
|
1844
|
+
if H.is_valid_buf(buf_id) then
|
|
1845
|
+
vim.api.nvim_buf_clear_namespace(buf_id, H.ns_id.view, 0, -1)
|
|
1846
|
+
else
|
|
1847
|
+
buf_id = vim.api.nvim_create_buf(false, true)
|
|
1848
|
+
H.set_buf_name(buf_id, 'content')
|
|
1849
|
+
vim.bo[buf_id].filetype = 'miniinput'
|
|
1850
|
+
state.data.floatwin_buf_id = buf_id
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1853
|
+
local text_arr, extmark_data, cur_len = {}, {}, 0
|
|
1854
|
+
for _, ch in ipairs(chunks) do
|
|
1855
|
+
table.insert(text_arr, ch[1])
|
|
1856
|
+
local new_len = cur_len + ch[1]:len()
|
|
1857
|
+
table.insert(extmark_data, { cur_len, { end_row = 0, end_col = new_len, hl_group = ch[2] } })
|
|
1858
|
+
cur_len = new_len
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
-- Show input as a single line with special chars taken care of
|
|
1862
|
+
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, { H.get_chunks_text(chunks) })
|
|
1863
|
+
for _, ext in ipairs(extmark_data) do
|
|
1864
|
+
pcall(vim.api.nvim_buf_set_extmark, buf_id, H.ns_id.view, 0, ext[1], ext[2])
|
|
1865
|
+
end
|
|
1866
|
+
end
|
|
1867
|
+
|
|
1868
|
+
H.ensure_floatwin_win = function(state, config)
|
|
1869
|
+
local buf_id = state.data.floatwin_buf_id
|
|
1870
|
+
local win_id = state.data.floatwin_win_id
|
|
1871
|
+
if H.is_valid_win(win_id) then
|
|
1872
|
+
config.noautocmd = nil
|
|
1873
|
+
vim.api.nvim_win_set_config(win_id, config)
|
|
1874
|
+
else
|
|
1875
|
+
win_id = vim.api.nvim_open_win(buf_id, false, config)
|
|
1876
|
+
vim.wo[win_id].list = vim.go.list
|
|
1877
|
+
vim.wo[win_id].listchars = vim.go.listchars
|
|
1878
|
+
vim.wo[win_id].winhighlight = 'NormalFloat:MiniInputNormal,FloatBorder:MiniInputBorder'
|
|
1879
|
+
vim.wo[win_id].wrap = false
|
|
1880
|
+
state.data.floatwin_win_id = win_id
|
|
1881
|
+
end
|
|
1882
|
+
|
|
1883
|
+
return win_id
|
|
1884
|
+
end
|
|
1885
|
+
|
|
1886
|
+
H.uiline_handle_option_values = function(state, style_was_changed)
|
|
1887
|
+
local win_id = vim.api.nvim_get_current_win()
|
|
1888
|
+
local needs_reset = win_id ~= state.data.win_id or style_was_changed
|
|
1889
|
+
if not (H.state_is_end(state) or needs_reset) then return end
|
|
1890
|
+
|
|
1891
|
+
-- Unset all previously set option values
|
|
1892
|
+
H.uiline_unset_option_value(state, 'laststatus')
|
|
1893
|
+
H.uiline_unset_option_value(state, 'showtabline')
|
|
1894
|
+
H.uiline_unset_option_value(state, 'statusline', win_id)
|
|
1895
|
+
H.uiline_unset_option_value(state, 'tabline')
|
|
1896
|
+
H.uiline_unset_option_value(state, 'winbar', win_id)
|
|
1897
|
+
|
|
1898
|
+
if not needs_reset then return end
|
|
1899
|
+
|
|
1900
|
+
-- Cache all necessary values
|
|
1901
|
+
state.data.laststatus = vim.o.laststatus
|
|
1902
|
+
state.data.showtabline = vim.o.showtabline
|
|
1903
|
+
state.data.statusline = vim.wo.statusline
|
|
1904
|
+
state.data.status_height = H.get_curwin_info().status_height
|
|
1905
|
+
state.data.tabline = vim.o.tabline
|
|
1906
|
+
state.data.winbar = vim.wo.winbar
|
|
1907
|
+
|
|
1908
|
+
state.data.win_id = win_id
|
|
1909
|
+
end
|
|
1910
|
+
|
|
1911
|
+
H.uiline_unset_option_value = function(state, option_name, win_id)
|
|
1912
|
+
-- Make sure to not set 'statusline' if it was not previously used
|
|
1913
|
+
local new_val = state.data[option_name]
|
|
1914
|
+
if option_name == 'statusline' and (state.data.status_height or 1) == 0 and state.data.laststatus < 3 then
|
|
1915
|
+
new_val = nil
|
|
1916
|
+
end
|
|
1917
|
+
local option_opts = { scope = win_id ~= nil and 'local' or nil, win = win_id }
|
|
1918
|
+
local cur_val = vim.api.nvim_get_option_value(option_name, option_opts)
|
|
1919
|
+
if cur_val == state.data[option_name] then return end
|
|
1920
|
+
pcall(vim.api.nvim_set_option_value, option_name, new_val, option_opts)
|
|
1921
|
+
end
|
|
1922
|
+
|
|
1923
|
+
-- Chunks ---------------------------------------------------------------------
|
|
1924
|
+
H.get_chunks = function(opts, state, max_width)
|
|
1925
|
+
local res = opts.to_chunks(state, max_width)
|
|
1926
|
+
|
|
1927
|
+
-- Validate
|
|
1928
|
+
local name = string.format('opts.to_chunks(state, %s)', vim.inspect(max_width))
|
|
1929
|
+
H.check_array_of(name, res, 'table')
|
|
1930
|
+
for i, ch in ipairs(res) do
|
|
1931
|
+
local item = string.format('%s[%d]', name, i)
|
|
1932
|
+
H.check_type(item .. '[1]', ch[1], 'string')
|
|
1933
|
+
H.check_type(item .. '[2]', ch[2], 'string')
|
|
1934
|
+
end
|
|
1935
|
+
|
|
1936
|
+
return res
|
|
1937
|
+
end
|
|
1938
|
+
|
|
1939
|
+
H.append_chunks = function(arr, new, keytrans)
|
|
1940
|
+
if not keytrans then return vim.list_extend(arr, new) end
|
|
1941
|
+
|
|
1942
|
+
-- Sanitize chunks for view to display special keys in special way
|
|
1943
|
+
for _, ch in ipairs(new) do
|
|
1944
|
+
for s, special in string.gmatch(ch[1], '(%C*)(%c*)') do
|
|
1945
|
+
if s ~= '' then table.insert(arr, { s, ch[2] }) end
|
|
1946
|
+
if special ~= '' then table.insert(arr, { vim.fn.keytrans(special), 'MiniInputSpecial' }) end
|
|
1947
|
+
end
|
|
1948
|
+
end
|
|
1949
|
+
end
|
|
1950
|
+
|
|
1951
|
+
-- Split chunks at character index
|
|
1952
|
+
-- All chunks for text to the left of and including `at` index go to left.
|
|
1953
|
+
-- The rest - to right. Split chunk if `at` is in between.
|
|
1954
|
+
---@private
|
|
1955
|
+
H.split_chunks_at = function(chunks, at)
|
|
1956
|
+
local left, right, cur_width = {}, {}, 0
|
|
1957
|
+
for _, ch in ipairs(chunks) do
|
|
1958
|
+
local w = vim.fn.strchars(ch[1])
|
|
1959
|
+
local to_left, to_right = at - cur_width, cur_width + w - at
|
|
1960
|
+
cur_width = cur_width + w
|
|
1961
|
+
|
|
1962
|
+
if to_right <= 0 then table.insert(left, ch) end
|
|
1963
|
+
if to_left <= 0 then table.insert(right, ch) end
|
|
1964
|
+
if to_left > 0 and to_right > 0 then
|
|
1965
|
+
table.insert(left, { vim.fn.strcharpart(ch[1], 0, to_left), ch[2] })
|
|
1966
|
+
table.insert(right, { vim.fn.strcharpart(ch[1], to_left), ch[2] })
|
|
1967
|
+
end
|
|
1968
|
+
end
|
|
1969
|
+
|
|
1970
|
+
return left, right
|
|
1971
|
+
end
|
|
1972
|
+
|
|
1973
|
+
H.fit_chunks_to_displaywidth = function(chunks, width, center_offset)
|
|
1974
|
+
local chunks_text = H.get_chunks_text(chunks)
|
|
1975
|
+
local chunks_width = vim.fn.strdisplaywidth(chunks_text)
|
|
1976
|
+
if width == nil or chunks_width < width then return chunks end
|
|
1977
|
+
|
|
1978
|
+
-- Show center with as much and as equally as possible to left and right
|
|
1979
|
+
local right = math.min(chunks_width, math.floor(center_offset + 0.5 * width))
|
|
1980
|
+
local left = math.max(1, right - width + 1)
|
|
1981
|
+
right = left + math.min(width, chunks_width) - 1
|
|
1982
|
+
|
|
1983
|
+
-- Convert display cols into char cols (matters with double width characters)
|
|
1984
|
+
local left_char, right_char, cur_w = nil, nil, 0
|
|
1985
|
+
for i = 1, vim.fn.strchars(chunks_text) do
|
|
1986
|
+
local w = vim.fn.strdisplaywidth(vim.fn.strcharpart(chunks_text, i - 1, 1))
|
|
1987
|
+
|
|
1988
|
+
-- `left_char` = first char id with end at `left` or more, but only if the
|
|
1989
|
+
-- start is within `width` from center offset (otherwise - next char).
|
|
1990
|
+
if left_char == nil and left <= (cur_w + w) then
|
|
1991
|
+
local is_within_width_from_center = center_offset - cur_w <= width
|
|
1992
|
+
left_char = i + (is_within_width_from_center and 0 or 1)
|
|
1993
|
+
-- Update `left` display column to point at left character start
|
|
1994
|
+
left = cur_w + (is_within_width_from_center and 0 or w) + 1
|
|
1995
|
+
end
|
|
1996
|
+
|
|
1997
|
+
-- `right_char` = last char id with end at most `width` from updated `left`
|
|
1998
|
+
if (cur_w + w - left + 1) <= width then right_char = i end
|
|
1999
|
+
|
|
2000
|
+
cur_w = cur_w + w
|
|
2001
|
+
end
|
|
2002
|
+
|
|
2003
|
+
local res, _ = H.split_chunks_at(chunks, right_char)
|
|
2004
|
+
_, res = H.split_chunks_at(res, left_char - 1)
|
|
2005
|
+
return res
|
|
2006
|
+
end
|
|
2007
|
+
|
|
2008
|
+
H.get_chunks_text = function(chunks)
|
|
2009
|
+
return table.concat(vim.tbl_map(function(ch) return ch[1] end, chunks))
|
|
2010
|
+
end
|
|
2011
|
+
|
|
2012
|
+
H.get_chunks_displaywidth = function(chunks) return vim.fn.strdisplaywidth(H.get_chunks_text(chunks)) end
|
|
2013
|
+
|
|
2014
|
+
-- Highlight ranges -----------------------------------------------------------
|
|
2015
|
+
--- Add new proper highlight range on top of an array of ranges that don't
|
|
2016
|
+
--- intersect and are ordered from left to right.
|
|
2017
|
+
---@private
|
|
2018
|
+
H.insert_hl_range = function(arr, max_to, from, to, hl)
|
|
2019
|
+
-- Insert only proper range that fits constraints
|
|
2020
|
+
if from == nil or to == nil or to < 1 or from > max_to then return arr end
|
|
2021
|
+
from, to = H.clamp(from, 1, max_to), H.clamp(to, 1, max_to)
|
|
2022
|
+
if from > to then return arr end
|
|
2023
|
+
|
|
2024
|
+
local res, was_inserted = {}, false
|
|
2025
|
+
for _, cur in ipairs(arr) do
|
|
2026
|
+
-- No overlap
|
|
2027
|
+
if cur.to < from or to < cur.from then table.insert(res, cur) end
|
|
2028
|
+
-- Overlap with outside left part of current
|
|
2029
|
+
if cur.from < from and from <= cur.to then table.insert(res, { from = cur.from, to = from - 1, hl = cur.hl }) end
|
|
2030
|
+
-- Overlap
|
|
2031
|
+
if from <= cur.to and cur.from <= to and not was_inserted then
|
|
2032
|
+
table.insert(res, { from = from, to = to, hl = hl })
|
|
2033
|
+
was_inserted = true
|
|
2034
|
+
end
|
|
2035
|
+
-- Overlap with outside right part of current
|
|
2036
|
+
if cur.from <= to and to < cur.to then table.insert(res, { from = to + 1, to = cur.to, hl = cur.hl }) end
|
|
2037
|
+
end
|
|
2038
|
+
if not was_inserted then table.insert(res, { from = from, to = to, hl = hl }) end
|
|
2039
|
+
return res
|
|
2040
|
+
end
|
|
2041
|
+
|
|
2042
|
+
H.make_ui_select_hl_fun = function(hl_fun)
|
|
2043
|
+
if not vim.is_callable(hl_fun) then return end
|
|
2044
|
+
|
|
2045
|
+
local hl_handler = H.get_config().handlers.highlight or MiniInput.default_highlight
|
|
2046
|
+
return function(state)
|
|
2047
|
+
local input = state.input
|
|
2048
|
+
local hl_ranges = hl_fun(input)
|
|
2049
|
+
if not H.islist(hl_ranges) then hl_ranges = {} end
|
|
2050
|
+
|
|
2051
|
+
-- Convert from Vim ranges `r` to 'mini.input' ranges:
|
|
2052
|
+
-- - r[1] - zero-based byte index at first multibyte start
|
|
2053
|
+
-- - r[2] - zero-based byte index one more than last multibyte end
|
|
2054
|
+
-- - 'mini.input' ranges: named fields, 1-based char indexes
|
|
2055
|
+
local highlight = {}
|
|
2056
|
+
for i, r in ipairs(hl_ranges) do
|
|
2057
|
+
if type(r) == 'table' and type(r[1]) == 'number' and type(r[2]) == 'number' and type(r[3]) == 'string' then
|
|
2058
|
+
highlight[i] = { from = vim.fn.charidx(input, r[1]) + 1, to = vim.fn.charidx(input, r[2]), hl = r[3] }
|
|
2059
|
+
end
|
|
2060
|
+
end
|
|
2061
|
+
state.highlight = highlight
|
|
2062
|
+
|
|
2063
|
+
-- Combine with user-defined handler, like to show added completion chars
|
|
2064
|
+
hl_handler(state)
|
|
2065
|
+
end
|
|
2066
|
+
end
|
|
2067
|
+
|
|
2068
|
+
-- Utilities ------------------------------------------------------------------
|
|
2069
|
+
H.error = function(msg) error('(mini.input) ' .. msg, 0) end
|
|
2070
|
+
|
|
2071
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
2072
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
2073
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
2074
|
+
end
|
|
2075
|
+
|
|
2076
|
+
H.check_one_of = function(name, x, choices)
|
|
2077
|
+
if vim.tbl_contains(choices, x) then return end
|
|
2078
|
+
local choices_string = table.concat(vim.tbl_map(vim.inspect, choices), ', ')
|
|
2079
|
+
local msg = string.format('`%s` should be one of %s', name, choices_string)
|
|
2080
|
+
H.error(msg)
|
|
2081
|
+
end
|
|
2082
|
+
|
|
2083
|
+
H.check_array_of = function(name, x, ref_type)
|
|
2084
|
+
if not H.islist(x) then H.error('`' .. name .. '` should be array') end
|
|
2085
|
+
for i, k in ipairs(x) do
|
|
2086
|
+
if type(k) ~= ref_type then H.error('Every `' .. name .. '` item should be ' .. ref_type) end
|
|
2087
|
+
end
|
|
2088
|
+
end
|
|
2089
|
+
|
|
2090
|
+
H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'miniinput://' .. buf_id .. '/' .. name) end
|
|
2091
|
+
|
|
2092
|
+
H.notify = function(msg, level_name) vim.notify('(mini.input) ' .. msg, vim.log.levels[level_name]) end
|
|
2093
|
+
|
|
2094
|
+
H.getcharstr = function(lmap, allow_ctrl_c)
|
|
2095
|
+
H.cache.is_in_getcharstr = true
|
|
2096
|
+
local ok, char = H.safe_fn_getcharstr()
|
|
2097
|
+
H.cache.is_in_getcharstr = nil
|
|
2098
|
+
|
|
2099
|
+
-- Cache possible error if it doesn't come from pressing <C-c>
|
|
2100
|
+
local is_ctrl_c = (not ok and char == 'Keyboard interrupt') or (ok and char == '\3')
|
|
2101
|
+
if not ok and not is_ctrl_c then H.cache_error(H.state, char) end
|
|
2102
|
+
|
|
2103
|
+
-- Terminate if no input or on hard-coded <C-c>
|
|
2104
|
+
if allow_ctrl_c and is_ctrl_c then return '\3' end
|
|
2105
|
+
if not ok or char == '' or char == '\3' then return nil end
|
|
2106
|
+
|
|
2107
|
+
-- Respect language mappings only if needed
|
|
2108
|
+
return vim.o.iminsert == 0 and char or ((lmap or {})[char] or char)
|
|
2109
|
+
end
|
|
2110
|
+
|
|
2111
|
+
-- TODO: Remove after compatibility with Neovim=0.10 is dropped
|
|
2112
|
+
H.safe_fn_getcharstr = function() return pcall(vim.fn.getcharstr, -1, { cursor = 'hide' }) end
|
|
2113
|
+
if vim.fn.has('nvim-0.11') == 0 then H.safe_fn_getcharstr = function() return pcall(vim.fn.getcharstr) end end
|
|
2114
|
+
|
|
2115
|
+
H.getcharstr_many = function(n, early_stop)
|
|
2116
|
+
local res = {}
|
|
2117
|
+
for i = 1, n do
|
|
2118
|
+
res[i] = H.getcharstr()
|
|
2119
|
+
if res[i] == nil or early_stop[res[i]] then return nil end
|
|
2120
|
+
end
|
|
2121
|
+
return table.concat(res)
|
|
2122
|
+
end
|
|
2123
|
+
|
|
2124
|
+
H.is_special_char = function(x)
|
|
2125
|
+
-- Remove allowed character that are specially translated
|
|
2126
|
+
x = x:gsub('[%s<]', '')
|
|
2127
|
+
return vim.fn.keytrans(x) ~= x
|
|
2128
|
+
end
|
|
2129
|
+
|
|
2130
|
+
H.cache_error = function(state, msg)
|
|
2131
|
+
if not vim.startswith(msg, '(mini.input) ') then msg = '(mini.input) ' .. msg end
|
|
2132
|
+
if H.state == nil then error(msg) end
|
|
2133
|
+
state.errmsg = state.errmsg or msg
|
|
2134
|
+
state.status = 'cancel'
|
|
2135
|
+
end
|
|
2136
|
+
|
|
2137
|
+
H.redraw = function() vim.cmd('redraw!') end
|
|
2138
|
+
|
|
2139
|
+
H.clamp = function(x, from, to) return math.min(math.max(x, from), to) end
|
|
2140
|
+
|
|
2141
|
+
H.is_valid_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_valid(buf_id) end
|
|
2142
|
+
|
|
2143
|
+
H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
|
|
2144
|
+
|
|
2145
|
+
H.is_valid_char = function(char) return vim.fn.strchars(char) == 1 and vim.fn.char2nr(char) > 31 end
|
|
2146
|
+
|
|
2147
|
+
H.get_curwin_info = function()
|
|
2148
|
+
local res = vim.fn.getwininfo(vim.api.nvim_get_current_win())[1]
|
|
2149
|
+
-- TODO: Remove after compatibility with Neovim=0.11 is dropped
|
|
2150
|
+
res.status_height = res.status_height or (vim.api.nvim_win_get_config(0).relative == '' and 1 or 0)
|
|
2151
|
+
return res
|
|
2152
|
+
end
|
|
2153
|
+
|
|
2154
|
+
H.fit_to_displaywidth = function(text, width)
|
|
2155
|
+
if vim.fn.strdisplaywidth(text) <= width then return text end
|
|
2156
|
+
for i = 1, vim.fn.strchars(text) do
|
|
2157
|
+
local truncated = '…' .. vim.fn.strcharpart(text, i)
|
|
2158
|
+
if vim.fn.strdisplaywidth(truncated) <= width then return truncated end
|
|
2159
|
+
end
|
|
2160
|
+
return ''
|
|
2161
|
+
end
|
|
2162
|
+
|
|
2163
|
+
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
|
|
2164
|
+
H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
|
|
2165
|
+
|
|
2166
|
+
H.get_lmap = function()
|
|
2167
|
+
local lmap = {}
|
|
2168
|
+
for _, map in ipairs(vim.fn.maplist()) do
|
|
2169
|
+
-- NOTE: Account only for characters that resolve to proper input character
|
|
2170
|
+
local is_query_lmap = map.mode == 'l' and H.is_valid_char(map.rhs)
|
|
2171
|
+
if is_query_lmap then lmap[map.lhs] = map.rhs end
|
|
2172
|
+
end
|
|
2173
|
+
return lmap
|
|
2174
|
+
end
|
|
2175
|
+
if vim.fn.has('nvim-0.10') == 0 then H.get_lmap = function() return {} end end
|
|
2176
|
+
|
|
2177
|
+
-- A copy of `vim.deepcopy()` that doesn't error on userdata and threads
|
|
2178
|
+
H.copy_tables = function(x)
|
|
2179
|
+
return type(x) == 'table' and setmetatable(vim.tbl_map(H.copy_tables, x), getmetatable(x)) or x
|
|
2180
|
+
end
|
|
2181
|
+
|
|
2182
|
+
MiniInput.default_view = MiniInput.gen_view.floatwin()
|
|
2183
|
+
return MiniInput
|