retrovim 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. package/zsh/zsh-patina.toml +2 -0
@@ -0,0 +1,2096 @@
1
+ --- *mini.clue* Show next key clues
2
+ ---
3
+ --- MIT License Copyright (c) 2023 Evgeni Chasnovski
4
+
5
+ --- Features:
6
+ --- - Implement custom key query process to reach target key combination:
7
+ --- - Starts after customizable opt-in triggers (mode + keys).
8
+ ---
9
+ --- - Each key press narrows down set of possible targets.
10
+ --- Pressing `<BS>` removes previous user entry.
11
+ --- Pressing `<Esc>` or `<C-c>` leads to an early stop.
12
+ --- Doesn't depend on |'timeoutlen'| and has basic support for |'langmap'|.
13
+ ---
14
+ --- - Ends when there is at most one target left or user pressed `<CR>`.
15
+ --- Results into emulating pressing all query keys plus possible postkeys.
16
+ ---
17
+ --- - Show window (after configurable delay) with clues. It lists available
18
+ --- next keys along with their descriptions (auto generated from descriptions
19
+ --- present keymaps and user-supplied clues; preferring the former).
20
+ ---
21
+ --- - Configurable "postkeys" for key combinations - keys which will be emulated
22
+ --- after combination is reached during key query process.
23
+ ---
24
+ --- - Provide customizable sets of clues for common built-in keys/concepts:
25
+ --- - `g` key.
26
+ --- - `z` key.
27
+ --- - Window commands.
28
+ --- - Built-in completion.
29
+ --- - Marks.
30
+ --- - Registers.
31
+ ---
32
+ --- - Lua functions to disable/enable triggers globally or per buffer.
33
+ ---
34
+ --- For more details see:
35
+ --- - |MiniClue-key-query-process|.
36
+ --- - |MiniClue-examples|.
37
+ --- - |MiniClue.config|.
38
+ --- - |MiniClue.gen_clues|.
39
+ ---
40
+ --- Notes:
41
+ --- - There is no functionality to create mappings while defining clues.
42
+ --- This is done to clearly separate these two different actions.
43
+ --- The best suggested practice is to manually create mappings with
44
+ --- descriptions (`desc` field in options), as they will be automatically
45
+ --- used inside clue window.
46
+ ---
47
+ --- - Triggers are implemented as special buffer-local mappings. This leads to
48
+ --- several caveats:
49
+ --- - They will override same regular buffer-local mappings and have
50
+ --- precedence over global one.
51
+ ---
52
+ --- Example: having set `<C-w>` as Normal mode trigger means that
53
+ --- there should not be another `<C-w>` mapping.
54
+ ---
55
+ --- - They need to be the latest created buffer-local mappings or they will
56
+ --- not function properly. Most common indicator of this is that some
57
+ --- mapping starts to work only after clue window is shown.
58
+ ---
59
+ --- Example: `g` is set as Normal mode trigger, but `gcc` from |mini.comment|
60
+ --- doesn't work right away. This is probably because there are some
61
+ --- other buffer-local mappings starting with `g` which were created after
62
+ --- mapping for `g` trigger. Most common places for this are in LSP server's
63
+ --- `on_attach` or during tree-sitter start in buffer.
64
+ ---
65
+ --- To check if trigger is the most recent buffer-local mapping, execute
66
+ --- `:<mode-char>map <trigger-keys>` (like `:nmap g` for previous example).
67
+ --- Mapping for trigger should be the first listed.
68
+ ---
69
+ --- This module makes the best effort to work out of the box and cover
70
+ --- most common cases, but it is not foolproof. The solution here is to
71
+ --- ensure that triggers are created after making all buffer-local mappings:
72
+ --- run either |MiniClue.setup()| or |MiniClue.ensure_buf_triggers()|.
73
+ ---
74
+ --- - Descriptions from existing mappings take precedence over user-supplied
75
+ --- clues. This is to ensure that information shown in clue window is as
76
+ --- relevant as possible. To add/customize description of an already existing
77
+ --- mapping, use |MiniClue.set_mapping_desc()|.
78
+ ---
79
+ --- - Due to technical difficulties, there is no foolproof support for
80
+ --- Operator-pending mode triggers (like `a`/`i` from |mini.ai|):
81
+ --- - Doesn't work as part of a command in "temporary Normal mode" (like
82
+ --- after |i_CTRL-O|) due to implementation difficulties.
83
+ --- - Can have unexpected behavior with custom operators.
84
+ ---
85
+ --- - Has (mostly solved) issues with macros:
86
+ --- - All triggers are disabled during macro recording due to technical
87
+ --- reasons.
88
+ --- - The `@` and `Q` keys are specially mapped inside |MiniClue.setup()|
89
+ --- (if the key is not already mapped) to temporarily disable triggers.
90
+ ---
91
+ --- # Setup ~
92
+ ---
93
+ --- This module needs a setup with `require('mini.clue').setup({})` (replace
94
+ --- `{}` with your `config` table). It will create global Lua table `MiniClue`
95
+ --- which you can use for scripting or manually (with `:lua MiniClue.*`).
96
+ ---
97
+ --- Config table NEEDS TO HAVE TRIGGERS CONFIGURED, none is set up by default.
98
+ ---
99
+ --- See |MiniClue.config| for available config settings.
100
+ ---
101
+ --- You can override runtime config settings (like clues or window options)
102
+ --- locally to a buffer inside `vim.b.miniclue_config` which should have same
103
+ --- structure as `MiniClue.config`. See |mini.nvim-buffer-local-config| for
104
+ --- more details.
105
+ ---
106
+ --- # Comparisons ~
107
+ ---
108
+ --- - [folke/which-key.nvim](https://github.com/folke/which-key.nvim):
109
+ --- - Both have the same main goal: show available next keys along with
110
+ --- their customizable descriptions.
111
+ --- - Has different UI and content layout.
112
+ --- - Allows creating mappings inside its configuration, while this module
113
+ --- doesn't have this by design (to clearly separate two different tasks).
114
+ --- - Doesn't allow creating submodes, while this module does (via `postkeys`).
115
+ ---
116
+ --- - [anuvyklack/hydra.nvim](https://github.com/anuvyklack/hydra.nvim):
117
+ --- - Both allow creating submodes: state which starts at certain key
118
+ --- combination; treats some keys differently; ends after `<Esc>`.
119
+ --- - Doesn't show information about available next keys (outside of
120
+ --- submodes), while that is this module's main goal.
121
+ ---
122
+ --- # Highlight groups ~
123
+ --- *MiniClue-hl-groups*
124
+ ---
125
+ --- - `MiniClueBorder` - window border.
126
+ --- - `MiniClueDescGroup` - group description in clue window.
127
+ --- - `MiniClueDescSingle` - single target description in clue window.
128
+ --- - `MiniClueNextKey` - next key label in clue window.
129
+ --- - `MiniClueNextKeyWithPostkeys` - next key label with postkeys in clue window.
130
+ --- - `MiniClueSeparator` - separator in clue window.
131
+ --- - `MiniClueTitle` - window title.
132
+ ---
133
+ --- To change any highlight group, set it directly with |nvim_set_hl()|.
134
+ ---
135
+ --- # Disabling ~
136
+ ---
137
+ --- To disable creating triggers, set `vim.g.miniclue_disable` (globally) or
138
+ --- `vim.b.miniclue_disable` (for a buffer) to `true`. Considering high number
139
+ --- of different scenarios and customization intentions, writing exact rules
140
+ --- for disabling module's functionality is left to user. See
141
+ --- |mini.nvim-disabling-recipes| for common recipes.
142
+ ---@tag MiniClue
143
+
144
+ --- # General info ~
145
+ ---
146
+ --- This module implements custom key query process imitating a usual built-in
147
+ --- mechanism of user pressing keys in order to execute a mapping. General idea
148
+ --- is the same: narrow down key combinations until the target is reached.
149
+ ---
150
+ --- Main goals of its existence are:
151
+ ---
152
+ --- - Allow reaching certain mappings be independent of |'timeoutlen'|. That is,
153
+ --- there is no fixed timeout after which currently typed keys are executed.
154
+ ---
155
+ --- - Enable automated showing of next key clues after user-supplied delay
156
+ --- (also independent of |'timeoutlen'|).
157
+ ---
158
+ --- - Allow emulating configurable key presses after certain key combination is
159
+ --- reached. This granular control allows creating so called "submodes".
160
+ --- See more at |MiniClue-examples-submodes|.
161
+ ---
162
+ --- This process is primarily designed for nested `<Leader>` mappings in Normal
163
+ --- mode but works in all other main modes: Visual, Insert, Operator-pending
164
+ --- (with caveats; no foolproof guarantees), Command-line, Terminal.
165
+ ---
166
+ --- # Lifecycle ~
167
+ ---
168
+ --- - Key query process starts when user types a trigger: certain keys in certain
169
+ --- mode. Those keys are put into key query as a single user input. All possible
170
+ --- mode key combinations are filtered to ones starting with the trigger keys.
171
+ ---
172
+ --- Note: trigger is implemented as a regular mapping, so if it has at least
173
+ --- two keys, they should be pressed within |'timeoutlen'| milliseconds.
174
+ ---
175
+ --- - Wait (indefinitely) for user to press a key. Advance depending on the key:
176
+ ---
177
+ --- - Special key:
178
+ ---
179
+ --- - If `<Esc>` or `<C-c>`, stop the process without any action.
180
+ ---
181
+ --- - If `<CR>`, stop the process and execute current key query, meaning
182
+ --- emulate (with |nvim_feedkeys()|) user pressing those keys.
183
+ ---
184
+ --- - If `<BS>`, remove previous user input from the query. If query becomes
185
+ --- empty, stop the process without any action.
186
+ ---
187
+ --- - If a key for scrolling clue window (`scroll_down` / `scroll_up`
188
+ --- in `config.window`; `<C-d>` / `<C-u>` by default), scroll clue window
189
+ --- and wait for the next user key.
190
+ --- Note: if clue window is not shown, treated as a not special key.
191
+ ---
192
+ --- - Not special key. Add key to the query while filtering all available
193
+ --- key combinations to start with the current key query. Advance:
194
+ ---
195
+ --- - If there is a single available key combination matching current
196
+ --- key query, execute it.
197
+ ---
198
+ --- - If there is no key combinations starting with the current query,
199
+ --- execute it. This, for instance, allows a seamless execution of
200
+ --- operators in presence of a longer key combinations. Example: with
201
+ --- `g` as trigger in Normal mode and available mappings `gc` / `gcc`
202
+ --- (like from |mini.comment|), this allows typing `gcip` to comment
203
+ --- current paragraph, although there are no key combinations
204
+ --- starting with `gci`.
205
+ ---
206
+ --- - Otherwise wait for the new user key press.
207
+ ---
208
+ --- # Clue window ~
209
+ ---
210
+ --- After initiating key query process and after each key press, a timer is
211
+ --- started to show a clue window: floating window with information about
212
+ --- available next keys along with their descriptions. Note: if window is
213
+ --- already shown, its content is updated right away.
214
+ ---
215
+ --- Clues can have these types:
216
+ ---
217
+ --- - "Terminal next key": when pressed, will lead to query execution.
218
+ ---
219
+ --- - "Terminal next key with postkeys": when pressed, will lead to query
220
+ --- execution plus some configured postkeys.
221
+ ---
222
+ --- - "Group next key": when pressed, will narrow down available key combinations
223
+ --- and wait for another key press. Note: can have configured description
224
+ --- (inside `config.clues`) or it will be auto generated based on the number of
225
+ --- available key combinations.
226
+ ---@tag MiniClue-key-query-process
227
+
228
+ --- # Full starter example ~
229
+ ---
230
+ --- If not sure where to start, try this example with all provided clues from
231
+ --- this module plus all |<Leader>| mappings in Normal and Visual modes: >lua
232
+ ---
233
+ --- local miniclue = require('mini.clue')
234
+ --- miniclue.setup({
235
+ --- triggers = {
236
+ --- -- Leader triggers
237
+ --- { mode = { 'n', 'x' }, keys = '<Leader>' },
238
+ ---
239
+ --- -- `[` and `]` keys
240
+ --- { mode = 'n', keys = '[' },
241
+ --- { mode = 'n', keys = ']' },
242
+ ---
243
+ --- -- Built-in completion
244
+ --- { mode = 'i', keys = '<C-x>' },
245
+ ---
246
+ --- -- `g` key
247
+ --- { mode = { 'n', 'x' }, keys = 'g' },
248
+ ---
249
+ --- -- Marks
250
+ --- { mode = { 'n', 'x' }, keys = "'" },
251
+ --- { mode = { 'n', 'x' }, keys = '`' },
252
+ ---
253
+ --- -- Registers
254
+ --- { mode = { 'n', 'x' }, keys = '"' },
255
+ --- { mode = { 'i', 'c' }, keys = '<C-r>' },
256
+ ---
257
+ --- -- Window commands
258
+ --- { mode = 'n', keys = '<C-w>' },
259
+ ---
260
+ --- -- `z` key
261
+ --- { mode = { 'n', 'x' }, keys = 'z' },
262
+ --- },
263
+ ---
264
+ --- clues = {
265
+ --- -- Enhance this by adding descriptions for <Leader> mapping groups
266
+ --- miniclue.gen_clues.square_brackets(),
267
+ --- miniclue.gen_clues.builtin_completion(),
268
+ --- miniclue.gen_clues.g(),
269
+ --- miniclue.gen_clues.marks(),
270
+ --- miniclue.gen_clues.registers(),
271
+ --- miniclue.gen_clues.windows(),
272
+ --- miniclue.gen_clues.z(),
273
+ --- },
274
+ --- })
275
+ --- <
276
+ --- # Leader clues ~
277
+ ---
278
+ --- Assume there are these |<Leader>| mappings set up: >lua
279
+ ---
280
+ --- -- Set `<Leader>` before making any mappings and configuring 'mini.clue'
281
+ --- vim.g.mapleader = ' '
282
+ ---
283
+ --- local nmap_leader = function(suffix, rhs, desc)
284
+ --- vim.keymap.set('n', '<Leader>' .. suffix, rhs, { desc = desc })
285
+ --- end
286
+ --- local xmap_leader = function(suffix, rhs, desc)
287
+ --- vim.keymap.set('x', '<Leader>' .. suffix, rhs, { desc = desc })
288
+ --- end
289
+ ---
290
+ --- nmap_leader('bd', '<Cmd>lua MiniBufremove.delete()<CR>', 'Delete')
291
+ --- nmap_leader('bw', '<Cmd>lua MiniBufremove.wipeout()<CR>', 'Wipeout')
292
+ ---
293
+ --- nmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
294
+ --- xmap_leader('lf', '<Cmd>lua vim.lsp.buf.format()<CR>', 'Format')
295
+ --- nmap_leader('lr', '<Cmd>lua vim.lsp.buf.rename()<CR>', 'Rename')
296
+ --- nmap_leader('lR', '<Cmd>lua vim.lsp.buf.references()<CR>', 'References')
297
+ --- <
298
+ --- The following setup will enable |<Leader>| as trigger in Normal and Visual
299
+ --- modes and add descriptions to mapping groups: >lua
300
+ ---
301
+ --- require('mini.clue').setup({
302
+ --- -- Register `<Leader>` as trigger
303
+ --- triggers = {
304
+ --- { mode = { 'n', 'x' }, keys = '<Leader>' },
305
+ --- },
306
+ ---
307
+ --- -- Add descriptions for mapping groups
308
+ --- clues = {
309
+ --- { mode = 'n', keys = '<Leader>b', desc = '+Buffers' },
310
+ --- { mode = 'n', keys = '<Leader>l', desc = '+LSP' },
311
+ --- },
312
+ --- })
313
+ --- <
314
+ --- # Clues without mappings ~
315
+ ---
316
+ --- Clues can be shown not only for actually present mappings. This is helpful for
317
+ --- showing clues for built-in key combinations. Here is an example of clues for
318
+ --- a subset of built-in completion (see |MiniClue.gen_clues.builtin_completion()|
319
+ --- to generate clues for all available completion sources): >lua
320
+ ---
321
+ --- require('mini.clue').setup({
322
+ --- -- Make `<C-x>` a trigger. Otherwise, key query process won't start.
323
+ --- triggers = {
324
+ --- { mode = 'i', keys = '<C-x>' },
325
+ --- },
326
+ ---
327
+ --- -- Register custom clues
328
+ --- clues = {
329
+ --- { mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
330
+ --- { mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
331
+ --- { mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
332
+ --- { mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
333
+ --- { mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
334
+ --- }
335
+ --- })
336
+ --- <
337
+ --- # Triggers in special buffers ~
338
+ ---
339
+ --- By default triggers are automatically created in listed (|'buflisted'|) and some
340
+ --- special non-listed buffers. Use |MiniClue.ensure_buf_triggers()| to manually
341
+ --- enable in when you need them. For example: >vim
342
+ ---
343
+ --- au FileType special_ft lua MiniClue.ensure_buf_triggers()
344
+ --- <
345
+ --- # Submodes ~
346
+ --- *MiniClue-examples-submodes*
347
+ ---
348
+ --- Submode is a state initiated after pressing certain key combination ("prefix")
349
+ --- during which some keys are interpreted differently.
350
+ ---
351
+ --- In this module submode can be implemented following these steps:
352
+ ---
353
+ --- - Create mappings for each key inside submode. Left hand side of mappings
354
+ --- should consist from prefix followed by the key.
355
+ ---
356
+ --- - Create clue for each key inside submode with `postkeys` value equal to
357
+ --- prefix. It would mean that after executing particular key combination from
358
+ --- this submode, pressing its prefix will be automatically emulated (leading
359
+ --- back to being inside submode).
360
+ ---
361
+ --- - Register submode prefix (or some of its starting part) as trigger. Do not
362
+ --- register "overlapping" triggers, like `<Leader>` and `<Leader>m`.
363
+ ---
364
+ --- ## Submode examples ~
365
+ ---
366
+ --- - Submode for moving with |mini.move|:
367
+ --- - Press `<Leader>m` to start submode.
368
+ --- - Press any of `h`/`j`/`k`/`l` to move selection/line.
369
+ --- - Press `<Esc>` to stop submode.
370
+ ---
371
+ --- The code: >lua
372
+ ---
373
+ --- require('mini.move').setup({
374
+ --- mappings = {
375
+ --- left = '<Leader>mh',
376
+ --- right = '<Leader>ml',
377
+ --- down = '<Leader>mj',
378
+ --- up = '<Leader>mk',
379
+ --- line_left = '<Leader>mh',
380
+ --- line_right = '<Leader>ml',
381
+ --- line_down = '<Leader>mj',
382
+ --- line_up = '<Leader>mk',
383
+ --- },
384
+ --- })
385
+ ---
386
+ --- require('mini.clue').setup({
387
+ --- triggers = {
388
+ --- -- This can also set up directly `<Leader>m` as a trigger, but make
389
+ --- -- sure to not also use `<Leader>`, as they would "overlap"
390
+ --- { mode = { 'n', 'x' }, keys = '<Leader>' },
391
+ --- },
392
+ --- clues = {
393
+ --- { mode = 'n', keys = '<Leader>m', desc = '+Move' },
394
+ ---
395
+ --- { mode = { 'n', 'x' }, keys = '<Leader>mh', postkeys = '<Leader>m' },
396
+ --- { mode = { 'n', 'x' }, keys = '<Leader>mj', postkeys = '<Leader>m' },
397
+ --- { mode = { 'n', 'x' }, keys = '<Leader>mk', postkeys = '<Leader>m' },
398
+ --- { mode = { 'n', 'x' }, keys = '<Leader>ml', postkeys = '<Leader>m' },
399
+ --- },
400
+ --- })
401
+ --- <
402
+ --- - Submode for iterating buffers and windows with |mini.bracketed|:
403
+ --- - Press `[` or `]` to start key query process for certain direction.
404
+ --- - Press `b` / `w` to iterate buffers/windows until reach target one.
405
+ --- - Press `<Esc>` to stop submode.
406
+ ---
407
+ --- The code: >lua
408
+ ---
409
+ --- require('mini.bracketed').setup()
410
+ ---
411
+ --- require('mini.clue').setup({
412
+ --- triggers = {
413
+ --- { mode = 'n', keys = ']' },
414
+ --- { mode = 'n', keys = '[' },
415
+ --- },
416
+ --- clues = {
417
+ --- { mode = 'n', keys = ']b', postkeys = ']' },
418
+ --- { mode = 'n', keys = ']w', postkeys = ']' },
419
+ ---
420
+ --- { mode = 'n', keys = '[b', postkeys = '[' },
421
+ --- { mode = 'n', keys = '[w', postkeys = '[' },
422
+ --- },
423
+ --- })
424
+ --- <
425
+ --- - Submode for window commands using |MiniClue.gen_clues.windows()|:
426
+ --- - Press `<C-w>` to start key query process.
427
+ --- - Press keys which move / change focus / resize windows.
428
+ --- - Press `<Esc>` to stop submode.
429
+ ---
430
+ --- The code: >lua
431
+ ---
432
+ --- local miniclue = require('mini.clue')
433
+ --- miniclue.setup({
434
+ --- triggers = {
435
+ --- { mode = 'n', keys = '<C-w>' },
436
+ --- },
437
+ --- clues = {
438
+ --- miniclue.gen_clues.windows({
439
+ --- submode_move = true,
440
+ --- submode_navigate = true,
441
+ --- submode_resize = true,
442
+ --- })
443
+ --- },
444
+ --- })
445
+ --- <
446
+ --- # Window config ~
447
+ --- >lua
448
+ --- require('mini.clue').setup({
449
+ --- triggers = { { mode = 'n', keys = '<Leader>' } },
450
+ ---
451
+ --- window = {
452
+ --- -- Show window immediately
453
+ --- delay = 0,
454
+ ---
455
+ --- config = {
456
+ --- -- Compute window width automatically
457
+ --- width = 'auto',
458
+ ---
459
+ --- -- Use double-line border
460
+ --- border = 'double',
461
+ --- },
462
+ --- },
463
+ --- })
464
+ --- <
465
+ ---@tag MiniClue-examples
466
+
467
+ ---@diagnostic disable:undefined-field
468
+ ---@diagnostic disable:discard-returns
469
+ ---@diagnostic disable:unused-local
470
+ ---@diagnostic disable:cast-local-type
471
+
472
+ -- Module definition ==========================================================
473
+ local MiniClue = {}
474
+ local H = {}
475
+
476
+ --- Module setup
477
+ ---
478
+ ---@param config table|nil Module config table. See |MiniClue.config|.
479
+ ---
480
+ ---@usage >lua
481
+ --- require('mini.clue').setup({}) -- replace {} with your config table
482
+ --- -- needs `triggers` field present
483
+ --- <
484
+ MiniClue.setup = function(config)
485
+ -- TODO: Remove after Neovim=0.9 support is dropped
486
+ if vim.fn.has('nvim-0.10') == 0 then
487
+ vim.notify(
488
+ '(mini.clue) Neovim<0.10 is soft deprecated (module works but is not supported).'
489
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
490
+ .. ' Please update your Neovim version.'
491
+ )
492
+ end
493
+
494
+ -- Export module
495
+ _G.MiniClue = MiniClue
496
+
497
+ -- Setup config
498
+ config = H.setup_config(config)
499
+
500
+ -- Apply config
501
+ H.apply_config(config)
502
+
503
+ -- Define behavior
504
+ H.create_autocommands()
505
+
506
+ -- Create default highlighting
507
+ H.create_default_hl()
508
+ end
509
+
510
+ --stylua: ignore
511
+ --- Defaults ~
512
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
513
+ ---@text # General info ~
514
+ ---
515
+ --- - To use |<Leader>| as part of the config (either as trigger or inside clues),
516
+ --- set it prior to running |MiniClue.setup()|.
517
+ ---
518
+ --- - See |MiniClue-examples| for examples.
519
+ ---
520
+ --- # Clues ~
521
+ ---
522
+ --- `config.clues` is an array with extra information about key combinations.
523
+ --- Each element can be one of:
524
+ --- - Clue table.
525
+ --- - Array (possibly nested) of clue tables.
526
+ --- - Callable (function) returning either of the previous two.
527
+ ---
528
+ --- A clue table is a table with the following fields:
529
+ --- - <mode> `(string|table)` - single character describing mode short-name of
530
+ --- key combination as in `nvim_set_keymap()` (`'n'`, `'x'`, `'i'`, `'o'`, `'c'`, etc.),
531
+ --- or a array thereof.
532
+ --- - <keys> `(string)` - key combination for which clue will be shown.
533
+ --- "Human-readable" key names as in |key-notation| (like "<Leader>", "<Space>",
534
+ --- "<Tab>", etc.) are allowed.
535
+ --- - <desc> `(string|function|nil)` - optional key combination description which is
536
+ --- shown in clue window. If function, should return string description.
537
+ --- - <postkeys> `(string|nil)` - optional postkeys which will be executed
538
+ --- automatically after `keys`. Allows creation of submodes
539
+ --- (see |MiniClue-examples-submodes|).
540
+ ---
541
+ --- Notes:
542
+ --- - Postkeys are literal simulation of keypresses with |nvim_feedkeys()|.
543
+ ---
544
+ --- - Suggested approach to configuring clues is to create mappings with `desc`
545
+ --- field while supplying to `config.clues` only elements describing groups,
546
+ --- postkeys, and built-in mappings.
547
+ ---
548
+ --- # Triggers ~
549
+ ---
550
+ --- `config.triggers` is an array with information when |MiniClue-key-query-process|
551
+ --- should start. Each element is a trigger table with the fields <mode> and
552
+ --- <keys> which are treated the same as in clue table.
553
+ ---
554
+ --- # Window ~
555
+ ---
556
+ --- `config.window` defines behavior of clue window.
557
+ ---
558
+ --- `config.window.delay` is a number of milliseconds after which clue window will
559
+ --- appear. Can be 0 to show immediately.
560
+ ---
561
+ --- `config.window.config` is a table defining floating window characteristics
562
+ --- or a callable returning such table (will be called with identifier of
563
+ --- window's buffer already showing all clues). It should have the same
564
+ --- structure as in |nvim_open_win()| with the following enhancements:
565
+ --- - <width> field can be equal to `"auto"` leading to window width being
566
+ --- computed automatically based on its content. Default is fixed width of 30.
567
+ --- - <row> and <col> can be equal to `"auto"` in which case they will be
568
+ --- computed to "stick" to set anchor ("SE" by default; see |nvim_open_win()|).
569
+ --- This allows changing corner in which window is shown: >lua
570
+ ---
571
+ --- -- Pick one anchor
572
+ --- local anchor = 'NW' -- top-left
573
+ --- local anchor = 'NE' -- top-right
574
+ --- local anchor = 'SW' -- bottom-left
575
+ --- local anchor = 'SE' -- bottom-right
576
+ ---
577
+ --- require('mini.clue').setup({
578
+ --- window = {
579
+ --- config = { anchor = anchor, row = 'auto', col = 'auto' },
580
+ --- },
581
+ --- })
582
+ --- <
583
+ --- `config.window.scroll_down` / `config.window.scroll_up` are strings defining
584
+ --- keys which will scroll clue window down / up which is useful in case not
585
+ --- all clues fit in current window height. Set to empty string `''` to disable
586
+ --- either of them.
587
+ MiniClue.config = {
588
+ -- Array of extra clues to show
589
+ clues = {},
590
+
591
+ -- Array of opt-in triggers which start custom key query process.
592
+ -- **Needs to have something in order to show clues**.
593
+ triggers = {},
594
+
595
+ -- Clue window settings
596
+ window = {
597
+ -- Floating window config
598
+ config = {},
599
+
600
+ -- Delay before showing clue window
601
+ delay = 1000,
602
+
603
+ -- Keys to scroll inside the clue window
604
+ scroll_down = '<C-d>',
605
+ scroll_up = '<C-u>',
606
+ },
607
+ }
608
+ --minidoc_afterlines_end
609
+
610
+ --- Enable triggers in loaded listed and some special buffers
611
+ MiniClue.enable_all_triggers = function()
612
+ local loaded_bufs = vim.tbl_filter(vim.api.nvim_buf_is_loaded, vim.api.nvim_list_bufs())
613
+ for _, buf_id in ipairs(loaded_bufs) do
614
+ -- Map only inside valid listed buffers and ones with special filetypes
615
+ local is_special = H.ft_to_enable[vim.bo[buf_id].filetype]
616
+ if vim.fn.buflisted(buf_id) == 1 or is_special then H.map_buf_triggers(buf_id) end
617
+ end
618
+ end
619
+
620
+ --- Enable triggers in buffer
621
+ ---
622
+ ---@param buf_id number|nil Buffer identifier. Default: current buffer.
623
+ MiniClue.enable_buf_triggers = function(buf_id)
624
+ buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
625
+ if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
626
+ H.map_buf_triggers(buf_id)
627
+ end
628
+
629
+ --- Disable triggers in loaded buffers
630
+ MiniClue.disable_all_triggers = function()
631
+ local loaded_bufs = vim.tbl_filter(vim.api.nvim_buf_is_loaded, vim.api.nvim_list_bufs())
632
+ for _, buf_id in ipairs(loaded_bufs) do
633
+ H.unmap_buf_triggers(buf_id)
634
+ end
635
+ end
636
+
637
+ --- Disable triggers in buffer
638
+ ---
639
+ ---@param buf_id number|nil Buffer identifier. Default: current buffer.
640
+ MiniClue.disable_buf_triggers = function(buf_id)
641
+ buf_id = (buf_id == nil or buf_id == 0) and vim.api.nvim_get_current_buf() or buf_id
642
+ if not H.is_valid_buf(buf_id) then H.error('`buf_id` should be a valid buffer identifier.') end
643
+ H.unmap_buf_triggers(buf_id)
644
+ end
645
+
646
+ --- Ensure all triggers are valid
647
+ MiniClue.ensure_all_triggers = function()
648
+ MiniClue.disable_all_triggers()
649
+ MiniClue.enable_all_triggers()
650
+ end
651
+
652
+ --- Ensure buffer triggers are valid
653
+ ---
654
+ ---@param buf_id number|nil Buffer identifier. Default: current buffer.
655
+ MiniClue.ensure_buf_triggers = function(buf_id)
656
+ MiniClue.disable_buf_triggers(buf_id)
657
+ MiniClue.enable_buf_triggers(buf_id)
658
+ end
659
+
660
+ --- Update description of an existing mapping
661
+ ---
662
+ --- Notes:
663
+ --- - Uses buffer-local mapping in case there are both global and buffer-local
664
+ --- mappings with same mode and LHS. Similar to |maparg()|.
665
+ ---
666
+ ---@param mode string Mapping mode (as in `maparg()`).
667
+ ---@param lhs string Mapping left hand side (as `name` in `maparg()`).
668
+ ---@param desc string New description to set.
669
+ MiniClue.set_mapping_desc = function(mode, lhs, desc)
670
+ if type(mode) ~= 'string' then H.error('`mode` should be string.') end
671
+ if type(lhs) ~= 'string' then H.error('`lhs` should be string.') end
672
+ if type(desc) ~= 'string' then H.error('`desc` should be string.') end
673
+
674
+ local ok_get, map_data = pcall(vim.fn.maparg, lhs, mode, false, true)
675
+ if not ok_get or vim.tbl_count(map_data) == 0 then
676
+ local msg = string.format('No mapping found for mode %s and LHS %s.', vim.inspect(mode), vim.inspect(lhs))
677
+ H.error(msg)
678
+ end
679
+
680
+ map_data.desc = desc
681
+ local ok_set = pcall(vim.fn.mapset, mode, false, map_data)
682
+ if not ok_set then H.error(vim.inspect(desc) .. ' is not a valid description.') end
683
+ end
684
+
685
+ --- Generate pre-configured clues
686
+ ---
687
+ --- This is a table with function elements. Call to actually get array of clues.
688
+ MiniClue.gen_clues = {}
689
+
690
+ --- Generate clues for built-in completion
691
+ ---
692
+ --- Contains clues for the following triggers: >lua
693
+ ---
694
+ --- { mode = 'i', keys = '<C-x>' }
695
+ --- <
696
+ ---@return table Array of clues.
697
+ MiniClue.gen_clues.builtin_completion = function()
698
+ --stylua: ignore
699
+ return {
700
+ { mode = 'i', keys = '<C-x><C-d>', desc = 'Defined identifiers' },
701
+ { mode = 'i', keys = '<C-x><C-e>', desc = 'Scroll up' },
702
+ { mode = 'i', keys = '<C-x><C-f>', desc = 'File names' },
703
+ { mode = 'i', keys = '<C-x><C-i>', desc = 'Identifiers' },
704
+ { mode = 'i', keys = '<C-x><C-k>', desc = 'Identifiers from dictionary' },
705
+ { mode = 'i', keys = '<C-x><C-l>', desc = 'Whole lines' },
706
+ { mode = 'i', keys = '<C-x><C-n>', desc = 'Next completion' },
707
+ { mode = 'i', keys = '<C-x><C-o>', desc = 'Omni completion' },
708
+ { mode = 'i', keys = '<C-x><C-p>', desc = 'Previous completion' },
709
+ { mode = 'i', keys = '<C-x><C-s>', desc = 'Spelling suggestions' },
710
+ { mode = 'i', keys = '<C-x><C-t>', desc = 'Identifiers from thesaurus' },
711
+ { mode = 'i', keys = '<C-x><C-y>', desc = 'Scroll down' },
712
+ { mode = 'i', keys = '<C-x><C-u>', desc = "With 'completefunc'" },
713
+ { mode = 'i', keys = '<C-x><C-v>', desc = 'Like in command line' },
714
+ { mode = 'i', keys = '<C-x><C-z>', desc = 'Stop completion' },
715
+ { mode = 'i', keys = '<C-x><C-]>', desc = 'Tags' },
716
+ { mode = 'i', keys = '<C-x>s', desc = 'Spelling suggestions' },
717
+ }
718
+ end
719
+
720
+ --- Generate clues for `g` key
721
+ ---
722
+ --- Contains clues for the following triggers: >lua
723
+ ---
724
+ --- { mode = { 'n', 'x' }, keys = 'g' }
725
+ --- <
726
+ ---@return table Array of clues.
727
+ MiniClue.gen_clues.g = function()
728
+ local gr_clue = vim.fn.has('nvim-0.11') == 1 and { mode = 'n', keys = 'gr', desc = '+LSP' }
729
+ or { mode = 'n', keys = 'gr', desc = 'Virtual replace with character' }
730
+ local gr_clue_viz = vim.fn.has('nvim-0.11') == 1 and { mode = 'x', keys = 'gr', desc = '+LSP' } or {}
731
+
732
+ --stylua: ignore
733
+ return {
734
+ { mode = 'n', keys = 'g0', desc = 'Go to leftmost visible column' },
735
+ { mode = 'n', keys = 'g8', desc = 'Print hex value of char under cursor' },
736
+ { mode = 'n', keys = 'ga', desc = 'Print ascii value' },
737
+ { mode = 'n', keys = 'gD', desc = 'Go to definition in file' },
738
+ { mode = 'n', keys = 'gd', desc = 'Go to definition in function' },
739
+ { mode = 'n', keys = 'gE', desc = 'Go backwards to end of previous WORD' },
740
+ { mode = 'n', keys = 'ge', desc = 'Go backwards to end of previous word' },
741
+ { mode = 'n', keys = 'gF', desc = 'Edit file under cursor + jump line' },
742
+ { mode = 'n', keys = 'gf', desc = 'Edit file under cursor' },
743
+ { mode = 'n', keys = 'gg', desc = 'Go to line (def: first)' },
744
+ { mode = 'n', keys = 'gH', desc = 'Start Select line mode' },
745
+ { mode = 'n', keys = 'gh', desc = 'Start Select mode' },
746
+ { mode = 'n', keys = 'gI', desc = 'Start Insert at column 1' },
747
+ { mode = 'n', keys = 'gi', desc = 'Start Insert where it stopped' },
748
+ { mode = 'n', keys = 'gJ', desc = 'Join lines without extra spaces' },
749
+ { mode = 'n', keys = 'gj', desc = 'Go down by screen lines' },
750
+ { mode = 'n', keys = 'gk', desc = 'Go up by screen lines' },
751
+ { mode = 'n', keys = 'gM', desc = 'Go to middle of text line' },
752
+ { mode = 'n', keys = 'gm', desc = 'Go to middle of screen line' },
753
+ { mode = 'n', keys = 'gN', desc = 'Select previous search match' },
754
+ { mode = 'n', keys = 'gn', desc = 'Select next search match' },
755
+ { mode = 'n', keys = 'go', desc = 'Go to byte' },
756
+ { mode = 'n', keys = 'gP', desc = 'Put text before cursor + stay after it' },
757
+ { mode = 'n', keys = 'gp', desc = 'Put text after cursor + stay after it' },
758
+ { mode = 'n', keys = 'gQ', desc = 'Switch to "Ex" mode' },
759
+ { mode = 'n', keys = 'gq', desc = 'Format text (operator)' },
760
+ { mode = 'n', keys = 'gR', desc = 'Enter Virtual Replace mode' },
761
+ gr_clue,
762
+ { mode = 'n', keys = 'gs', desc = 'Sleep' },
763
+ { mode = 'n', keys = 'gT', desc = 'Go to previous tabpage' },
764
+ { mode = 'n', keys = 'gt', desc = 'Go to next tabpage' },
765
+ { mode = 'n', keys = 'gU', desc = 'Make uppercase (operator)' },
766
+ { mode = 'n', keys = 'gu', desc = 'Make lowercase (operator)' },
767
+ { mode = 'n', keys = 'gV', desc = 'Avoid reselect' },
768
+ { mode = 'n', keys = 'gv', desc = 'Reselect previous Visual area' },
769
+ { mode = 'n', keys = 'gw', desc = 'Format text + keep cursor (operator)' },
770
+ { mode = 'n', keys = 'gx', desc = 'Execute app for file under cursor' },
771
+ { mode = 'n', keys = 'g<C-]>', desc = '`:tjump` to tag under cursor' },
772
+ { mode = 'n', keys = 'g<C-a>', desc = 'Dump a memory profile' },
773
+ { mode = 'n', keys = 'g<C-g>', desc = 'Show information about cursor' },
774
+ { mode = 'n', keys = 'g<C-h>', desc = 'Start Select block mode' },
775
+ { mode = 'n', keys = 'g<Tab>', desc = 'Go to last accessed tabpage' },
776
+ { mode = 'n', keys = "g'", desc = "Jump to mark (don't affect jumplist)" },
777
+ { mode = 'n', keys = 'g#', desc = 'Search backwards word under cursor' },
778
+ { mode = 'n', keys = 'g$', desc = 'Go to rightmost visible column' },
779
+ { mode = 'n', keys = 'g%', desc = 'Cycle through matching groups' },
780
+ { mode = 'n', keys = 'g&', desc = 'Repeat last `:s` on all lines' },
781
+ { mode = 'n', keys = 'g*', desc = 'Search word under cursor' },
782
+ { mode = 'n', keys = 'g+', desc = 'Go to newer text state' },
783
+ { mode = 'n', keys = 'g,', desc = 'Go to newer position in change list' },
784
+ { mode = 'n', keys = 'g-', desc = 'Go to older text state' },
785
+ { mode = 'n', keys = 'g;', desc = 'Go to older position in change list' },
786
+ { mode = 'n', keys = 'g<', desc = 'Display previous command output' },
787
+ { mode = 'n', keys = 'g?', desc = 'Rot13 encode (operator)' },
788
+ { mode = 'n', keys = 'g@', desc = "Call 'operatorfunc' (operator)" },
789
+ { mode = 'n', keys = 'g]', desc = '`:tselect` tag under cursor' },
790
+ { mode = 'n', keys = 'g^', desc = 'Go to leftmost visible non-whitespace' },
791
+ { mode = 'n', keys = 'g_', desc = 'Go to lower line' },
792
+ { mode = 'n', keys = 'g`', desc = "Jump to mark (don't affect jumplist)" },
793
+ { mode = 'n', keys = 'g~', desc = 'Swap case (operator)' },
794
+
795
+ { mode = 'x', keys = 'gf', desc = 'Edit selected file' },
796
+ { mode = 'x', keys = 'gJ', desc = 'Join selected lines without extra spaces' },
797
+ { mode = 'x', keys = 'gq', desc = 'Format selection' },
798
+ gr_clue_viz,
799
+ { mode = 'x', keys = 'gV', desc = 'Avoid reselect' },
800
+ { mode = 'x', keys = 'gw', desc = 'Format selection + keep cursor' },
801
+ { mode = 'x', keys = 'g<C-]>', desc = '`:tjump` to selected tag' },
802
+ { mode = 'x', keys = 'g<C-a>', desc = 'Increment with compound' },
803
+ { mode = 'x', keys = 'g<C-g>', desc = 'Show information about selection' },
804
+ { mode = 'x', keys = 'g<C-x>', desc = 'Decrement with compound' },
805
+ { mode = 'x', keys = 'g]', desc = '`:tselect` selected tag' },
806
+ { mode = 'x', keys = 'g?', desc = 'Rot13 encode selection' },
807
+ }
808
+ end
809
+
810
+ --- Generate clues for `[` and `]` keys
811
+ ---
812
+ --- Contains clues for the following triggers: >lua
813
+ ---
814
+ --- { mode = 'n', keys = '[' }
815
+ --- { mode = 'n', keys = ']' }
816
+ --- <
817
+ ---@return table Array of clues.
818
+ MiniClue.gen_clues.square_brackets = function()
819
+ --stylua: ignore
820
+ return {
821
+ { mode = 'n', keys = '[<C-D>', desc = 'Go to first macro def with cursor word', },
822
+ { mode = 'n', keys = '[<C-I>', desc = 'Go to first match with cursor word', },
823
+ { mode = 'n', keys = '[%', desc = 'Go to previous unmatched group' },
824
+ { mode = 'n', keys = '[#', desc = 'Go to previous unmatched #if/#else/#ifdef' },
825
+ { mode = 'n', keys = "['", desc = 'Go to previous mark, first non-blank' },
826
+ { mode = 'n', keys = '[`', desc = 'Go to previous mark' },
827
+ { mode = 'n', keys = '[(', desc = "Go to previous unmatched '('" },
828
+ { mode = 'n', keys = '[/', desc = 'Go to previous C comment start' },
829
+ { mode = 'n', keys = '[*', desc = 'Go to previous C comment start' },
830
+ { mode = 'n', keys = '[I', desc = 'Show lines with cursor word', },
831
+ { mode = 'n', keys = '[D', desc = 'Show macro defs with cursor word' },
832
+ { mode = 'n', keys = '[p', desc = 'Paste with current indent' },
833
+ { mode = 'n', keys = '[P', desc = 'Paste with current indent' },
834
+ { mode = 'n', keys = '[[', desc = 'Go to previous section' },
835
+ { mode = 'n', keys = '[]', desc = 'Go to previous SECTION' },
836
+ { mode = 'n', keys = '[c', desc = 'Go to previous change' },
837
+ { mode = 'n', keys = '[d', desc = 'Show first macro def with cursor word' },
838
+ { mode = 'n', keys = '[f', desc = 'Edit file under cursor' },
839
+ { mode = 'n', keys = '[i', desc = 'Show first line with cursor word', },
840
+ { mode = 'n', keys = '[m', desc = 'Go to previous method start' },
841
+ { mode = 'n', keys = '[M', desc = 'Go to previous method end' },
842
+ { mode = 'n', keys = '[s', desc = 'Go to previous misspelled word' },
843
+ { mode = 'n', keys = '[z', desc = 'Go to current open fold start' },
844
+ { mode = 'n', keys = '[{', desc = "Go to previous unmatched '{'" },
845
+ { mode = 'n', keys = ']<C-D>', desc = 'Go to next macro def with cursor word', },
846
+ { mode = 'n', keys = ']<C-I>', desc = 'Go to next match with cursor word', },
847
+ { mode = 'n', keys = ']%', desc = 'Go to next unmatched group' },
848
+ { mode = 'n', keys = ']#', desc = 'Go to next unmatched #if/#else/#ifdef' },
849
+ { mode = 'n', keys = "]'", desc = "Go to next mark, first non-blank" },
850
+ { mode = 'n', keys = ']`', desc = 'Go to next mark' },
851
+ { mode = 'n', keys = '])', desc = "Go to next unmatched ')'" },
852
+ { mode = 'n', keys = ']/', desc = 'Go to next C comment end' },
853
+ { mode = 'n', keys = ']*', desc = 'Go to next C comment end' },
854
+ { mode = 'n', keys = ']D', desc = 'Show below macro defs with cursor word' },
855
+ { mode = 'n', keys = ']I', desc = 'Show below lines with cursor word', },
856
+ { mode = 'n', keys = ']P', desc = 'Paste with current indent' },
857
+ { mode = 'n', keys = '][', desc = 'Go to next SECTION' },
858
+ { mode = 'n', keys = ']]', desc = 'Go to next section' },
859
+ { mode = 'n', keys = ']c', desc = 'Go to next change' },
860
+ { mode = 'n', keys = ']d', desc = 'Show next macro def with cursor word' },
861
+ { mode = 'n', keys = ']f', desc = 'Edit file under cursor' },
862
+ { mode = 'n', keys = ']i', desc = 'Show next line with cursor word', },
863
+ { mode = 'n', keys = ']m', desc = 'Go to next method start' },
864
+ { mode = 'n', keys = ']M', desc = 'Go to next method end' },
865
+ { mode = 'n', keys = ']p', desc = 'Paste with current indent' },
866
+ { mode = 'n', keys = ']s', desc = 'Go to next misspelled word' },
867
+ { mode = 'n', keys = ']z', desc = 'Go to current open fold end' },
868
+ { mode = 'n', keys = ']}', desc = "Go to next unmatched '}'" },
869
+ }
870
+ end
871
+
872
+ --- Generate clues for marks
873
+ ---
874
+ --- Contains clues for the following triggers: >lua
875
+ ---
876
+ --- { mode = { 'n', 'x' }, keys = "'" }
877
+ --- { mode = { 'n', 'x' }, keys = "g'" }
878
+ --- { mode = { 'n', 'x' }, keys = '`' }
879
+ --- { mode = { 'n', 'x' }, keys = 'g`' }
880
+ --- <
881
+ --- Note: if you use `g` as trigger (like to enable |MiniClue.gen_clues.g()|),
882
+ --- don't add |g'| and |g`| as triggers: they already will be taken into account.
883
+ ---
884
+ ---@return table Array of clues.
885
+ ---
886
+ ---@seealso |mark-motions|
887
+ MiniClue.gen_clues.marks = function()
888
+ local describe_marks = function(mode, prefix)
889
+ local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
890
+
891
+ return {
892
+ make_clue('^', 'Latest insert position'),
893
+ make_clue('.', 'Latest change'),
894
+ make_clue('"', 'Latest exited position'),
895
+ make_clue("'", 'Line before jump'),
896
+ make_clue('`', 'Position before jump'),
897
+ make_clue('[', 'Start of latest changed or yanked text'),
898
+ make_clue(']', 'End of latest changed or yanked text'),
899
+ make_clue('(', 'Start of sentence'),
900
+ make_clue(')', 'End of sentence'),
901
+ make_clue('{', 'Start of paragraph'),
902
+ make_clue('}', 'End of paragraph'),
903
+ make_clue('<', 'Start of latest visual selection'),
904
+ make_clue('>', 'End of latest visual selection'),
905
+ }
906
+ end
907
+
908
+ --stylua: ignore
909
+ return {
910
+ -- Normal and Visual mode
911
+ describe_marks({ 'n', 'x' }, "'"),
912
+ describe_marks({ 'n', 'x' }, "g'"),
913
+ describe_marks({ 'n', 'x' }, "`"),
914
+ describe_marks({ 'n', 'x' }, "g`"),
915
+ }
916
+ end
917
+
918
+ --- Generate clues for registers
919
+ ---
920
+ --- Contains clues for the following triggers: >lua
921
+ ---
922
+ --- { mode = { 'n', 'x' }, keys = '"' }
923
+ --- { mode = { 'i', 'c' }, keys = '<C-r>' }
924
+ --- <
925
+ ---@param opts table|nil Options. Possible keys:
926
+ --- - <show_contents> `(boolean)` - whether to show contents of all possible
927
+ --- registers. If `false`, only description of special registers is shown.
928
+ --- Default: `false`.
929
+ ---
930
+ ---@return table Array of clues.
931
+ ---
932
+ ---@seealso |registers|
933
+ MiniClue.gen_clues.registers = function(opts)
934
+ opts = vim.tbl_deep_extend('force', { show_contents = false }, opts or {})
935
+
936
+ local describe_registers
937
+ if opts.show_contents then
938
+ describe_registers = H.make_clues_with_register_contents
939
+ else
940
+ describe_registers = function(mode, prefix)
941
+ local make_clue = function(register, desc) return { mode = mode, keys = prefix .. register, desc = desc } end
942
+ return {
943
+ make_clue('0', 'Latest yank'),
944
+ make_clue('1', 'Latest big delete'),
945
+ make_clue('"', 'Default register'),
946
+ make_clue('#', 'Alternate buffer'),
947
+ make_clue('%', 'Name of the current file'),
948
+ make_clue('*', 'Selection clipboard'),
949
+ make_clue('+', 'System clipboard'),
950
+ make_clue('-', 'Latest small delete'),
951
+ make_clue('.', 'Latest inserted text'),
952
+ make_clue('/', 'Latest search pattern'),
953
+ make_clue(':', 'Latest executed command'),
954
+ make_clue('=', 'Result of expression'),
955
+ make_clue('_', 'Black hole'),
956
+ }
957
+ end
958
+ end
959
+
960
+ --stylua: ignore
961
+ return {
962
+ -- Normal and Visual mode
963
+ describe_registers({ 'n', 'x' }, '"'),
964
+
965
+ -- Insert mode
966
+ describe_registers('i', '<C-r>'),
967
+
968
+ { mode = 'i', keys = '<C-r><C-r>', desc = '+Insert literally' },
969
+ describe_registers('i', '<C-r><C-r>'),
970
+
971
+ { mode = 'i', keys = '<C-r><C-o>', desc = '+Insert literally + not auto-indent' },
972
+ describe_registers('i', '<C-r><C-o>'),
973
+
974
+ { mode = 'i', keys = '<C-r><C-p>', desc = '+Insert + fix indent' },
975
+ describe_registers('i', '<C-r><C-p>'),
976
+
977
+ -- Command-line mode
978
+ describe_registers('c', '<C-r>'),
979
+
980
+ { mode = 'c', keys = '<C-r><C-r>', desc = '+Insert literally' },
981
+ describe_registers('c', '<C-r><C-r>'),
982
+
983
+ { mode = 'c', keys = '<C-r><C-o>', desc = '+Insert literally' },
984
+ describe_registers('c', '<C-r><C-o>'),
985
+ }
986
+ end
987
+
988
+ --- Generate clues for window commands
989
+ ---
990
+ --- Contains clues for the following triggers: >lua
991
+ ---
992
+ --- { mode = 'n', keys = '<C-w>' }
993
+ --- <
994
+ --- Note: only non-duplicated commands are included. For full list see |CTRL-W|.
995
+ ---
996
+ ---@param opts table|nil Options. Possible keys:
997
+ --- - <submode_move> `(boolean)` - whether to make move (change layout)
998
+ --- commands a submode by using `postkeys` field. Default: `false`.
999
+ --- - <submode_navigate> `(boolean)` - whether to make navigation (change
1000
+ --- focus) commands a submode by using `postkeys` field. Default: `false`.
1001
+ --- - <submode_resize> `(boolean)` - whether to make resize (change size)
1002
+ --- commands a submode by using `postkeys` field. Default: `false`.
1003
+ ---
1004
+ ---@return table Array of clues.
1005
+ MiniClue.gen_clues.windows = function(opts)
1006
+ local default_opts = { submode_navigate = false, submode_move = false, submode_resize = false }
1007
+ opts = vim.tbl_deep_extend('force', default_opts, opts or {})
1008
+
1009
+ local postkeys_move, postkeys_navigate, postkeys_resize = nil, nil, nil
1010
+ if opts.submode_move then postkeys_move = '<C-w>' end
1011
+ if opts.submode_navigate then postkeys_navigate = '<C-w>' end
1012
+ if opts.submode_resize then postkeys_resize = '<C-w>' end
1013
+
1014
+ --stylua: ignore
1015
+ return {
1016
+ { mode = 'n', keys = '<C-w>+', desc = 'Increase height', postkeys = postkeys_resize },
1017
+ { mode = 'n', keys = '<C-w>-', desc = 'Decrease height', postkeys = postkeys_resize },
1018
+ { mode = 'n', keys = '<C-w><', desc = 'Decrease width', postkeys = postkeys_resize },
1019
+ { mode = 'n', keys = '<C-w>>', desc = 'Increase width', postkeys = postkeys_resize },
1020
+ { mode = 'n', keys = '<C-w>=', desc = 'Make windows same dimensions' },
1021
+ { mode = 'n', keys = '<C-w>]', desc = 'Split + jump to tag' },
1022
+ { mode = 'n', keys = '<C-w>^', desc = 'Split + edit alternate file' },
1023
+ { mode = 'n', keys = '<C-w>_', desc = 'Set height (def: very high)' },
1024
+ { mode = 'n', keys = '<C-w>|', desc = 'Set width (def: very wide)' },
1025
+ { mode = 'n', keys = '<C-w>}', desc = 'Show tag in preview' },
1026
+ { mode = 'n', keys = '<C-w>b', desc = 'Focus bottom', postkeys = postkeys_navigate },
1027
+ { mode = 'n', keys = '<C-w>c', desc = 'Close' },
1028
+ { mode = 'n', keys = '<C-w>d', desc = 'Split + jump to definition' },
1029
+ { mode = 'n', keys = '<C-w>F', desc = 'Split + edit file name + jump' },
1030
+ { mode = 'n', keys = '<C-w>f', desc = 'Split + edit file name' },
1031
+ { mode = 'n', keys = '<C-w>g', desc = '+Extra actions' },
1032
+ { mode = 'n', keys = '<C-w>g]', desc = 'Split + list tags' },
1033
+ { mode = 'n', keys = '<C-w>g}', desc = 'Do `:ptjump`' },
1034
+ { mode = 'n', keys = '<C-w>g<C-]>', desc = 'Split + jump to tag with `:tjump`' },
1035
+ { mode = 'n', keys = '<C-w>g<Tab>', desc = 'Focus last accessed tab', postkeys = postkeys_navigate },
1036
+ { mode = 'n', keys = '<C-w>gF', desc = 'New tabpage + edit file name + jump' },
1037
+ { mode = 'n', keys = '<C-w>gf', desc = 'New tabpage + edit file name' },
1038
+ { mode = 'n', keys = '<C-w>gT', desc = 'Focus previous tabpage', postkeys = postkeys_navigate },
1039
+ { mode = 'n', keys = '<C-w>gt', desc = 'Focus next tabpage', postkeys = postkeys_navigate },
1040
+ { mode = 'n', keys = '<C-w>H', desc = 'Move to very left', postkeys = postkeys_move },
1041
+ { mode = 'n', keys = '<C-w>h', desc = 'Focus left', postkeys = postkeys_navigate },
1042
+ { mode = 'n', keys = '<C-w>i', desc = 'Split + jump to declaration' },
1043
+ { mode = 'n', keys = '<C-w>J', desc = 'Move to very bottom', postkeys = postkeys_move },
1044
+ { mode = 'n', keys = '<C-w>j', desc = 'Focus down', postkeys = postkeys_navigate },
1045
+ { mode = 'n', keys = '<C-w>K', desc = 'Move to very top', postkeys = postkeys_move },
1046
+ { mode = 'n', keys = '<C-w>k', desc = 'Focus up', postkeys = postkeys_navigate },
1047
+ { mode = 'n', keys = '<C-w>L', desc = 'Move to very right', postkeys = postkeys_move },
1048
+ { mode = 'n', keys = '<C-w>l', desc = 'Focus right', postkeys = postkeys_navigate },
1049
+ { mode = 'n', keys = '<C-w>n', desc = 'Open new' },
1050
+ { mode = 'n', keys = '<C-w>o', desc = 'Close all but current' },
1051
+ { mode = 'n', keys = '<C-w>P', desc = 'Focus preview', postkeys = postkeys_navigate },
1052
+ { mode = 'n', keys = '<C-w>p', desc = 'Focus last accessed', postkeys = postkeys_navigate },
1053
+ { mode = 'n', keys = '<C-w>q', desc = 'Quit current' },
1054
+ { mode = 'n', keys = '<C-w>R', desc = 'Rotate up/left', postkeys = postkeys_move },
1055
+ { mode = 'n', keys = '<C-w>r', desc = 'Rotate down/right', postkeys = postkeys_move },
1056
+ { mode = 'n', keys = '<C-w>s', desc = 'Split horizontally' },
1057
+ { mode = 'n', keys = '<C-w>T', desc = 'Create new tabpage + move' },
1058
+ { mode = 'n', keys = '<C-w>t', desc = 'Focus top', postkeys = postkeys_navigate },
1059
+ { mode = 'n', keys = '<C-w>v', desc = 'Split vertically' },
1060
+ { mode = 'n', keys = '<C-w>W', desc = 'Focus previous', postkeys = postkeys_navigate },
1061
+ { mode = 'n', keys = '<C-w>w', desc = 'Focus next', postkeys = postkeys_navigate },
1062
+ { mode = 'n', keys = '<C-w>x', desc = 'Exchange windows', postkeys = postkeys_move },
1063
+ { mode = 'n', keys = '<C-w>z', desc = 'Close preview' },
1064
+ }
1065
+ end
1066
+
1067
+ --- Generate clues for `z` key
1068
+ ---
1069
+ --- Contains clues for the following triggers: >lua
1070
+ ---
1071
+ --- { mode = { 'n', 'x' }, keys = 'z' }
1072
+ --- <
1073
+ ---@return table Array of clues.
1074
+ MiniClue.gen_clues.z = function()
1075
+ --stylua: ignore
1076
+ return {
1077
+ { mode = 'n', keys = 'zA', desc = 'Toggle folds recursively' },
1078
+ { mode = 'n', keys = 'za', desc = 'Toggle fold' },
1079
+ { mode = 'n', keys = 'zb', desc = 'Redraw at bottom' },
1080
+ { mode = 'n', keys = 'zC', desc = 'Close folds recursively' },
1081
+ { mode = 'n', keys = 'zc', desc = 'Close fold' },
1082
+ { mode = 'n', keys = 'zD', desc = 'Delete folds recursively' },
1083
+ { mode = 'n', keys = 'zd', desc = 'Delete fold' },
1084
+ { mode = 'n', keys = 'zE', desc = 'Eliminate all folds' },
1085
+ { mode = 'n', keys = 'ze', desc = 'Scroll to cursor on right screen side' },
1086
+ { mode = 'n', keys = 'zF', desc = 'Create fold' },
1087
+ { mode = 'n', keys = 'zf', desc = 'Create fold (operator)' },
1088
+ { mode = 'n', keys = 'zG', desc = 'Temporarily mark as correctly spelled' },
1089
+ { mode = 'n', keys = 'zg', desc = 'Permanently mark as correctly spelled' },
1090
+ { mode = 'n', keys = 'zH', desc = 'Scroll left half screen' },
1091
+ { mode = 'n', keys = 'zh', desc = 'Scroll left' },
1092
+ { mode = 'n', keys = 'zi', desc = "Toggle 'foldenable'" },
1093
+ { mode = 'n', keys = 'zj', desc = 'Move to start of next fold' },
1094
+ { mode = 'n', keys = 'zk', desc = 'Move to end of previous fold' },
1095
+ { mode = 'n', keys = 'zL', desc = 'Scroll right half screen' },
1096
+ { mode = 'n', keys = 'zl', desc = 'Scroll right' },
1097
+ { mode = 'n', keys = 'zM', desc = 'Close all folds' },
1098
+ { mode = 'n', keys = 'zm', desc = 'Fold more' },
1099
+ { mode = 'n', keys = 'zN', desc = "Set 'foldenable'" },
1100
+ { mode = 'n', keys = 'zn', desc = "Reset 'foldenable'" },
1101
+ { mode = 'n', keys = 'zO', desc = 'Open folds recursively' },
1102
+ { mode = 'n', keys = 'zo', desc = 'Open fold' },
1103
+ { mode = 'n', keys = 'zP', desc = 'Paste without trailspace' },
1104
+ { mode = 'n', keys = 'zp', desc = 'Paste without trailspace' },
1105
+ { mode = 'n', keys = 'zR', desc = 'Open all folds' },
1106
+ { mode = 'n', keys = 'zr', desc = 'Fold less' },
1107
+ { mode = 'n', keys = 'zs', desc = 'Scroll to cursor on left screen side' },
1108
+ { mode = 'n', keys = 'zt', desc = 'Redraw at top' },
1109
+ { mode = 'n', keys = 'zu', desc = '+Undo spelling commands' },
1110
+ { mode = 'n', keys = 'zug', desc = 'Undo `zg`' },
1111
+ { mode = 'n', keys = 'zuG', desc = 'Undo `zG`' },
1112
+ { mode = 'n', keys = 'zuw', desc = 'Undo `zw`' },
1113
+ { mode = 'n', keys = 'zuW', desc = 'Undo `zW`' },
1114
+ { mode = 'n', keys = 'zv', desc = 'Open enough folds' },
1115
+ { mode = 'n', keys = 'zW', desc = 'Temporarily mark as incorrectly spelled' },
1116
+ { mode = 'n', keys = 'zw', desc = 'Permanently mark as incorrectly spelled' },
1117
+ { mode = 'n', keys = 'zX', desc = 'Update folds' },
1118
+ { mode = 'n', keys = 'zx', desc = 'Update folds + open enough folds' },
1119
+ { mode = 'n', keys = 'zy', desc = 'Yank without trailing spaces (operator)' },
1120
+ { mode = 'n', keys = 'zz', desc = 'Redraw at center' },
1121
+ { mode = 'n', keys = 'z+', desc = 'Redraw under bottom at top' },
1122
+ { mode = 'n', keys = 'z-', desc = 'Redraw at bottom + cursor on first non-blank' },
1123
+ { mode = 'n', keys = 'z.', desc = 'Redraw at center + cursor on first non-blank' },
1124
+ { mode = 'n', keys = 'z=', desc = 'Show spelling suggestions' },
1125
+ { mode = 'n', keys = 'z^', desc = 'Redraw above top at bottom' },
1126
+
1127
+ { mode = 'x', keys = 'zf', desc = 'Create fold from selection' },
1128
+ }
1129
+ end
1130
+
1131
+ -- Helper data ================================================================
1132
+ -- Module default config
1133
+ H.default_config = vim.deepcopy(MiniClue.config)
1134
+
1135
+ -- Namespaces
1136
+ H.ns_id = {
1137
+ highlight = vim.api.nvim_create_namespace('MiniClueHighlight'),
1138
+ }
1139
+
1140
+ -- State of user input
1141
+ H.state = {
1142
+ trigger = nil,
1143
+ -- Array of raw keys
1144
+ query = {},
1145
+ clues = {},
1146
+ timer = vim.loop.new_timer(),
1147
+ buf_id = nil,
1148
+ win_id = nil,
1149
+ is_after_postkeys = false,
1150
+ }
1151
+
1152
+ -- Default window config
1153
+ H.default_win_config = {
1154
+ anchor = 'SE',
1155
+ focusable = false,
1156
+ relative = 'editor',
1157
+ style = 'minimal',
1158
+ width = 30,
1159
+ -- Use high enough value to be on top of built-in windows (pmenu, etc.)
1160
+ zindex = 251,
1161
+ }
1162
+
1163
+ -- Precomputed raw keys
1164
+ H.keys = {
1165
+ bs = vim.api.nvim_replace_termcodes('<BS>', true, true, true),
1166
+ cr = vim.api.nvim_replace_termcodes('<CR>', true, true, true),
1167
+ exit = vim.api.nvim_replace_termcodes([[<C-\><C-n>]], true, true, true),
1168
+ ctrl_d = vim.api.nvim_replace_termcodes('<C-d>', true, true, true),
1169
+ ctrl_u = vim.api.nvim_replace_termcodes('<C-u>', true, true, true),
1170
+ }
1171
+
1172
+ -- Special filetypes for which to enable triggers. These are common interactive
1173
+ -- not listed filetypes. NOTE: no 'minifiles' as `'` trigger conflicts with its
1174
+ -- local `'`. Plus it pollutes `g?` content.
1175
+ H.ft_to_enable = { help = true, git = true, ministarter = true }
1176
+
1177
+ -- Timers
1178
+ H.timers = {
1179
+ getcharstr = vim.loop.new_timer(),
1180
+ }
1181
+
1182
+ -- Undo autocommand to be created for several operator tweaks
1183
+ H.undo_autocommand = 'au ModeChanged * ++once undo!'
1184
+
1185
+ -- Helper functionality =======================================================
1186
+ -- Settings -------------------------------------------------------------------
1187
+ H.setup_config = function(config)
1188
+ H.check_type('config', config, 'table', true)
1189
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1190
+
1191
+ H.check_type('clues', config.clues, 'table')
1192
+ H.check_type('triggers', config.triggers, 'table')
1193
+
1194
+ H.check_type('window', config.window, 'table')
1195
+ if not (type(config.window.config) == 'table' or vim.is_callable(config.window.config)) then
1196
+ H.error('`window.config` should be table or callable, not ' .. type(config.window.config))
1197
+ end
1198
+ H.check_type('window.delay', config.window.delay, 'number')
1199
+ H.check_type('window.scroll_down', config.window.scroll_down, 'string')
1200
+ H.check_type('window.scroll_up', config.window.scroll_up, 'string')
1201
+
1202
+ return config
1203
+ end
1204
+
1205
+ H.apply_config = function(config)
1206
+ MiniClue.config = config
1207
+
1208
+ -- Create trigger keymaps for all existing buffers
1209
+ MiniClue.enable_all_triggers()
1210
+
1211
+ -- Tweak macro execution
1212
+ local exec_macro = function(key, register)
1213
+ if register == nil then return end
1214
+ MiniClue.disable_all_triggers()
1215
+ vim.schedule(function() MiniClue.enable_all_triggers() end)
1216
+ -- NOTE: Use `t` flag for "Handle as if typed" for better integration with
1217
+ -- other modules/plugins (like 'mini.jump').
1218
+ pcall(vim.api.nvim_feedkeys, vim.v.count1 .. key .. register, 'nt', false)
1219
+ end
1220
+
1221
+ local macro_keymap_opts = { nowait = true, desc = "Execute macro without 'mini.clue' triggers" }
1222
+
1223
+ local exec_register_macro = function() exec_macro('@', H.getcharstr()) end
1224
+ if vim.fn.maparg('@', 'n') == '' then vim.keymap.set('n', '@', exec_register_macro, macro_keymap_opts) end
1225
+
1226
+ local exec_latest_macro = function() exec_macro('Q', '') end
1227
+ if vim.fn.maparg('Q', 'n') == '' then vim.keymap.set('n', 'Q', exec_latest_macro, macro_keymap_opts) end
1228
+ end
1229
+
1230
+ H.is_disabled = function(buf_id)
1231
+ local buf_disable = H.get_buf_var(buf_id, 'miniclue_disable')
1232
+ return vim.g.miniclue_disable == true or buf_disable == true
1233
+ end
1234
+
1235
+ H.create_autocommands = function()
1236
+ local gr = vim.api.nvim_create_augroup('MiniClue', {})
1237
+
1238
+ local au = function(event, pattern, callback, desc)
1239
+ vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
1240
+ end
1241
+
1242
+ -- Ensure buffer-local mappings for triggers are the latest ones to fully
1243
+ -- utilize `<nowait>`. Use `vim.schedule_wrap` to allow other events to
1244
+ -- create `vim.b.miniclue_config` and `vim.b.miniclue_disable`.
1245
+ -- Check for listed buffer in `BufWinEnter` (instead of using `BufAdd`) to
1246
+ -- delay acting until buffer is loaded (otherwise buffer-local options can be
1247
+ -- prematurely "finalized"). Process it at most once for performance.
1248
+ local did_ensure = {}
1249
+ local ensure_triggers = vim.schedule_wrap(function(ev)
1250
+ if not H.is_valid_buf(ev.buf) then return end
1251
+ local skip_triggers = ev.event == 'BufWinEnter' and (did_ensure[ev.buf] or vim.fn.buflisted(ev.buf) ~= 1)
1252
+ did_ensure[ev.buf] = true
1253
+ if skip_triggers then return end
1254
+ MiniClue.ensure_buf_triggers(ev.buf)
1255
+ end)
1256
+ -- - Respect `LspAttach` as it is a common source of buffer-local mappings
1257
+ au({ 'BufWinEnter', 'LspAttach' }, '*', ensure_triggers, 'Ensure buffer-local trigger keymaps')
1258
+ au('BufUnload', '*', function(ev) did_ensure[ev.buf] = nil end, 'Track buffer-local trigger keymaps')
1259
+ au('Filetype', vim.tbl_keys(H.ft_to_enable), ensure_triggers, 'Ensure buffer-local trigger keymaps')
1260
+
1261
+ -- Disable all triggers (current and future) when recording macro as they
1262
+ -- interfere with what is actually recorded
1263
+ local cache_disable
1264
+ local disable_all_plus = function()
1265
+ MiniClue.disable_all_triggers()
1266
+ cache_disable = vim.g.miniclue_disable
1267
+ vim.g.miniclue_disable = true
1268
+ end
1269
+ local enable_all_plus = function()
1270
+ vim.g.miniclue_disable = cache_disable
1271
+ MiniClue.enable_all_triggers()
1272
+ end
1273
+ au('RecordingEnter', '*', disable_all_plus, 'Disable all triggers')
1274
+ au('RecordingLeave', '*', enable_all_plus, 'Enable all triggers')
1275
+
1276
+ au('VimResized', '*', H.window_update, 'Update window on resize')
1277
+ au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
1278
+ end
1279
+
1280
+ --stylua: ignore
1281
+ H.create_default_hl = function()
1282
+ local hi = function(name, opts)
1283
+ opts.default = true
1284
+ vim.api.nvim_set_hl(0, name, opts)
1285
+ end
1286
+
1287
+ hi('MiniClueBorder', { link = 'FloatBorder' })
1288
+ hi('MiniClueDescGroup', { link = 'DiagnosticFloatingWarn' })
1289
+ hi('MiniClueDescSingle', { link = 'NormalFloat' })
1290
+ hi('MiniClueNextKey', { link = 'DiagnosticFloatingHint' })
1291
+ hi('MiniClueNextKeyWithPostkeys', { link = 'DiagnosticFloatingError' })
1292
+ hi('MiniClueSeparator', { link = 'DiagnosticFloatingInfo' })
1293
+ hi('MiniClueTitle', { link = 'FloatTitle' })
1294
+ end
1295
+
1296
+ H.get_config = function(config, buf_id)
1297
+ config = config or {}
1298
+ local buf_config = H.get_buf_var(buf_id, 'miniclue_config') or {}
1299
+ local global_config = MiniClue.config
1300
+
1301
+ -- Manually reconstruct to allow array elements to be concatenated
1302
+ local res = {
1303
+ clues = H.list_concat(global_config.clues, buf_config.clues, config.clues),
1304
+ triggers = H.list_concat(global_config.triggers, buf_config.triggers, config.triggers),
1305
+ window = vim.tbl_deep_extend('force', global_config.window, buf_config.window or {}, config.window or {}),
1306
+ }
1307
+ return res
1308
+ end
1309
+
1310
+ H.get_buf_var = function(buf_id, name)
1311
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
1312
+ if not H.is_valid_buf(buf_id) then return nil end
1313
+ return vim.b[buf_id][name]
1314
+ end
1315
+
1316
+ -- Triggers -------------------------------------------------------------------
1317
+ H.map_buf_triggers = function(buf_id)
1318
+ if not H.is_valid_buf(buf_id) or H.is_disabled(buf_id) then return end
1319
+
1320
+ for _, trigger in ipairs(H.get_config(nil, buf_id).triggers) do
1321
+ local modes = type(trigger.mode) == 'table' and trigger.mode or { trigger.mode }
1322
+ for _, mode in ipairs(modes) do
1323
+ H.map_trigger(buf_id, { mode = mode, keys = trigger.keys })
1324
+ end
1325
+ end
1326
+ end
1327
+
1328
+ H.unmap_buf_triggers = function(buf_id)
1329
+ if not H.is_valid_buf(buf_id) or H.is_disabled(buf_id) then return end
1330
+
1331
+ for _, trigger in ipairs(H.get_config(nil, buf_id).triggers) do
1332
+ local modes = type(trigger.mode) == 'table' and trigger.mode or { trigger.mode }
1333
+ for _, mode in ipairs(modes) do
1334
+ H.unmap_trigger(buf_id, { mode = mode, keys = trigger.keys })
1335
+ end
1336
+ end
1337
+ end
1338
+
1339
+ H.map_trigger = function(buf_id, trigger)
1340
+ if not H.is_valid_buf(buf_id) then return end
1341
+
1342
+ -- Compute mapping RHS
1343
+ trigger.keys = H.replace_termcodes(trigger.keys)
1344
+ local lhs = H.keytrans(trigger.keys)
1345
+
1346
+ local is_ministarter_map = vim.bo[buf_id].filetype == 'ministarter'
1347
+ and vim.api.nvim_buf_call(buf_id, function() return vim.fn.maparg(lhs, trigger.mode) ~= '' end)
1348
+ if is_ministarter_map then return end
1349
+
1350
+ local rhs = function()
1351
+ -- Don't act if for some reason entered the same trigger during state exec
1352
+ local is_in_exec = type(H.exec_trigger) == 'table'
1353
+ and H.exec_trigger.mode == trigger.mode
1354
+ and H.exec_trigger.keys == trigger.keys
1355
+ if is_in_exec then
1356
+ H.exec_trigger = nil
1357
+ return
1358
+ end
1359
+
1360
+ -- Start user query
1361
+ H.state_set(trigger, { trigger.keys })
1362
+
1363
+ -- Do not advance if no other clues to query. NOTE: it is `<= 1` and not
1364
+ -- `<= 0` because the "init query" mapping should match.
1365
+ if vim.tbl_count(H.state.clues) <= 1 then return H.state_exec() end
1366
+
1367
+ H.state_advance()
1368
+ end
1369
+
1370
+ -- Use buffer-local mappings and `nowait` to make it a primary source of
1371
+ -- keymap execution
1372
+ local desc = string.format('Query keys after "%s"', lhs)
1373
+ local opts = { buffer = buf_id, nowait = true, desc = desc }
1374
+
1375
+ -- Create mapping. Use translated variant to make it work with <F*> keys.
1376
+ vim.keymap.set(trigger.mode, lhs, rhs, opts)
1377
+ end
1378
+
1379
+ H.unmap_trigger = function(buf_id, trigger)
1380
+ if not H.is_valid_buf(buf_id) then return end
1381
+ pcall(vim.keymap.del, trigger.mode, H.keytrans(trigger.keys), { buffer = buf_id })
1382
+ end
1383
+
1384
+ -- State ----------------------------------------------------------------------
1385
+ H.state_advance = function(opts)
1386
+ opts = opts or {}
1387
+ local config_window = H.get_config().window
1388
+
1389
+ -- Show clues: delay (debounce) first show; update immediately if shown or
1390
+ -- after postkeys (for visual feedback that extra key is needed to stop)
1391
+ H.state.timer:stop()
1392
+ local show_immediately = H.is_valid_win(H.state.win_id) or H.state.is_after_postkeys
1393
+ local delay = show_immediately and 0 or config_window.delay
1394
+ H.state.timer:start(delay, 0, function() H.window_update(opts.same_content) end)
1395
+
1396
+ -- Reset postkeys right now to not flicker when trying to close window during
1397
+ -- "not querying" check
1398
+ H.state.is_after_postkeys = false
1399
+
1400
+ -- Query user for new key
1401
+ local key = H.getcharstr()
1402
+
1403
+ -- Handle key
1404
+ if key == nil then return H.state_reset() end
1405
+
1406
+ if key == H.keys.cr then return H.state_exec() end
1407
+
1408
+ local is_window_shown = H.is_valid_win(H.state.win_id)
1409
+ local is_scroll_down = key == H.replace_termcodes(config_window.scroll_down)
1410
+ local is_scroll_up = key == H.replace_termcodes(config_window.scroll_up)
1411
+ if is_window_shown and (is_scroll_down or is_scroll_up) then
1412
+ H.window_scroll(is_scroll_down)
1413
+ return H.state_advance({ same_content = true })
1414
+ end
1415
+
1416
+ if key == H.keys.bs then
1417
+ H.state_pop()
1418
+ else
1419
+ H.state_push(key)
1420
+ end
1421
+
1422
+ -- Advance state
1423
+ -- - Execute if reached single target keymap
1424
+ if H.state_is_at_target() then return H.state_exec() end
1425
+
1426
+ -- - Reset if there are no keys (like after `<BS>`)
1427
+ if #H.state.query == 0 then return H.state_reset() end
1428
+
1429
+ -- - Query user for more information if there is not enough
1430
+ -- NOTE: still advance even if there is single clue because it is still not
1431
+ -- a target but can be one.
1432
+ if vim.tbl_count(H.state.clues) >= 1 then return H.state_advance() end
1433
+
1434
+ -- - Fall back for executing what user typed
1435
+ H.state_exec()
1436
+ end
1437
+
1438
+ H.state_set = function(trigger, query)
1439
+ H.state.trigger = trigger
1440
+ H.state.query = query
1441
+ H.state.clues = H.clues_filter(H.clues_get_all(trigger.mode), query)
1442
+ end
1443
+
1444
+ H.state_reset = function(keep_window)
1445
+ H.state.trigger = nil
1446
+ H.state.query = {}
1447
+ H.state.clues = {}
1448
+ H.state.is_after_postkeys = false
1449
+
1450
+ H.state.timer:stop()
1451
+ if not keep_window then H.window_close() end
1452
+ end
1453
+
1454
+ H.state_exec = function()
1455
+ -- Compute keys to type
1456
+ local keys_to_type = H.compute_exec_keys()
1457
+
1458
+ -- Add extra (redundant) safety flag to try to avoid infinite recursion
1459
+ local trigger, clue = H.state.trigger, H.state_get_query_clue()
1460
+ H.exec_trigger = trigger
1461
+ vim.schedule(function() H.exec_trigger = nil end)
1462
+
1463
+ -- Reset state
1464
+ local has_postkeys = (clue or {}).postkeys ~= nil
1465
+ H.state_reset(has_postkeys)
1466
+
1467
+ -- Disable trigger !!!VERY IMPORTANT!!!
1468
+ -- This is a workaround against infinite recursion (like if `g` is trigger
1469
+ -- then typing `gg`/`g~` would introduce infinite recursion).
1470
+ local buf_id = vim.api.nvim_get_current_buf()
1471
+ H.unmap_trigger(buf_id, trigger)
1472
+
1473
+ -- Execute keys. The `i` flag is used to fully support Operator-pending mode.
1474
+ -- Flag `t` imitates keys as if user typed, which is reasonable but has small
1475
+ -- downside with edge cases of 'langmap' (like ':\;;\;:') as it "inverts" key
1476
+ -- meaning second time (at least in Normal mode).
1477
+ vim.api.nvim_feedkeys(keys_to_type, 'mit', false)
1478
+
1479
+ -- Enable trigger back after it can no longer harm
1480
+ vim.schedule(function() H.map_trigger(buf_id, trigger) end)
1481
+
1482
+ -- Apply postkeys (in scheduled fashion)
1483
+ if has_postkeys then H.state_apply_postkeys(clue.postkeys) end
1484
+ end
1485
+
1486
+ H.state_push = function(keys)
1487
+ table.insert(H.state.query, keys)
1488
+ H.state.clues = H.clues_filter(H.state.clues, H.state.query)
1489
+ end
1490
+
1491
+ H.state_pop = function()
1492
+ H.state.query[#H.state.query] = nil
1493
+ H.state.clues = H.clues_filter(H.clues_get_all(H.state.trigger.mode), H.state.query)
1494
+ end
1495
+
1496
+ H.state_apply_postkeys = vim.schedule_wrap(function(postkeys)
1497
+ -- Register that possible future querying is a result of postkeys.
1498
+ -- This enables (keep) showing window immediately.
1499
+ H.state.is_after_postkeys = true
1500
+
1501
+ -- Use `nvim_feedkeys()` because using `state_set()` and
1502
+ -- `state_advance()` directly does not work: it doesn't guarantee to be
1503
+ -- executed **after** keys from `nvim_feedkeys()`.
1504
+ vim.api.nvim_feedkeys(postkeys, 'mit', false)
1505
+
1506
+ -- Defer check of whether postkeys resulted into window.
1507
+ -- Could not find proper way to check this which guarantees to be executed
1508
+ -- after `nvim_feedkeys()` takes effect **end** doesn't result into flicker
1509
+ -- when consecutively applying "submode" keys.
1510
+ vim.defer_fn(function()
1511
+ if #H.state.query == 0 then H.window_close() end
1512
+ end, 50)
1513
+ end)
1514
+
1515
+ H.state_is_at_target = function()
1516
+ return vim.tbl_count(H.state.clues) == 1 and H.state.clues[H.query_to_keys(H.state.query)] ~= nil
1517
+ end
1518
+
1519
+ H.state_get_query_clue = function()
1520
+ local keys = H.query_to_keys(H.state.query)
1521
+ return H.state.clues[keys]
1522
+ end
1523
+
1524
+ H.compute_exec_keys = function()
1525
+ local keys_count = vim.v.count > 0 and vim.v.count or ''
1526
+ local keys_query = H.query_to_keys(H.state.query)
1527
+ local res = keys_count .. keys_query
1528
+
1529
+ local cur_mode = vim.fn.mode(1)
1530
+
1531
+ -- Using `feedkeys()` inside Operator-pending mode leads to its cancel into
1532
+ -- Normal/Insert mode so extra work should be done to rebuild all keys
1533
+ if vim.startswith(cur_mode, 'no') then
1534
+ local operator_tweak = H.operator_tweaks[vim.v.operator] or function(x) return x end
1535
+ res = operator_tweak(vim.v.operator .. H.get_forced_submode() .. res)
1536
+ elseif not vim.startswith(cur_mode, 'i') and H.get_default_register() ~= vim.v.register then
1537
+ -- Force non-default register but not in Insert mode
1538
+ local expr_reg_keys = vim.v.register == '=' and (vim.fn.getreginfo('=').regcontents[1] .. '\r') or ''
1539
+ res = '"' .. vim.v.register .. expr_reg_keys .. res
1540
+ end
1541
+
1542
+ -- `feedkeys()` inside "temporary" Normal mode is executed **after** it is
1543
+ -- already back from Normal mode. Go into it again with `<C-o>` ('\15').
1544
+ -- NOTE: This only works when Normal mode trigger is triggered in
1545
+ -- "temporary" Normal mode. Still doesn't work when Operator-pending mode is
1546
+ -- triggered afterwards (like in `<C-o>gUiw` with 'i' as trigger).
1547
+ if cur_mode:find('^ni') ~= nil then res = '\15' .. res end
1548
+
1549
+ return res
1550
+ end
1551
+
1552
+ -- Some operators needs special tweaking due to their nature:
1553
+ -- - Some operators perform on register. Solution: add register explicitly.
1554
+ -- - Some operators end up changing mode which affects `feedkeys()`.
1555
+ -- Solution: explicitly exit to Normal mode with '<C-\><C-n>'.
1556
+ -- - Some operators still perform some redundant operation before `feedkeys()`
1557
+ -- takes effect. Solution: add one-shot autocommand undoing that.
1558
+ H.operator_tweaks = {
1559
+ ['c'] = function(keys)
1560
+ -- Doing '<C-\><C-n>' moves cursor one space to left (same as `i<Esc>`).
1561
+ -- Solution: add one-shot autocommand correcting cursor position.
1562
+ vim.cmd('au InsertLeave * ++once normal! l')
1563
+ return H.keys.exit .. '"' .. vim.v.register .. keys
1564
+ end,
1565
+ ['d'] = function(keys) return '"' .. vim.v.register .. keys end,
1566
+ ['y'] = function(keys) return '"' .. vim.v.register .. keys end,
1567
+ ['~'] = function(keys)
1568
+ if vim.fn.col('.') == 1 then vim.cmd(H.undo_autocommand) end
1569
+ return keys
1570
+ end,
1571
+ ['g~'] = function(keys)
1572
+ if vim.fn.col('.') == 1 then vim.cmd(H.undo_autocommand) end
1573
+ return keys
1574
+ end,
1575
+ ['g?'] = function(keys)
1576
+ if vim.fn.col('.') == 1 then vim.cmd(H.undo_autocommand) end
1577
+ return keys
1578
+ end,
1579
+ ['!'] = function(keys) return H.keys.exit .. keys end,
1580
+ ['>'] = function(keys)
1581
+ vim.cmd(H.undo_autocommand)
1582
+ return keys
1583
+ end,
1584
+ ['<'] = function(keys)
1585
+ vim.cmd(H.undo_autocommand)
1586
+ return keys
1587
+ end,
1588
+ ['g@'] = function(keys)
1589
+ -- Cancelling in-process `g@` operator seems to be particularly hard.
1590
+ -- Not even sure why specifically this combination works, but having `x`
1591
+ -- flag in `feedkeys()` is crucial.
1592
+ vim.api.nvim_feedkeys(H.keys.exit, 'nx', false)
1593
+ return H.keys.exit .. keys
1594
+ end,
1595
+ }
1596
+
1597
+ H.query_to_keys = function(query) return table.concat(query, '') end
1598
+
1599
+ -- Window ---------------------------------------------------------------------
1600
+ H.window_update = vim.schedule_wrap(function(same_content)
1601
+ -- Make sure that outdated windows are not shown
1602
+ if #H.state.query == 0 then return H.window_close() end
1603
+ local win_id = H.state.win_id
1604
+
1605
+ -- Close window if it is not in current tabpage (as only window is tracked)
1606
+ local is_different_tabpage = H.is_valid_win(win_id)
1607
+ and vim.api.nvim_win_get_tabpage(win_id) ~= vim.api.nvim_get_current_tabpage()
1608
+ if is_different_tabpage then H.window_close() end
1609
+
1610
+ -- Create-update buffer showing clues
1611
+ if not same_content then H.state.buf_id = H.buffer_update() end
1612
+
1613
+ -- Create-update window showing buffer
1614
+ local win_config = H.window_get_config()
1615
+ if not H.is_valid_win(win_id) then
1616
+ win_config.noautocmd = true
1617
+ win_id = H.window_open(win_config)
1618
+ H.state.win_id = win_id
1619
+ else
1620
+ vim.api.nvim_win_set_config(win_id, win_config)
1621
+ vim.wo[win_id].list = true
1622
+ end
1623
+
1624
+ -- Make scroll not persist. NOTE: Don't use 'normal! gg' inside target window
1625
+ -- as it resets `v:count` and `v:register` which results into invalid keys
1626
+ -- reproduction in Operator-pending mode.
1627
+ if not same_content then vim.api.nvim_win_set_cursor(win_id, { 1, 0 }) end
1628
+
1629
+ -- Add redraw because Neovim won't do it when `getcharstr()` is active
1630
+ vim.cmd('redraw')
1631
+ end)
1632
+
1633
+ H.window_scroll = function(is_scroll_down)
1634
+ local scroll_key = is_scroll_down and H.keys.ctrl_d or H.keys.ctrl_u
1635
+ local f = function()
1636
+ local cache_scroll, bot_line, n_lines = vim.wo.scroll, vim.fn.line('w$'), vim.api.nvim_buf_line_count(0)
1637
+ -- Do not scroll past the end of buffer
1638
+ local scroll_count = is_scroll_down and math.min(cache_scroll, n_lines - bot_line) or cache_scroll
1639
+ if scroll_count > 0 then pcall(vim.cmd, 'normal! ' .. scroll_count .. scroll_key) end
1640
+ vim.wo.scroll = cache_scroll
1641
+ end
1642
+ vim.api.nvim_win_call(H.state.win_id, f)
1643
+ end
1644
+
1645
+ H.window_open = function(config)
1646
+ local win_id = vim.api.nvim_open_win(H.state.buf_id, false, config)
1647
+
1648
+ vim.wo[win_id].foldenable = false
1649
+ vim.wo[win_id].foldmethod = 'manual'
1650
+ vim.wo[win_id].wrap = false
1651
+ vim.wo[win_id].list = true
1652
+ vim.wo[win_id].listchars = 'extends:…'
1653
+
1654
+ local win_hl = 'FloatBorder:MiniClueBorder,FloatTitle:MiniClueTitle'
1655
+ vim.wo[win_id].winhighlight = win_hl
1656
+
1657
+ return win_id
1658
+ end
1659
+
1660
+ H.window_close = function()
1661
+ -- Closing floating window when Command-line window is active is not allowed
1662
+ -- on Neovim<0.10. Make sure it is closed after leaving it.
1663
+ -- See https://github.com/neovim/neovim/issues/24452
1664
+ local win_id = H.state.win_id
1665
+ if vim.fn.has('nvim-0.10') == 0 and vim.fn.getcmdwintype() ~= '' then
1666
+ vim.api.nvim_create_autocmd(
1667
+ 'CmdwinLeave',
1668
+ { once = true, callback = function() pcall(vim.api.nvim_win_close, win_id, true) end }
1669
+ )
1670
+ return
1671
+ else
1672
+ pcall(vim.api.nvim_win_close, win_id, true)
1673
+ end
1674
+
1675
+ H.state.win_id = nil
1676
+ end
1677
+
1678
+ H.window_get_config = function()
1679
+ local has_statusline = vim.o.laststatus > 0
1680
+ local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
1681
+ -- Remove 2 from maximum height to account for top and bottom borders
1682
+ local max_height = vim.o.lines - vim.o.cmdheight - (has_tabline and 1 or 0) - (has_statusline and 1 or 0) - 2
1683
+ max_height = math.max(max_height, 1)
1684
+
1685
+ local keys = H.query_to_keys(H.state.query)
1686
+ local query_clue = (H.state.clues[keys] or {}).desc or ''
1687
+ local title = (#H.state.query <= 1 or query_clue == '') and H.keytrans(keys) or query_clue
1688
+
1689
+ local buf_id = H.state.buf_id
1690
+ local cur_config_fields = {
1691
+ row = vim.o.lines - vim.o.cmdheight - (has_statusline and 1 or 0),
1692
+ col = vim.o.columns,
1693
+ height = math.min(vim.api.nvim_buf_line_count(buf_id), max_height),
1694
+ title = ' ' .. title .. ' ',
1695
+ border = (vim.fn.exists('+winborder') == 0 or vim.o.winborder == '') and 'single' or nil,
1696
+ }
1697
+ local user_config = H.expand_callable(H.get_config().window.config, buf_id) or {}
1698
+ local res = vim.tbl_deep_extend('force', H.default_win_config, cur_config_fields, user_config)
1699
+
1700
+ -- Tweak "auto" fields
1701
+ if res.width == 'auto' then res.width = H.buffer_get_width() + 1 end
1702
+
1703
+ if res.row == 'auto' then
1704
+ local is_on_top = res.anchor == 'NW' or res.anchor == 'NE'
1705
+ res.row = is_on_top and (has_tabline and 1 or 0) or cur_config_fields.row
1706
+ end
1707
+
1708
+ if res.col == 'auto' then
1709
+ local is_on_left = res.anchor == 'NW' or res.anchor == 'SW'
1710
+ res.col = is_on_left and 0 or cur_config_fields.col
1711
+ end
1712
+
1713
+ -- Ensure proper config
1714
+ res.width = math.min(math.max(res.width, 1), vim.o.columns - 2)
1715
+ if type(res.title) == 'string' then res.title = H.fit_to_width(res.title, res.width) end
1716
+
1717
+ return res
1718
+ end
1719
+
1720
+ -- Buffer ---------------------------------------------------------------------
1721
+ H.buffer_update = function()
1722
+ local buf_id = H.state.buf_id
1723
+ if not H.is_loaded_buf(buf_id) then
1724
+ pcall(vim.api.nvim_buf_delete, buf_id, { force = true })
1725
+ buf_id = vim.api.nvim_create_buf(false, true)
1726
+ H.set_buf_name(buf_id, 'content')
1727
+ end
1728
+
1729
+ -- Compute content data
1730
+ local keys = H.query_to_keys(H.state.query)
1731
+ local content = H.clues_to_buffer_content(H.state.clues, keys)
1732
+
1733
+ -- Add lines
1734
+ local lines = {}
1735
+ for _, line_content in ipairs(content) do
1736
+ table.insert(lines, string.format(' %s │ %s', line_content.next_key, line_content.desc))
1737
+ end
1738
+ vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
1739
+
1740
+ -- Add highlighting
1741
+ local ns_id = H.ns_id.highlight
1742
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
1743
+
1744
+ local set_hl = function(hl_group, line_from, col_from, line_to, col_to)
1745
+ local opts = { end_row = line_to, end_col = col_to, hl_group = hl_group, hl_eol = true }
1746
+ vim.api.nvim_buf_set_extmark(buf_id, ns_id, line_from, col_from, opts)
1747
+ end
1748
+
1749
+ for i, line_content in ipairs(content) do
1750
+ local sep_start = line_content.next_key:len() + 3
1751
+ local next_key_hl_group = line_content.has_postkeys and 'MiniClueNextKeyWithPostkeys' or 'MiniClueNextKey'
1752
+ set_hl(next_key_hl_group, i - 1, 0, i - 1, sep_start - 1)
1753
+
1754
+ -- NOTE: Separator '│' is 3 bytes long
1755
+ set_hl('MiniClueSeparator', i - 1, sep_start - 1, i - 1, sep_start + 2)
1756
+
1757
+ local desc_hl_group = line_content.is_group and 'MiniClueDescGroup' or 'MiniClueDescSingle'
1758
+ set_hl(desc_hl_group, i - 1, sep_start + 2, i, 0)
1759
+ end
1760
+
1761
+ return buf_id
1762
+ end
1763
+
1764
+ H.buffer_get_width = function()
1765
+ if not H.is_valid_buf(H.state.buf_id) then return end
1766
+ local lines = vim.api.nvim_buf_get_lines(H.state.buf_id, 0, -1, false)
1767
+ local res = 0
1768
+ for _, l in ipairs(lines) do
1769
+ res = math.max(res, vim.fn.strdisplaywidth(l))
1770
+ end
1771
+ return res
1772
+ end
1773
+
1774
+ -- Clues ----------------------------------------------------------------------
1775
+ H.clues_get_all = function(mode)
1776
+ local res = {}
1777
+
1778
+ -- Order of clue precedence: config clues < buffer mappings < global mappings
1779
+ local config_clues = H.clues_normalize(H.get_config().clues) or {}
1780
+ local mode_filter = function(x) return type(x.mode) == 'table' and vim.tbl_contains(x.mode, mode) or x.mode == mode end
1781
+ local mode_clues = vim.tbl_filter(mode_filter, config_clues)
1782
+ for _, clue in ipairs(mode_clues) do
1783
+ local lhsraw = H.replace_termcodes(clue.keys)
1784
+
1785
+ local res_data = res[lhsraw] or {}
1786
+
1787
+ -- - Allow callable clue description
1788
+ local desc = H.expand_callable(clue.desc)
1789
+ -- - Fall back to possibly already present fields to allow partial
1790
+ -- overwrite in later clues. Like to add `postkeys` and inherit `desc`.
1791
+ res_data.desc = desc or res_data.desc
1792
+ res_data.postkeys = H.replace_termcodes(clue.postkeys) or res_data.postkeys
1793
+
1794
+ res[lhsraw] = res_data
1795
+ end
1796
+
1797
+ for _, map_data in ipairs(vim.api.nvim_get_keymap(mode)) do
1798
+ local lhsraw = H.replace_termcodes(map_data.lhs)
1799
+ local res_data = res[lhsraw] or {}
1800
+ res_data.desc = map_data.desc or ''
1801
+ res[lhsraw] = res_data
1802
+ end
1803
+
1804
+ for _, map_data in ipairs(vim.api.nvim_buf_get_keymap(0, mode)) do
1805
+ local lhsraw = H.replace_termcodes(map_data.lhs)
1806
+ local res_data = res[lhsraw] or {}
1807
+ res_data.desc = map_data.desc or ''
1808
+ res[lhsraw] = res_data
1809
+ end
1810
+
1811
+ return res
1812
+ end
1813
+
1814
+ H.clues_normalize = function(clues)
1815
+ local res = {}
1816
+ local process
1817
+ process = function(x)
1818
+ x = H.expand_callable(x)
1819
+ if H.is_clue(x) then return table.insert(res, x) end
1820
+ if not H.islist(x) then return nil end
1821
+ for _, y in ipairs(x) do
1822
+ process(y)
1823
+ end
1824
+ end
1825
+
1826
+ process(clues)
1827
+ return res
1828
+ end
1829
+
1830
+ H.clues_filter = function(clues, query)
1831
+ local keys = H.query_to_keys(query)
1832
+ for clue_keys, _ in pairs(clues) do
1833
+ if not vim.startswith(clue_keys, keys) then clues[clue_keys] = nil end
1834
+ end
1835
+ return clues
1836
+ end
1837
+
1838
+ H.clues_to_buffer_content = function(clues, keys)
1839
+ -- Use translated keys to properly handle cases like `<Del>`, `<End>`, etc.
1840
+ keys = H.keytrans(keys)
1841
+
1842
+ -- Gather clue data
1843
+ local keys_len = keys:len()
1844
+ local keys_pattern = string.format('^%s(.+)$', vim.pesc(keys))
1845
+
1846
+ local next_key_data, next_key_max_width = {}, 0
1847
+ for clue_keys, clue_data in pairs(clues) do
1848
+ local left, _, rest_keys = H.keytrans(clue_keys):find(keys_pattern)
1849
+
1850
+ -- Add non-trivial next key data only if clue matches current keys plus
1851
+ -- something more
1852
+ if left ~= nil then
1853
+ local next_key = H.clues_get_first_key(rest_keys)
1854
+
1855
+ -- Update description data
1856
+ local data = next_key_data[next_key] or {}
1857
+ data.n_choices = (data.n_choices or 0) + 1
1858
+
1859
+ -- - Add description directly if it is group clue with description or
1860
+ -- a non-group clue
1861
+ if next_key == rest_keys then
1862
+ data.desc = clue_data.desc or ''
1863
+ data.has_postkeys = clue_data.postkeys ~= nil
1864
+ end
1865
+
1866
+ next_key_data[next_key] = data
1867
+
1868
+ -- Update width data
1869
+ local next_key_width = vim.fn.strchars(next_key)
1870
+ data.next_key_width = next_key_width
1871
+ next_key_max_width = math.max(next_key_max_width, next_key_width)
1872
+ end
1873
+ end
1874
+
1875
+ -- Convert to array sorted by keys and finalize content
1876
+ local next_keys_extra = vim.tbl_map(
1877
+ function(x) return { key = x, keytype = H.clues_get_next_key_type(x) } end,
1878
+ vim.tbl_keys(next_key_data)
1879
+ )
1880
+ table.sort(next_keys_extra, H.clues_compare_next_key)
1881
+ local next_keys = vim.tbl_map(function(x) return x.key end, next_keys_extra)
1882
+
1883
+ local res = {}
1884
+ for _, key in ipairs(next_keys) do
1885
+ local data = next_key_data[key]
1886
+ local is_group = data.n_choices > 1
1887
+ local desc = data.desc or string.format('+%d choice%s', data.n_choices, is_group and 's' or '')
1888
+ local next_key = key .. string.rep(' ', next_key_max_width - data.next_key_width)
1889
+ table.insert(res, { next_key = next_key, desc = desc, is_group = is_group, has_postkeys = data.has_postkeys })
1890
+ end
1891
+
1892
+ return res
1893
+ end
1894
+
1895
+ H.clues_get_first_key = function(keys)
1896
+ -- `keys` are assumed to be translated
1897
+ -- Special keys
1898
+ local special = keys:match('^(%b<>)')
1899
+ if special ~= nil then return special end
1900
+
1901
+ -- <
1902
+ if keys:find('^<') ~= nil then return '<' end
1903
+
1904
+ -- Other characters
1905
+ return vim.fn.strcharpart(keys, 0, 1)
1906
+ end
1907
+
1908
+ H.clues_get_next_key_type = function(x)
1909
+ if x:find('^%w$') ~= nil then return 'alphanum' end
1910
+ if x:find('^<.*>$') ~= nil then return 'mod' end
1911
+ return 'other'
1912
+ end
1913
+
1914
+ H.clues_compare_next_key = function(a, b)
1915
+ local a_type, b_type = a.keytype, b.keytype
1916
+ if a_type == b_type then
1917
+ local cmp = vim.stricmp(a.key, b.key)
1918
+ return cmp == -1 or (cmp == 0 and a.key < b.key)
1919
+ end
1920
+
1921
+ if a_type == 'alphanum' then return true end
1922
+ if b_type == 'alphanum' then return false end
1923
+
1924
+ if a_type == 'mod' then return true end
1925
+ if b_type == 'mod' then return false end
1926
+ end
1927
+
1928
+ -- Clue generators ------------------------------------------------------------
1929
+ H.make_clues_with_register_contents = function(mode, prefix)
1930
+ local make_register_desc = function(register)
1931
+ return function()
1932
+ local ok, value = pcall(vim.fn.getreg, register, 1)
1933
+ if not ok or value == '' then return nil end
1934
+ return vim.inspect(value)
1935
+ end
1936
+ end
1937
+
1938
+ local all_registers = vim.split('0123456789abcdefghijklmnopqrstuvwxyz*+"-:.%/#', '')
1939
+
1940
+ local res = {}
1941
+ for _, register in ipairs(all_registers) do
1942
+ table.insert(res, { mode = mode, keys = prefix .. register, desc = make_register_desc(register) })
1943
+ end
1944
+ table.insert(res, { mode = mode, keys = prefix .. '=', desc = 'Result of expression' })
1945
+
1946
+ return res
1947
+ end
1948
+
1949
+ -- Predicates -----------------------------------------------------------------
1950
+ H.is_trigger = function(x)
1951
+ return type(x) == 'table' and (type(x.mode) == 'string' or type(x.mode) == 'table') and type(x.keys) == 'string'
1952
+ end
1953
+
1954
+ H.is_clue = function(x)
1955
+ if type(x) ~= 'table' then return false end
1956
+ local mandatory = (type(x.mode) == 'string' or type(x.mode) == 'table') and type(x.keys) == 'string'
1957
+ local extra = (x.desc == nil or type(x.desc) == 'string' or vim.is_callable(x.desc))
1958
+ and (x.postkeys == nil or type(x.postkeys) == 'string')
1959
+ return mandatory and extra
1960
+ end
1961
+
1962
+ H.is_array_of = function(x, predicate)
1963
+ if not H.islist(x) then return false end
1964
+ for _, v in ipairs(x) do
1965
+ if not predicate(v) then return false end
1966
+ end
1967
+ return true
1968
+ end
1969
+
1970
+ -- Utilities ------------------------------------------------------------------
1971
+ H.error = function(msg) error('(mini.clue) ' .. msg, 0) end
1972
+
1973
+ H.check_type = function(name, val, ref, allow_nil)
1974
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1975
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1976
+ end
1977
+
1978
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'miniclue://' .. buf_id .. '/' .. name) end
1979
+
1980
+ H.map = function(mode, lhs, rhs, opts)
1981
+ if lhs == '' then return end
1982
+ opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
1983
+ vim.keymap.set(mode, lhs, rhs, opts)
1984
+ end
1985
+
1986
+ H.replace_termcodes = function(x)
1987
+ if x == nil then return nil end
1988
+ -- Use `keytrans` prior replacing termcodes to work correctly on already
1989
+ -- replaced variant of `<F*>` keys
1990
+ return vim.api.nvim_replace_termcodes(H.keytrans(x), true, true, true)
1991
+ end
1992
+
1993
+ H.keytrans = function(x)
1994
+ local res = vim.fn.keytrans(x):gsub('<NL>', '<C-J>'):gsub('<S%-NL>', '<C-S-J>'):gsub('<M%-NL>', '<C-M-J>')
1995
+ return (res:gsub('<lt>', '<'))
1996
+ end
1997
+
1998
+ H.get_forced_submode = function()
1999
+ local mode = vim.fn.mode(1)
2000
+ if not mode:sub(1, 2) == 'no' then return '' end
2001
+ return mode:sub(3)
2002
+ end
2003
+
2004
+ H.get_default_register = function()
2005
+ local clipboard = vim.o.clipboard
2006
+ if clipboard:find('unnamedplus') ~= nil then return '+' end
2007
+ if clipboard:find('unnamed') ~= nil then return '*' end
2008
+ return '"'
2009
+ end
2010
+
2011
+ H.is_valid_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_valid(buf_id) end
2012
+
2013
+ H.is_loaded_buf = function(buf_id) return type(buf_id) == 'number' and vim.api.nvim_buf_is_loaded(buf_id) end
2014
+
2015
+ H.is_valid_win = function(win_id) return type(win_id) == 'number' and vim.api.nvim_win_is_valid(win_id) end
2016
+
2017
+ H.fit_to_width = function(text, width)
2018
+ local t_width = vim.fn.strchars(text)
2019
+ return t_width <= width and text or ('…' .. vim.fn.strcharpart(text, t_width - width + 1, width - 1))
2020
+ end
2021
+
2022
+ H.expand_callable = function(x, ...)
2023
+ if vim.is_callable(x) then return x(...) end
2024
+ return x
2025
+ end
2026
+
2027
+ H.redraw_scheduled = vim.schedule_wrap(function() vim.cmd('redraw') end)
2028
+
2029
+ H.getcharstr = function()
2030
+ -- Ensure redraws still happen. This is needed to not block "interactive"
2031
+ -- behavior (smooth scrolling) and not immediate submodes (scheduled DAP).
2032
+ -- TODO: Use "do not block redraw" `getcharstr()` option if it ever happens
2033
+ H.timers.getcharstr:start(0, 50, H.redraw_scheduled)
2034
+ local ok, char = pcall(vim.fn.getcharstr)
2035
+ H.timers.getcharstr:stop()
2036
+ -- Terminate if couldn't get input (like with <C-c>) or on `<Esc>`
2037
+ if not ok or char == '' or char == '\3' or char == '\27' then return nil end
2038
+ return H.get_langmap()[char] or char
2039
+ end
2040
+
2041
+ H.get_langmap = function()
2042
+ if vim.o.langmap == '' then return {} end
2043
+
2044
+ -- Get langmap parts by splitting at "," not preceded by "\"
2045
+ local langmap_parts = vim.fn.split(vim.o.langmap, '[^\\\\]\\zs,')
2046
+
2047
+ -- Process each langmap part
2048
+ local res = {}
2049
+ for _, part in ipairs(langmap_parts) do
2050
+ H.process_langmap_part(res, part)
2051
+ end
2052
+ return res
2053
+ end
2054
+
2055
+ H.process_langmap_part = function(res, part)
2056
+ local semicolon_byte_ind = vim.fn.match(part, '[^\\\\]\\zs;') + 1
2057
+
2058
+ -- Part is without ';', like 'aAbB'
2059
+ if semicolon_byte_ind == 0 then
2060
+ -- Drop backslash escapes
2061
+ part = part:gsub('\\([^\\])', '%1')
2062
+
2063
+ for i = 1, vim.fn.strchars(part), 2 do
2064
+ -- `strcharpart()` has 0-based indexes
2065
+ local from, to = vim.fn.strcharpart(part, i - 1, 1), vim.fn.strcharpart(part, i, 1)
2066
+ if from ~= '' and to ~= '' then res[from] = to end
2067
+ end
2068
+
2069
+ return
2070
+ end
2071
+
2072
+ -- Part is with ';', like 'ab;AB'
2073
+ -- - Drop backslash escape
2074
+ local left = part:sub(1, semicolon_byte_ind - 1):gsub('\\([^\\])', '%1')
2075
+ local right = part:sub(semicolon_byte_ind + 1):gsub('\\([^\\])', '%1')
2076
+
2077
+ for i = 1, vim.fn.strchars(left) do
2078
+ local from, to = vim.fn.strcharpart(left, i - 1, 1), vim.fn.strcharpart(right, i - 1, 1)
2079
+ if from ~= '' and to ~= '' then res[from] = to end
2080
+ end
2081
+ end
2082
+
2083
+ H.list_concat = function(...)
2084
+ local res = {}
2085
+ for i = 1, select('#', ...) do
2086
+ for _, x in ipairs(select(i, ...) or {}) do
2087
+ table.insert(res, x)
2088
+ end
2089
+ end
2090
+ return res
2091
+ end
2092
+
2093
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2094
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2095
+
2096
+ return MiniClue