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,1709 @@
1
+ --- *mini.map* Window with buffer text overview
2
+ ---
3
+ --- MIT License Copyright (c) 2022 Evgeni Chasnovski
4
+
5
+ --- Features:
6
+ --- - Show and manage special floating window displaying automatically updated
7
+ --- overview of current buffer text. Window takes up whole height of Neovim
8
+ --- instance and is fixed to a left/right side. Map content is computed by
9
+ --- taking all current lines, converting it to binary whitespace/non-whitespace
10
+ --- mask, rescaling to appropriate dimensions, and converting back to strings
11
+ --- consisting from special encoding symbols. All this is done very fast and
12
+ --- asynchronously. See |MiniMap.open()|, |MiniMap.refresh()|, |MiniMap.close()|,
13
+ --- |MiniMap.toggle()|, |MiniMap.toggle_side()|.
14
+ --- For a general overview and tips, see |mini.map-usage|.
15
+ ---
16
+ --- - Show scrollbar next to map content. It represents current line and view
17
+ --- (top and bottom visible lines). Can be the only thing shown, making map
18
+ --- window a "pure scrollbar". See "Pure scrollbar config" section in
19
+ --- |MiniMap.config|.
20
+ ---
21
+ --- - Highlight map lines representing certain data in current buffer. This is
22
+ --- done via extensible set of callables, called integrations (see
23
+ --- "Integrations" section in |MiniMap.config|). There are pre-built generators
24
+ --- for common integrations:
25
+ --- - Builtin search (as result of |/| and similar).
26
+ --- - Builtin diagnostic (taken from |vim.diagnostic.get()|).
27
+ --- - General diff hunks provided by |mini.diff|.
28
+ --- - Hunks computed provided by `lewis6991/gitsigns.nvim`.
29
+ --- For more details see |MiniMap.gen_integration|.
30
+ ---
31
+ --- - Focus on map window to quickly browse current (source) buffer. Moving inside
32
+ --- map window updates cursor position in source window enabling fast and
33
+ --- targeted buffer exploration. To focus back, hit `<CR>` to accept current
34
+ --- explored position or `<Esc>` to go back to original position. See
35
+ --- |MiniMap.toggle_focus()|.
36
+ ---
37
+ --- - Customizable via |MiniMap.config| and/or `opts` argument of |MiniMap.open()|
38
+ --- or |MiniMap.refresh()|:
39
+ --- - Encoding symbols used to display binary information of different
40
+ --- resolution (default is 3x2). There are pre-built generators for
41
+ --- different basic character families and resolutions. See
42
+ --- |MiniMap.gen_encode_symbols|.
43
+ --- - Scrollbar symbols, separate for line and view. Can have any width
44
+ --- (even zero, which virtually disables scrollbar).
45
+ --- - Integrations producing map line highlights.
46
+ --- - Window options: side (left/right), width, |'winblend'|, and more.
47
+ ---
48
+ --- What it doesn't do:
49
+ --- - Automatically refresh when typing in Insert mode. Although it can be done in
50
+ --- non-blocking way, it still might introduce considerable computation overhead
51
+ --- (especially in very large files).
52
+ --- - Has more flexible window configuration. In case a full height floating
53
+ --- window obstructs vision of underlying buffers, use |MiniMap.toggle()| or
54
+ --- |MiniMap.toggle_side()|. Works best with global statusline.
55
+ --- - Provide autoopen functionality. Due to vast differences in user preference
56
+ --- of when map window should be shown, set up of automatic opening is left to
57
+ --- user. A common approach would be to call `MiniMap.open()` on |VimEnter| event.
58
+ --- If you use |mini.starter|, you can modify `<CR>` buffer mapping: >lua
59
+ ---
60
+ --- local set_map_keymap = function()
61
+ --- local rhs = function()
62
+ --- MiniStarter.eval_current_item()
63
+ --- MiniMap.open()
64
+ --- end
65
+ --- vim.keymap.set('n', '<CR>', rhs, { buffer = true })
66
+ --- end
67
+ --- local opts = { pattern = 'MiniStarterOpened', callback = set_map_keymap }
68
+ --- vim.api.nvim_create_autocmd('User', opts)
69
+ --- <
70
+ --- # Setup ~
71
+ ---
72
+ --- This module needs a setup with `require('mini.map').setup({})` (replace
73
+ --- `{}` with your `config` table). It will create global Lua table `MiniMap`
74
+ --- which you can use for scripting or manually (with `:lua MiniMap.*`).
75
+ ---
76
+ --- See |MiniMap.config| for available config settings.
77
+ ---
78
+ --- You can override runtime config settings (like `config.modifiers`) locally
79
+ --- to buffer inside `vim.b.minimap_config` which should have same structure
80
+ --- as `MiniMap.config`. See |mini.nvim-buffer-local-config| for more details.
81
+ ---
82
+ --- # Dependencies ~
83
+ ---
84
+ --- Suggested dependencies (provide extra functionality for integrations):
85
+ --- - Enabled |mini.diff| module for general diff highlighting via
86
+ --- |MiniMap.gen_integration.diff()|. If missing, no highlighting is added.
87
+ --- - Plugin [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim)
88
+ --- for Git status highlighting via |MiniMap.gen_integration.gitsigns()|.
89
+ --- If missing, no highlighting is added.
90
+ ---
91
+ --- # Comparisons ~
92
+ ---
93
+ --- - [wfxr/minimap.vim](https://github.com/wfxr/minimap.vim):
94
+ --- - |mini.map| doesn't have dependencies while being as fast as written
95
+ --- in Rust dependency of `minimap.vim`.
96
+ --- - |mini.map| uses floating window, while `minimap.vim` uses regular one.
97
+ --- - |mini.map| provides slightly different visual interface with
98
+ --- scrollbar and integration counts.
99
+ --- - |mini.map| allows encode symbols customization, `minimap.vim` does not.
100
+ --- - |mini.map| allows extending highlight integrations, while only
101
+ --- builtin search and git status are supported in `minimap.vim`.
102
+ --- - |mini.map| updates in asynchronous (non-blocking) fashion, `minimap.vim`
103
+ --- does not.
104
+ --- - |mini.map| can be used as a pure scrollbar, `minimap.vim` can not.
105
+ --- - [dstein64/nvim-scrollview](https://github.com/dstein64/nvim-scrollview):
106
+ --- - |mini.map| has two-part scrollbar showing current line and view (with
107
+ --- variable height), while `nvim-scrollview` shows only current view
108
+ --- (with fixed height).
109
+ --- - `nvim-scrollview` respects folds, i.e. shows view of visible lines,
110
+ --- while |mini.map| by design always shows view based on actual lines.
111
+ --- - `nvim-scrollview` creates scrollbar which can be dragged with mouse,
112
+ --- while |mini.nvim| does not, by design (use |MiniMap.toggle_focus()|).
113
+ --- - |mini.map| can show buffer outline, while `nvim-scrollview` can not.
114
+ --- - |mini.map| can show highlight integrations, while `nvim-scrollview`
115
+ --- can not.
116
+ --- - [petertriho/nvim-scrollbar](https://github.com/petertriho/nvim-scrollbar):
117
+ --- - |mini.map| has two-part scrollbar showing current line and view (with
118
+ --- variable height), while `nvim-scrollbar` shows only current view.
119
+ --- - |mini.map| can show buffer outline, while `nvim-scrollbar` can not.
120
+ --- - |mini.map| has fully extendable highlight integrations, while
121
+ --- `nvim-scrollbar` only supports diagnostic and search (with dependency).
122
+ --- - [lewis6991/satellite.nvim](https://github.com/lewis6991/satellite.nvim):
123
+ --- - Almost the same differences as with `dstein64/nvim-scrollview`, except
124
+ --- `satellite.nvim` can display some set of integration highlights.
125
+ ---
126
+ --- # Highlight groups ~
127
+ --- *MiniMap-hl-groups*
128
+ ---
129
+ --- - `MiniMapNormal` - basic highlight of whole window.
130
+ --- - `MiniMapSymbolCount` - counts of per-line integration items.
131
+ --- - `MiniMapSymbolLine` - scrollbar part representing current line.
132
+ --- - `MiniMapSymbolView` - scrollbar part representing current view.
133
+ ---
134
+ --- To change any highlight group, set it directly with |nvim_set_hl()|.
135
+ ---
136
+ --- # Disabling ~
137
+ ---
138
+ --- To disable, set `vim.g.minimap_disable` (globally) or `vim.b.minimap_disable`
139
+ --- (for a buffer) to `true`. Considering high number of different scenarios
140
+ --- and customization intentions, writing exact rules for disabling module's
141
+ --- functionality is left to user. See |mini.nvim-disabling-recipes| for common
142
+ --- recipes.
143
+ ---@tag MiniMap
144
+
145
+ --- # Mappings ~
146
+ ---
147
+ --- This module doesn't make mappings, only provides functions for users to map
148
+ --- manually. Here is how one |<Leader>| set of mappings can be constructed: >lua
149
+ ---
150
+ --- vim.keymap.set('n', '<Leader>mc', MiniMap.close)
151
+ --- vim.keymap.set('n', '<Leader>mf', MiniMap.toggle_focus)
152
+ --- vim.keymap.set('n', '<Leader>mo', MiniMap.open)
153
+ --- vim.keymap.set('n', '<Leader>mr', MiniMap.refresh)
154
+ --- vim.keymap.set('n', '<Leader>ms', MiniMap.toggle_side)
155
+ --- vim.keymap.set('n', '<Leader>mt', MiniMap.toggle)
156
+ --- <
157
+ --- # How automatic refresh works ~
158
+ ---
159
+ --- Automatic refresh is done by calling |MiniMap.refresh()| when appropriate
160
+ --- |events| occur. It is done with specially chosen `parts` argument value (to
161
+ --- avoid unnecessary computations). For example, when only cursor has moved
162
+ --- (|CursorMoved|), only scrollbar is updated; so no recomputation of integrations
163
+ --- or line encoding is done.
164
+ ---
165
+ --- To avoid visual clutter, automatic refresh is done only in normal buffers
166
+ --- and help pages (i.e. with |'buftype'| being empty or "help")
167
+ ---
168
+ --- When you think content is not up to date, try one of these:
169
+ --- - Call |MiniMap.refresh()| manually. Make mapping to make it easier.
170
+ --- - Save current buffer, for example with |:write|.
171
+ --- - Exit and enter Normal mode (if your Neovim version supports |ModeChanged|).
172
+ ---@tag mini.map-usage
173
+
174
+ ---@alias __map_opts table|nil Options used to define map configuration. Same structure
175
+ --- as |MiniMap.config|. Will have effect until at least one tabpage has opened
176
+ --- map window. Default values are taken in the following order:
177
+ --- - From `opts` field of |MiniMap.current|.
178
+ --- - From `vim.b.minimap_config`.
179
+ --- - From |MiniMap.config|.
180
+
181
+ ---@diagnostic disable:undefined-field
182
+
183
+ -- Module definition ==========================================================
184
+ local MiniMap = {}
185
+ local H = {}
186
+
187
+ --- Module setup
188
+ ---
189
+ ---@param config table|nil Module config table. See |MiniMap.config|.
190
+ ---
191
+ ---@usage >lua
192
+ --- require('mini.map').setup() -- use default config
193
+ --- -- OR
194
+ --- require('mini.map').setup({}) -- replace {} with your config table
195
+ --- <
196
+ MiniMap.setup = function(config)
197
+ -- TODO: Remove after Neovim=0.9 support is dropped
198
+ if vim.fn.has('nvim-0.10') == 0 then
199
+ vim.notify(
200
+ '(mini.map) Neovim<0.10 is soft deprecated (module works but is not supported).'
201
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
202
+ .. ' Please update your Neovim version.'
203
+ )
204
+ end
205
+
206
+ -- Export module
207
+ _G.MiniMap = MiniMap
208
+
209
+ -- Setup config
210
+ config = H.setup_config(config)
211
+
212
+ -- Apply config
213
+ H.apply_config(config)
214
+
215
+ -- Define behavior
216
+ H.create_autocommands()
217
+
218
+ -- Create default highlighting
219
+ H.create_default_hl()
220
+ end
221
+
222
+ --stylua: ignore
223
+ --- Defaults ~
224
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
225
+ ---@text # Symbols ~
226
+ ---
227
+ --- Options in `config.symbols` define characters used to display various
228
+ --- information in map window.
229
+ ---
230
+ --- ## Encode symbols ~
231
+ ---
232
+ --- The `config.symbols.encode` option defines which characters are used to
233
+ --- encode source buffer lines. For details of encode algorithm, see
234
+ --- |MiniMap.encode_strings()|.
235
+ ---
236
+ --- This option should be a table with the following structure:
237
+ --- - <resolution> field - table containing <row> and <col> elements with row
238
+ --- and column resolution of each symbol. This defines encoding structure and
239
+ --- number of needed encode symbols.
240
+ --- - Numerical fields 1, 2, ..., 2^(row_resolution * col_resolution). Each symbol
241
+ --- represents a `(row_resolution, col_resolution)` boolean mask (`true` for
242
+ --- non-whitespace, `false` for whitespace), created as (reversed) binary digit:
243
+ --- `true` as 1; `false` as 0. Traversing left-right, top-bottom (top-left is
244
+ --- lowest bit, bottom-right - highest). So first symbol encodes a complete
245
+ --- whitespace, last - complete non-whitespace.
246
+ ---
247
+ --- If `nil` (default), output of |MiniMap.gen_encode_symbols.block()| with `'3x2'`
248
+ --- identifier is used.
249
+ ---
250
+ --- Example: `{ '1', '2', '3', '4', resolution = { row = 1, col = 2 } }`. This
251
+ --- will encode two characters in each input row. So a string `' a aaa'` will
252
+ --- be encoded as `'1234'`.
253
+ ---
254
+ --- There are pre-built generators of encode symbols:
255
+ --- - |MiniMap.gen_encode_symbols.block()|
256
+ --- - |MiniMap.gen_encode_symbols.dot()|
257
+ --- - |MiniMap.gen_encode_symbols.shade()|
258
+ ---
259
+ --- ## Scrollbar symbols ~
260
+ ---
261
+ --- Options `config.symbols.scroll_line` and `config.symbols.scroll_view` define
262
+ --- strings used to represent current line and current view inside map window.
263
+ --- Can have any length, map window content will adjust.
264
+ ---
265
+ --- If supplied window width is small enough so that only (part of) of
266
+ --- scrollbar can be shown, it is called a "pure scrollbar". The behavior differs
267
+ --- slightly from normal map window. See "Pure scrollbar config" later section.
268
+ ---
269
+ --- Some suggestions for scrollbar symbols:
270
+ --- - View-line pairs: `'▒'` and `'█'`.
271
+ --- - Line - `'🮚'`, `'▶'`.
272
+ --- - View - `'╎'`, `'┋'`, `'┋'`.
273
+ ---
274
+ --- # Integrations ~
275
+ ---
276
+ --- Option `config.integrations` is an array of integrations. Each one is used
277
+ --- to define map line highlights representing some important lines in source
278
+ --- buffer. If `nil` (default), no integrations are used.
279
+ ---
280
+ --- Each integration should be a callable returning an array with data about
281
+ --- SOURCE BUFFER lines it wants to highlight. Each array element should be
282
+ --- a table with <line> (source buffer line number) and <hl_group> (string with
283
+ --- highlight group name) keys. Note: line number outside of source buffer
284
+ --- count will be converted to a nearest appropriate one.
285
+ ---
286
+ --- Example output of single integration: >lua
287
+ ---
288
+ --- {
289
+ --- { line = 1, hl_group = 'Search' },
290
+ --- { line = 2, hl_group = 'Operator' },
291
+ --- { line = 9, hl_group = 'Search'}
292
+ --- }
293
+ --- <
294
+ --- Conversion to map highlights is done on a "first seen" basis: actual
295
+ --- highlight group applied to a map line is taken from the first integration
296
+ --- output convertible to that map line. Other outputs with same map line
297
+ --- (after conversion) contribute to integration count shown between scrollbar
298
+ --- and encoded lines (if `config.window.show_integration_count` is `true`).
299
+ ---
300
+ --- Previous example output with default `'3x2'` resolution will add |hl-Search|
301
+ --- highlight on map lines 1 and 3, and show integration count 2 on first line.
302
+ ---
303
+ --- Every element of integrations array is called one by one from start to end
304
+ --- with their outputs appended to end of single array. This means that more
305
+ --- important integrations should be placed in the beginning of an array, as
306
+ --- this will make them have higher priority in case other integrations will
307
+ --- highlight same map line.
308
+ ---
309
+ --- Example of using `config.integrations`: >lua
310
+ ---
311
+ --- local map = require('mini.map')
312
+ --- map.setup({
313
+ --- integrations = {
314
+ --- map.gen_integration.builtin_search(),
315
+ --- map.gen_integration.diff(),
316
+ --- map.gen_integration.diagnostic(),
317
+ --- },
318
+ --- })
319
+ --- <
320
+ --- # Window config ~
321
+ ---
322
+ --- Option `config.window` defines some properties of map window.
323
+ ---
324
+ --- `window.focusable` - whether to allow focusing on map window with other
325
+ --- methods beside |MiniMap.toggle_focus()| (like |:wincmd|, |CTRL-W|, or mouse).
326
+ --- Default: `false`.
327
+ ---
328
+ --- `window.side` - which side to stick map window: `'left'` or `'right'` (default).
329
+ ---
330
+ --- `window.show_integration_count` - whether to show integration count between
331
+ --- scrollbar and encoded lines. Integration count is a number of integration
332
+ --- outputs which were converted to same map line. When `true`, adds single
333
+ --- cell column with numbers from 2 to 9 and character `+` indicating count
334
+ --- greater than 9. Count 1 is not shown, because it is redundant to highlighted
335
+ --- map line. Default: `true`.
336
+ ---
337
+ --- `window.width` - width of floating window, including scrollbar and
338
+ --- integration count column. Default: 10.
339
+ ---
340
+ --- `window.winblend` - value of |'winblend'| of floating window. Value 0 makes it
341
+ --- completely non-transparent, 100 - completely transparent (content is still
342
+ --- visible, but with slightly different highlights).
343
+ ---
344
+ --- `window.zindex` - z-index of floating window. Default: 10.
345
+ ---
346
+ --- # Pure scrollbar config ~
347
+ ---
348
+ --- "Pure scrollbar" is a configuration when window width is not enough to show
349
+ --- encoded content. It has following differences from default "map" approach:
350
+ --- - It doesn't perform line encoding with |MiniMap.encode_strings()|
351
+ --- but instead uses encoding with fixed number of lines (equal to window
352
+ --- height).
353
+ --- - Integration highlights are not computed.
354
+ ---
355
+ --- Config: >lua
356
+ ---
357
+ --- require('mini.map').setup({
358
+ --- -- Customize `symbols` to your liking
359
+ ---
360
+ --- window = {
361
+ --- -- Set this to the maximum width of your scroll symbols
362
+ --- width = 1,
363
+ ---
364
+ --- -- Set this to your liking. Try values 0, 25, 50, 75, 100
365
+ --- winblend = 100,
366
+ ---
367
+ --- -- Don't need extra column
368
+ --- show_integration_count = false,
369
+ --- }
370
+ --- })
371
+ --- <
372
+ MiniMap.config = {
373
+ -- Highlight integrations (none by default)
374
+ integrations = nil,
375
+
376
+ -- Symbols used to display data
377
+ symbols = {
378
+ -- Encode symbols. See `:h MiniMap.config` for specification and
379
+ -- `:h MiniMap.gen_encode_symbols` for pre-built ones.
380
+ -- Default: solid blocks with 3x2 resolution.
381
+ encode = nil,
382
+
383
+ -- Scrollbar parts for view and line. Use empty string to disable any.
384
+ scroll_line = '█',
385
+ scroll_view = '┃',
386
+ },
387
+
388
+ -- Window options
389
+ window = {
390
+ -- Whether window is focusable in normal way (with `wincmd` or mouse)
391
+ focusable = false,
392
+
393
+ -- Side to stick ('left' or 'right')
394
+ side = 'right',
395
+
396
+ -- Whether to show count of multiple integration highlights
397
+ show_integration_count = true,
398
+
399
+ -- Total width
400
+ width = 10,
401
+
402
+ -- Value of 'winblend' option
403
+ winblend = 25,
404
+
405
+ -- Z-index
406
+ zindex = 10,
407
+ },
408
+ }
409
+ --minidoc_afterlines_end
410
+
411
+ --- Table with information about current state of map
412
+ ---
413
+ --- At least these keys are supported:
414
+ --- - <buf_data> - table with buffer identifiers. Field <map> contains
415
+ --- identifier of a buffer used to display map. Field <source> - buffer
416
+ --- identifier which content map is displaying (i.e. source buffer).
417
+ --- - <win_data> - table of window identifiers used to display map in certain
418
+ --- tabpage. Keys: tabpage identifier. Values: window identifier.
419
+ --- - <opts> - current options used to control map display. Same structure
420
+ --- as |MiniMap.config|. Takes precedence over global and buffer-local configs.
421
+ --- Is reset when last map window is closed with |MiniMap.close()|.
422
+ MiniMap.current = {
423
+ buf_data = {},
424
+ win_data = {},
425
+ opts = {},
426
+ }
427
+
428
+ -- Module functionality =======================================================
429
+ --- Encode strings
430
+ ---
431
+ --- This takes arbitrary array of strings and computes its non-whitespace
432
+ --- outline. Output is an array of strings with configurable array length, string
433
+ --- width, and symbols representing encoding.
434
+ ---
435
+ --- Each encode symbol is assumed to have resolution within which it can convey
436
+ --- binary information. For example, resolution `3x2` (row resolution 3,
437
+ --- column - 2) means that each symbol can encode 3 rows and 2 columns of
438
+ --- binary data. Here it is used to encode non-whitespace mask. See more in
439
+ --- "Encode symbols" section of |MiniMap.config|.
440
+ ---
441
+ --- Encoding has the following steps:
442
+ --- - Convert strings to boolean mask: 2d boolean array with each row
443
+ --- representing a string. Element in every row subarray is `true` if
444
+ --- respective (possibly multibyte) character in a string is not a whitespace,
445
+ --- `false` otherwise. Note: tabs are expanded into |'tabstop'| spaces.
446
+ --- - Rescale to appropriate dimensions:
447
+ --- - Each output dimension is just enough to encode all input strings, but
448
+ --- not more than supplied dimensions (`opts.n_rows * resolution.row` and
449
+ --- `opts.n_cols * resolution.col` respectively).
450
+ --- - If input dimensions are too big to fit inside output, perform grid
451
+ --- downscaling with loss of information. Input boolean mask is divided
452
+ --- into 2d-bins with as equal as possible dimensions. Each bin then
453
+ --- converted into single boolean value: `true` if bin contains at least
454
+ --- one `true` element, `false` otherwise. This leads to a whitespace
455
+ --- output meaning that ALL entries in a bin are whitespace, while
456
+ --- non-whitespace output means that SOME entry is non-whitespace.
457
+ --- - Convert boolean mask to symbol strings:
458
+ --- - Input rescaled boolean mask is divided into bins with dimensions of
459
+ --- symbol resolution (assuming `false` outer padding).
460
+ --- - Each bin with resolution dimensions is transformed into encode symbol.
461
+ --- Single convertible `(resolution.row, resolution.col)` boolean
462
+ --- mask is treated as (reversed) binary digit: `true` as 1; `false` as 0.
463
+ --- Traversing left-right, top-bottom (top-left is lowest bit,
464
+ --- bottom-right - highest).
465
+ ---
466
+ --- Example:
467
+ ---
468
+ --- Assume the output should have 3 rows of symbols each with width 2. Encode
469
+ --- symbols are `' '`, `'▌'`, `'▐'`, `'█'` with `1x2` resolution.
470
+ ---
471
+ --- Assume input strings: >
472
+ --- aaaaa
473
+ --- b b
474
+ ---
475
+ --- d d
476
+ --- e e
477
+ --- <
478
+ --- Steps:
479
+ --- - Convert to boolean mask (each row is a boolean array, "t"/"f" ~ `true`/`false`,
480
+ --- empty spots are equivalent to being `false`): >
481
+ --- ttttt
482
+ --- ftft
483
+ ---
484
+ --- ftft
485
+ --- tft
486
+ --- <
487
+ --- - Rescale. Output dimensions are `n_rows * resolution.row = 3 * 1 = 3` rows and
488
+ --- `n_cols * resolution.col = 2 * 2 = 4`. It creates as equal as possible grid
489
+ --- with 3 rows and 4 columns and converts bins to single booleans. Result: >
490
+ --- tttt
491
+ --- tftf
492
+ --- ttff
493
+ --- <
494
+ --- - Convert to symbols. It makes `1x2` bins, treats their input as (reversed)
495
+ --- binary digits (`ff=00=0`, `tf=10=1`, `ft=01=2`, `tt=11=3`) and takes
496
+ --- corresponding symbols from supplied options (value plus 1). Result: >
497
+ --- ██
498
+ --- ▌▌
499
+ --- █
500
+ --- <
501
+ ---@param strings table Array of arbitrary strings.
502
+ ---@param opts table|nil Options. Possible fields:
503
+ --- - <n_rows> - number of rows in output encoding. If too big, will be
504
+ --- truncated to be maximum needed to encode all input strings (taking into
505
+ --- account symbols row resolution). Default: `math.huge`.
506
+ --- - <n_cols> - width of every encoding string. If too big, will be truncated
507
+ --- to be maximum needed to encode all input strings (taking into account
508
+ --- symbols column resolution). Default: `math.huge`.
509
+ --- - <symbols> - array of symbols with extra `resolution` field. See "Encode
510
+ --- symbols" section of |MiniMap.config| for more details. Default: output
511
+ --- of |MiniMap.gen_encode_symbols.block()| with `'3x2'` identifier.
512
+ ---
513
+ ---@return table Array of encoded strings.
514
+ MiniMap.encode_strings = function(strings, opts)
515
+ -- Validate input
516
+ if not H.is_array_of(strings, H.is_string) then
517
+ H.error('`strings` argument of `encode_strings()` should be array of strings.')
518
+ end
519
+
520
+ opts = vim.tbl_deep_extend(
521
+ 'force',
522
+ { n_rows = math.huge, n_cols = math.huge, symbols = H.get_config().symbols.encode or H.default_symbols },
523
+ opts or {}
524
+ )
525
+
526
+ -- Compute encoding
527
+ local mask = H.mask_from_strings(strings, opts)
528
+ mask = H.mask_rescale(mask, opts)
529
+ return H.mask_to_symbols(mask, opts)
530
+ end
531
+
532
+ --- Open map window
533
+ ---
534
+ --- This creates and shows map window in current tabpage. It basically has
535
+ --- two steps:
536
+ --- - If not already done, create map buffer (used to set lines and other
537
+ --- visual indicators) and map window.
538
+ --- - Call |MiniMap.refresh()|.
539
+ ---
540
+ ---@param opts __map_opts
541
+ MiniMap.open = function(opts)
542
+ -- Early returns
543
+ if H.is_disabled() then return end
544
+
545
+ -- Normalize input
546
+ opts = H.normalize_opts(opts)
547
+
548
+ -- Allow execution in case of already opened window
549
+ if H.is_window_open() then
550
+ MiniMap.refresh(opts)
551
+ return
552
+ end
553
+
554
+ -- Open buffer and window
555
+ local buf_id = MiniMap.current.buf_data.map
556
+ if buf_id == nil or not vim.api.nvim_buf_is_loaded(buf_id) then
557
+ pcall(vim.api.nvim_buf_delete, buf_id, { force = true })
558
+ buf_id = H.create_map_buffer()
559
+ MiniMap.current.buf_data.map = buf_id
560
+ end
561
+
562
+ local win_id = vim.api.nvim_open_win(buf_id, false, H.normalize_window_options(opts.window))
563
+ H.set_current_map_win(win_id)
564
+
565
+ -- Set buffer and window options. Other important options are handled by
566
+ -- `style = 'minimal'` in `nvim_open_win()`.
567
+ vim.api.nvim_win_call(win_id, function()
568
+ --stylua: ignore
569
+ local options = {
570
+ 'buftype=nofile', 'foldcolumn=0', 'foldlevel=999', 'matchpairs=', 'nobuflisted',
571
+ 'nomodeline', 'noreadonly', 'noswapfile', 'synmaxcol&', 'nowrap',
572
+ }
573
+ -- Vim's `setlocal` is currently more robust compared to `opt_local`
574
+ -- Use `noautocmd` to make it more invisible for others
575
+ vim.cmd(('silent! noautocmd setlocal %s'):format(table.concat(options, ' ')))
576
+
577
+ -- Override Normal highlighting locally for map window
578
+ vim.cmd('silent! setlocal winhighlight=NormalFloat:MiniMapNormal')
579
+ end)
580
+
581
+ -- Refresh content
582
+ MiniMap.refresh(opts)
583
+ end
584
+
585
+ --- Refresh map window
586
+ ---
587
+ --- This function serves two purposes:
588
+ --- - Update current map configuration via `opts`.
589
+ --- - Update parts of displayed content via `parts`.
590
+ ---
591
+ ---@param opts __map_opts
592
+ ---@param parts table|nil Which parts to update. Recognised keys with boolean
593
+ --- values (all `true` by default):
594
+ --- - <integrations> - whether to update integration highlights.
595
+ --- - <lines> - whether to update map lines.
596
+ --- - <scrollbar> - whether to update scrollbar.
597
+ MiniMap.refresh = function(opts, parts)
598
+ -- Early return
599
+ if H.is_disabled() or not H.is_window_open() then return end
600
+
601
+ -- Normalize input
602
+ opts = H.normalize_opts(opts)
603
+ parts = vim.tbl_deep_extend('force', { integrations = true, lines = true, scrollbar = true }, parts or {})
604
+
605
+ -- Update current data
606
+ H.cache.scrollbar_data.offset = math.max(H.str_width(opts.symbols.scroll_line), H.str_width(opts.symbols.scroll_view))
607
+ + (opts.window.show_integration_count and 1 or 0)
608
+ MiniMap.current.opts = opts
609
+
610
+ -- Update window options
611
+ H.update_window_opts()
612
+
613
+ -- Possibly update parts in asynchronous fashion
614
+ if parts.lines then vim.schedule(H.update_map_lines) end
615
+ if parts.scrollbar then vim.schedule(H.update_map_scrollbar) end
616
+ if parts.integrations then vim.schedule(H.update_map_integrations) end
617
+ end
618
+
619
+ --- Close map window
620
+ ---
621
+ --- Also resets `opts` field of |MiniMap.current| after closing last map window
622
+ --- (among possibly several tabpages).
623
+ MiniMap.close = function()
624
+ pcall(vim.api.nvim_win_close, H.get_current_map_win(), true)
625
+ H.set_current_map_win(nil)
626
+
627
+ -- Reset current options if closed last window so as to use config during
628
+ -- next opening
629
+ if vim.tbl_count(MiniMap.current.win_data) == 0 then MiniMap.current.opts = {} end
630
+ end
631
+
632
+ --- Toggle map window
633
+ ---
634
+ --- Open if not shown in current tabpage, close otherwise.
635
+ ---
636
+ ---@param opts table|nil Input for |MiniMap.open()|.
637
+ MiniMap.toggle = function(opts)
638
+ if H.is_window_open() then
639
+ MiniMap.close()
640
+ else
641
+ MiniMap.open(opts)
642
+ end
643
+ end
644
+
645
+ --- Toggle focus to/from map window
646
+ ---
647
+ --- When not inside map window, put cursor inside map window; otherwise put
648
+ --- cursor in previous window with source buffer.
649
+ ---
650
+ --- When cursor is moving inside map window (but not just after focusing), view of
651
+ --- source window is updated to show first line convertible to current map line.
652
+ --- This allows quick targeted source buffer exploration.
653
+ ---
654
+ --- There are at least these extra methods to focus back from map window:
655
+ --- - Press `<CR>` to accept current explored position in source buffer.
656
+ --- Equivalent to calling this function with `false` argument.
657
+ --- - Press `<Esc>` to go back to original position prior focusing on map window.
658
+ --- Equivalent to calling this function with `true` argument.
659
+ ---
660
+ ---@param use_previous_cursor boolean|nil Whether to focus on source window at
661
+ --- original cursor position (the one prior focusing on map window).
662
+ MiniMap.toggle_focus = function(use_previous_cursor)
663
+ if not H.is_window_open() then return end
664
+ local cur_win, map_win = vim.api.nvim_get_current_win(), H.get_current_map_win()
665
+
666
+ if cur_win == map_win then
667
+ -- Focus on previous window
668
+ vim.api.nvim_set_current_win(H.cache.previous_win.id)
669
+
670
+ -- Use either previous cursor or first non-whitespace character (if this
671
+ -- was the result of cursor movement inside map window)
672
+ if use_previous_cursor and H.cache.previous_win.cursor ~= nil then
673
+ vim.api.nvim_win_set_cursor(0, H.cache.previous_win.cursor)
674
+ elseif H.cache.n_map_cursor_moves > 1 then
675
+ vim.cmd('normal! ^')
676
+ end
677
+ else
678
+ -- Focus on map window. Cursor is set on `BufEnter` to account for other
679
+ -- ways of focusing on buffer (for example, with `<C-w><C-w>`)
680
+ vim.api.nvim_set_current_win(map_win)
681
+ end
682
+ end
683
+
684
+ --- Toggle side of map window
685
+ ---
686
+ --- A small convenience wrapper for calling |MiniMap.refresh()| to change the
687
+ --- side of map window.
688
+ MiniMap.toggle_side = function()
689
+ if not H.is_window_open() then return end
690
+ local cur_side = MiniMap.current.opts.window.side
691
+ MiniMap.refresh(
692
+ { window = { side = cur_side == 'left' and 'right' or 'left' } },
693
+ { integrations = false, lines = false, scrollbar = false }
694
+ )
695
+ end
696
+
697
+ --- Generate encode symbols
698
+ ---
699
+ --- This is a table with function elements. Call to actually get encode symbols.
700
+ ---
701
+ --- Each element takes a string resolution identifier of a form `'rxc'` (like `'3x2'`)
702
+ --- where `r` is a row resolution of each symbol (how many rows of binary data it
703
+ --- can encode) and `c` is a column resolution (how many columns it can encode).
704
+ MiniMap.gen_encode_symbols = {}
705
+
706
+ --- Generate block encode symbols
707
+ ---
708
+ --- Outputs use solid block to encode binary data. Example: `'🬗'`, `'▟'`, `'█'`.
709
+ ---
710
+ ---@param id string Resolution identifier.
711
+ --- Available values: `'1x2'`, `'2x1'`, `'2x2'`, `'3x2'` (default in |mini.map|).
712
+ MiniMap.gen_encode_symbols.block = function(id) return H.block_symbols[id] end
713
+
714
+ --- Generate dot encode symbols
715
+ ---
716
+ --- Outputs use dots to encode binary data. Example: `'⡪'`, `'⣼'`, `'⣿'`.
717
+ ---
718
+ ---@param id string Resolution identifier. Available values: `'4x2'`, `'3x2'`.
719
+ MiniMap.gen_encode_symbols.dot = function(id) return H.dot_symbols[id] end
720
+
721
+ --- Generate shade encode symbols
722
+ ---
723
+ --- Outputs use whole cell shades to encode binary data. They use same set of
724
+ --- characters (`'░'`, `'▒'`, `'▒'`, `'▓'`), but with different resolution.
725
+ ---
726
+ ---@param id string Resolution identifier. Available values: `'1x2'`, `'2x1'`.
727
+ MiniMap.gen_encode_symbols.shade = function(id) return H.shade_symbols[id] end
728
+
729
+ --- Generate integrations
730
+ ---
731
+ --- This is a table with function elements. Call to actually get encode symbols.
732
+ ---
733
+ --- Each element takes a table defining highlight groups used for to highlight
734
+ --- map lines.
735
+ MiniMap.gen_integration = {}
736
+
737
+ --- Builtin search
738
+ ---
739
+ --- Highlight lines with matches of current builtin search (like with |/|, |?|, etc.).
740
+ --- Integration count reflects number of actual matches.
741
+ ---
742
+ --- It prompts integration highlighting update on every change of |'hlsearch'|
743
+ --- (see |OptionSet|) or |v:hlsearch|. Note that it is not happening for some keys:
744
+ --- - After starting search with |n|, |N|, |star|, or |#|.
745
+ --- To enable highlight update on this keys, make custom mappings. Like this: >lua
746
+ ---
747
+ --- for _, key in ipairs({ 'n', 'N', '*', '#' }) do
748
+ --- local rhs = key ..
749
+ --- '<Cmd>lua MiniMap.refresh({}, {lines = false, scrollbar = false})<CR>'
750
+ --- vim.keymap.set('n', key, rhs)
751
+ --- end
752
+ --- <
753
+ ---@param hl_groups table|nil Table defining highlight groups. Can have the
754
+ --- following fields:
755
+ --- - <search> - highlight group for search matches. Default: |hl-Search|.
756
+ MiniMap.gen_integration.builtin_search = function(hl_groups)
757
+ if hl_groups == nil then hl_groups = { search = 'Search' } end
758
+
759
+ -- Update when necessary. Not ideal, because it won't react on `n/N/*`, etc.
760
+ -- See https://github.com/neovim/neovim/issues/18879
761
+ local gr = vim.api.nvim_create_augroup('MiniMapBuiltinSearch', {})
762
+ local opts = { group = gr, pattern = 'hlsearch', callback = H.on_integration_update, desc = "On 'hlsearch' update" }
763
+ vim.api.nvim_create_autocmd('OptionSet', opts)
764
+
765
+ -- - NOTE: beware of possible https://github.com/neovim/neovim/issues/21469
766
+ vim.cmd([[
767
+ silent! call dictwatcherdel(v:, 'hlsearch', 'MiniMapOnHLSearchChanged')
768
+ function! MiniMapOnHLSearchChanged(d,k,z)
769
+ lua MiniMap.refresh(nil, { lines = false, scrollbar = false })
770
+ endfunction
771
+ call dictwatcheradd(v:, 'hlsearch', 'MiniMapOnHLSearchChanged')
772
+ ]])
773
+
774
+ local search_hl = hl_groups.search
775
+
776
+ return function()
777
+ -- Do nothing of search is not active
778
+ if vim.v.hlsearch == 0 or not vim.o.hlsearch then return {} end
779
+
780
+ -- Do nothing if not inside source buffer (can happen in map buffer, for example)
781
+ if not H.is_source_buffer() then return {} end
782
+
783
+ -- Save window view to later restore, as the only way to get positions of
784
+ -- search matches seems to be consecutive application of `search()` and
785
+ -- retrieving cursor position.
786
+ local win_view = vim.fn.winsaveview()
787
+
788
+ vim.api.nvim_win_set_cursor(0, { 1, 0 })
789
+ local search_count = vim.fn.searchcount({ recompute = true, maxcount = 0 })
790
+ local search_pattern = vim.fn.getreg('/')
791
+ local line_hl = {}
792
+ for _ = 1, (search_count.total or 0) do
793
+ vim.fn.search(search_pattern)
794
+ table.insert(line_hl, { line = vim.fn.line('.'), hl_group = search_hl })
795
+ end
796
+
797
+ vim.fn.winrestview(win_view)
798
+
799
+ return line_hl
800
+ end
801
+ end
802
+
803
+ --- Builtin diagnostic
804
+ ---
805
+ --- Highlight lines with matches of current diagnostic items. Items are computed
806
+ --- with |vim.diagnostic.get()| for current (source) buffer.
807
+ ---
808
+ --- It prompts integration highlighting update on every |DiagnosticChanged| event.
809
+ --- Diagnostic items with higher severity (see |vim.diagnostic.severity|) have
810
+ --- higher highlight priority (errors will be shown over all others, etc.).
811
+ ---
812
+ ---@param hl_groups table|nil Table defining highlight groups. Supplied fields
813
+ --- also define which diagnostic severity to highlight.
814
+ --- Can have the following fields:
815
+ --- - <error> - highlight group for error items.
816
+ --- Default: |hl-DiagnosticFloatingError|.
817
+ --- - <warn> - highlight group for warning items. Default: `nil` (not shown).
818
+ --- - <info> - highlight group for info items. Default: `nil` (not shown).
819
+ --- - <hint> - highlight group for hint items. Default: `nil` (not shown).
820
+ ---
821
+ ---@usage >lua
822
+ --- -- Show all diagnostic levels
823
+ --- local map = require('mini.map')
824
+ --- local diagnostic_integration = map.gen_integration.diagnostic({
825
+ --- error = 'DiagnosticFloatingError',
826
+ --- warn = 'DiagnosticFloatingWarn',
827
+ --- info = 'DiagnosticFloatingInfo',
828
+ --- hint = 'DiagnosticFloatingHint',
829
+ --- })
830
+ --- map.setup({ integrations = { diagnostic_integration } })
831
+ --- <
832
+ MiniMap.gen_integration.diagnostic = function(hl_groups)
833
+ if hl_groups == nil then hl_groups = { error = 'DiagnosticFloatingError' } end
834
+
835
+ -- Precompute ordered array of supported levels. Using keys of
836
+ -- `severity_highlights` is not enough because higher severity should be
837
+ -- processed later in order to appear on top.
838
+ local severity_level_names = vim.tbl_filter(
839
+ function(x) return vim.tbl_contains(vim.tbl_keys(hl_groups), x) end,
840
+ { 'error', 'warn', 'info', 'hint' }
841
+ )
842
+ local severity_data = vim.tbl_map(
843
+ function(x) return { severity = vim.diagnostic.severity[x:upper()], hl_group = hl_groups[x] } end,
844
+ severity_level_names
845
+ )
846
+
847
+ -- Refresh map when needed
848
+ local augroup = vim.api.nvim_create_augroup('MiniMapDiagnostics', {})
849
+ vim.api.nvim_create_autocmd(
850
+ 'DiagnosticChanged',
851
+ { group = augroup, callback = H.on_integration_update, desc = 'On DiagnosticChanged' }
852
+ )
853
+
854
+ return function()
855
+ local line_hl = {}
856
+ local diagnostic_arr = vim.diagnostic.get(MiniMap.current.buf_data.source)
857
+ for _, data in ipairs(severity_data) do
858
+ local severity_diagnostic_arr = vim.tbl_filter(function(x) return x.severity == data.severity end, diagnostic_arr)
859
+ for _, diag in ipairs(severity_diagnostic_arr) do
860
+ -- Add all diagnostic lines to highlight
861
+ for i = diag.lnum, diag.end_lnum do
862
+ table.insert(line_hl, { line = i + 1, hl_group = data.hl_group })
863
+ end
864
+ end
865
+ end
866
+
867
+ return line_hl
868
+ end
869
+ end
870
+
871
+ --- General diff hunks from |mini.diff|
872
+ ---
873
+ --- Highlight lines which are part of current diff.
874
+ --- Requires |mini.diff| as dependency.
875
+ ---
876
+ ---@param hl_groups table|nil Table defining highlight groups. If `nil` (not
877
+ --- supplied), this status is not highlighted. Can have the following fields:
878
+ --- - <add> - group name for "add" hunks. Default: "MiniDiffSignAdd".
879
+ --- - <change> - group name for "change" hunks. Default: "MiniDiffSignChange".
880
+ --- - <delete> - group name for "delete" hunks. Default: "MiniDiffSignDelete".
881
+ MiniMap.gen_integration.diff = function(hl_groups)
882
+ if hl_groups == nil then
883
+ hl_groups = { add = 'MiniDiffSignAdd', change = 'MiniDiffSignChange', delete = 'MiniDiffSignDelete' }
884
+ end
885
+
886
+ local augroup = vim.api.nvim_create_augroup('MiniMapDiff', {})
887
+ vim.api.nvim_create_autocmd(
888
+ 'User',
889
+ { group = augroup, pattern = 'MiniDiffUpdated', callback = H.on_integration_update, desc = 'On MiniDiffUpdated' }
890
+ )
891
+
892
+ return function()
893
+ local has_diff, diff = pcall(require, 'mini.diff')
894
+ if not has_diff or diff == nil then return {} end
895
+
896
+ local has_buf_data, buf_data = pcall(diff.get_buf_data, MiniMap.current.buf_data.source)
897
+ if not has_buf_data or buf_data == nil then return {} end
898
+
899
+ return H.hunks_to_line_hl(buf_data.hunks, hl_groups)
900
+ end
901
+ end
902
+
903
+ --- Hunks from `lewis6991/gitsigns.nvim`
904
+ ---
905
+ --- Highlight lines which have non-trivial Git status.
906
+ --- Requires [lewis6991/gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim).
907
+ ---
908
+ ---@param hl_groups table|nil Table defining highlight groups. If `nil` (not
909
+ --- supplied), this status is not highlighted. Can have the following fields:
910
+ --- - <add> - group name for added lines. Default: "GitSignsAdd".
911
+ --- - <change> - group name for changed lines. Default: "GitSignsChange".
912
+ --- - <delete> - group name for deleted lines. Default: "GitSignsDelete".
913
+ MiniMap.gen_integration.gitsigns = function(hl_groups)
914
+ if hl_groups == nil then hl_groups = { add = 'GitSignsAdd', change = 'GitSignsChange', delete = 'GitSignsDelete' } end
915
+
916
+ local augroup = vim.api.nvim_create_augroup('MiniMapGitsigns', {})
917
+ vim.api.nvim_create_autocmd(
918
+ 'User',
919
+ { group = augroup, pattern = 'GitSignsUpdate', callback = H.on_integration_update, desc = 'On GitSignsUpdate' }
920
+ )
921
+
922
+ return function()
923
+ local has_gitsigns, gitsigns = pcall(require, 'gitsigns')
924
+ if not has_gitsigns or gitsigns == nil then return {} end
925
+
926
+ local has_hunks, hunks = pcall(gitsigns.get_hunks, MiniMap.current.buf_data.source)
927
+ if not has_hunks or hunks == nil then return {} end
928
+
929
+ local diff_hunks = {}
930
+ for _, h in ipairs(hunks) do
931
+ --stylua: ignore
932
+ table.insert( diff_hunks, {
933
+ type = h.type,
934
+ buf_start = h.added.start, buf_count = h.added.count,
935
+ ref_start = h.removed.start, ref_count = h.removed.count,
936
+ })
937
+ end
938
+
939
+ return H.hunks_to_line_hl(diff_hunks, hl_groups)
940
+ end
941
+ end
942
+
943
+ -- Helper data ================================================================
944
+ -- Module default config
945
+ H.default_config = vim.deepcopy(MiniMap.config)
946
+
947
+ -- Cache for various operations
948
+ H.cache = {
949
+ -- Data about previous window. Used for focus related computations.
950
+ previous_win = {},
951
+
952
+ -- Table with information used for latest buffer lines encoding. Used for
953
+ -- quick conversion between source and map coordinates.
954
+ encode_data = {},
955
+
956
+ -- Table with information about scrollbar. Used for quick scrollbar related
957
+ -- computations.
958
+ scrollbar_data = { view = {}, line = nil },
959
+
960
+ -- Number of cursor movements inside map buffer since focusing. Needed to not
961
+ -- update source buffer view just after focusing.
962
+ n_map_cursor_moves = 0,
963
+ }
964
+
965
+ H.ns_id = {
966
+ integrations = vim.api.nvim_create_namespace('MiniMapIntegrations'),
967
+ scroll_view = vim.api.nvim_create_namespace('MiniMapScrollView'),
968
+ scroll_line = vim.api.nvim_create_namespace('MiniMapScrollLine'),
969
+ }
970
+
971
+ --stylua: ignore start
972
+ H.block_symbols = {}
973
+
974
+ H.block_symbols['1x2'] = { ' ', '▌', '▐', '█', resolution = { row = 1, col = 2 } }
975
+
976
+ H.block_symbols['2x1'] = { ' ', '▀', '▄', '█', resolution = { row = 2, col = 1 } }
977
+
978
+ H.block_symbols['2x2'] = {
979
+ ' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█',
980
+ resolution = { row = 2, col = 2 },
981
+ }
982
+
983
+ H.block_symbols['3x2'] = {
984
+ ' ', '🬀', '🬁', '🬂', '🬃', '🬄', '🬅', '🬆', '🬇', '🬈', '🬉', '🬊', '🬋', '🬌', '🬍', '🬎',
985
+ '🬏', '🬐', '🬑', '🬒', '🬓', '▌', '🬔', '🬕', '🬖', '🬗', '🬘', '🬙', '🬚', '🬛', '🬜', '🬝',
986
+ '🬞', '🬟', '🬠', '🬡', '🬢', '🬣', '🬤', '🬥', '🬦', '🬧', '▐', '🬨', '🬩', '🬪', '🬫', '🬬',
987
+ '🬭', '🬮', '🬯', '🬰', '🬱', '🬲', '🬳', '🬴', '🬵', '🬶', '🬷', '🬸', '🬹', '🬺', '🬻', '█',
988
+ resolution = { row = 3, col = 2 },
989
+ }
990
+
991
+ H.dot_symbols = {}
992
+
993
+ H.dot_symbols['4x2'] = {
994
+ '⠀', '⠁', '⠈', '⠉', '⠂', '⠃', '⠊', '⠋', '⠐', '⠑', '⠘', '⠙', '⠒', '⠓', '⠚', '⠛',
995
+ '⠄', '⠅', '⠌', '⠍', '⠆', '⠇', '⠎', '⠏', '⠔', '⠕', '⠜', '⠝', '⠖', '⠗', '⠞', '⠟',
996
+ '⠠', '⠡', '⠨', '⠩', '⠢', '⠣', '⠪', '⠫', '⠰', '⠱', '⠸', '⠹', '⠲', '⠳', '⠺', '⠻',
997
+ '⠤', '⠥', '⠬', '⠭', '⠦', '⠧', '⠮', '⠯', '⠴', '⠵', '⠼', '⠽', '⠶', '⠷', '⠾', '⠿',
998
+ '⡀', '⡁', '⡈', '⡉', '⡂', '⡃', '⡊', '⡋', '⡐', '⡑', '⡘', '⡙', '⡒', '⡓', '⡚', '⡛',
999
+ '⡄', '⡅', '⡌', '⡍', '⡆', '⡇', '⡎', '⡏', '⡔', '⡕', '⡜', '⡝', '⡖', '⡗', '⡞', '⡟',
1000
+ '⡠', '⡡', '⡨', '⡩', '⡢', '⡣', '⡪', '⡫', '⡰', '⡱', '⡸', '⡹', '⡲', '⡳', '⡺', '⡻',
1001
+ '⡤', '⡥', '⡬', '⡭', '⡦', '⡧', '⡮', '⡯', '⡴', '⡵', '⡼', '⡽', '⡶', '⡷', '⡾', '⡿',
1002
+ '⢀', '⢁', '⢈', '⢉', '⢂', '⢃', '⢊', '⢋', '⢐', '⢑', '⢘', '⢙', '⢒', '⢓', '⢚', '⢛',
1003
+ '⢄', '⢅', '⢌', '⢍', '⢆', '⢇', '⢎', '⢏', '⢔', '⢕', '⢜', '⢝', '⢖', '⢗', '⢞', '⢟',
1004
+ '⢠', '⢡', '⢨', '⢩', '⢢', '⢣', '⢪', '⢫', '⢰', '⢱', '⢸', '⢹', '⢲', '⢳', '⢺', '⢻',
1005
+ '⢤', '⢥', '⢬', '⢭', '⢦', '⢧', '⢮', '⢯', '⢴', '⢵', '⢼', '⢽', '⢶', '⢷', '⢾', '⢿',
1006
+ '⣀', '⣁', '⣈', '⣉', '⣂', '⣃', '⣊', '⣋', '⣐', '⣑', '⣘', '⣙', '⣒', '⣓', '⣚', '⣛',
1007
+ '⣄', '⣅', '⣌', '⣍', '⣆', '⣇', '⣎', '⣏', '⣔', '⣕', '⣜', '⣝', '⣖', '⣗', '⣞', '⣟',
1008
+ '⣠', '⣡', '⣨', '⣩', '⣢', '⣣', '⣪', '⣫', '⣰', '⣱', '⣸', '⣹', '⣲', '⣳', '⣺', '⣻',
1009
+ '⣤', '⣥', '⣬', '⣭', '⣦', '⣧', '⣮', '⣯', '⣴', '⣵', '⣼', '⣽', '⣶', '⣷', '⣾', '⣿',
1010
+ resolution = { row = 4, col = 2 },
1011
+ }
1012
+
1013
+ H.dot_symbols['3x2'] = { resolution = { row = 3, col = 2 } }
1014
+ for i = 1,64 do H.dot_symbols['3x2'][i] = H.dot_symbols['4x2'][i] end
1015
+
1016
+ H.shade_symbols = {}
1017
+
1018
+ H.shade_symbols['2x1'] = { '░', '▒', '▒', '▓', resolution = { row = 2, col = 1 } }
1019
+
1020
+ H.shade_symbols['1x2'] = { '░', '▒', '▒', '▓', resolution = { row = 1, col = 2 } }
1021
+
1022
+ H.default_symbols = H.block_symbols['3x2']
1023
+ --stylua: ignore end
1024
+
1025
+ -- Helper functionality =======================================================
1026
+ -- Settings -------------------------------------------------------------------
1027
+ H.setup_config = function(config)
1028
+ H.check_type('config', config, 'table', true)
1029
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1030
+
1031
+ local ok_integrations, msg_integrations = H.is_valid_config_integrations(config.integrations, 'integrations')
1032
+ if not ok_integrations then H.error(msg_integrations) end
1033
+
1034
+ local ok_symbols, msg_symbols = H.is_valid_config_symbols(config.symbols, 'symbols')
1035
+ if not ok_symbols then H.error(msg_symbols) end
1036
+
1037
+ local ok_window, msg_window = H.is_valid_config_window(config.window, 'window')
1038
+ if not ok_window then H.error(msg_window) end
1039
+
1040
+ return config
1041
+ end
1042
+
1043
+ H.apply_config = function(config) MiniMap.config = config end
1044
+
1045
+ H.create_autocommands = function()
1046
+ local gr = vim.api.nvim_create_augroup('MiniMap', {})
1047
+
1048
+ local au = function(event, pattern, callback, desc)
1049
+ vim.api.nvim_create_autocmd(event, { group = gr, pattern = pattern, callback = callback, desc = desc })
1050
+ end
1051
+
1052
+ au({ 'BufEnter', 'BufWritePost', 'TextChanged', 'VimResized' }, '*', H.on_content_change, 'On content change')
1053
+ au({ 'CursorMoved', 'WinScrolled' }, '*', H.on_view_change, 'On view change')
1054
+ au('WinLeave', '*', H.on_winleave, 'On WinLeave')
1055
+ au('WinClosed', '*', H.on_winclosed, 'On WinClosed')
1056
+ au('ModeChanged', '*:n', H.on_content_change, 'On return to Normal mode')
1057
+ au('ColorScheme', '*', H.create_default_hl, 'Ensure colors')
1058
+ end
1059
+
1060
+ --stylua: ignore
1061
+ H.create_default_hl = function()
1062
+ local set_default_hl = function(name, data)
1063
+ data.default = true
1064
+ vim.api.nvim_set_hl(0, name, data)
1065
+ end
1066
+
1067
+ set_default_hl('MiniMapNormal', { link = 'NormalFloat' })
1068
+ set_default_hl('MiniMapSymbolCount', { link = 'Special' })
1069
+ set_default_hl('MiniMapSymbolLine', { link = 'Title' })
1070
+ set_default_hl('MiniMapSymbolView', { link = 'Delimiter' })
1071
+ end
1072
+
1073
+ H.is_disabled = function() return vim.g.minimap_disable == true or vim.b.minimap_disable == true end
1074
+
1075
+ H.get_config = function(config)
1076
+ return vim.tbl_deep_extend('force', MiniMap.config, vim.b.minimap_config or {}, config or {})
1077
+ end
1078
+
1079
+ -- Autocommands ---------------------------------------------------------------
1080
+ H.on_content_change = vim.schedule_wrap(function()
1081
+ -- Using `vim.schedule_wrap()` helps computing more precise buffer data.
1082
+ -- Example: if omitted, terminal buffer is recognized as normal and thus map
1083
+ -- is updated.
1084
+ if not H.is_proper_buftype() then return end
1085
+ MiniMap.refresh()
1086
+ end)
1087
+
1088
+ H.on_view_change = vim.schedule_wrap(function()
1089
+ if not (H.is_proper_buftype() and H.is_source_buffer()) then return end
1090
+ MiniMap.refresh({}, { integrations = false, lines = false })
1091
+ end)
1092
+
1093
+ H.on_integration_update = vim.schedule_wrap(function()
1094
+ if not (H.is_proper_buftype() and H.is_source_buffer()) then return end
1095
+ MiniMap.refresh({}, { lines = false, scrollbar = false })
1096
+ end)
1097
+
1098
+ H.on_winleave = function()
1099
+ if not (H.is_proper_buftype() and H.is_source_buffer()) then return end
1100
+
1101
+ H.cache.previous_win.id = vim.api.nvim_get_current_win()
1102
+ H.cache.previous_win.cursor = vim.api.nvim_win_get_cursor(0)
1103
+ end
1104
+
1105
+ H.on_winclosed = function(data)
1106
+ -- Ensure that `H.cache.previous_win` always points to a valid normal window
1107
+ local ok, closed_win_id = pcall(tonumber, data.match)
1108
+ if not ok or closed_win_id ~= H.cache.previous_win.id then return end
1109
+
1110
+ for _, win_id in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
1111
+ if win_id ~= closed_win_id and vim.api.nvim_win_get_config(win_id).relative == '' then
1112
+ H.cache.previous_win = { id = win_id }
1113
+ return
1114
+ end
1115
+ end
1116
+ end
1117
+
1118
+ H.track_map_cursor = function()
1119
+ -- Operate only inside map window but not just after focusing
1120
+ H.cache.n_map_cursor_moves = H.cache.n_map_cursor_moves + 1
1121
+ local cur_win, map_win = vim.api.nvim_get_current_win(), H.get_current_map_win()
1122
+ if cur_win ~= map_win or H.cache.n_map_cursor_moves <= 1 then return end
1123
+
1124
+ -- Don't allow putting cursor inside offset (where scrollbar is)
1125
+ local cur_pos = vim.api.nvim_win_get_cursor(map_win)
1126
+ if cur_pos[2] < H.cache.scrollbar_data.offset then
1127
+ vim.api.nvim_win_set_cursor(map_win, { cur_pos[1], H.cache.scrollbar_data.offset })
1128
+ end
1129
+
1130
+ -- Synchronize cursors in map and previous window
1131
+ local prev_win_id = H.cache.previous_win.id
1132
+ if prev_win_id == nil then return end
1133
+
1134
+ vim.api.nvim_win_set_cursor(prev_win_id, { H.mapline_to_sourceline(cur_pos[1]), 0 })
1135
+
1136
+ -- Open just enough folds and center cursor
1137
+ vim.api.nvim_win_call(prev_win_id, function() vim.cmd('normal! zvzz') end)
1138
+ end
1139
+
1140
+ H.on_map_enter = function()
1141
+ -- Check if anything is present (window can be not opened because there is
1142
+ -- one buffer, but many possible windows; so this can be executed on second
1143
+ -- `MiniMap.open()` without opened window)
1144
+ if not H.is_window_open() or H.cache.previous_win.cursor == nil then return end
1145
+
1146
+ -- Put cursor in map window at line indicator to the right of scrollbar
1147
+ local map_line = H.sourceline_to_mapline(H.cache.previous_win.cursor[1])
1148
+ local win_id = H.get_current_map_win()
1149
+
1150
+ vim.api.nvim_win_set_cursor(win_id, { map_line, H.cache.scrollbar_data.offset })
1151
+
1152
+ -- Reset number of cursor moves to later check if should track cursor move
1153
+ H.cache.n_map_cursor_moves = 0
1154
+ end
1155
+
1156
+ -- Work with mask -------------------------------------------------------------
1157
+ ---@param strings table Array of strings
1158
+ ---@return table Non-whitespace mask, boolean 2d array. Each row corresponds to
1159
+ --- string, each column - to whether character with that number is a
1160
+ --- non-whitespace. Respects multibyte characters.
1161
+ ---@private
1162
+ H.mask_from_strings = function(strings, _)
1163
+ local tab_space = string.rep(' ', vim.o.tabstop)
1164
+
1165
+ local res = {}
1166
+ for i, s in ipairs(strings) do
1167
+ -- Expand tabs into spaces
1168
+ local s_ext = s:gsub('\t', tab_space)
1169
+ local n_cols = H.str_width(s_ext)
1170
+ local mask_row = H.tbl_repeat(true, n_cols)
1171
+
1172
+ -- Detect whitespace
1173
+ s_ext:gsub('()%s', function(j) mask_row[H.str_utfindex(s_ext, j)] = false end)
1174
+ res[i] = mask_row
1175
+ end
1176
+
1177
+ return res
1178
+ end
1179
+
1180
+ ---@param mask table Boolean 2d array.
1181
+ ---@return table Boolean 2d array rescaled to be shown by symbols:
1182
+ --- `opts.n_rows` lines and `opts.n_cols` within a row.
1183
+ ---@private
1184
+ H.mask_rescale = function(mask, opts)
1185
+ -- Infer output number of rows and columns. Should be multiples of
1186
+ -- `symbols.resolution.row` and `symbols.resolution.col` respectively.
1187
+ local source_rows = #mask
1188
+ local source_cols = 0
1189
+ for _, m_row in ipairs(mask) do
1190
+ source_cols = math.max(source_cols, #m_row)
1191
+ end
1192
+
1193
+ -- Compute effective number of rows and columns in output such that it can
1194
+ -- contain all encoded symbols (taking into account their resolution).
1195
+ -- Don't make it a multiple of resolution at this stage because it can later
1196
+ -- lead to inaccurate representation in some cases. Like with small source
1197
+ -- number of rows it will lead to conversion coefficients greater than 1
1198
+ -- (because `math.ceil()` should be used to round for resolution) and some
1199
+ -- rows in the middle of output will be skipped.
1200
+ local resolution = opts.symbols.resolution
1201
+ local n_rows = math.min(source_rows, opts.n_rows * resolution.row)
1202
+ local n_cols = math.min(source_cols, opts.n_cols * resolution.col)
1203
+
1204
+ -- Rescale. It uses unequal but optimal bins to map source lines/columns to
1205
+ -- boolean encoding (has target dimensions but multiplied by resolution).
1206
+ -- Value within 2d-bin is `true` if at least one value within it is `true`.
1207
+ local res = {}
1208
+ for i = 1, n_rows do
1209
+ res[i] = H.tbl_repeat(false, n_cols)
1210
+ end
1211
+
1212
+ local rows_coeff, cols_coeff = n_rows / source_rows, n_cols / source_cols
1213
+
1214
+ for i, m_row in ipairs(mask) do
1215
+ for j, m in ipairs(m_row) do
1216
+ local res_i = math.floor((i - 1) * rows_coeff) + 1
1217
+ local res_j = math.floor((j - 1) * cols_coeff) + 1
1218
+ res[res_i][res_j] = m or res[res_i][res_j]
1219
+ end
1220
+ end
1221
+
1222
+ return res
1223
+ end
1224
+
1225
+ --- Convert extended map mask to strings. Each bin with resolution dimensions
1226
+ --- is transformed into encode symbol. Single convertible `(resolution.row,
1227
+ --- resolution.col)` boolean mask is treated as binary digit: `true` as 1;
1228
+ --- `false` as 0; traversing left-right, top-bottom (top-left is lowest bit,
1229
+ --- bottom-right - highest).
1230
+ ---
1231
+ ---@param mask table Boolean 2d array to be shown as symbols.
1232
+ ---@return table Array of strings representing input `mask`.
1233
+ ---@private
1234
+ H.mask_to_symbols = function(mask, opts)
1235
+ local symbols = opts.symbols
1236
+ local row_resol, col_resol = symbols.resolution.row, symbols.resolution.col
1237
+
1238
+ local powers_of_two = {}
1239
+ for i = 0, (row_resol * col_resol - 1) do
1240
+ powers_of_two[i] = 2 ^ i
1241
+ end
1242
+
1243
+ -- Assumes rectangular table
1244
+ local symbols_n_rows, symbols_n_cols = math.ceil(#mask / row_resol), math.ceil(#mask[1] / col_resol)
1245
+
1246
+ -- Compute symbols array indexes (start from zero)
1247
+ local symbol_ind = {}
1248
+ for i = 1, symbols_n_rows do
1249
+ symbol_ind[i] = H.tbl_repeat(0, symbols_n_cols)
1250
+ end
1251
+
1252
+ for i = 0, #mask - 1 do
1253
+ local row = mask[i + 1]
1254
+ local row_div, row_mod = math.floor(i / row_resol), i % row_resol
1255
+ for j = 0, #row - 1 do
1256
+ local col_div, col_mod = math.floor(j / col_resol), j % col_resol
1257
+
1258
+ local two_power = row_mod * col_resol + col_mod
1259
+ local to_add = row[j + 1] and powers_of_two[two_power] or 0
1260
+
1261
+ local sym_i, sym_j = row_div + 1, col_div + 1
1262
+ symbol_ind[sym_i][sym_j] = symbol_ind[sym_i][sym_j] + to_add
1263
+ end
1264
+ end
1265
+
1266
+ -- Construct symbols strings
1267
+ local res = {}
1268
+ for i, row in ipairs(symbol_ind) do
1269
+ local syms = vim.tbl_map(function(id) return symbols[id + 1] end, row)
1270
+ res[i] = table.concat(syms)
1271
+ end
1272
+
1273
+ return res
1274
+ end
1275
+
1276
+ -- Work with config -----------------------------------------------------------
1277
+ H.normalize_opts = function(x)
1278
+ x = vim.tbl_deep_extend('force', H.get_config(), MiniMap.current.opts or {}, x or {})
1279
+ H.validate_if(H.is_valid_opts, x, 'opts')
1280
+ return x
1281
+ end
1282
+
1283
+ H.is_valid_opts = function(x, x_name)
1284
+ x_name = x_name or 'opts'
1285
+
1286
+ local ok_integrations, msg_integrations = H.is_valid_config_integrations(x.integrations, x_name .. '.integrations')
1287
+ if not ok_integrations then return ok_integrations, msg_integrations end
1288
+
1289
+ local ok_symbols, msg_symbols = H.is_valid_config_symbols(x.symbols, x_name .. '.symbols')
1290
+ if not ok_symbols then return ok_symbols, msg_symbols end
1291
+
1292
+ local ok_window, msg_window = H.is_valid_config_window(x.window, x_name .. '.window')
1293
+ if not ok_window then return ok_window, msg_window end
1294
+
1295
+ return true
1296
+ end
1297
+
1298
+ H.is_valid_config_integrations = function(x, x_name)
1299
+ x_name = x_name or 'config.integrations'
1300
+
1301
+ if x ~= nil then
1302
+ if not H.is_array_of(x, vim.is_callable) then return false, H.msg_config(x_name, 'array of callables') end
1303
+ end
1304
+
1305
+ return true
1306
+ end
1307
+
1308
+ H.is_valid_config_symbols = function(x, x_name)
1309
+ x_name = x_name or 'config.symbols'
1310
+
1311
+ if type(x) ~= 'table' then return false, H.msg_config(x_name, 'table') end
1312
+
1313
+ -- Encode symbols is `nil` by default
1314
+ if x.encode ~= nil then
1315
+ local ok_encode, msg_encode = H.is_encode_symbols(x.encode, x_name .. '.encode')
1316
+ if not ok_encode then return ok_encode, msg_encode end
1317
+ end
1318
+
1319
+ -- Current line
1320
+ if not H.is_string(x.scroll_line) then return false, H.msg_config(x_name .. '.scroll_line', 'string') end
1321
+
1322
+ -- Current view
1323
+ if not H.is_string(x.scroll_view) then return false, H.msg_config(x_name .. '.scroll_view', 'string') end
1324
+
1325
+ return true
1326
+ end
1327
+
1328
+ H.is_valid_config_window = function(x, x_name)
1329
+ x_name = x_name or 'config.window'
1330
+
1331
+ if type(x) ~= 'table' then return false, H.msg_config(x_name, 'table') end
1332
+
1333
+ -- Focusable
1334
+ if type(x.focusable) ~= 'boolean' then return false, H.msg_config(x_name .. '.focusable', 'boolean') end
1335
+
1336
+ -- Side
1337
+ if not (x.side == 'left' or x.side == 'right') then
1338
+ return false, H.msg_config(x_name .. '.side', [[one of 'left', 'right']])
1339
+ end
1340
+
1341
+ -- Width
1342
+ if not (type(x.width) == 'number' and x.width > 0) then
1343
+ return false, H.msg_config(x_name .. '.width', 'positive number')
1344
+ end
1345
+
1346
+ -- Show "more" integration symbols
1347
+ if type(x.show_integration_count) ~= 'boolean' then
1348
+ return false, H.msg_config(x_name .. '.show_integration_count', 'boolean')
1349
+ end
1350
+
1351
+ -- Window local 'winblend'
1352
+ if not (type(x.winblend) == 'number' and 0 <= x.winblend and x.winblend <= 100) then
1353
+ return false, H.msg_config(x_name .. '.winblend', 'number between 0 and 100')
1354
+ end
1355
+
1356
+ -- Z-index
1357
+ if not (type(x.zindex) == 'number' and x.zindex > 0) then
1358
+ return false, H.msg_config(x_name .. '.zindex', 'positive number')
1359
+ end
1360
+
1361
+ return true
1362
+ end
1363
+
1364
+ H.msg_config = function(x_name, msg) return string.format('`%s` should be %s.', x_name, msg) end
1365
+
1366
+ -- Work with map window -------------------------------------------------------
1367
+ H.normalize_window_options = function(win_opts, full)
1368
+ if full == nil then full = true end
1369
+
1370
+ local has_tabline = vim.o.showtabline == 2 or (vim.o.showtabline == 1 and #vim.api.nvim_list_tabpages() > 1)
1371
+ local has_statusline = vim.o.laststatus > 0
1372
+ local anchor, col = 'NE', vim.o.columns
1373
+ if win_opts.side == 'left' then
1374
+ anchor, col = 'NW', 0
1375
+ end
1376
+
1377
+ local res = {
1378
+ relative = 'editor',
1379
+ anchor = anchor,
1380
+ row = has_tabline and 1 or 0,
1381
+ col = col,
1382
+ width = win_opts.width,
1383
+ -- Can be updated at `VimResized` event
1384
+ height = vim.o.lines - vim.o.cmdheight - (has_tabline and 1 or 0) - (has_statusline and 1 or 0),
1385
+ focusable = win_opts.focusable,
1386
+ border = 'none',
1387
+ zindex = win_opts.zindex,
1388
+ }
1389
+ if not full then return res end
1390
+
1391
+ res.style = 'minimal'
1392
+ return res
1393
+ end
1394
+
1395
+ H.get_current_map_win = function() return MiniMap.current.win_data[vim.api.nvim_get_current_tabpage()] end
1396
+
1397
+ H.set_current_map_win = function(win_id) MiniMap.current.win_data[vim.api.nvim_get_current_tabpage()] = win_id end
1398
+
1399
+ H.is_window_open = function()
1400
+ local cur_win_id = H.get_current_map_win()
1401
+ return cur_win_id ~= nil and vim.api.nvim_win_is_valid(cur_win_id)
1402
+ end
1403
+
1404
+ -- Work with map updates ------------------------------------------------------
1405
+ H.create_map_buffer = function()
1406
+ local buf_id = vim.api.nvim_create_buf(false, true)
1407
+ H.set_buf_name(buf_id, 'content')
1408
+
1409
+ -- Set buffer local options (which don't involve `noautocmd`)
1410
+ vim.bo[buf_id].filetype = 'minimap'
1411
+
1412
+ -- Make buffer local mappings
1413
+ vim.keymap.set('n', '<CR>', '<Cmd>lua MiniMap.toggle_focus(false)<CR>', { buffer = buf_id })
1414
+ vim.keymap.set('n', '<Esc>', '<Cmd>lua MiniMap.toggle_focus(true)<CR>', { buffer = buf_id })
1415
+
1416
+ -- Make buffer local autocommands
1417
+ vim.api.nvim_create_autocmd('BufEnter', { buffer = buf_id, callback = H.on_map_enter, desc = 'On map enter' })
1418
+ vim.api.nvim_create_autocmd(
1419
+ 'CursorMoved',
1420
+ { buffer = buf_id, callback = H.track_map_cursor, desc = 'Track map cursor' }
1421
+ )
1422
+
1423
+ -- Make buffer play nicely with other 'mini.nvim' modules
1424
+ vim.api.nvim_buf_set_var(buf_id, 'minicursorword_disable', true)
1425
+
1426
+ return buf_id
1427
+ end
1428
+
1429
+ H.update_window_opts = function()
1430
+ local opts = MiniMap.current.opts
1431
+ local win_id = H.get_current_map_win()
1432
+
1433
+ -- Window config
1434
+ vim.api.nvim_win_set_config(win_id, H.normalize_window_options(opts.window, false))
1435
+
1436
+ -- 'winblend'
1437
+ vim.wo[win_id].winblend = opts.window.winblend
1438
+ end
1439
+
1440
+ H.update_map_lines = function()
1441
+ if not H.is_window_open() then return end
1442
+
1443
+ local buf_id, opts = MiniMap.current.buf_data.map, MiniMap.current.opts
1444
+ local win_id = H.get_current_map_win()
1445
+
1446
+ -- Compute output number of rows and columns to fit currently shown window
1447
+ local offset = H.cache.scrollbar_data.offset
1448
+ local n_cols = vim.api.nvim_win_get_width(win_id) - offset
1449
+ local n_rows = vim.api.nvim_win_get_height(win_id)
1450
+
1451
+ -- Encode lines from current buffer
1452
+ local source_buf_id = vim.api.nvim_get_current_buf()
1453
+ MiniMap.current.buf_data.source = source_buf_id
1454
+ local buf_lines = vim.api.nvim_buf_get_lines(source_buf_id, 0, -1, true)
1455
+ -- Ensure that current buffer has lines (can be not the case when this is
1456
+ -- executed asynchronously during Neovim closing)
1457
+ if #buf_lines == 0 then return end
1458
+
1459
+ local encode_symbols = opts.symbols.encode or H.default_symbols
1460
+ local source_rows, scrollbar_prefix = #buf_lines, string.rep(' ', offset)
1461
+ local encoded_lines, rescaled_rows, resolution_row
1462
+ if n_cols <= 0 then
1463
+ -- Case of "only scroll indicator". Needed to make scrollbar correctly
1464
+ -- travel from buffer top to bottom.
1465
+ encoded_lines = H.tbl_repeat(scrollbar_prefix, n_rows)
1466
+
1467
+ -- Note that full encoding was done with single whitespace per line
1468
+ rescaled_rows, resolution_row = n_rows, 1
1469
+ else
1470
+ -- Case of "full map"
1471
+ local encode_opts = { n_cols = n_cols, n_rows = n_rows, symbols = encode_symbols }
1472
+ encoded_lines = MiniMap.encode_strings(buf_lines, encode_opts)
1473
+
1474
+ -- Add whitespace for scrollbar
1475
+ encoded_lines = vim.tbl_map(function(x) return string.format('%s%s', scrollbar_prefix, x) end, encoded_lines)
1476
+
1477
+ -- Note that actual encoding was done
1478
+ resolution_row = encode_symbols.resolution.row
1479
+ rescaled_rows = math.min(source_rows, n_rows * resolution_row)
1480
+ end
1481
+
1482
+ -- Set map lines. Compute encode data in a way used in mask rescaling
1483
+ vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, encoded_lines)
1484
+
1485
+ -- Cache encode data to speed up most frequent scrollbar computation
1486
+ H.cache.encode_data = {
1487
+ source_rows = source_rows,
1488
+ rescaled_rows = rescaled_rows,
1489
+ resolution_row = resolution_row,
1490
+ map_rows = #encoded_lines,
1491
+ }
1492
+
1493
+ -- Force scrollbar update
1494
+ H.cache.scrollbar_data.view, H.cache.scrollbar_data.line = {}, nil
1495
+ end
1496
+
1497
+ H.update_map_scrollbar = function()
1498
+ if not H.is_window_open() then return end
1499
+
1500
+ local buf_id = MiniMap.current.buf_data.map
1501
+ local cur_view, cur_line = H.cache.scrollbar_data.view or {}, H.cache.scrollbar_data.line
1502
+ local symbols = MiniMap.current.opts.symbols
1503
+
1504
+ -- View
1505
+ local view = { from_line = vim.fn.line('w0'), to_line = vim.fn.line('w$') }
1506
+ if not (view.from_line == cur_view.from_line and view.to_line == cur_view.to_line) then
1507
+ local ns_id = H.ns_id.scroll_view
1508
+ local extmark_opts =
1509
+ { virt_text = { { symbols.scroll_view, 'MiniMapSymbolView' } }, virt_text_pos = 'overlay', priority = 10 }
1510
+
1511
+ -- Remove previous view
1512
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
1513
+
1514
+ -- Add current view
1515
+ local map_from_line = H.sourceline_to_mapline(view.from_line)
1516
+ local map_to_line = H.sourceline_to_mapline(view.to_line)
1517
+
1518
+ for i = map_from_line, map_to_line do
1519
+ H.set_extmark_safely(buf_id, ns_id, i - 1, 0, extmark_opts)
1520
+ end
1521
+
1522
+ H.cache.scrollbar_data.view = view
1523
+ end
1524
+
1525
+ -- Current line
1526
+ local scroll_line = vim.fn.line('.')
1527
+ if scroll_line ~= cur_line then
1528
+ local ns_id = H.ns_id.scroll_line
1529
+ -- Set higher priority than view signs to be visible over them
1530
+ local extmark_opts =
1531
+ { virt_text = { { symbols.scroll_line, 'MiniMapSymbolLine' } }, virt_text_pos = 'overlay', priority = 11 }
1532
+
1533
+ -- Remove previous line
1534
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
1535
+
1536
+ -- Add new line
1537
+ local map_line = H.sourceline_to_mapline(scroll_line)
1538
+
1539
+ H.set_extmark_safely(buf_id, ns_id, map_line - 1, 0, extmark_opts)
1540
+ H.cache.scrollbar_data.line = scroll_line
1541
+ end
1542
+ end
1543
+
1544
+ H.update_map_integrations = function()
1545
+ if not H.is_window_open() then return end
1546
+
1547
+ local buf_id = MiniMap.current.buf_data.map
1548
+ local integrations = MiniMap.current.opts.integrations or {}
1549
+
1550
+ -- Remove previous highlights and signs
1551
+ local ns_id = H.ns_id.integrations
1552
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
1553
+
1554
+ -- Do nothing more in case of pure scrollbar
1555
+ -- This is after removing "more" signs to allow switching to pure scrollbar
1556
+ -- after such were already visible
1557
+ if H.is_pure_scrollbar() then return end
1558
+
1559
+ -- Add line highlights. Use latest one for every map line.
1560
+ local line_counts = {}
1561
+ for _, integration in ipairs(integrations) do
1562
+ local line_hl = integration()
1563
+ for _, lh in ipairs(line_hl) do
1564
+ local map_line = H.sourceline_to_mapline(lh.line)
1565
+ local cur_count = line_counts[map_line] or 0
1566
+ line_counts[map_line] = cur_count + 1
1567
+
1568
+ -- Actually highlight only first map line occurrence
1569
+ if cur_count == 0 then H.add_line_hl(buf_id, ns_id, lh.hl_group, map_line - 1) end
1570
+ end
1571
+ end
1572
+
1573
+ -- Possibly add integration counts
1574
+ if not MiniMap.current.opts.window.show_integration_count then return end
1575
+
1576
+ local col = H.cache.scrollbar_data.offset - 1
1577
+ for l, count in pairs(line_counts) do
1578
+ if count > 1 then
1579
+ local text = count > 9 and '+' or tostring(count)
1580
+ local extmark_opts = {
1581
+ virt_text = { { text, 'MiniMapSymbolCount' } },
1582
+ virt_text_pos = 'overlay',
1583
+ hl_mode = 'blend',
1584
+ }
1585
+ H.set_extmark_safely(buf_id, ns_id, l - 1, col, extmark_opts)
1586
+ end
1587
+ end
1588
+ end
1589
+
1590
+ H.sourceline_to_mapline = function(source_line)
1591
+ local data = H.cache.encode_data
1592
+ local coef = data.rescaled_rows / data.source_rows
1593
+ local rescaled_row = math.floor(coef * (source_line - 1)) + 1
1594
+ local res = math.floor((rescaled_row - 1) / data.resolution_row) + 1
1595
+ return math.min(math.max(res, 1), data.map_rows)
1596
+ end
1597
+
1598
+ H.mapline_to_sourceline = function(map_line)
1599
+ local data = H.cache.encode_data
1600
+ local coef = data.rescaled_rows / data.source_rows
1601
+ local rescaled_row = (map_line - 1) * data.resolution_row + 1
1602
+ local res = math.ceil((rescaled_row - 1) / coef) + 1
1603
+ return math.min(math.max(res, 1), data.source_rows)
1604
+ end
1605
+
1606
+ -- Hunks ----------------------------------------------------------------------
1607
+ H.hunks_to_line_hl = function(hunks, hl_groups)
1608
+ local res = {}
1609
+ for _, h in ipairs(hunks) do
1610
+ local from_line = h.buf_start
1611
+ local n_added, n_removed = h.buf_count, h.ref_count
1612
+ local n_lines = math.max(n_added, 1)
1613
+ -- Highlight similar to hunk summary logic:
1614
+ -- - Delete - single first line if nothing was added.
1615
+ -- - Change - added lines that are within first removed lines.
1616
+ -- - Added - added lines after first removed lines.
1617
+ for i = 1, n_lines do
1618
+ local hl_type = (n_added < i and 'delete') or (i <= n_removed and 'change' or 'add')
1619
+ local hl_group = hl_groups[hl_type]
1620
+ if hl_group ~= nil then table.insert(res, { line = from_line + i - 1, hl_group = hl_group }) end
1621
+ end
1622
+ end
1623
+
1624
+ return res
1625
+ end
1626
+
1627
+ -- Predicates -----------------------------------------------------------------
1628
+ H.is_array_of = function(x, predicate)
1629
+ if not H.islist(x) then return false end
1630
+ for _, v in ipairs(x) do
1631
+ if not predicate(v) then return false end
1632
+ end
1633
+ return true
1634
+ end
1635
+
1636
+ H.is_string = function(x) return type(x) == 'string' end
1637
+
1638
+ H.is_encode_symbols = function(x, x_name)
1639
+ x_name = x_name or 'symbols'
1640
+
1641
+ if type(x) ~= 'table' then return false, H.msg_config(x_name, 'table') end
1642
+ if type(x.resolution) ~= 'table' then return false, H.msg_config(x_name .. '.resolution', 'table') end
1643
+ if type(x.resolution.col) ~= 'number' then return false, H.msg_config(x_name .. '.resolution.col', 'number') end
1644
+ if type(x.resolution.row) ~= 'number' then return false, H.msg_config(x_name .. '.resolution.row', 'number') end
1645
+
1646
+ local two_power = x.resolution.col * x.resolution.row
1647
+ for i = 1, 2 ^ two_power do
1648
+ if not H.is_string(x[i]) then return false, H.msg_config(string.format('%s[%d]', x_name, i), 'string') end
1649
+ end
1650
+
1651
+ return true
1652
+ end
1653
+
1654
+ H.is_proper_buftype = function()
1655
+ local buf_type = vim.bo.buftype
1656
+ return buf_type == '' or buf_type == 'help'
1657
+ end
1658
+
1659
+ H.is_source_buffer = function() return vim.api.nvim_get_current_buf() == MiniMap.current.buf_data.source end
1660
+
1661
+ H.is_pure_scrollbar = function()
1662
+ local win_id = H.get_current_map_win()
1663
+ local offset = H.cache.scrollbar_data.offset
1664
+ return vim.api.nvim_win_get_width(win_id) <= offset
1665
+ end
1666
+
1667
+ -- Utilities ------------------------------------------------------------------
1668
+ H.error = function(msg) error('(mini.map) ' .. msg, 0) end
1669
+
1670
+ H.check_type = function(name, val, ref, allow_nil)
1671
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1672
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1673
+ end
1674
+
1675
+ H.set_buf_name = function(buf_id, name) vim.api.nvim_buf_set_name(buf_id, 'minimap://' .. buf_id .. '/' .. name) end
1676
+
1677
+ H.validate_if = function(predicate, x, x_name)
1678
+ local is_valid, msg = predicate(x, x_name)
1679
+ if not is_valid then H.error(msg) end
1680
+ end
1681
+
1682
+ H.add_line_hl = function(buf_id, ns_id, hl_group, line)
1683
+ H.set_extmark_safely(buf_id, ns_id, line, H.cache.scrollbar_data.offset, {
1684
+ hl_group = hl_group,
1685
+ end_row = line + 1,
1686
+ end_col = 0,
1687
+ strict = false,
1688
+ })
1689
+ end
1690
+
1691
+ H.set_extmark_safely = function(...) pcall(vim.api.nvim_buf_set_extmark, ...) end
1692
+
1693
+ H.str_utfindex = function(s, i) return vim.str_utfindex(s, 'utf-32', i) end
1694
+ if vim.fn.has('nvim-0.11') == 0 then H.str_utfindex = function(s, i) return (vim.str_utfindex(s, i)) end end
1695
+
1696
+ H.str_width = function(x) return H.str_utfindex(x) end
1697
+
1698
+ H.tbl_repeat = function(x, n)
1699
+ local res = {}
1700
+ for _ = 1, n do
1701
+ table.insert(res, x)
1702
+ end
1703
+ return res
1704
+ end
1705
+
1706
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
1707
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
1708
+
1709
+ return MiniMap