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,1599 @@
1
+ --- *mini.starter* Start screen
2
+ ---
3
+ --- MIT License Copyright (c) 2021 Evgeni Chasnovski
4
+
5
+ --- Displayed items are fully customizable both in terms of what they do and
6
+ --- how they look (with reasonable defaults). Item selection can be done using
7
+ --- prefix query with instant visual feedback.
8
+ ---
9
+ --- Key design ideas:
10
+ --- - All available actions are defined inside items. Each item should have the
11
+ --- following info:
12
+ --- - <action> - function or string for |vim.cmd()| which is executed when
13
+ --- item is chosen. Empty string result in placeholder "inactive" item.
14
+ --- - <name> - string which will be displayed and used for choosing.
15
+ --- - <section> - string representing to which section item belongs.
16
+ --- There are pre-configured whole sections in |MiniStarter.sections|.
17
+ ---
18
+ --- - Configure what items are displayed by supplying an array which can be
19
+ --- normalized to an array of items. Read about how supplied items are
20
+ --- normalized in |MiniStarter.refresh()|.
21
+ ---
22
+ --- - Modify the final look by supplying content hooks: functions which take
23
+ --- buffer content (see |MiniStarter.get_content()|) and identifier as input
24
+ --- while returning buffer content as output. There are pre-configured
25
+ --- content hook generators in |MiniStarter.gen_hook|.
26
+ ---
27
+ --- - Choosing an item can be done in two ways:
28
+ --- - Type prefix query to filter item by matching its name (ignoring
29
+ --- case). Displayed information is updated after every typed character.
30
+ --- For every item its unique prefix is highlighted.
31
+ --- - Use Up/Down arrows and hit Enter.
32
+ ---
33
+ --- - Allow multiple simultaneously open Starter buffers.
34
+ ---
35
+ --- What is doesn't do:
36
+ --- - It doesn't support fuzzy query for items. And probably will never do.
37
+ ---
38
+ --- # Setup ~
39
+ ---
40
+ --- This module needs a setup with `require('mini.starter').setup({})`
41
+ --- (replace `{}` with your `config` table). It will create global Lua table
42
+ --- `MiniStarter` which you can use for scripting or manually (with
43
+ --- `:lua MiniStarter.*`).
44
+ ---
45
+ --- See |MiniStarter.config| for `config` structure and default values. For
46
+ --- some configuration examples (including one similar to `vim-startify` and
47
+ --- `dashboard-nvim`), see |MiniStarter-example-config|.
48
+ ---
49
+ --- You can override runtime config settings locally to buffer inside
50
+ --- `vim.b.ministarter_config` which should have same structure as
51
+ --- `MiniStarter.config`. See |mini.nvim-buffer-local-config| for more details.
52
+ --- Note: `vim.b.ministarter_config` is copied to Starter buffer from current
53
+ --- buffer allowing full customization.
54
+ ---
55
+ --- To stop module from showing non-error feedback, set `config.silent = true`.
56
+ ---
57
+ --- # Highlight groups ~
58
+ --- *MiniStarter-hl-groups*
59
+ ---
60
+ --- - `MiniStarterCurrent` - current item.
61
+ --- - `MiniStarterFooter` - footer units.
62
+ --- - `MiniStarterHeader` - header units.
63
+ --- - `MiniStarterInactive` - inactive item.
64
+ --- - `MiniStarterItem` - item name.
65
+ --- - `MiniStarterItemBullet` - units from |MiniStarter.gen_hook.adding_bullet()|.
66
+ --- - `MiniStarterItemPrefix` - unique query for item.
67
+ --- - `MiniStarterSection` - section units.
68
+ --- - `MiniStarterQuery` - current query in active items.
69
+ ---
70
+ --- To change any highlight group, set it directly with |nvim_set_hl()|.
71
+ ---
72
+ --- # Disabling ~
73
+ ---
74
+ --- To disable core functionality, set `vim.g.ministarter_disable` (globally) or
75
+ --- `vim.b.ministarter_disable` (for a buffer) to `true`. Considering high number
76
+ --- of different scenarios and customization intentions, writing exact rules
77
+ --- for disabling module's functionality is left to user. See
78
+ --- |mini.nvim-disabling-recipes| for common recipes.
79
+ ---@tag MiniStarter
80
+
81
+ --- # Similar to `mhinz/vim-startify` ~
82
+ --- >lua
83
+ --- local starter = require('mini.starter')
84
+ --- starter.setup({
85
+ --- evaluate_single = true,
86
+ --- items = {
87
+ --- starter.sections.builtin_actions(),
88
+ --- starter.sections.recent_files(10, false),
89
+ --- starter.sections.recent_files(10, true),
90
+ --- -- Use this if you set up 'mini.sessions'
91
+ --- starter.sections.sessions(5, true)
92
+ --- },
93
+ --- content_hooks = {
94
+ --- starter.gen_hook.adding_bullet(),
95
+ --- starter.gen_hook.indexing('all', { 'Builtin actions' }),
96
+ --- starter.gen_hook.padding(3, 2),
97
+ --- },
98
+ --- })
99
+ --- <
100
+ --- # Similar to `glepnir/dashboard-nvim` ~
101
+ --- >lua
102
+ --- local starter = require('mini.starter')
103
+ --- starter.setup({
104
+ --- items = {
105
+ --- starter.sections.telescope(),
106
+ --- },
107
+ --- content_hooks = {
108
+ --- starter.gen_hook.adding_bullet(),
109
+ --- starter.gen_hook.aligning('center', 'center'),
110
+ --- },
111
+ --- })
112
+ --- <
113
+ --- # Demo of capabilities ~
114
+ --- >lua
115
+ --- local my_items = {
116
+ --- { name = 'Echo random number', action = 'lua print(math.random())', section = 'Section 1' },
117
+ --- function()
118
+ --- return {
119
+ --- { name = 'Item #1 from function', action = [[echo 'Item #1']], section = 'From function' },
120
+ --- { name = 'Placeholder (always inactive) item', action = '', section = 'From function' },
121
+ --- function()
122
+ --- return {
123
+ --- name = 'Item #1 from double function',
124
+ --- action = [[echo 'Double function']],
125
+ --- section = 'From double function',
126
+ --- }
127
+ --- end,
128
+ --- }
129
+ --- end,
130
+ --- { name = [[Another item in 'Section 1']], action = 'lua print(math.random() + 10)', section = 'Section 1' },
131
+ --- }
132
+ ---
133
+ --- local footer_n_seconds = (function()
134
+ --- local timer = vim.loop.new_timer()
135
+ --- local n_seconds = 0
136
+ --- timer:start(0, 1000, vim.schedule_wrap(function()
137
+ --- if vim.bo.filetype ~= 'ministarter' then
138
+ --- timer:stop()
139
+ --- return
140
+ --- end
141
+ --- n_seconds = n_seconds + 1
142
+ --- MiniStarter.refresh()
143
+ --- end))
144
+ ---
145
+ --- return function()
146
+ --- return 'Number of seconds since opening: ' .. n_seconds
147
+ --- end
148
+ --- end)()
149
+ ---
150
+ --- local hook_top_pad_10 = function(content)
151
+ --- -- Pad from top
152
+ --- for _ = 1, 10 do
153
+ --- -- Insert at start a line with single content unit
154
+ --- table.insert(content, 1, { { type = 'empty', string = '' } })
155
+ --- end
156
+ --- return content
157
+ --- end
158
+ ---
159
+ --- local starter = require('mini.starter')
160
+ --- starter.setup({
161
+ --- items = my_items,
162
+ --- footer = footer_n_seconds,
163
+ --- content_hooks = { hook_top_pad_10 },
164
+ --- })
165
+ --- <
166
+ ---@tag MiniStarter-example-config
167
+
168
+ --- - Open with |MiniStarter.open()|. It includes creating buffer with
169
+ --- appropriate options, mappings, behavior; call to |MiniStarter.refresh()|;
170
+ --- issue `MiniStarterOpened` |User| event.
171
+ --- - Wait for user to choose an item. This is done using following logic:
172
+ --- - Typing any character from `MiniStarter.config.query_updaters` leads
173
+ --- to updating query. Read more in |MiniStarter.add_to_query()|.
174
+ --- - <BS> deletes latest character from query.
175
+ --- - <Down>/<Up>, <C-n>/<C-p>, <M-j>/<M-k> move current item.
176
+ --- - <CR> executes action of current item.
177
+ --- - <C-c> closes Starter buffer.
178
+ --- - Evaluate current item when appropriate (after `<CR>` or when there is a
179
+ --- single item and `MiniStarter.config.evaluate_single` is `true`). This
180
+ --- executes item's `action`.
181
+ ---@tag MiniStarter-lifecycle
182
+
183
+ ---@alias __starter_buf_id number|nil Buffer identifier of a valid Starter buffer.
184
+ --- Default: current buffer.
185
+ ---@alias __starter_section_fun function Function which returns array of items.
186
+
187
+ -- Module definition ==========================================================
188
+ local MiniStarter = {}
189
+ local H = {}
190
+
191
+ --- Module setup
192
+ ---
193
+ ---@param config table|nil Module config table. See |MiniStarter.config|.
194
+ ---
195
+ ---@usage >lua
196
+ --- require('mini.starter').setup() -- use default config
197
+ --- -- OR
198
+ --- require('mini.starter').setup({}) -- replace {} with your config table
199
+ --- <
200
+ MiniStarter.setup = function(config)
201
+ -- TODO: Remove after Neovim=0.9 support is dropped
202
+ if vim.fn.has('nvim-0.10') == 0 then
203
+ vim.notify(
204
+ '(mini.starter) Neovim<0.10 is soft deprecated (module works but is not supported).'
205
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
206
+ .. ' Please update your Neovim version.'
207
+ )
208
+ end
209
+
210
+ -- Export module
211
+ _G.MiniStarter = MiniStarter
212
+
213
+ -- Setup config
214
+ config = H.setup_config(config)
215
+
216
+ -- Apply config
217
+ H.apply_config(config)
218
+
219
+ -- Define behavior
220
+ H.create_autocommands(config)
221
+
222
+ -- Create default highlighting
223
+ H.create_default_hl()
224
+ end
225
+
226
+ --- Defaults ~
227
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
228
+ MiniStarter.config = {
229
+ -- Whether to open Starter buffer on VimEnter. Not opened if Neovim was
230
+ -- started with intent to show something else.
231
+ autoopen = true,
232
+
233
+ -- Whether to evaluate action of single active item
234
+ evaluate_single = false,
235
+
236
+ -- Items to be displayed. Should be an array with the following elements:
237
+ -- - Item: table with <action>, <name>, and <section> keys.
238
+ -- - Function: should return one of these three categories.
239
+ -- - Array: elements of these three types (i.e. item, array, function).
240
+ -- If `nil` (default), default items will be used (see |mini.starter|).
241
+ items = nil,
242
+
243
+ -- Header to be displayed before items. Converted to single string via
244
+ -- `tostring` (use `\n` to display several lines). If function, it is
245
+ -- evaluated first. If `nil` (default), polite greeting will be used.
246
+ header = nil,
247
+
248
+ -- Footer to be displayed after items. Converted to single string via
249
+ -- `tostring` (use `\n` to display several lines). If function, it is
250
+ -- evaluated first. If `nil` (default), default usage help will be shown.
251
+ footer = nil,
252
+
253
+ -- Array of functions to be applied consecutively to initial content.
254
+ -- Each function should take and return content for Starter buffer (see
255
+ -- |mini.starter| and |MiniStarter.get_content()| for more details).
256
+ content_hooks = nil,
257
+
258
+ -- Characters to update query. Each character will have special buffer
259
+ -- mapping overriding your global ones. Be careful to not add `:` as it
260
+ -- allows you to go into command mode.
261
+ query_updaters = 'abcdefghijklmnopqrstuvwxyz0123456789_-.',
262
+
263
+ -- Whether to disable showing non-error feedback
264
+ silent = false,
265
+ }
266
+ --minidoc_afterlines_end
267
+
268
+ -- Module functionality =======================================================
269
+ --- Open Starter buffer
270
+ ---
271
+ --- - Create buffer if necessary and move into it.
272
+ --- - Set buffer options. Note that settings are done with |:noautocmd| to
273
+ --- achieve a massive speedup.
274
+ --- - Set buffer mappings. Besides basic mappings (described inside "Lifecycle
275
+ --- of Starter buffer" of |mini.starter|), map every character from
276
+ --- `MiniStarter.config.query_updaters` to add itself to query with
277
+ --- |MiniStarter.add_to_query()|.
278
+ --- - Populate buffer with |MiniStarter.refresh()|.
279
+ --- - Issue custom `MiniStarterOpened` event to allow acting upon opening
280
+ --- Starter buffer. Use it with
281
+ --- `autocmd User MiniStarterOpened <your command>`.
282
+ ---
283
+ --- Note: to fully use it in autocommand, use |autocmd-nested|. Example: >lua
284
+ ---
285
+ --- local starter_open = function() MiniStarter.open() end
286
+ --- local au_opts = { nested = true, callback = starter_open }
287
+ --- vim.api.nvim_create_autocmd('TabNewEntered', au_opts)
288
+ --- <
289
+ ---@param buf_id number|nil Identifier of existing valid buffer (see |bufnr()|) to
290
+ --- open inside. Default: create a new one.
291
+ MiniStarter.open = function(buf_id)
292
+ if H.is_disabled() then return end
293
+
294
+ -- Ensure proper buffer and open it
295
+ if H.is_in_vimenter then
296
+ -- Use current buffer as it should be empty and not needed. This also
297
+ -- solves the issue of redundant buffer when opening a file from Starter.
298
+ buf_id = vim.api.nvim_get_current_buf()
299
+ end
300
+
301
+ if buf_id == nil or not vim.api.nvim_buf_is_valid(buf_id) then buf_id = vim.api.nvim_create_buf(false, true) end
302
+
303
+ -- Create buffer data entry
304
+ H.buffer_data[buf_id] = { current_item_id = 1, query = '' }
305
+
306
+ -- Ensure that local config in opened Starter buffer is the same as current.
307
+ -- This allow more advanced usage of buffer local configuration.
308
+ local config_local = vim.b.ministarter_config
309
+ vim.api.nvim_set_current_buf(buf_id)
310
+ vim.b.ministarter_config = config_local
311
+
312
+ -- Setup buffer behavior
313
+ H.set_buf_name(buf_id, 'welcome')
314
+ H.make_buffer_autocmd(buf_id)
315
+ H.apply_buffer_options(buf_id)
316
+ H.apply_buffer_mappings(buf_id)
317
+
318
+ -- Populate buffer
319
+ MiniStarter.refresh()
320
+
321
+ -- Issue custom event. Delay at startup, as it is executed with `noautocmd`.
322
+ local trigger_event = function() vim.api.nvim_exec_autocmds('User', { pattern = 'MiniStarterOpened' }) end
323
+ if H.is_in_vimenter then trigger_event = vim.schedule_wrap(trigger_event) end
324
+ trigger_event()
325
+
326
+ -- Ensure not being in VimEnter
327
+ H.is_in_vimenter = false
328
+ end
329
+
330
+ --- Refresh Starter buffer
331
+ ---
332
+ --- - Normalize `MiniStarter.config.items`:
333
+ --- - Flatten: recursively (in depth-first fashion) parse its elements. If
334
+ --- function is found, execute it and continue with parsing its output
335
+ --- (this allows deferring item collection up until it is actually
336
+ --- needed). If proper item is found (table with fields `action`,
337
+ --- `name`, `section`), add it to output.
338
+ --- - Sort: order first by section and then by item id (both in order of
339
+ --- appearance).
340
+ --- - Normalize `MiniStarter.config.header` and `MiniStarter.config.footer` to
341
+ --- be multiple lines by splitting at `\n`. If function - evaluate it first.
342
+ --- - Make initial buffer content (see |MiniStarter.get_content()| for a
343
+ --- description of what a buffer content is). It consist from content lines
344
+ --- with single content unit:
345
+ --- - First lines contain strings of normalized header.
346
+ --- - Body is for normalized items. Section names have own lines preceded
347
+ --- by empty line.
348
+ --- - Last lines contain separate strings of normalized footer.
349
+ --- - Sequentially apply hooks from `MiniStarter.config.content_hooks` to
350
+ --- content. All hooks are applied with `(content, buf_id)` signature. Output
351
+ --- of one hook serves as first argument to the next.
352
+ --- - Gather final items from content with |MiniStarter.content_to_items()|.
353
+ --- - Convert content to buffer lines with |MiniStarter.content_to_lines()| and
354
+ --- add them to buffer.
355
+ --- - Add highlighting of content units.
356
+ --- - Position cursor.
357
+ --- - Make current query. This results into some items being marked as
358
+ --- "inactive" and updating highlighting of current query on "active" items.
359
+ ---
360
+ --- Note: this function is executed on every |VimResized| to allow more
361
+ --- responsive behavior.
362
+ ---
363
+ ---@param buf_id __starter_buf_id
364
+ MiniStarter.refresh = function(buf_id)
365
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
366
+ if not H.validate_starter_buf_id(buf_id, 'refresh()') then return end
367
+
368
+ local data = H.buffer_data[buf_id]
369
+ local config = H.get_config()
370
+
371
+ -- Normalize certain config values
372
+ data.header = H.normalize_header_footer(config.header or H.default_header)
373
+ local items = H.normalize_items(config.items or H.default_items)
374
+ data.footer = H.normalize_header_footer(config.footer or H.default_footer)
375
+
376
+ -- Evaluate content
377
+ local content = H.make_initial_content(data.header, items, data.footer)
378
+ local hooks = config.content_hooks or H.default_content_hooks
379
+ for _, f in ipairs(hooks) do
380
+ content = f(content, buf_id)
381
+ end
382
+ data.content = content
383
+
384
+ -- Set items. Possibly reset current item id if items have changed.
385
+ local old_items = data.items
386
+ data.items = MiniStarter.content_to_items(content)
387
+ if not vim.deep_equal(data.items, old_items) then data.current_item_id = 1 end
388
+
389
+ -- Add content
390
+ vim.bo[buf_id].modifiable = true
391
+ vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, MiniStarter.content_to_lines(content))
392
+ vim.bo[buf_id].modifiable = false
393
+
394
+ -- Add highlighting
395
+ H.content_highlight(buf_id)
396
+ H.items_highlight(buf_id)
397
+
398
+ -- -- Always position cursor on current item
399
+ H.position_cursor_on_current_item(buf_id)
400
+ H.add_hl_current_item(buf_id)
401
+
402
+ -- Apply current query (clear command line afterwards)
403
+ H.make_query(buf_id)
404
+ end
405
+
406
+ --- Close Starter buffer
407
+ ---
408
+ ---@param buf_id __starter_buf_id
409
+ MiniStarter.close = function(buf_id)
410
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
411
+ if not H.validate_starter_buf_id(buf_id, 'close()') then return end
412
+
413
+ -- Use `pcall` to allow calling for already non-existing buffer
414
+ pcall(vim.api.nvim_buf_delete, buf_id, {})
415
+ end
416
+
417
+ -- Sections -------------------------------------------------------------------
418
+ --- Table of pre-configured sections
419
+ MiniStarter.sections = {}
420
+
421
+ --- Section with builtin actions
422
+ ---
423
+ ---@return table Array of items.
424
+ MiniStarter.sections.builtin_actions = function()
425
+ return {
426
+ { name = 'Edit new buffer', action = 'enew', section = 'Builtin actions' },
427
+ { name = 'Quit Neovim', action = 'qall', section = 'Builtin actions' },
428
+ }
429
+ end
430
+
431
+ --- Section with |mini.sessions| sessions
432
+ ---
433
+ --- Sessions are taken from |MiniSessions.detected|. Notes:
434
+ --- - If it shows `'mini.sessions' is not set up`, it means that you didn't
435
+ --- call `require('mini.sessions').setup()`.
436
+ --- - If it shows `There are no detected sessions in 'mini.sessions'`, it means
437
+ --- that there are no sessions at the current sessions directory. Either
438
+ --- create session or supply different directory where session files are
439
+ --- stored (see |MiniSessions.setup()|).
440
+ --- - Local session (if detected) is always displayed first.
441
+ ---
442
+ ---@param n number|nil Number of returned items. Default: 5.
443
+ ---@param recent boolean|nil Whether to use recent sessions (instead of
444
+ --- alphabetically by name). Default: true.
445
+ ---
446
+ ---@return __starter_section_fun
447
+ MiniStarter.sections.sessions = function(n, recent)
448
+ n = n or 5
449
+ if recent == nil then recent = true end
450
+
451
+ return function()
452
+ if _G.MiniSessions == nil then
453
+ return { { name = [['mini.sessions' is not set up]], action = '', section = 'Sessions' } }
454
+ end
455
+
456
+ local items = {}
457
+ for session_name, session in pairs(_G.MiniSessions.detected) do
458
+ table.insert(items, {
459
+ _session = session,
460
+ name = ('%s%s'):format(session_name, session.type == 'local' and ' (local)' or ''),
461
+ action = ([[lua _G.MiniSessions.read('%s')]]):format(session_name),
462
+ section = 'Sessions',
463
+ })
464
+ end
465
+
466
+ if vim.tbl_count(items) == 0 then
467
+ return { { name = [[There are no detected sessions in 'mini.sessions']], action = '', section = 'Sessions' } }
468
+ end
469
+
470
+ local sort_fun
471
+ if recent then
472
+ sort_fun = function(a, b)
473
+ local a_time = a._session.type == 'local' and math.huge or a._session.modify_time
474
+ local b_time = b._session.type == 'local' and math.huge or b._session.modify_time
475
+ return a_time > b_time
476
+ end
477
+ else
478
+ sort_fun = function(a, b)
479
+ local a_name = a._session.type == 'local' and '' or a.name
480
+ local b_name = b._session.type == 'local' and '' or b.name
481
+ return a_name < b_name
482
+ end
483
+ end
484
+ table.sort(items, sort_fun)
485
+
486
+ -- Take only first `n` elements and remove helper fields
487
+ return vim.tbl_map(function(x)
488
+ x._session = nil
489
+ return x
490
+ end, vim.list_slice(items, 1, n))
491
+ end
492
+ end
493
+
494
+ --- Section with most recently used files
495
+ ---
496
+ --- Files are taken from |v:oldfiles|.
497
+ ---
498
+ ---@param n number|nil Number of returned items. Default: 5.
499
+ ---@param current_dir boolean|nil Whether to return files only from current working
500
+ --- directory and its subdirectories. Default: `false`.
501
+ ---@param show_path boolean|function|nil Whether to append file name with its path.
502
+ --- If callable, will be called with full path and should return string to be
503
+ --- directly appended to file name. Default: `true`.
504
+ ---
505
+ ---@return __starter_section_fun
506
+ MiniStarter.sections.recent_files = function(n, current_dir, show_path)
507
+ n = n or 5
508
+ if current_dir == nil then current_dir = false end
509
+
510
+ if show_path == nil then show_path = true end
511
+ if show_path == false then show_path = function() return '' end end
512
+ if show_path == true then
513
+ show_path = function(path) return string.format(' (%s)', vim.fn.fnamemodify(path, ':~:.')) end
514
+ end
515
+ if not vim.is_callable(show_path) then H.error('`show_path` should be boolean or callable.') end
516
+
517
+ return function()
518
+ local section = string.format('Recent files%s', current_dir and ' (current directory)' or '')
519
+ local sep = vim.loop.os_uname().sysname == 'Windows_NT' and '\\' or '/'
520
+ local cwd = vim.fn.getcwd() .. sep
521
+
522
+ local files = {}
523
+ for _, f in ipairs(vim.v.oldfiles) do
524
+ -- Use only actually readable files possibly respecting current directory
525
+ if vim.fn.filereadable(f) == 1 and (not current_dir or vim.startswith(f, cwd)) then
526
+ table.insert(files, f)
527
+ if #files >= n then break end
528
+ end
529
+ end
530
+
531
+ if #files == 0 then
532
+ local suffix = current_dir and 'in current directory' or '(`v:oldfiles` is empty)'
533
+ local text = 'There are no recent files ' .. suffix
534
+ return { { name = text, action = '', section = section } }
535
+ end
536
+
537
+ -- Create items
538
+ local items = {}
539
+ for _, f in ipairs(files) do
540
+ local name = vim.fn.fnamemodify(f, ':t') .. show_path(f)
541
+ table.insert(items, { action = function() H.edit(f) end, name = name, section = section })
542
+ end
543
+
544
+ return items
545
+ end
546
+ end
547
+
548
+ -- stylua: ignore
549
+ --- Section with |mini.pick| pickers
550
+ ---
551
+ --- Notes:
552
+ --- - All actions require |mini.pick| module of |mini.nvim|.
553
+ --- - "Command history", "Explorer", and "Visited paths" items
554
+ --- require |mini.extra| module of |mini.nvim|.
555
+ --- - "Visited paths" items requires |mini.visits| module of |mini.nvim|.
556
+ ---
557
+ ---@return __starter_section_fun
558
+ MiniStarter.sections.pick = function()
559
+ return function()
560
+ return {
561
+ { action = 'Pick history scope=":"', name = 'Command history', section = 'Pick' },
562
+ { action = 'Pick explorer', name = 'Explorer', section = 'Pick' },
563
+ { action = 'Pick files', name = 'Files', section = 'Pick' },
564
+ { action = 'Pick grep_live', name = 'Grep live', section = 'Pick' },
565
+ { action = 'Pick help', name = 'Help tags', section = 'Pick' },
566
+ { action = 'Pick visit_paths', name = 'Visited paths', section = 'Pick' },
567
+ }
568
+ end
569
+ end
570
+
571
+ -- stylua: ignore
572
+ --- Section with basic Telescope pickers relevant to start screen
573
+ ---
574
+ --- Notes:
575
+ --- - All actions require
576
+ --- [nvim-telescope/telescope.nvim](https://github.com/nvim-telescope/telescope.nvim).
577
+ --- - "Browser" item requires
578
+ --- [nvim-telescope/telescope-file-browser.nvim](https://github.com/nvim-telescope/telescope-file-browser.nvim).
579
+ ---
580
+ ---@return __starter_section_fun
581
+ MiniStarter.sections.telescope = function()
582
+ return function()
583
+ return {
584
+ { action = 'Telescope file_browser', name = 'Browser', section = 'Telescope' },
585
+ { action = 'Telescope command_history', name = 'Command history', section = 'Telescope' },
586
+ { action = 'Telescope find_files', name = 'Files', section = 'Telescope' },
587
+ { action = 'Telescope help_tags', name = 'Help tags', section = 'Telescope' },
588
+ { action = 'Telescope live_grep', name = 'Live grep', section = 'Telescope' },
589
+ { action = 'Telescope oldfiles', name = 'Old files', section = 'Telescope' },
590
+ }
591
+ end
592
+ end
593
+
594
+ -- Content hooks --------------------------------------------------------------
595
+ --- Table with pre-configured content hook generators
596
+ ---
597
+ --- Each element is a function which returns content hook. So to use them
598
+ --- inside |MiniStarter.setup()|, call them.
599
+ MiniStarter.gen_hook = {}
600
+
601
+ --- Hook generator for padding
602
+ ---
603
+ --- Output is a content hook which adds constant padding from left and top.
604
+ --- This allows tweaking the screen position of buffer content.
605
+ ---
606
+ ---@param left number|nil Number of empty spaces to add to start of each content
607
+ --- line. Default: 0.
608
+ ---@param top number|nil Number of empty lines to add to start of content.
609
+ --- Default: 0.
610
+ ---
611
+ ---@return function Content hook.
612
+ MiniStarter.gen_hook.padding = function(left, top)
613
+ left = math.max(left or 0, 0)
614
+ top = math.max(top or 0, 0)
615
+ return function(content, _)
616
+ -- Add left padding
617
+ local left_pad = string.rep(' ', left)
618
+ for _, line in ipairs(content) do
619
+ local is_empty_line = #line == 0 or (#line == 1 and line[1].string == '')
620
+ if not is_empty_line then table.insert(line, 1, H.content_unit(left_pad, 'empty', nil)) end
621
+ end
622
+
623
+ -- Add top padding
624
+ local top_lines = {}
625
+ for _ = 1, top do
626
+ table.insert(top_lines, { H.content_unit('', 'empty', nil) })
627
+ end
628
+ content = vim.list_extend(top_lines, content)
629
+
630
+ return content
631
+ end
632
+ end
633
+
634
+ --- Hook generator for adding bullet to items
635
+ ---
636
+ --- Output is a content hook which adds supplied string to be displayed to the
637
+ --- left of item.
638
+ ---
639
+ ---@param bullet string|nil String to be placed to the left of item name.
640
+ --- Default: "░ ".
641
+ ---@param place_cursor boolean|nil Whether to place cursor on the first character
642
+ --- of bullet when corresponding item becomes current. Default: true.
643
+ ---
644
+ ---@return function Content hook.
645
+ MiniStarter.gen_hook.adding_bullet = function(bullet, place_cursor)
646
+ bullet = bullet or '░ '
647
+ if place_cursor == nil then place_cursor = true end
648
+ return function(content)
649
+ local coords = MiniStarter.content_coords(content, 'item')
650
+ -- Go backwards to avoid conflict when inserting units
651
+ for i = #coords, 1, -1 do
652
+ local l_num, u_num = coords[i].line, coords[i].unit
653
+ local bullet_unit = {
654
+ string = bullet,
655
+ type = 'item_bullet',
656
+ hl = 'MiniStarterItemBullet',
657
+ -- Use `_item` instead of `item` because it is better to be 'private'
658
+ _item = content[l_num][u_num].item,
659
+ _place_cursor = place_cursor,
660
+ }
661
+ table.insert(content[l_num], u_num, bullet_unit)
662
+ end
663
+
664
+ return content
665
+ end
666
+ end
667
+
668
+ --- Hook generator for indexing items
669
+ ---
670
+ --- Output is a content hook which adds unique index to the start of item's
671
+ --- name. It results into shortening queries required to choose an item (at
672
+ --- expense of clarity).
673
+ ---
674
+ ---@param grouping string|nil One of "all" (number indexing across all sections) or
675
+ --- "section" (letter-number indexing within each section). Default: "all".
676
+ ---@param exclude_sections table|nil Array of section names (values of `section`
677
+ --- element of item) for which index won't be added. Default: `{}`.
678
+ ---
679
+ ---@return function Content hook.
680
+ MiniStarter.gen_hook.indexing = function(grouping, exclude_sections)
681
+ grouping = grouping or 'all'
682
+ exclude_sections = exclude_sections or {}
683
+ local per_section = grouping == 'section'
684
+
685
+ return function(content, _)
686
+ local cur_section, n_section, n_item = nil, 0, 0
687
+ local coords = MiniStarter.content_coords(content, 'item')
688
+
689
+ for _, c in ipairs(coords) do
690
+ local unit = content[c.line][c.unit]
691
+ local item = unit.item
692
+
693
+ if not vim.tbl_contains(exclude_sections, item.section) then
694
+ n_item = n_item + 1
695
+ if cur_section ~= item.section then
696
+ cur_section = item.section
697
+ -- Cycle through lower case letters
698
+ n_section = math.fmod(n_section, 26) + 1
699
+ n_item = per_section and 1 or n_item
700
+ end
701
+
702
+ local section_index = per_section and string.char(96 + n_section) or ''
703
+ unit.string = ('%s%s. %s'):format(section_index, n_item, unit.string)
704
+ end
705
+ end
706
+
707
+ return content
708
+ end
709
+ end
710
+
711
+ --- Hook generator for aligning content
712
+ ---
713
+ --- Output is a content hook which independently aligns content horizontally
714
+ --- and vertically. Window width and height are taken from first window in current
715
+ --- tabpage displaying the Starter buffer.
716
+ ---
717
+ --- Basically, this computes left and top pads for |MiniStarter.gen_hook.padding()|
718
+ --- such that output lines would appear aligned in certain way.
719
+ ---
720
+ ---@param horizontal string|nil One of "left", "center", "right". Default: "left".
721
+ ---@param vertical string|nil One of "top", "center", "bottom". Default: "top".
722
+ ---
723
+ ---@return function Content hook.
724
+ MiniStarter.gen_hook.aligning = function(horizontal, vertical)
725
+ horizontal = horizontal or 'left'
726
+ vertical = vertical or 'top'
727
+
728
+ local horiz_coef = ({ left = 0, center = 0.5, right = 1.0 })[horizontal]
729
+ local vert_coef = ({ top = 0, center = 0.5, bottom = 1.0 })[vertical]
730
+
731
+ return function(content, buf_id)
732
+ local win_id = vim.fn.bufwinid(buf_id)
733
+ if win_id < 0 then return end
734
+
735
+ local line_strings = MiniStarter.content_to_lines(content)
736
+
737
+ -- Align horizontally
738
+ -- Don't use `string.len()` to account for multibyte characters
739
+ local lines_width = vim.tbl_map(function(l) return vim.fn.strdisplaywidth(l) end, line_strings)
740
+ local min_right_space = vim.api.nvim_win_get_width(win_id) - math.max(unpack(lines_width))
741
+ local left_pad = math.max(math.floor(horiz_coef * min_right_space), 0)
742
+
743
+ -- Align vertically
744
+ local bottom_space = vim.api.nvim_win_get_height(win_id) - #line_strings
745
+ local top_pad = math.max(math.floor(vert_coef * bottom_space), 0)
746
+
747
+ return MiniStarter.gen_hook.padding(left_pad, top_pad)(content)
748
+ end
749
+ end
750
+
751
+ -- Work with content ----------------------------------------------------------
752
+ --- Get content of Starter buffer
753
+ ---
754
+ --- Generally, buffer content is a table in the form of "2d array" (or rather
755
+ --- "2d list" because number of elements can differ):
756
+ --- - Each element represents content line: an array with content units to be
757
+ --- displayed in one buffer line.
758
+ --- - Each content unit is a table with at least the following elements:
759
+ --- - "type" - string with type of content. Something like "item",
760
+ --- "section", "header", "footer", "empty", etc.
761
+ --- - "string" - which string should be displayed. May be an empty string.
762
+ --- - "hl" - which highlighting should be applied to content string. May be
763
+ --- `nil` for no highlighting.
764
+ ---
765
+ --- See |MiniStarter.content_to_lines()| for converting content to buffer lines
766
+ --- and |MiniStarter.content_to_items()| - to list of parsed items.
767
+ ---
768
+ --- Notes:
769
+ --- - Content units with type "item" also have `item` element with all
770
+ --- information about an item it represents. Those elements are used directly
771
+ --- to create an array of items used for query.
772
+ ---
773
+ ---@param buf_id __starter_buf_id
774
+ MiniStarter.get_content = function(buf_id)
775
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
776
+ if not H.validate_starter_buf_id(buf_id, 'get_content()', 'error') then return end
777
+
778
+ return H.buffer_data[buf_id].content
779
+ end
780
+
781
+ --- Helper to iterate through content
782
+ ---
783
+ --- Basically, this traverses content "2d array" (in depth-first fashion; top
784
+ --- to bottom, left to right) and returns "coordinates" of units for which
785
+ --- `predicate` is true-ish.
786
+ ---
787
+ ---@param content table|nil Content "2d array". Default: content of current buffer.
788
+ ---@param predicate function|string|nil Predictate to filter units. If it is:
789
+ --- - Function, then it is evaluated with unit as input.
790
+ --- - String, then it checks unit to have this type (allows easy getting of
791
+ --- units with some type).
792
+ --- - `nil`, all units are kept.
793
+ ---
794
+ ---@return table Array with coordinates of resulting units. Each coordinate is a
795
+ --- table with <line> and <unit> keys. To retrieve actual unit from coordinate
796
+ --- `c`, use `content[c.line][c.unit]`.
797
+ MiniStarter.content_coords = function(content, predicate)
798
+ content = content or MiniStarter.get_content()
799
+ if predicate == nil then predicate = function(_) return true end end
800
+ if type(predicate) == 'string' then
801
+ local pred_type = predicate
802
+ predicate = function(unit) return unit.type == pred_type end
803
+ end
804
+
805
+ local res = {}
806
+ for l_num, line in ipairs(content) do
807
+ for u_num, unit in ipairs(line) do
808
+ if predicate(unit) then table.insert(res, { line = l_num, unit = u_num }) end
809
+ end
810
+ end
811
+ return res
812
+ end
813
+
814
+ -- stylua: ignore start
815
+ --- Convert content to buffer lines
816
+ ---
817
+ --- One buffer line is made by concatenating `string` element of units within
818
+ --- same content line.
819
+ ---
820
+ ---@param content table|nil Content "2d array". Default: content of current buffer.
821
+ ---
822
+ ---@return table Array of strings for each buffer line.
823
+ MiniStarter.content_to_lines = function(content)
824
+ return vim.tbl_map(
825
+ function(content_line)
826
+ return table.concat(
827
+ -- Ensure that each content line is indeed a single buffer line
828
+ vim.tbl_map(function(x) return x.string:gsub('\n', ' ') end, content_line), ''
829
+ )
830
+ end,
831
+ content or MiniStarter.get_content()
832
+ )
833
+ end
834
+ -- stylua: ignore end
835
+
836
+ --- Convert content to items
837
+ ---
838
+ --- Parse content (in depth-first fashion) and retrieve each item from `item`
839
+ --- element of content units with type "item". This also:
840
+ --- - Computes some helper information about how item will be actually
841
+ --- displayed (after |MiniStarter.content_to_lines()|) and minimum number of
842
+ --- prefix characters needed for a particular item to be queried single.
843
+ --- - Modifies item's `name` element taking it from corresponding `string`
844
+ --- element of content unit. This allows modifying item's `name` at the stage
845
+ --- of content hooks (like, for example, in |MiniStarter.gen_hook.indexing()|).
846
+ ---
847
+ ---@param content table|nil Content "2d array". Default: content of current buffer.
848
+ ---
849
+ ---@return table Array of items.
850
+ MiniStarter.content_to_items = function(content)
851
+ content = content or MiniStarter.get_content()
852
+
853
+ -- NOTE: this havily utilizes 'modify by reference' nature of Lua tables
854
+ local items = {}
855
+ for l_num, line in ipairs(content) do
856
+ -- Track 0-based starting column of current unit (using byte length)
857
+ local start_col = 0
858
+ for _, unit in ipairs(line) do
859
+ -- Cursor position is (1, 0)-based
860
+ local cursorpos = { l_num, start_col }
861
+
862
+ if unit.type == 'item' then
863
+ local item = unit.item
864
+ -- Take item's name from content string
865
+ item.name = unit.string:gsub('\n', ' ')
866
+ item._line = l_num - 1
867
+ item._start_col = start_col
868
+ item._end_col = start_col + unit.string:len()
869
+ -- Don't overwrite possible cursor position from item's bullet
870
+ item._cursorpos = item._cursorpos or cursorpos
871
+
872
+ table.insert(items, item)
873
+ end
874
+
875
+ -- Prefer placing cursor at start of item's bullet
876
+ if unit.type == 'item_bullet' and unit._place_cursor then
877
+ -- Item bullet uses 'private' `_item` element instead of `item`
878
+ unit._item._cursorpos = cursorpos
879
+ end
880
+
881
+ start_col = start_col + unit.string:len()
882
+ end
883
+ end
884
+
885
+ -- Compute length of unique prefix for every item's name (ignoring case)
886
+ local strings = vim.tbl_map(function(x) return x.name:lower() end, items)
887
+ local nprefix = H.unique_nprefix(strings)
888
+ for i, n in ipairs(nprefix) do
889
+ items[i]._nprefix = n
890
+ end
891
+
892
+ return items
893
+ end
894
+
895
+ -- Other exported functions ---------------------------------------------------
896
+ --- Evaluate current item
897
+ ---
898
+ --- Note that it resets current query before evaluation, as it is rarely needed
899
+ --- any more.
900
+ ---
901
+ ---@param buf_id __starter_buf_id
902
+ MiniStarter.eval_current_item = function(buf_id)
903
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
904
+ if not H.validate_starter_buf_id(buf_id, 'eval_current_item()') then return end
905
+
906
+ -- Reset query before evaluation without query echo (avoids hit-enter-prompt)
907
+ H.make_query(vim.api.nvim_get_current_buf(), '', false)
908
+
909
+ local data = H.buffer_data[buf_id]
910
+ H.eval_fun_or_string(data.items[data.current_item_id].action, true)
911
+ end
912
+
913
+ --- Update current item
914
+ ---
915
+ --- This makes next (with respect to `direction`) active item to be current.
916
+ ---
917
+ ---@param direction string One of "next" or "previous".
918
+ ---@param buf_id __starter_buf_id
919
+ MiniStarter.update_current_item = function(direction, buf_id)
920
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
921
+ if not H.validate_starter_buf_id(buf_id, 'update_current_item()') then return end
922
+
923
+ local data = H.buffer_data[buf_id]
924
+
925
+ -- Advance current item
926
+ local prev_current = data.current_item_id
927
+ data.current_item_id = H.next_active_item_id(buf_id, data.current_item_id, direction)
928
+ if data.current_item_id == prev_current then return end
929
+
930
+ -- Update cursor position
931
+ H.position_cursor_on_current_item(buf_id)
932
+
933
+ -- Highlight current item
934
+ vim.api.nvim_buf_clear_namespace(buf_id, H.ns.current_item, 0, -1)
935
+ H.add_hl_current_item(buf_id)
936
+ end
937
+
938
+ --- Add character to current query
939
+ ---
940
+ --- - Update current query by appending `char` to its end (only if it results
941
+ --- into at least one active item) or delete latest character if `char` is `nil`.
942
+ --- - Recompute status of items: "active" if its name starts with new query,
943
+ --- "inactive" otherwise.
944
+ --- - Update highlighting: whole strings for "inactive" items, current query
945
+ --- for "active" items.
946
+ ---
947
+ ---@param char string|nil Single character to be added to query. If `nil`, deletes
948
+ --- latest character from query.
949
+ ---@param buf_id __starter_buf_id
950
+ MiniStarter.add_to_query = function(char, buf_id)
951
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
952
+ if not H.validate_starter_buf_id(buf_id, 'add_to_query()') then return end
953
+
954
+ local data = H.buffer_data[buf_id]
955
+
956
+ local new_query
957
+ if char == nil then
958
+ new_query = data.query:sub(0, data.query:len() - 1)
959
+ else
960
+ new_query = ('%s%s'):format(data.query, char)
961
+ end
962
+ H.make_query(buf_id, new_query)
963
+ end
964
+
965
+ --- Set current query
966
+ ---
967
+ ---@param query string|nil Query to be set (only if it results into at least one
968
+ --- active item). Default: `nil` for setting query to empty string, which
969
+ --- essentially resets query.
970
+ ---@param buf_id __starter_buf_id
971
+ MiniStarter.set_query = function(query, buf_id)
972
+ query = query or ''
973
+ if type(query) ~= 'string' then error('`query` should be either `nil` or string.') end
974
+
975
+ buf_id = buf_id or vim.api.nvim_get_current_buf()
976
+ if not H.validate_starter_buf_id(buf_id, 'add_to_query()') then return end
977
+
978
+ H.make_query(buf_id, query)
979
+ end
980
+
981
+ -- Helper data ================================================================
982
+ -- Module default config
983
+ H.default_config = vim.deepcopy(MiniStarter.config)
984
+
985
+ -- Default config values
986
+ H.default_items = {
987
+ function()
988
+ if _G.MiniSessions == nil then return {} end
989
+ return MiniStarter.sections.sessions(5, true)()
990
+ end,
991
+ MiniStarter.sections.recent_files(5, false, false),
992
+ MiniStarter.sections.builtin_actions(),
993
+ }
994
+
995
+ H.default_header = function()
996
+ local hour = tonumber(vim.fn.strftime('%H'))
997
+ -- [04:00, 12:00) - morning, [12:00, 20:00) - day, [20:00, 04:00) - evening
998
+ local part_id = math.floor((hour + 4) / 8) + 1
999
+ local day_part = ({ 'evening', 'morning', 'afternoon', 'evening' })[part_id]
1000
+ local username = vim.loop.os_get_passwd()['username'] or 'USERNAME'
1001
+
1002
+ return ('Good %s, %s'):format(day_part, username)
1003
+ end
1004
+
1005
+ H.default_footer = [[
1006
+ Type query to filter items
1007
+ <BS> deletes latest character from query
1008
+ <Esc> resets current query
1009
+ <Down/Up>, <C-n/p>, <M-j/k> move current item
1010
+ <CR> executes action of current item
1011
+ <C-c> closes this buffer]]
1012
+
1013
+ H.default_content_hooks = { MiniStarter.gen_hook.adding_bullet(), MiniStarter.gen_hook.aligning('center', 'center') }
1014
+
1015
+ -- Storage for all Starter buffers. Fields - buffer number. Values - table:
1016
+ -- - <content> - buffer content (2d array of units)
1017
+ -- - <current_item_id> - identifier of current item
1018
+ -- - <footer> - table of strings
1019
+ -- - <header> - table of strings
1020
+ -- - <items> - normalized items gathered from final content
1021
+ -- - <query> - current search query
1022
+ H.buffer_data = {}
1023
+
1024
+ -- Namespaces for highlighting
1025
+ H.ns = {
1026
+ activity = vim.api.nvim_create_namespace(''),
1027
+ current_item = vim.api.nvim_create_namespace(''),
1028
+ general = vim.api.nvim_create_namespace(''),
1029
+ }
1030
+
1031
+ -- Helper functionality =======================================================
1032
+ -- Settings -------------------------------------------------------------------
1033
+ H.setup_config = function(config)
1034
+ H.check_type('config', config, 'table', true)
1035
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1036
+
1037
+ H.check_type('autoopen', config.autoopen, 'boolean')
1038
+ H.check_type('evaluate_single', config.evaluate_single, 'boolean')
1039
+ H.check_type('items', config.items, 'table', true)
1040
+ -- `header` and `footer` can have any type
1041
+ H.check_type('content_hooks', config.content_hooks, 'table', true)
1042
+ H.check_type('query_updaters', config.query_updaters, 'string')
1043
+ H.check_type('silent', config.silent, 'boolean')
1044
+
1045
+ return config
1046
+ end
1047
+
1048
+ H.apply_config = function(config) MiniStarter.config = config end
1049
+
1050
+ H.create_autocommands = function(config)
1051
+ local gr = vim.api.nvim_create_augroup('MiniStarter', {})
1052
+
1053
+ if config.autoopen then
1054
+ local on_vimenter = function()
1055
+ if H.is_something_shown() then return end
1056
+
1057
+ -- Set indicator used to make different decision on startup
1058
+ H.is_in_vimenter = true
1059
+ -- Use 'noautocmd' for better startup time
1060
+ vim.cmd('noautocmd lua MiniStarter.open()')
1061
+ end
1062
+
1063
+ local au_opts = { group = gr, nested = true, once = true, callback = on_vimenter, desc = 'Open on VimEnter' }
1064
+ vim.api.nvim_create_autocmd('VimEnter', au_opts)
1065
+ end
1066
+
1067
+ vim.api.nvim_create_autocmd('ColorScheme', { group = gr, callback = H.create_default_hl, desc = 'Ensure colors' })
1068
+ end
1069
+
1070
+ --stylua: ignore
1071
+ H.create_default_hl = function()
1072
+ local set_default_hl = function(name, data)
1073
+ data.default = true
1074
+ vim.api.nvim_set_hl(0, name, data)
1075
+ end
1076
+
1077
+ set_default_hl('MiniStarterCurrent', { link = 'MiniStarterItem' })
1078
+ set_default_hl('MiniStarterFooter', { link = 'Title' })
1079
+ set_default_hl('MiniStarterHeader', { link = 'Title' })
1080
+ set_default_hl('MiniStarterInactive', { link = 'Comment' })
1081
+ set_default_hl('MiniStarterItem', { link = 'Normal' })
1082
+ set_default_hl('MiniStarterItemBullet', { link = 'Delimiter' })
1083
+ set_default_hl('MiniStarterItemPrefix', { link = 'WarningMsg' })
1084
+ set_default_hl('MiniStarterSection', { link = 'Delimiter' })
1085
+ set_default_hl('MiniStarterQuery', { link = 'MoreMsg' })
1086
+ end
1087
+
1088
+ H.is_disabled = function() return vim.g.ministarter_disable == true or vim.b.ministarter_disable == true end
1089
+
1090
+ H.get_config = function(config)
1091
+ return vim.tbl_deep_extend('force', MiniStarter.config, vim.b.ministarter_config or {}, config or {})
1092
+ end
1093
+
1094
+ -- Normalize config elements --------------------------------------------------
1095
+ H.normalize_items = function(items)
1096
+ local res = H.items_flatten(items)
1097
+ if #res == 0 then return { { name = '`config.items` is empty', action = '', section = '' } } end
1098
+ return H.items_sort(res)
1099
+ end
1100
+
1101
+ H.normalize_header_footer = function(x)
1102
+ if type(x) == 'function' then x = x() end
1103
+ local res = tostring(x)
1104
+ if res == '' then return {} end
1105
+ return vim.split(res, '\n')
1106
+ end
1107
+
1108
+ -- Work with buffer content ---------------------------------------------------
1109
+ H.make_initial_content = function(header, items, footer)
1110
+ local content = {}
1111
+
1112
+ -- Add header lines
1113
+ for _, l in ipairs(header) do
1114
+ H.content_add_line(content, { H.content_unit(l, 'header', 'MiniStarterHeader') })
1115
+ end
1116
+ H.content_add_empty_lines(content, #header > 0 and 1 or 0)
1117
+
1118
+ -- Add item lines
1119
+ H.content_add_items(content, items)
1120
+
1121
+ -- Add footer lines
1122
+ H.content_add_empty_lines(content, #footer > 0 and 1 or 0)
1123
+ for _, l in ipairs(footer) do
1124
+ H.content_add_line(content, { H.content_unit(l, 'footer', 'MiniStarterFooter') })
1125
+ end
1126
+
1127
+ return content
1128
+ end
1129
+
1130
+ H.content_unit = function(string, type, hl, extra)
1131
+ return vim.tbl_extend('force', { string = string, type = type, hl = hl }, extra or {})
1132
+ end
1133
+
1134
+ H.content_add_line = function(content, content_line) table.insert(content, content_line) end
1135
+
1136
+ H.content_add_empty_lines = function(content, n)
1137
+ for _ = 1, n do
1138
+ H.content_add_line(content, { H.content_unit('', 'empty', nil) })
1139
+ end
1140
+ end
1141
+
1142
+ H.content_add_items = function(content, items)
1143
+ local cur_section
1144
+ for _, item in ipairs(items) do
1145
+ -- Possibly add section line
1146
+ if cur_section ~= item.section then
1147
+ -- Don't add empty line before first section line
1148
+ H.content_add_empty_lines(content, cur_section == nil and 0 or 1)
1149
+ H.content_add_line(content, { H.content_unit(item.section, 'section', 'MiniStarterSection') })
1150
+ cur_section = item.section
1151
+ end
1152
+
1153
+ H.content_add_line(content, { H.content_unit(item.name, 'item', 'MiniStarterItem', { item = item }) })
1154
+ end
1155
+ end
1156
+
1157
+ H.content_highlight = function(buf_id)
1158
+ for l_num, content_line in ipairs(MiniStarter.get_content(buf_id)) do
1159
+ -- Track 0-based starting column of current unit (using byte length)
1160
+ local start_col = 0
1161
+ for _, unit in ipairs(content_line) do
1162
+ if unit.hl ~= nil then
1163
+ H.buf_hl(buf_id, H.ns.general, unit.hl, l_num - 1, start_col, start_col + unit.string:len(), 50)
1164
+ end
1165
+ start_col = start_col + unit.string:len()
1166
+ end
1167
+ end
1168
+ end
1169
+
1170
+ -- Work with items -----------------------------------------------------------
1171
+ H.items_flatten = function(items)
1172
+ local res, f = {}, nil
1173
+ f = function(x)
1174
+ -- Expand (possibly recursively) functions immediately
1175
+ local n_nested = 0
1176
+ while type(x) == 'function' and n_nested <= 100 do
1177
+ n_nested = n_nested + 1
1178
+ if n_nested > 100 then H.message('Too many nested functions in `config.items`.') end
1179
+ x = x()
1180
+ end
1181
+
1182
+ if H.is_item(x) then
1183
+ -- Use deepcopy to allow adding fields to items without changing original
1184
+ table.insert(res, vim.deepcopy(x))
1185
+ return
1186
+ end
1187
+
1188
+ if type(x) ~= 'table' then return end
1189
+ return vim.tbl_map(f, x)
1190
+ end
1191
+
1192
+ f(items)
1193
+ return res
1194
+ end
1195
+
1196
+ H.items_sort = function(items)
1197
+ -- Order first by section and then by item id (both in order of appearance)
1198
+ -- Gather items grouped per section in order of their appearance
1199
+ local sections, section_order = {}, {}
1200
+ for _, item in ipairs(items) do
1201
+ local sec = item.section
1202
+ if section_order[sec] == nil then
1203
+ table.insert(sections, {})
1204
+ section_order[sec] = #sections
1205
+ end
1206
+ table.insert(sections[section_order[sec]], item)
1207
+ end
1208
+
1209
+ -- Unroll items in depth-first fashion
1210
+ local res = {}
1211
+ for _, section_items in ipairs(sections) do
1212
+ for _, item in ipairs(section_items) do
1213
+ table.insert(res, item)
1214
+ end
1215
+ end
1216
+
1217
+ return res
1218
+ end
1219
+
1220
+ H.items_highlight = function(buf_id)
1221
+ for _, item in ipairs(H.buffer_data[buf_id].items) do
1222
+ H.buf_hl(
1223
+ buf_id,
1224
+ H.ns.general,
1225
+ 'MiniStarterItemPrefix',
1226
+ item._line,
1227
+ item._start_col,
1228
+ item._start_col + item._nprefix,
1229
+ 52
1230
+ )
1231
+ end
1232
+ end
1233
+
1234
+ H.next_active_item_id = function(buf_id, item_id, direction)
1235
+ local items = H.buffer_data[buf_id].items
1236
+
1237
+ -- Advance in cyclic fashion
1238
+ local id = item_id
1239
+ local n_items = vim.tbl_count(items)
1240
+ local increment = direction == 'next' and 1 or (n_items - 1)
1241
+
1242
+ -- Increment modulo `n` but for 1-based indexing
1243
+ id = math.fmod(id + increment - 1, n_items) + 1
1244
+ while not (items[id]._active or id == item_id) do
1245
+ id = math.fmod(id + increment - 1, n_items) + 1
1246
+ end
1247
+
1248
+ return id
1249
+ end
1250
+
1251
+ H.position_cursor_on_current_item = function(buf_id)
1252
+ local data = H.buffer_data[buf_id]
1253
+ local cursorpos = data.items[data.current_item_id]._cursorpos
1254
+ for _, win_id in ipairs(H.get_buffer_windows(buf_id)) do
1255
+ vim.api.nvim_win_set_cursor(win_id, cursorpos)
1256
+ end
1257
+ end
1258
+
1259
+ H.item_is_active = function(item, query)
1260
+ -- Item is active = item's name starts with query (ignoring case) and item's
1261
+ -- action is non-empty
1262
+ return vim.startswith(item.name:lower(), query) and item.action ~= ''
1263
+ end
1264
+
1265
+ -- Work with queries ----------------------------------------------------------
1266
+ H.make_query = function(buf_id, query, echo_msg)
1267
+ if echo_msg == nil then echo_msg = true end
1268
+
1269
+ local data = H.buffer_data[buf_id]
1270
+ -- Ignore case
1271
+ query = (query or data.query):lower()
1272
+
1273
+ -- Don't make query if it results into no active items
1274
+ local n_active = 0
1275
+ for _, item in ipairs(data.items) do
1276
+ n_active = n_active + (H.item_is_active(item, query) and 1 or 0)
1277
+ end
1278
+
1279
+ if n_active == 0 and query ~= '' then
1280
+ H.message(('Query %s results into no active items. Current query: %s'):format(vim.inspect(query), data.query))
1281
+ return
1282
+ end
1283
+
1284
+ -- Update current query and active items
1285
+ data.query = query
1286
+ for _, item in ipairs(data.items) do
1287
+ item._active = H.item_is_active(item, query)
1288
+ end
1289
+
1290
+ -- Move to next active item if current is not active
1291
+ if not data.items[data.current_item_id]._active then MiniStarter.update_current_item('next', buf_id) end
1292
+
1293
+ -- Update activity highlighting. This should go before `evaluate_single`
1294
+ -- check because evaluation might not result into closing Starter buffer.
1295
+ vim.api.nvim_buf_clear_namespace(buf_id, H.ns.activity, 0, -1)
1296
+ H.add_hl_activity(buf_id, query)
1297
+
1298
+ -- Possibly evaluate single active item
1299
+ if H.get_config().evaluate_single and n_active == 1 then
1300
+ MiniStarter.eval_current_item(buf_id)
1301
+ return
1302
+ end
1303
+
1304
+ -- Notify about new query if not in VimEnter, where it might lead to
1305
+ -- unpleasant flickering due to startup process (lazy loading, etc.).
1306
+ if echo_msg and not H.is_in_vimenter and vim.o.cmdheight > 0 then
1307
+ -- Make sure that output of `echo` will be shown
1308
+ vim.cmd('redraw')
1309
+
1310
+ H.echo(('Query: %s'):format(query))
1311
+ end
1312
+ end
1313
+
1314
+ -- Work with Starter buffer ---------------------------------------------------
1315
+ H.make_buffer_autocmd = function(buf_id)
1316
+ local augroup = vim.api.nvim_create_augroup('MiniStarterBuffer', {})
1317
+
1318
+ local au = function(event, callback, desc)
1319
+ vim.api.nvim_create_autocmd(event, { group = augroup, buffer = buf_id, callback = callback, desc = desc })
1320
+ end
1321
+
1322
+ au({ 'VimResized', 'BufEnter' }, function() MiniStarter.refresh(buf_id) end, 'Refresh')
1323
+ au('CursorMoved', function() H.position_cursor_on_current_item(buf_id) end, 'Position cursor')
1324
+
1325
+ local cache_showtabline = vim.o.showtabline
1326
+ au('BufLeave', function()
1327
+ if vim.o.cmdheight > 0 then vim.cmd("echo ''") end
1328
+ if vim.o.showtabline == 1 then vim.o.showtabline = cache_showtabline end
1329
+ end, 'On BufLeave')
1330
+ end
1331
+
1332
+ H.apply_buffer_options = function(buf_id)
1333
+ -- NOTE: assumed that it is executing with `buf_id` being current buffer
1334
+
1335
+ -- Force Normal mode. NOTEs:
1336
+ -- - Using `vim.cmd('normal! \28\14')` weirdly does not work.
1337
+ -- - Using `vim.api.nvim_input([[<C-\><C-n>]])` doesn't play nice if `<C-\>`
1338
+ -- mapping is present (maybe due to non-blocking nature of `nvim_input()`).
1339
+ vim.api.nvim_feedkeys('\28\14', 'nx', false)
1340
+
1341
+ -- Having `noautocmd` is crucial for performance: ~9ms without it, ~1.6ms with it
1342
+ vim.cmd('noautocmd silent! set filetype=ministarter')
1343
+
1344
+ local options = {
1345
+ -- Taken from 'vim-startify'
1346
+ 'bufhidden=wipe',
1347
+ 'colorcolumn=',
1348
+ 'foldcolumn=0',
1349
+ 'matchpairs=',
1350
+ 'nobuflisted',
1351
+ 'nocursorcolumn',
1352
+ 'nocursorline',
1353
+ 'nolist',
1354
+ 'nonumber',
1355
+ 'noreadonly',
1356
+ 'norelativenumber',
1357
+ 'nospell',
1358
+ 'noswapfile',
1359
+ 'signcolumn=no',
1360
+ 'statuscolumn=',
1361
+ 'synmaxcol&',
1362
+ -- Differ from 'vim-startify'
1363
+ 'buftype=nofile',
1364
+ 'nomodeline',
1365
+ 'nomodifiable',
1366
+ 'foldlevel=999',
1367
+ 'nowrap',
1368
+ }
1369
+ -- Vim's `setlocal` is currently more robust compared to `opt_local`
1370
+ vim.cmd(('silent! noautocmd setlocal %s'):format(table.concat(options, ' ')))
1371
+
1372
+ -- Hide tabline on single tab by setting `showtabline` to default value (but
1373
+ -- not statusline as it weirdly feels 'naked' without it).
1374
+ vim.o.showtabline = 1
1375
+
1376
+ -- Make it a better user experience with other modules
1377
+ vim.b.minicursorword_disable = true
1378
+ vim.b.minitrailspace_disable = true
1379
+ if _G.MiniTrailspace ~= nil then _G.MiniTrailspace.unhighlight() end
1380
+ end
1381
+
1382
+ H.apply_buffer_mappings = function(buf_id)
1383
+ local buf_keymap = function(key, cmd)
1384
+ vim.keymap.set('n', key, ('<Cmd>lua %s<CR>'):format(cmd), { buffer = buf_id, nowait = true, silent = true })
1385
+ end
1386
+
1387
+ buf_keymap('<CR>', 'MiniStarter.eval_current_item()')
1388
+
1389
+ buf_keymap('<Up>', [[MiniStarter.update_current_item('prev')]])
1390
+ buf_keymap('<C-p>', [[MiniStarter.update_current_item('prev')]])
1391
+ buf_keymap('<M-k>', [[MiniStarter.update_current_item('prev')]])
1392
+ buf_keymap('<Down>', [[MiniStarter.update_current_item('next')]])
1393
+ buf_keymap('<C-n>', [[MiniStarter.update_current_item('next')]])
1394
+ buf_keymap('<M-j>', [[MiniStarter.update_current_item('next')]])
1395
+
1396
+ -- Make all special symbols to update query
1397
+ for _, key in ipairs(vim.split(H.get_config().query_updaters, '')) do
1398
+ local key_string = vim.inspect(tostring(key))
1399
+ buf_keymap(key, ('MiniStarter.add_to_query(%s)'):format(key_string))
1400
+ end
1401
+
1402
+ buf_keymap('<Esc>', [[MiniStarter.set_query('')]])
1403
+ buf_keymap('<BS>', 'MiniStarter.add_to_query()')
1404
+ buf_keymap('<C-c>', 'MiniStarter.close()')
1405
+ end
1406
+
1407
+ H.add_hl_activity = function(buf_id, query)
1408
+ for _, item in ipairs(H.buffer_data[buf_id].items) do
1409
+ local l = item._line
1410
+ local s = item._start_col
1411
+ local e = item._end_col
1412
+ if item._active then
1413
+ H.buf_hl(buf_id, H.ns.activity, 'MiniStarterQuery', l, s, s + query:len(), 53)
1414
+ else
1415
+ H.buf_hl(buf_id, H.ns.activity, 'MiniStarterInactive', l, s, e, 53)
1416
+ end
1417
+ end
1418
+ end
1419
+
1420
+ H.add_hl_current_item = function(buf_id)
1421
+ local data = H.buffer_data[buf_id]
1422
+ local cur_item = data.items[data.current_item_id]
1423
+ H.buf_hl(buf_id, H.ns.current_item, 'MiniStarterCurrent', cur_item._line, cur_item._start_col, cur_item._end_col, 51)
1424
+ end
1425
+
1426
+ -- Predicates -----------------------------------------------------------------
1427
+ H.is_fun_or_string = function(x, allow_nil)
1428
+ if allow_nil == nil then allow_nil = true end
1429
+ return (allow_nil and x == nil) or type(x) == 'function' or type(x) == 'string'
1430
+ end
1431
+
1432
+ H.is_item = function(x)
1433
+ return type(x) == 'table'
1434
+ and H.is_fun_or_string(x['action'], false)
1435
+ and type(x['name']) == 'string'
1436
+ and type(x['section']) == 'string'
1437
+ end
1438
+
1439
+ H.is_something_shown = function()
1440
+ -- Don't open Starter buffer if Neovim is opened to show something. That is
1441
+ -- when at least one of the following is true:
1442
+ -- - There are files in arguments (like `nvim foo.txt` with new file).
1443
+ if vim.fn.argc() > 0 then return true end
1444
+
1445
+ -- - Several buffers are listed (like session with placeholder buffers). That
1446
+ -- means unlisted buffers (like from `nvim-tree`) don't affect decision.
1447
+ local listed_buffers = vim.tbl_filter(
1448
+ function(buf_id) return vim.fn.buflisted(buf_id) == 1 end,
1449
+ vim.api.nvim_list_bufs()
1450
+ )
1451
+ if #listed_buffers > 1 then return true end
1452
+
1453
+ -- - Current buffer is meant to show something else
1454
+ if vim.bo.filetype ~= '' then return true end
1455
+
1456
+ -- - Current buffer has any lines (something opened explicitly).
1457
+ -- NOTE: Usage of `line2byte(line('$') + 1) < 0` seemed to be fine, but it
1458
+ -- doesn't work if some automated changed was made to buffer while leaving it
1459
+ -- empty (returns 2 instead of -1). This was also the reason of not being
1460
+ -- able to test with child Neovim process from 'tests/helpers'.
1461
+ local n_lines = vim.api.nvim_buf_line_count(0)
1462
+ if n_lines > 1 then return true end
1463
+ local first_line = vim.api.nvim_buf_get_lines(0, 0, 1, true)[1]
1464
+ if string.len(first_line) > 0 then return true end
1465
+
1466
+ return false
1467
+ end
1468
+
1469
+ -- Utilities ------------------------------------------------------------------
1470
+ H.error = function(msg) error('(mini.starter) ' .. msg, 0) end
1471
+
1472
+ H.check_type = function(name, val, ref, allow_nil)
1473
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1474
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1475
+ end
1476
+
1477
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'ministarter://' .. buf_id .. '/' .. name) end
1478
+
1479
+ H.echo = function(msg, is_important)
1480
+ if H.get_config().silent then return end
1481
+
1482
+ -- Construct message chunks
1483
+ msg = type(msg) == 'string' and { { msg } } or msg
1484
+ table.insert(msg, 1, { '(mini.starter) ', 'WarningMsg' })
1485
+
1486
+ -- Avoid hit-enter-prompt
1487
+ local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace
1488
+ local chunks, tot_width = {}, 0
1489
+ for _, ch in ipairs(msg) do
1490
+ local new_ch = { vim.fn.strcharpart(ch[1], 0, max_width - tot_width), ch[2] }
1491
+ table.insert(chunks, new_ch)
1492
+ tot_width = tot_width + vim.fn.strdisplaywidth(new_ch[1])
1493
+ if tot_width >= max_width then break end
1494
+ end
1495
+
1496
+ -- Echo. Force redraw to ensure that it is effective (`:h echo-redraw`)
1497
+ vim.cmd([[echo '' | redraw]])
1498
+ vim.api.nvim_echo(chunks, is_important, {})
1499
+ end
1500
+
1501
+ H.message = function(msg) H.echo(msg, true) end
1502
+
1503
+ H.edit = function(path, win_id)
1504
+ if type(path) ~= 'string' then return end
1505
+ local b = vim.api.nvim_win_get_buf(win_id or 0)
1506
+ local try_mimic_buf_reuse = (vim.fn.bufname(b) == '' and vim.bo[b].buftype ~= 'quickfix' and not vim.bo[b].modified)
1507
+ and (#vim.fn.win_findbuf(b) == 1 and vim.deep_equal(vim.fn.getbufline(b, 1, '$'), { '' }))
1508
+ local buf_id = vim.fn.bufadd(vim.fn.fnamemodify(path, ':.'))
1509
+ -- Showing in window also loads. Use `pcall` to not error with swap messages.
1510
+ pcall(vim.api.nvim_win_set_buf, win_id or 0, buf_id)
1511
+ vim.bo[buf_id].buflisted = true
1512
+ if try_mimic_buf_reuse then pcall(vim.api.nvim_buf_delete, b, { unload = false }) end
1513
+ return buf_id
1514
+ end
1515
+
1516
+ H.validate_starter_buf_id = function(buf_id, fun_name, severity)
1517
+ local is_starter_buf_id = type(buf_id) == 'number'
1518
+ and vim.tbl_contains(vim.tbl_keys(H.buffer_data), buf_id)
1519
+ and vim.api.nvim_buf_is_valid(buf_id)
1520
+ if is_starter_buf_id then return true end
1521
+
1522
+ local msg = string.format('`buf_id` in `%s` is not an identifier of valid Starter buffer.', fun_name)
1523
+ if severity == 'error' then H.error(msg) end
1524
+
1525
+ H.message(msg)
1526
+ return false
1527
+ end
1528
+
1529
+ H.eval_fun_or_string = function(x, string_as_cmd)
1530
+ if type(x) == 'function' then return x() end
1531
+ if type(x) == 'string' then
1532
+ if string_as_cmd then
1533
+ vim.cmd(x)
1534
+ else
1535
+ return x
1536
+ end
1537
+ end
1538
+ end
1539
+
1540
+ -- Use `priority` because of the regression bug (highlights are not stacked
1541
+ -- properly): https://github.com/neovim/neovim/issues/17358
1542
+ H.buf_hl = function(buf_id, ns_id, hl_group, line, col_start, col_end, priority)
1543
+ local opts = { end_row = line, end_col = col_end, hl_group = hl_group, priority = priority }
1544
+ vim.api.nvim_buf_set_extmark(buf_id, ns_id, line, col_start, opts)
1545
+ end
1546
+
1547
+ H.get_buffer_windows = function(buf_id)
1548
+ return vim.tbl_filter(
1549
+ function(win_id) return vim.api.nvim_win_get_buf(win_id) == buf_id end,
1550
+ vim.api.nvim_list_wins()
1551
+ )
1552
+ end
1553
+
1554
+ H.unique_nprefix = function(strings)
1555
+ -- For every string compute minimum width of unique prefix. NOTE: this can be
1556
+ -- done simpler but it would be O(n^2) which *will* have noticeable effect
1557
+ -- when there are a) many items and b) some of them are identical and have
1558
+ -- big length (like recent files with full paths).
1559
+
1560
+ -- Make copy because it will be modified
1561
+ local str_set = vim.deepcopy(strings)
1562
+ local res, cur_n = {}, 0
1563
+ while vim.tbl_count(str_set) > 0 do
1564
+ cur_n = cur_n + 1
1565
+
1566
+ -- `prefix_tbl`: string id's with current prefix
1567
+ -- `nowhere_to_go` is `true` if all strings have lengths less than `cur_n`
1568
+ local prefix_tbl, nowhere_to_go = {}, true
1569
+ for id, s in pairs(str_set) do
1570
+ nowhere_to_go = nowhere_to_go and (#s < cur_n)
1571
+ local prefix = s:sub(1, cur_n)
1572
+ prefix_tbl[prefix] = prefix_tbl[prefix] == nil and {} or prefix_tbl[prefix]
1573
+ table.insert(prefix_tbl[prefix], id)
1574
+ end
1575
+
1576
+ -- Output for non-unique string is its length
1577
+ if nowhere_to_go then
1578
+ for k, s in pairs(str_set) do
1579
+ res[k] = #s
1580
+ end
1581
+ break
1582
+ end
1583
+
1584
+ for _, keys_with_prefix in pairs(prefix_tbl) do
1585
+ -- If prefix is seen only once, it is unique
1586
+ if #keys_with_prefix == 1 then
1587
+ local k = keys_with_prefix[1]
1588
+ -- Use `math.min` to account for empty strings and non-unique ones
1589
+ res[k] = math.min(#str_set[k], cur_n)
1590
+ -- Remove this string as it already has final nprefix
1591
+ str_set[k] = nil
1592
+ end
1593
+ end
1594
+ end
1595
+
1596
+ return res
1597
+ end
1598
+
1599
+ return MiniStarter