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,2076 @@
1
+ --- *mini.align* Align text interactively
2
+ ---
3
+ --- MIT License Copyright (c) 2022 Evgeni Chasnovski
4
+
5
+ --- Rich and flexible customization of both alignment rules and user interaction.
6
+ --- Works with charwise, linewise, and blockwise selections in both Normal mode
7
+ --- (on textobject/motion; with dot-repeat) and Visual mode.
8
+ ---
9
+ --- Features:
10
+ --- - Alignment is done in three main steps:
11
+ --- - <Split> lines into parts based on Lua pattern(s) or user-supplied rule.
12
+ --- - <Justify> parts for certain side(s) to be same width inside columns.
13
+ --- - <Merge> parts to be lines, with customizable delimiter(s).
14
+ ---
15
+ --- Each main step can be preceded by other steps (pre-steps) to achieve
16
+ --- highly customizable outcome. See `steps` value in |MiniAlign.config|.
17
+ --- For more details, see |MiniAlign-glossary| and |MiniAlign-algorithm|.
18
+ ---
19
+ --- - User can control alignment interactively by pressing customizable modifiers
20
+ --- (single keys representing how alignment steps and/or options should change).
21
+ --- Some of default modifiers:
22
+ --- - Press `s` to enter split Lua pattern.
23
+ --- - Press `j` to choose justification side from available ones ("left",
24
+ --- "center", "right", "none").
25
+ --- - Press `m` to enter merge delimiter.
26
+ --- - Press `f` to enter filter Lua expression to configure which parts
27
+ --- will be affected (like "align only first column").
28
+ --- - Press `i` to ignore some commonly unwanted split matches.
29
+ --- - Press `p` to pair neighboring parts so they be aligned together.
30
+ --- - Press `t` to trim whitespace from parts.
31
+ --- - Press `<BS>` (backspace) to delete some last pre-step.
32
+ ---
33
+ --- For more details, see |MiniAlign-modifiers-builtin| and |MiniAlign-examples|.
34
+ ---
35
+ --- - Alignment can be done with instant preview (result is updated after each
36
+ --- modifier) or without it (result is shown and accepted after non-default
37
+ --- split pattern is set).
38
+ ---
39
+ --- - Every user interaction is accompanied with helper status message showing
40
+ --- relevant information about current alignment process.
41
+ ---
42
+ --- # Setup ~
43
+ ---
44
+ --- This module needs a setup with `require('mini.align').setup({})` (replace
45
+ --- `{}` with your `config` table). It will create global Lua table `MiniAlign`
46
+ --- which you can use for scripting or manually (with `:lua MiniAlign.*`).
47
+ ---
48
+ --- See |MiniAlign.config| for available config settings.
49
+ ---
50
+ --- You can override runtime config settings (like `config.modifiers`) locally
51
+ --- to buffer inside `vim.b.minialign_config` which should have same structure
52
+ --- as `MiniAlign.config`. See |mini.nvim-buffer-local-config| for more details.
53
+ ---
54
+ --- To stop module from showing non-error feedback, set `config.silent = true`.
55
+ ---
56
+ --- # Comparisons ~
57
+ ---
58
+ --- - [junegunn/vim-easy-align](https://github.com/junegunn/vim-easy-align):
59
+ --- - |mini.align| is mostly designed after `junegunn/vim-easy-align`, so
60
+ --- there are a lot of similarities.
61
+ --- - Both plugins allow users to change alignment options interactively by
62
+ --- pressing modifier keys (albeit completely different default ones).
63
+ --- `junegunn/vim-easy-align` has those modifiers fixed, while |mini.align|
64
+ --- allows their full customization. See |MiniAlign.config| for examples.
65
+ --- - `junegunn/vim-easy-align` is designed to treat delimiters differently
66
+ --- than other parts of strings. |mini.align| doesn't distinguish split
67
+ --- parts from one another by design: splitting is allowed to be done
68
+ --- based on some other logic than by splitting on delimiters.
69
+ --- - `junegunn/vim-easy-align` initially aligns by only first delimiter.
70
+ --- |mini.align| initially aligns by all delimiter.
71
+ --- - `junegunn/vim-easy-align` implements special filtering by delimiter
72
+ --- row number. |mini.align| has builtin filtering based on Lua code
73
+ --- supplied by user in modifier phase. See |MiniAlign.gen_step.filter()|
74
+ --- and `f` builtin modifier.
75
+ --- - |mini.align| treats any non-registered modifier as a plain delimiter
76
+ --- pattern, while `junegunn/vim-easy-align` does not.
77
+ --- - |mini.align| exports core Lua function used for aligning strings
78
+ --- (|MiniAlign.align_strings()|).
79
+ --- - [godlygeek/tabular](https://github.com/godlygeek/tabular):
80
+ --- - `godlygeek/tabular` is mostly designed around single command which is
81
+ --- customized by printing its parameters. |mini.align| implements
82
+ --- different concept of interactive alignment through pressing
83
+ --- customizable single character modifiers.
84
+ --- - `godlygeek/tabular` can detect region upon which alignment can be
85
+ --- desirable. |mini.align| does not by design: use Visual selection or
86
+ --- textobject/motion to explicitly define region to align.
87
+ ---
88
+ --- # Disabling ~
89
+ ---
90
+ --- To disable, set `vim.g.minialign_disable` (globally) or `vim.b.minialign_disable`
91
+ --- (for a buffer) to `true`. Considering high number of different scenarios
92
+ --- and customization intentions, writing exact rules for disabling module's
93
+ --- functionality is left to user. See |mini.nvim-disabling-recipes| for common
94
+ --- recipes.
95
+ ---@tag MiniAlign
96
+
97
+ --- PARTS ~
98
+ --- 2d array of strings (array of arrays of strings).
99
+ --- See more in |MiniAlign.as_parts()|.
100
+ ---
101
+ --- ROW ~
102
+ --- First-level array of parts (like `parts[1]`).
103
+ ---
104
+ --- COLUMN ~
105
+ --- Array of strings, constructed from parts elements with the same
106
+ --- second-level index (like `{ parts[1][1],` `parts[2][1], ... }`).
107
+ ---
108
+ --- STEP ~
109
+ --- A named callable. See |MiniAlign.new_step()|. When used in terms of alignment
110
+ --- steps, callable takes two arguments: some object (parts or string array)
111
+ --- and option table.
112
+ ---
113
+ --- SPLIT ~
114
+ --- Process of taking array of strings and converting it into parts.
115
+ ---
116
+ --- JUSTIFY ~
117
+ --- Process of taking parts and converting them to aligned parts (all elements
118
+ --- have same widths inside columns).
119
+ ---
120
+ --- MERGE ~
121
+ --- Process of taking parts and converting it back to array of strings. Usually
122
+ --- by concatenating rows into strings.
123
+ ---
124
+ --- REGION ~
125
+ --- Table representing region in a buffer. Fields <from> / <to> have inclusive
126
+ --- start / end positions (<to> might be `nil` to describe empty region).
127
+ --- Positions are also tables with <line> and <col> fields (both start at 1).
128
+ ---
129
+ --- MODE ~
130
+ --- Either charwise ("char", `v`, |charwise|), linewise ("line", `V`, |linewise|)
131
+ --- or blockwise ("block", `<C-v>`, |blockwise-visual|)
132
+ ---@tag MiniAlign-glossary
133
+
134
+ --- There are two main processes implemented in |mini.align|: strings alignment
135
+ --- and interactive region alignment. See |MiniAlign-glossary| for more information
136
+ --- about used terms.
137
+ ---
138
+ --- # Strings alignment ~
139
+ ---
140
+ --- Main implementation is in |MiniAlign.align_strings()|. Its input is array of
141
+ --- strings and output - array of aligned strings. The process consists from three
142
+ --- main steps (split, justify, merge) which can be preceded by any number of
143
+ --- preliminary steps (pre-split, pre-justify, pre-merge).
144
+ ---
145
+ --- Algorithm:
146
+ --- - <Pre-split>. Take input array of strings and consecutively apply all
147
+ --- pre-split steps (`steps.pre_split`). Each one has `(strings, opts)` signature
148
+ --- and should modify array in place.
149
+ --- - <Split>. Take array of strings and convert it to parts with `steps.split()`.
150
+ --- It has `(strings, opts)` signature and should return parts.
151
+ --- - <Pre-justify>. Take parts and consecutively apply all pre-justify
152
+ --- steps (`steps.pre_justify`). Each one has `(parts, opts)` signature and
153
+ --- should modify parts in place.
154
+ --- - <Justify>. Take parts and apply `steps.justify()`. It has `(parts, opts)`
155
+ --- signature and should modify parts in place.
156
+ --- - <Pre-merge>. Take parts and consecutively apply all pre-merge
157
+ --- steps (`steps.pre_merge`). Each one has `(parts, opts)` signature and
158
+ --- should modify parts in place.
159
+ --- - <Merge>. Take parts and convert it to array of strings with `steps.merge()`.
160
+ --- It has `(parts, opts)` signature and should return array of strings.
161
+ ---
162
+ --- Notes:
163
+ --- - All table objects are initially copied so that modification in place doesn't
164
+ --- affect workflow.
165
+ --- - Default main steps are designed to be controlled via options. See
166
+ --- |MiniAlign.align_strings()| and default step entries in |MiniAlign.gen_step|.
167
+ --- - All steps are guaranteed to take same option table as second argument.
168
+ --- This allows steps to "talk" to each other, i.e. earlier steps can pass data
169
+ --- to later ones.
170
+ ---
171
+ --- # Interactive region alignment ~
172
+ ---
173
+ --- Interactive alignment is a main entry point for most users. It can be done
174
+ --- in two flavors:
175
+ --- - <Without-preview>. Initiated via mapping defined in `start` of
176
+ --- `MiniAlign.config.mappings`. Alignment is accepted once split pattern becomes
177
+ --- non-default.
178
+ --- - <With-preview>. Initiated via mapping defined in `start_with_preview` of
179
+ --- `MiniAlign.config.mappings`. Alignment result is shown after every modifier
180
+ --- and is accepted after `<CR>` (`Enter`) is hit. Note: each preview is done by
181
+ --- applying current alignment steps and options to the initial region lines,
182
+ --- not the ones currently displaying in preview.
183
+ ---
184
+ --- Lifecycle (assuming default mappings):
185
+ --- - <Initiate-alignment>:
186
+ --- - In Normal mode type `ga` (or `gA` to show preview) followed by textobject
187
+ --- or motion defining region to be aligned.
188
+ --- - In Visual mode select region and type `ga` (or `gA` to show preview).
189
+ --- Strings contained in selected region will be used as input to
190
+ --- |MiniAlign.align_strings()|.
191
+ --- Beware of mode when selecting region: charwise (`v`), linewise (`V`), or
192
+ --- blockwise (`<C-v>`). They all behave differently.
193
+ --- - <Press-modifiers>. Press single keys one at a time:
194
+ --- - If pressed key is among table keys of `modifiers` table of
195
+ --- |MiniAlign.config|, its function value is executed. It usually modifies
196
+ --- some options(s) and/or affects some pre-step(s).
197
+ --- - If pressed key is not among defined modifiers, it is treated as plain
198
+ --- split pattern.
199
+ --- This process can either end by itself (usually in case of no preview and
200
+ --- non-default split pattern being set) or you can choose to end it manually.
201
+ --- - <Accept-or-discard>. In case of active preview, accept current result by
202
+ --- pressing `<CR>`. Discard any result and return to initial regions with
203
+ --- either `<Esc>` or `<C-c>`.
204
+ ---
205
+ --- See more in |MiniAlign-modifiers-builtin| and |MiniAlign-examples|.
206
+ ---
207
+ --- Notes:
208
+ --- - Visual blockwise selection works best with |'virtualedit'| equal to "block"
209
+ --- or "all".
210
+ --- - Alignment with preview works best with |'showmode'| disabled.
211
+ ---@tag MiniAlign-algorithm
212
+
213
+ --- Overview of builtin modifiers
214
+ ---
215
+ --- All examples assume interactive alignment with preview in linewise mode. With
216
+ --- default mappings, use `V` to select lines and `gA` to initiate alignment. It
217
+ --- might be helpful to copy lines into modifiable buffer and experiment yourself.
218
+ ---
219
+ --- Notes:
220
+ --- - Any pressed key which doesn't have defined modifier will be treated as
221
+ --- plain split pattern.
222
+ --- - All modifiers can be customized inside |MiniAlign.setup()|. See "Modifiers"
223
+ --- section of |MiniAlign.config|.
224
+ ---
225
+ --- # Main option modifiers ~
226
+ ---
227
+ --- <s> Enter split pattern (confirm prompt by pressing `<CR>`). Input is treated
228
+ --- as plain delimiter.
229
+ ---
230
+ --- Before: >
231
+ --- a-b-c
232
+ --- aa-bb-cc
233
+ --- <
234
+ --- After typing `s-<CR>`: >
235
+ --- a -b -c
236
+ --- aa-bb-cc
237
+ --- <
238
+ --- <j> Choose justify side. Prompts user (with helper message) to type single
239
+ --- character identifier of side: `l`eft, `c`enter, `r`ight, `n`one.
240
+ ---
241
+ --- Before: >
242
+ --- a_b_c
243
+ --- aa_bb_cc
244
+ --- <
245
+ --- After typing `_jr` (first make split by `_`): >
246
+ --- a_ b_ c
247
+ --- aa_bb_cc
248
+ --- <
249
+ --- <m> Enter merge delimiter (confirm prompt by pressing `<CR>`).
250
+ ---
251
+ --- Before: >
252
+ --- a_b_c
253
+ --- aa_bb_cc
254
+ --- <
255
+ --- After typing `_m--<CR>` (first make split by `_`): >
256
+ --- a --_--b --_--c
257
+ --- aa--_--bb--_--cc
258
+ --- <
259
+ --- # Modifiers adding pre-steps ~
260
+ ---
261
+ --- <f> Enter filter expression. See more details in |MiniAlign.gen_step.filter()|.
262
+ ---
263
+ --- Before: >
264
+ --- a_b_c
265
+ --- aa_bb_cc
266
+ --- <
267
+ --- After typing `_fn==1<CR>` (first make split by `_`): >
268
+ --- a _b_c
269
+ --- aa_bb_cc
270
+ --- <
271
+ --- <i> Ignore some split matches. It modifies `split_exclude_patterns` option by
272
+ --- adding commonly wanted patterns. See more details in
273
+ --- |MiniAlign.gen_step.ignore_split()|.
274
+ ---
275
+ --- Before: >
276
+ --- /* This_is_assumed_to_be_comment */
277
+ --- a"_"_b
278
+ --- aa_bb
279
+ --- <
280
+ --- After typing `_i` (first make split by `_`): >
281
+ --- /* This_is_assumed_to_be_comment */
282
+ --- a"_"_b
283
+ --- aa _bb
284
+ --- <
285
+ --- <p> Pair neighboring parts.
286
+ ---
287
+ --- Before: >
288
+ --- a_b_c
289
+ --- aaa_bbb_ccc
290
+ --- <
291
+ --- After typing `_p` (first make split by `_`): >
292
+ --- a_ b_ c
293
+ --- aaa_bbb_ccc
294
+ --- <
295
+ --- <t> Trim parts from whitespace on both sides (keeping indentation).
296
+ ---
297
+ --- Before: >
298
+ --- a _ b _ c
299
+ --- aa _bb _cc
300
+ --- <
301
+ --- After typing `_t` (first make split by `_`): >
302
+ --- a _b _c
303
+ --- aa_bb_cc
304
+ --- <
305
+ --- # Delete some last pre-step ~
306
+ ---
307
+ --- <BS> Delete one of the pre-steps. If there is only one kind of pre-steps,
308
+ --- remove its latest added one. If not, prompt user to choose pre-step kind
309
+ --- by entering single character: `s`plit, `j`ustify, `m`erge.
310
+ ---
311
+ --- Examples:
312
+ --- - `tp<BS>` results in only "trim" step to be left.
313
+ --- - `it<BS>` prompts to choose which step to delete (pre-split or
314
+ --- pre-justify in this case).
315
+ ---
316
+ --- # Special configurations for common splits ~
317
+ ---
318
+ --- <=> Use special pattern to align by a group of consecutive "=". It can be
319
+ --- preceded by any number of punctuation marks and followed by some sommon
320
+ --- punctuation characters. Trim whitespace and merge with single space.
321
+ ---
322
+ --- Before: >
323
+ --- a=b
324
+ --- aa<=bb
325
+ --- aaa===bbb
326
+ --- aaaa = cccc
327
+ --- <
328
+ --- After typing `=`: >
329
+ --- a = b
330
+ --- aa <= bb
331
+ --- aaa === bbb
332
+ --- aaaa = cccc
333
+ --- <
334
+ --- <,> Besides splitting by "," character, trim whitespace, pair neighboring
335
+ --- parts and merge with single space.
336
+ ---
337
+ --- Before: >
338
+ --- a,b
339
+ --- aa,bb
340
+ --- aaa , bbb
341
+ --- <
342
+ --- After typing `,`: >
343
+ --- a, b
344
+ --- aa, bb
345
+ --- aaa, bbb
346
+ --- <
347
+ --- <|> Split by "|" character, trim whitespace, merge with single space.
348
+ ---
349
+ --- Before: >
350
+ --- |a|b|
351
+ --- |aa|bb|
352
+ --- |aaa | bbb |
353
+ --- <
354
+ --- After typing `|`: >
355
+ --- | a | b |
356
+ --- | aa | bb |
357
+ --- | aaa | bbb |
358
+ --- <
359
+ --- <Space> (Space bar) Squash consecutive whitespace into single space (except
360
+ --- possible indentation) and split by `%s+` pattern (keeps indentation).
361
+ ---
362
+ --- Before: >
363
+ --- a b c
364
+ --- aa bb cc
365
+ --- <
366
+ --- After typing `<Space>`: >
367
+ --- a b c
368
+ --- aa bb cc
369
+ --- <
370
+ ---@tag MiniAlign-modifiers-builtin
371
+
372
+ --- Copy lines in modifiable buffer, initiate alignment with preview (`gAip`)
373
+ --- and try typing suggested key sequences.
374
+ --- These are modified examples taken from `junegunn/vim-easy-align`.
375
+ ---
376
+ --- # Equal sign ~
377
+ ---
378
+ --- Lines: >
379
+ ---
380
+ --- # This=is=assumed=to be a comment
381
+ --- "a ="
382
+ --- a =
383
+ --- a = 1
384
+ --- bbbb = 2
385
+ --- ccccccc = 3
386
+ --- ccccccccccccccc
387
+ --- ddd = 4
388
+ --- eeee === eee = eee = eee=f
389
+ --- fff = ggg += gg &&= gg
390
+ --- g != hhhhhhhh == 888
391
+ --- i := 5
392
+ --- i %= 5
393
+ --- i *= 5
394
+ --- j =~ 5
395
+ --- j >= 5
396
+ --- aa => 123
397
+ --- aa <<= 123
398
+ --- aa >>= 123
399
+ --- bbb => 123
400
+ --- c => 1233123
401
+ --- d => 123
402
+ --- dddddd &&= 123
403
+ --- dddddd ||= 123
404
+ --- dddddd /= 123
405
+ --- gg <=> ee
406
+ --- <
407
+ --- Key sequences:
408
+ --- - `=`
409
+ --- - `=jc`
410
+ --- - `=jr`
411
+ --- - `=m!<CR>`
412
+ --- - `=p`
413
+ --- - `=i` (execute `:lua vim.o.commentstring = '# %s'` for full experience)
414
+ --- - `=<BS>`
415
+ --- - `=<BS>p`
416
+ --- - `=fn==1<CR>`
417
+ --- - `=<BS>fn==1<CR>t`
418
+ --- - `=frow>7<CR>`
419
+ ---@tag MiniAlign-examples
420
+
421
+ ---@alias __align_with_preview boolean|nil Whether to align with live preview.
422
+
423
+ -- Module definition ==========================================================
424
+ local MiniAlign = {}
425
+ local H = {}
426
+
427
+ --- Module setup
428
+ ---
429
+ ---@param config table|nil Module config table. See |MiniAlign.config|.
430
+ ---
431
+ ---@usage >lua
432
+ --- require('mini.align').setup() -- use default config
433
+ --- -- OR
434
+ --- require('mini.align').setup({}) -- replace {} with your config table
435
+ --- <
436
+ MiniAlign.setup = function(config)
437
+ -- TODO: Remove after Neovim=0.9 support is dropped
438
+ if vim.fn.has('nvim-0.10') == 0 then
439
+ vim.notify(
440
+ '(mini.align) Neovim<0.10 is soft deprecated (module works but is not supported).'
441
+ .. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
442
+ .. ' Please update your Neovim version.'
443
+ )
444
+ end
445
+
446
+ -- Export module
447
+ _G.MiniAlign = MiniAlign
448
+
449
+ -- Setup config
450
+ config = H.setup_config(config)
451
+
452
+ -- Apply config
453
+ H.apply_config(config)
454
+ end
455
+
456
+ --- Defaults ~
457
+ ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
458
+ ---@text # Modifiers ~
459
+ ---
460
+ --- `MiniAlign.config.modifiers` is used to define interactive user experience
461
+ --- of managing alignment process. It is a table with single character keys and
462
+ --- modifier function values.
463
+ ---
464
+ --- Each modifier function:
465
+ --- - Is called when corresponding modifier key is pressed.
466
+ --- - Has signature `(steps, opts)` and should modify any of its input in place.
467
+ ---
468
+ --- Examples:
469
+ --- - Modifier function used for default `i` modifier: >lua
470
+ ---
471
+ --- function(steps, _)
472
+ --- table.insert(steps.pre_split, MiniAlign.gen_step.ignore_split())
473
+ --- end
474
+ --- <
475
+ --- - Tweak `t` modifier to use highest indentation instead of keeping it: >lua
476
+ ---
477
+ --- require('mini.align').setup({
478
+ --- modifiers = {
479
+ --- t = function(steps, _)
480
+ --- local trim_high = MiniAlign.gen_step.trim('both', 'high')
481
+ --- table.insert(steps.pre_justify, trim_high)
482
+ --- end
483
+ --- }
484
+ --- })
485
+ --- <
486
+ --- - Tweak `j` modifier to cycle through available "justify_side" option
487
+ --- values (like in `junegunn/vim-easy-align`): >lua
488
+ ---
489
+ --- require('mini.align').setup({
490
+ --- modifiers = {
491
+ --- j = function(_, opts)
492
+ --- local next_option = ({
493
+ --- left = 'center', center = 'right', right = 'none', none = 'left',
494
+ --- })[opts.justify_side]
495
+ --- opts.justify_side = next_option or 'left'
496
+ --- end,
497
+ --- },
498
+ --- })
499
+ --- <
500
+ --- # Options ~
501
+ ---
502
+ --- `MiniAlign.config.options` defines default values of options used to control
503
+ --- behavior of steps.
504
+ ---
505
+ --- Examples:
506
+ --- - Set `justify_side = 'center'` to center align at initialization.
507
+ ---
508
+ --- For more details about options see |MiniAlign.align_strings()| and entries of
509
+ --- |MiniAlign.gen_step| for default main steps.
510
+ ---
511
+ --- # Steps ~
512
+ ---
513
+ --- `MiniAlign.config.steps` defines default steps to be applied during
514
+ --- alignment process.
515
+ ---
516
+ --- Examples:
517
+ --- - Align by default only first pair of columns: >lua
518
+ ---
519
+ --- local align = require('mini.align')
520
+ --- align.setup({
521
+ --- steps = {
522
+ --- pre_justify = { align.gen_step.filter('n == 1') }
523
+ --- },
524
+ --- })
525
+ --- <
526
+ MiniAlign.config = {
527
+ -- Module mappings. Use `''` (empty string) to disable one.
528
+ mappings = {
529
+ start = 'ga',
530
+ start_with_preview = 'gA',
531
+ },
532
+
533
+ -- Modifiers changing alignment steps and/or options
534
+ modifiers = {
535
+ -- Main option modifiers
536
+ --minidoc_replace_start ['s'] = --<function: enter split pattern>,
537
+ ['s'] = function(_, opts)
538
+ local input = H.user_input('Enter split Lua pattern')
539
+ if input == nil then return end
540
+ opts.split_pattern = input
541
+ end,
542
+ --minidoc_replace_end
543
+ --minidoc_replace_start ['j'] = --<function: choose justify side>,
544
+ ['j'] = function(_, opts)
545
+ -- stylua: ignore
546
+ H.echo({
547
+ { 'Select justify: ', 'ModeMsg' }, { 'l', 'Question' }, { 'eft, ' },
548
+ { 'c', 'Question' }, { 'enter, ' }, { 'r', 'Question' }, { 'ight, ' },
549
+ { 'n', 'Question' }, { 'one' }
550
+ })
551
+ local ok, char = pcall(vim.fn.getcharstr)
552
+ local direction = ({ l = 'left', c = 'center', r = 'right', n = 'none' })[char]
553
+ if not ok or direction == nil then return end
554
+ opts.justify_side = direction
555
+ end,
556
+ --minidoc_replace_end
557
+ --minidoc_replace_start ['m'] = --<function: enter merge delimiter>,
558
+ ['m'] = function(_, opts)
559
+ local input = H.user_input('Enter merge delimiter')
560
+ if input == nil then return end
561
+ opts.merge_delimiter = input
562
+ end,
563
+ --minidoc_replace_end
564
+
565
+ -- Modifiers adding pre-steps
566
+ --minidoc_replace_start ['f'] = --<function: filter parts by entering Lua expression>,
567
+ ['f'] = function(steps, _)
568
+ local input = H.user_input('Enter filter expression')
569
+ local step = MiniAlign.gen_step.filter(input)
570
+ if step == nil then return end
571
+ table.insert(steps.pre_justify, step)
572
+ end,
573
+ --minidoc_replace_end
574
+ --minidoc_replace_start ['i'] = --<function: ignore some split matches>,
575
+ ['i'] = function(steps, _) table.insert(steps.pre_split, MiniAlign.gen_step.ignore_split()) end,
576
+ --minidoc_replace_end
577
+ --minidoc_replace_start ['p'] = --<function: pair parts>,
578
+ ['p'] = function(steps, _) table.insert(steps.pre_justify, MiniAlign.gen_step.pair()) end,
579
+ --minidoc_replace_end
580
+ --minidoc_replace_start ['t'] = --<function: trim parts>,
581
+ ['t'] = function(steps, _) table.insert(steps.pre_justify, MiniAlign.gen_step.trim()) end,
582
+ --minidoc_replace_end
583
+
584
+ -- Delete some last pre-step
585
+ --minidoc_replace_start ['<BS>'] = --<function: delete some last pre-step>,
586
+ [vim.api.nvim_replace_termcodes('<BS>', true, true, true)] = function(steps, _)
587
+ local has_pre = {}
588
+ for _, pre in ipairs({ 'pre_split', 'pre_justify', 'pre_merge' }) do
589
+ if #steps[pre] > 0 then table.insert(has_pre, pre) end
590
+ end
591
+
592
+ if #has_pre == 0 then return end
593
+
594
+ if #has_pre == 1 then
595
+ local pre = steps[has_pre[1]]
596
+ table.remove(pre, #pre)
597
+ return
598
+ end
599
+
600
+ --stylua: ignore
601
+ H.echo({
602
+ { 'Select pre-step to remove: ', 'ModeMsg' }, { 's', 'Question' }, { 'plit, ' },
603
+ { 'j', 'Question' }, { 'ustify, ' }, { 'm', 'Question' }, { 'erge' },
604
+ })
605
+ local ok, char = pcall(vim.fn.getcharstr)
606
+ if not ok then return nil end
607
+
608
+ if char == 's' then table.remove(steps.pre_split, #steps.pre_split) end
609
+ if char == 'j' then table.remove(steps.pre_justify, #steps.pre_justify) end
610
+ if char == 'm' then table.remove(steps.pre_merge, #steps.pre_merge) end
611
+ end,
612
+ --minidoc_replace_end
613
+
614
+ -- Special configurations for common splits
615
+ --minidoc_replace_start ['='] = --<function: enhanced setup for '='>,
616
+ ['='] = function(steps, opts)
617
+ opts.split_pattern = '%p*=+[<>~]*'
618
+ table.insert(steps.pre_justify, MiniAlign.gen_step.trim())
619
+ opts.merge_delimiter = ' '
620
+ end,
621
+ --minidoc_replace_end
622
+ --minidoc_replace_start [','] = --<function: enhanced setup for ','>,
623
+ [','] = function(steps, opts)
624
+ opts.split_pattern = ','
625
+ table.insert(steps.pre_justify, MiniAlign.gen_step.trim())
626
+ table.insert(steps.pre_justify, MiniAlign.gen_step.pair())
627
+ opts.merge_delimiter = ' '
628
+ end,
629
+ --minidoc_replace_end
630
+ --minidoc_replace_start ['|'] = --<function: enhanced setup for '|'>,
631
+ ['|'] = function(steps, opts)
632
+ opts.split_pattern = '|'
633
+ table.insert(steps.pre_justify, MiniAlign.gen_step.trim())
634
+ opts.merge_delimiter = ' '
635
+ end,
636
+ --minidoc_replace_end
637
+ --minidoc_replace_start [' '] = --<function: enhanced setup for ' '>,
638
+ [' '] = function(steps, opts)
639
+ table.insert(
640
+ steps.pre_split,
641
+ MiniAlign.new_step('squash', function(strings)
642
+ -- Replace all space sequences with single space (except indent)
643
+ for i, s in ipairs(strings) do
644
+ strings[i] = s:gsub('()(%s+)', function(n, space) return n == 1 and space or ' ' end)
645
+ end
646
+ end)
647
+ )
648
+ -- Don't use `' '` to respect indent
649
+ opts.split_pattern = '%s+'
650
+ end,
651
+ --minidoc_replace_end
652
+ },
653
+
654
+ -- Default options controlling alignment process
655
+ options = {
656
+ split_pattern = '',
657
+ justify_side = 'left',
658
+ merge_delimiter = '',
659
+ },
660
+
661
+ -- Default steps performing alignment (if `nil`, default is used)
662
+ steps = {
663
+ pre_split = {},
664
+ split = nil,
665
+ pre_justify = {},
666
+ justify = nil,
667
+ pre_merge = {},
668
+ merge = nil,
669
+ },
670
+
671
+ -- Whether to disable showing non-error feedback
672
+ -- This also affects (purely informational) helper messages shown after
673
+ -- idle time if user input is required.
674
+ silent = false,
675
+ }
676
+ --minidoc_afterlines_end
677
+
678
+ -- Module functionality =======================================================
679
+ --- Align strings
680
+ ---
681
+ --- For details about alignment process see |MiniAlign-algorithm|.
682
+ ---
683
+ ---@param strings table Array of strings.
684
+ ---@param opts table|nil Options. Its copy will be passed to steps as second
685
+ --- argument. Extended with `MiniAlign.config.options`.
686
+ --- This is a place to control default main steps:
687
+ --- - `opts.split_pattern` - Lua pattern(s) used to make split parts.
688
+ --- - `opts.split_exclude_patterns` - which split matches should be ignored.
689
+ --- - `opts.justify_side` - which direction(s) alignment should be done.
690
+ --- - `opts.justify_offsets` - offsets tweaking width of first column
691
+ --- - `opts.merge_delimiter` - which delimiter(s) to use when merging.
692
+ --- For more information see |MiniAlign.gen_step| entry for corresponding
693
+ --- default step.
694
+ ---@param steps table|nil Steps. Extended with `MiniAlign.config.steps`.
695
+ --- Possible `nil` values are replaced with corresponding default steps:
696
+ --- - `split` - |MiniAlign.gen_step.default_split()|.
697
+ --- - `justify` - |MiniAlign.gen_step.default_justify()|.
698
+ --- - `merge` - |MiniAlign.gen_step.default_merge()|.
699
+ MiniAlign.align_strings = function(strings, opts, steps)
700
+ -- Validate arguments
701
+ if not H.is_array_of(strings, H.is_string) then
702
+ H.error('First argument of `MiniAlign.align_strings()` should be array of strings.')
703
+ end
704
+ opts = H.normalize_opts(opts)
705
+ steps = H.normalize_steps(steps, 'steps')
706
+
707
+ -- Make a copy so that modification in place doesn't affect input
708
+ strings = vim.deepcopy(strings)
709
+
710
+ -- Pre split
711
+ for _, step in ipairs(steps.pre_split) do
712
+ H.apply_step(step, strings, opts, 'pre_split')
713
+ end
714
+
715
+ -- Split
716
+ local parts = H.apply_step(steps.split, strings, opts, 'split')
717
+ if not H.is_parts(parts) then
718
+ if H.can_be_parts(parts) then
719
+ parts = MiniAlign.as_parts(parts)
720
+ else
721
+ H.error('Output of `split` step should be convertible to parts. See `:h MiniAlign.as_parts()`.')
722
+ end
723
+ end
724
+
725
+ -- Pre justify
726
+ for _, step in ipairs(steps.pre_justify) do
727
+ H.apply_step(step, parts, opts, 'pre_justify')
728
+ end
729
+
730
+ -- Justify
731
+ H.apply_step(steps.justify, parts, opts, 'justify')
732
+
733
+ -- Pre merge
734
+ for _, step in ipairs(steps.pre_merge) do
735
+ H.apply_step(step, parts, opts, 'pre_merge')
736
+ end
737
+
738
+ -- Merge
739
+ local new_strings = H.apply_step(steps.merge, parts, opts, 'merge')
740
+ if not H.is_array_of(new_strings, H.is_string) then H.error('Output of `merge` step should be array of strings.') end
741
+ return new_strings
742
+ end
743
+
744
+ --- Align current region with user-supplied steps
745
+ ---
746
+ --- Mostly designed to be used inside mappings.
747
+ ---
748
+ --- Will use |MiniAlign.align_strings()| and set the following options in `opts`:
749
+ --- - `justify_offsets` - array of offsets used to achieve actual alignment of
750
+ --- a region. It is non-trivial (not array of zeros) only for charwise
751
+ --- selection: offset of first string is computed as width of prefix to the
752
+ --- left of region start.
753
+ --- - `region` - current affected region (see |MiniAlign-glossary|). Can be
754
+ --- used to create more advanced steps.
755
+ --- - `mode` - mode of selection (see |MiniAlign-glossary|).
756
+ ---
757
+ ---@param mode string Selection mode. One of "char", "line", "block".
758
+ MiniAlign.align_user = function(mode)
759
+ local modifiers = H.get_config().modifiers
760
+ local with_preview = H.cache.with_preview
761
+ local opts = H.cache.opts or H.normalize_opts()
762
+ local steps = H.cache.steps or H.normalize_steps()
763
+
764
+ local steps_are_from_cache = H.cache.steps ~= nil
765
+ H.cache.region = nil
766
+
767
+ -- Track if lines were actually changed to properly undo during preview
768
+ local lines_were_changed = false
769
+
770
+ -- Make initial process
771
+ lines_were_changed = H.process_current_region(lines_were_changed, mode, opts, steps)
772
+
773
+ -- Make early return:
774
+ -- - If cache is present (enables dot-repeat).
775
+ -- - If `split` is not default with no preview (no more information needed).
776
+ if steps_are_from_cache or (not with_preview and opts.split_pattern ~= '') then return end
777
+
778
+ -- Ask user to input modifier id until no more is needed
779
+ local n_iter = 0
780
+ while true do
781
+ -- Get modifier from user
782
+ local id = H.user_modifier(with_preview, H.make_status_msg_chunks(opts, steps))
783
+ n_iter = n_iter + 1
784
+
785
+ -- Stop in case user supplied inappropriate modifier id (abort)
786
+ -- Also stop in case of too many iterations (guard from infinite cycle)
787
+ if id == nil or n_iter > 1000 then
788
+ if lines_were_changed then H.undo() end
789
+ if n_iter > 1000 then H.echo({ { 'Too many modifiers typed.', 'WarningMsg' } }, true) end
790
+ break
791
+ end
792
+
793
+ -- Stop preview after `<CR>` (confirmation)
794
+ if with_preview and id == '\r' then break end
795
+
796
+ -- Apply modifier
797
+ local mod = modifiers[id]
798
+ if mod == nil then
799
+ -- Use supplied identifier as split pattern
800
+ opts.split_pattern = vim.pesc(id)
801
+ else
802
+ -- Modifier should change input `steps` table in place
803
+ local ok, out = pcall(mod, steps, opts)
804
+ if not ok then
805
+ -- Force message to appear for 500ms because it might be overridden by
806
+ -- helper status message
807
+ local msg = string.format('Modifier %s should be properly callable. Reason: %s', vim.inspect(id), out)
808
+ H.echo({ { msg, 'WarningMsg' } }, true)
809
+ vim.cmd('redraw')
810
+ vim.loop.sleep(500)
811
+ end
812
+ end
813
+
814
+ -- Normalize steps and options while validating their correctness
815
+ opts = H.normalize_opts(opts)
816
+ steps = H.normalize_steps(steps, opts)
817
+
818
+ -- Process region while tracking if lines were set at least once
819
+ local lines_now_changed = H.process_current_region(lines_were_changed, mode, opts, steps)
820
+ lines_were_changed = lines_were_changed or lines_now_changed
821
+
822
+ -- Stop in "no preview" mode right after `split` is defined
823
+ if not with_preview and opts.split_pattern ~= '' then break end
824
+ end
825
+
826
+ -- Remove helper status message (if shown)
827
+ H.unecho()
828
+ end
829
+
830
+ --- Convert 2d array of strings to parts
831
+ ---
832
+ --- This function verifies if input is a proper 2d array of strings and adds
833
+ --- methods to its copy.
834
+ ---
835
+ ---@class parts
836
+ ---
837
+ ---@field apply function Takes callable `f` and applies it to every part.
838
+ --- Callable should have signature `(s, data)`: `s` is a string part,
839
+ --- `data` - table with its data (<row> has row number, <col> has column number).
840
+ --- Returns new 2d array.
841
+ ---
842
+ ---@field apply_inplace function Takes callable `f` and applies it to every part.
843
+ --- Should have same signature as in `apply` method. Outputs (should all be
844
+ --- strings) are assigned in place to a corresponding parts element. Returns
845
+ --- parts itself to enable method chaining.
846
+ ---
847
+ ---@field get_dims function Return dimensions of parts array: a table with
848
+ --- <row> and <col> keys having number of rows and number of columns (maximum
849
+ --- number of elements across all rows).
850
+ ---
851
+ ---@field group function Concatenate neighboring strings based on supplied
852
+ --- boolean mask and direction (one of "left", default, or "right"). Has
853
+ --- signature `(mask, direction)` and modifies parts in place. Returns parts
854
+ --- itself to enable method chaining.
855
+ --- Example:
856
+ --- - Parts: `{ { "a", "b", "c" }, { "d", "e" }, { "f" } }`
857
+ --- - Mask: `{ { false, false, true }, { true, false }, { false } }`
858
+ --- - Result for direction "left": `{ { "abc" }, { "d", "e" }, { "f" } }`
859
+ --- - Result for direction "right": `{ { "ab","c" }, { "de" }, { "f" } }`
860
+ ---
861
+ ---@field pair function Concatenate neighboring element pairs. Takes
862
+ --- `direction` as input (one of "left", default, or "right") and applies
863
+ --- `group()` for an alternating mask.
864
+ --- Example:
865
+ --- - Parts: `{ { "a", "b", "c" }, { "d", "e" }, { "f" } }`
866
+ --- - Result for direction "left": `{ { "ab", "c" }, { "de" }, { "f" } }`
867
+ --- - Result for direction "right": `{ { "a", "bc" }, { "de" }, { "f" } }`
868
+ ---
869
+ ---@field slice_col function Return column with input index `j`. Note: it might
870
+ --- not be an array if rows have unequal number of columns.
871
+ ---
872
+ ---@field slice_row function Return row with input index `i`.
873
+ ---
874
+ ---@field trim function Trim elements whitespace. Has signature `(direction, indent)`
875
+ --- and modifies parts in place. Returns parts itself to enable method chaining.
876
+ --- - Possible values of `direction`: "both" (default), "left", "right",
877
+ --- "none". Defines from which side whitespaces should be removed.
878
+ --- - Possible values of `indent`: "keep" (default), "low", "high", "remove".
879
+ --- Defines what to do with possible indent (left whitespace of first string
880
+ --- in a row). Value "keep" keeps it; "low" makes all indent equal to the
881
+ --- lowest across rows; "high" - highest across rows; "remove" - removes indent.
882
+ ---
883
+ ---@usage >lua
884
+ --- parts = MiniAlign.as_parts({ { 'a', 'b' }, { 'c' } })
885
+ --- print(vim.inspect(parts.get_dims())) -- Should be { row = 2, col = 2 }
886
+ ---
887
+ --- parts.apply_inplace(function(s, data)
888
+ --- return ' ' .. data.row .. s .. data.col .. ' '
889
+ --- end)
890
+ --- print(vim.inspect(parts)) -- Should be { { ' 1a1 ', ' 1b2 ' }, { ' 2c1 ' } }
891
+ ---
892
+ --- parts.trim('both', 'remove').pair()
893
+ --- print(vim.inspect(parts)) -- Should be { { '1a11b2' }, { '2c1' } }
894
+ --- <
895
+ MiniAlign.as_parts = function(arr2d)
896
+ local ok, msg = H.can_be_parts(arr2d)
897
+ if not ok then H.error('Input of `as_parts()` ' .. msg) end
898
+
899
+ local parts = vim.deepcopy(arr2d)
900
+ local methods = {}
901
+
902
+ methods.apply = function(f)
903
+ local res = {}
904
+ for i, row in ipairs(parts) do
905
+ res[i] = {}
906
+ for j, s in ipairs(row) do
907
+ res[i][j] = f(s, { row = i, col = j })
908
+ end
909
+ end
910
+ return res
911
+ end
912
+
913
+ methods.apply_inplace = function(f)
914
+ for i, row in ipairs(parts) do
915
+ for j, s in ipairs(row) do
916
+ local new_val = f(s, { row = i, col = j })
917
+ if type(new_val) ~= 'string' then H.error('Input of `apply_inplace()` method should always return string.') end
918
+ parts[i][j] = new_val
919
+ end
920
+ end
921
+
922
+ return parts
923
+ end
924
+
925
+ methods.get_dims = function()
926
+ local n_cols = 0
927
+ for _, row in ipairs(parts) do
928
+ n_cols = math.max(n_cols, #row)
929
+ end
930
+ return { row = #parts, col = n_cols }
931
+ end
932
+
933
+ -- Group cells into single string based on boolean mask.
934
+ -- Can be used for filtering separators and sticking separator to its part.
935
+ methods.group = function(mask, direction)
936
+ direction = direction or 'left'
937
+ for i, row in ipairs(parts) do
938
+ local group_tables = H.group_by_mask(row, mask[i], direction)
939
+ parts[i] = vim.tbl_map(table.concat, group_tables)
940
+ end
941
+ return parts
942
+ end
943
+
944
+ methods.pair = function(direction)
945
+ direction = direction or 'left'
946
+
947
+ local mask = {}
948
+ for i, row in ipairs(parts) do
949
+ mask[i] = {}
950
+ for j, _ in ipairs(row) do
951
+ -- Count from corresponding end
952
+ local num = direction == 'left' and j or (#row - j + 1)
953
+ mask[i][j] = num % 2 == 0
954
+ end
955
+ end
956
+
957
+ parts.group(mask, direction)
958
+ return parts
959
+ end
960
+
961
+ -- NOTE: output might not be an array (some rows can not have input column)
962
+ -- Use `vim.tbl_keys()` and `vim.tbl_values()`
963
+ methods.slice_col = function(j)
964
+ return vim.tbl_map(function(row) return row[j] end, parts)
965
+ end
966
+
967
+ methods.slice_row = function(i) return parts[i] or {} end
968
+
969
+ methods.trim = function(direction, indent)
970
+ direction = direction or 'both'
971
+ indent = indent or 'keep'
972
+
973
+ -- Verify arguments
974
+ local trim_fun = H.trim_functions[direction]
975
+ if not vim.is_callable(trim_fun) then
976
+ local allowed = vim.tbl_map(vim.inspect, vim.tbl_keys(H.trim_functions))
977
+ table.sort(allowed)
978
+ H.error('`direction` should be one of ' .. table.concat(allowed, ', ') .. '.')
979
+ end
980
+
981
+ local indent_fun = H.indent_functions[indent]
982
+ if not vim.is_callable(indent_fun) then
983
+ local allowed = vim.tbl_map(vim.inspect, vim.tbl_keys(H.indent_functions))
984
+ table.sort(allowed)
985
+ H.error('`indent` should be one of ' .. table.concat(allowed, ', ') .. '.')
986
+ end
987
+
988
+ -- Compute indentation to restore later
989
+ local row_indent = vim.tbl_map(function(row) return row[1]:match('^(%s*)') end, parts)
990
+ row_indent = indent_fun(row_indent)
991
+
992
+ -- Trim
993
+ parts.apply_inplace(trim_fun)
994
+
995
+ -- Restore indentation if it was removed
996
+ if vim.tbl_contains({ 'both', 'left' }, direction) then
997
+ for i, row in ipairs(parts) do
998
+ row[1] = string.format('%s%s', row_indent[i], row[1])
999
+ end
1000
+ end
1001
+
1002
+ return parts
1003
+ end
1004
+
1005
+ return setmetatable(parts, { class = 'parts', __index = methods })
1006
+ end
1007
+
1008
+ --- Create step
1009
+ ---
1010
+ --- A step is basically a named callable object. Having a name bundled with
1011
+ --- some action powers helper status message during interactive alignment process.
1012
+ ---
1013
+ ---@param name string Step name.
1014
+ ---@param action function|table Step action. Should be a callable object
1015
+ --- (see |vim.is_callable()|).
1016
+ ---
1017
+ ---@return table A table with keys: <name> with `name` argument, <action> with `action`.
1018
+ MiniAlign.new_step = function(name, action)
1019
+ if type(name) ~= 'string' then H.error('Step name should be string.') end
1020
+ if not vim.is_callable(action) then H.error('Step action should be callable.') end
1021
+ return { name = name, action = action }
1022
+ end
1023
+
1024
+ --- Generate common action steps
1025
+ ---
1026
+ --- This is a table with function elements. Call to actually get step.
1027
+ ---
1028
+ --- Each step action is a function that has signature `(object, opts)`, where
1029
+ --- `object` is either parts or array of strings (depends on which stage of
1030
+ --- alignment process it is assumed to be applied) and `opts` is table of options.
1031
+ ---
1032
+ --- Outputs of elements named `default_*` are used as default corresponding main
1033
+ --- step (split, justify, merge). Behavior of all of them depend on values from
1034
+ --- supplied options (second argument).
1035
+ ---
1036
+ --- Outputs of other elements depend on both step generator input values and
1037
+ --- options supplied at execution. This design is mostly because their output
1038
+ --- can be used several times in pre-steps.
1039
+ ---
1040
+ ---@usage >lua
1041
+ --- local align = require('mini.align')
1042
+ --- align.setup({
1043
+ --- modifiers = {
1044
+ --- -- Use 'T' modifier to remove both whitespace and indent
1045
+ --- T = function(steps, _)
1046
+ --- table.insert(steps.pre_justify, align.gen_step.trim('both', 'remove'))
1047
+ --- end,
1048
+ --- },
1049
+ --- options = {
1050
+ --- -- By default align "right", "left", "right", "left", ...
1051
+ --- justify_side = { 'right', 'left' },
1052
+ --- },
1053
+ --- steps = {
1054
+ --- -- Align by default only first pair of columns
1055
+ --- pre_justify = { align.gen_step.filter('n == 1') },
1056
+ --- },
1057
+ --- })
1058
+ --- <
1059
+ MiniAlign.gen_step = {}
1060
+
1061
+ --- Generate default split step
1062
+ ---
1063
+ --- Output splits strings using matches of Lua pattern(s) from `split_pattern`
1064
+ --- option which are not dismissed by `split_exclude_patterns` option.
1065
+ ---
1066
+ --- Outline of how single string is split:
1067
+ --- - Convert `split_pattern` option to array of strings (string is converted
1068
+ --- as one-element array). This array will be recycled in case there are more
1069
+ --- split matches than in converted `split_pattern` array (which almost always).
1070
+ --- - Find all forbidden spans (intervals inside string) - all matches of all
1071
+ --- patterns in `split_exclude_patterns`.
1072
+ --- - Find match for the next pattern. If it is not inside any forbidden span,
1073
+ --- add preceding unmatched substring and matched split as two parts. Repeat
1074
+ --- with the next pattern.
1075
+ --- - If no pattern match is found, add the rest of string as final part.
1076
+ ---
1077
+ --- Output uses following options (as part second argument, `opts` table):
1078
+ --- - <split_pattern> - string or array of strings used to detect split matches
1079
+ --- and create parts. Default: `''` meaning no matches (whole string is used
1080
+ --- as part). Examples: `'%s+'`, `{ '<', '>' }`.
1081
+ --- - <split_exclude_patterns> - array of strings defining which regions to
1082
+ --- exclude from being matched. Default: `{}`. Examples: `{ '".-"', '^%s*#.*' }`.
1083
+ ---
1084
+ ---@return table A step named "split" and with appropriate callable action.
1085
+ ---
1086
+ ---@seealso |MiniAlign.gen_step.ignore_split()| heavily uses `split_exclude_patterns`.
1087
+ MiniAlign.gen_step.default_split = function() return MiniAlign.new_step('split', H.default_action_split) end
1088
+
1089
+ --- Generate default justify step
1090
+ ---
1091
+ --- Output makes column elements of string parts have equal width by adding
1092
+ --- left and/or right whitespace padding. Which side(s) to pad is defined by
1093
+ --- `justify_side` option. Width of first column can be tweaked with `justify_offsets`
1094
+ --- option.
1095
+ ---
1096
+ --- Outline of how parts are justified:
1097
+ --- - Convert `justify_side` option to array of strings (single string is
1098
+ --- converted as one-element array). Recycle this array to have length equal
1099
+ --- to number of columns in parts.
1100
+ --- - For all columns compute maximum width of strings from it (add offsets from
1101
+ --- `justify_offsets` to first column widths). Note: for left alignment, width
1102
+ --- of last row element does not affect column width. This is mainly because
1103
+ --- it won't be padded and helps dealing with "no single match" lines.
1104
+ --- - Make all elements have same width inside column by adding appropriate
1105
+ --- amount of whitespace. Which side(s) to add is controlled by the corresponding
1106
+ --- `justify_side` array element. Note: padding is done with spaces which
1107
+ --- might conflict with tab indentation.
1108
+ ---
1109
+ --- Output uses following options (as part second argument, `opts` table):
1110
+ --- - <justify_side> - string or array of strings. Each element can be one of
1111
+ --- "left" (pad right side), "center" (pad both sides equally), "right" (pad
1112
+ --- left side), "none" (no padding). Default: "left".
1113
+ --- - <justify_offsets> - array of numeric left offsets of rows. Used to adjust
1114
+ --- for possible not equal indents, like in case of charwise selection when
1115
+ --- left edge is not on the first column. Default: array of zeros. Set
1116
+ --- automatically during interactive alignment in charwise mode.
1117
+ ---
1118
+ ---@return table A step named "justify" and with appropriate callable action.
1119
+ MiniAlign.gen_step.default_justify = function() return MiniAlign.new_step('justify', H.default_action_justify) end
1120
+
1121
+ --- Generate default merge step
1122
+ ---
1123
+ --- Output merges rows of parts into strings by placing merge delimiter(s)
1124
+ --- between them.
1125
+ ---
1126
+ --- Outline of how parts are converted to array of strings:
1127
+ --- - Convert `merge_delimiter` option to array of strings (single string is
1128
+ --- converted as one-element array). Recycle this array to have length equal
1129
+ --- to number of columns in parts minus 1. Also possibly trim leading whitespace
1130
+ --- in first merge character to not affect indentation.
1131
+ --- - Exclude empty strings from parts. They add nothing to output except extra
1132
+ --- usage of merge delimiter.
1133
+ --- - Concatenate each row interleaving with array of merge delimiters.
1134
+ ---
1135
+ --- Output uses following options (as part second argument, `opts` table):
1136
+ --- - <merge_delimiter> - string or array of strings. Default: `''`.
1137
+ --- Examples: `' '`, `{ '', ' ' }`.
1138
+ ---
1139
+ ---@return table A step named "merge" and with appropriate callable action.
1140
+ MiniAlign.gen_step.default_merge = function() return MiniAlign.new_step('merge', H.default_action_merge) end
1141
+
1142
+ --- Generate filter step
1143
+ ---
1144
+ --- Construct function predicate from supplied Lua string expression and make
1145
+ --- step evaluating it on every part element.
1146
+ ---
1147
+ --- Outline of how filtering is done:
1148
+ --- - Convert Lua filtering expression into function predicate which can be
1149
+ --- evaluated in manually created context (some specific variables being set).
1150
+ --- - Compute boolean mask for parts by applying predicate to each element of
1151
+ --- 2d array with special variables set to specific values (see next section).
1152
+ --- - Group parts with computed mask. See `group()` method of parts in
1153
+ --- |MiniAlign.as_parts()|.
1154
+ ---
1155
+ --- Special variables which can be used in expression:
1156
+ --- - <row> - row number of current element.
1157
+ --- - <ROW> - total number of rows in parts.
1158
+ --- - <col> - column number of current element.
1159
+ --- - <COL> - total number of columns in current row.
1160
+ --- - <s> - string value of current element.
1161
+ --- - <n> - column pair number of current element. Useful when filtering by
1162
+ --- result of pattern splitting.
1163
+ --- - <N> - total number of column pairs in current row.
1164
+ --- - All variables from global table `_G`.
1165
+ ---
1166
+ --- Tips:
1167
+ --- - This general filtering approach can be used to both include and exclude
1168
+ --- certain parts from alignment. Examples:
1169
+ --- - Use `row ~= 2` to align all parts except from second row.
1170
+ --- - Use `n == 1` to align only by first pair of columns.
1171
+ --- - Filtering by last equal sign usually can be done with `n >= (N - 1)`
1172
+ --- (because there is usually something to the right of it).
1173
+ ---
1174
+ ---@param expr string Lua expression as a string which will be used as predicate.
1175
+ ---
1176
+ ---@return table|nil A step named "filter" and with appropriate callable action.
1177
+ MiniAlign.gen_step.filter = function(expr)
1178
+ local action = H.make_filter_action(expr)
1179
+ if action == nil then return end
1180
+ return MiniAlign.new_step('filter', action)
1181
+ end
1182
+
1183
+ --- Generate ignore step
1184
+ ---
1185
+ --- Output adds certain values to `split_exclude_patterns` option. Should be
1186
+ --- used as pre-split step.
1187
+ ---
1188
+ ---@param patterns table Array of patterns to be added to
1189
+ --- `split_exclude_patterns` as is. Default: `{ [[".-"]] }` (excludes strings
1190
+ --- for most cases).
1191
+ ---@param exclude_comment boolean|nil Whether to add comment pattern to
1192
+ --- `split_exclude_patterns`. Comment pattern is derived from |'commentstring'|
1193
+ --- option. Default: `true`.
1194
+ ---
1195
+ ---@return table A step named "ignore" and with appropriate callable action.
1196
+ ---
1197
+ ---@seealso |MiniAlign.gen_step.default_split()| for details about
1198
+ --- `split_exclude_patterns` option.
1199
+ MiniAlign.gen_step.ignore_split = function(patterns, exclude_comment)
1200
+ patterns = patterns or { '".-"' }
1201
+ if exclude_comment == nil then exclude_comment = true end
1202
+
1203
+ -- Validate ingput
1204
+ if not H.is_array_of(patterns, H.is_string) then
1205
+ H.error('Argument `patterns` of `ignore_split()` should be array of strings.')
1206
+ end
1207
+ if type(exclude_comment) ~= 'boolean' then
1208
+ H.error('Argument `exclude_comment` of `ignore_split()` should be boolean.')
1209
+ end
1210
+
1211
+ -- Make action which modifies `opts.split_exclude_patterns`
1212
+ local action = function(_, opts)
1213
+ local excl = opts.split_exclude_patterns or {}
1214
+
1215
+ -- Add supplied patterns while avoiding duplication
1216
+ for _, patt in ipairs(patterns) do
1217
+ if not vim.tbl_contains(excl, patt) then table.insert(excl, patt) end
1218
+ end
1219
+
1220
+ -- Possibly add current comment pattern while avoiding duplication
1221
+ if exclude_comment then
1222
+ -- In 'commentstring', `%s` denotes the comment content
1223
+ local comment_pattern = vim.pesc(vim.o.commentstring):gsub('%%%%s', '.-')
1224
+ -- Ignore to the end of the string if 'commentstring' is like "xxx%s"
1225
+ comment_pattern = comment_pattern:gsub('%.%-%s*$', '.*')
1226
+ if not vim.tbl_contains(excl, comment_pattern) then table.insert(excl, comment_pattern) end
1227
+ end
1228
+
1229
+ opts.split_exclude_patterns = excl
1230
+ end
1231
+
1232
+ return MiniAlign.new_step('ignore', action)
1233
+ end
1234
+
1235
+ --- Generate pair step
1236
+ ---
1237
+ --- Output calls `pair()` method of parts (see |MiniAlign.as_parts()|) with
1238
+ --- supplied `direction` argument.
1239
+ ---
1240
+ ---@param direction string Which direction to pair. One of "left" (default) or
1241
+ ---"right".
1242
+ ---
1243
+ ---@return table A step named "pair" and with appropriate callable action.
1244
+ MiniAlign.gen_step.pair = function(direction)
1245
+ return MiniAlign.new_step('pair', function(parts, _) parts.pair(direction) end)
1246
+ end
1247
+
1248
+ --- Generate trim step
1249
+ ---
1250
+ --- Output calls `trim()` method of parts (see |MiniAlign.as_parts()|) with
1251
+ --- supplied `direction` and `indent` arguments.
1252
+ ---
1253
+ ---@param direction string|nil Which sides to trim whitespace. One of "both"
1254
+ --- (default), "left", "right", "none".
1255
+ ---@param indent string|nil What to do with possible indent (left whitespace
1256
+ --- of first string in a row). One of "keep" (default), "low", "high", "remove".
1257
+ ---
1258
+ ---@return table A step named "trim" and with appropriate callable action.
1259
+ MiniAlign.gen_step.trim = function(direction, indent)
1260
+ return MiniAlign.new_step('trim', function(parts, _) parts.trim(direction, indent) end)
1261
+ end
1262
+
1263
+ -- Helper data ================================================================
1264
+ -- Module default config
1265
+ H.default_config = vim.deepcopy(MiniAlign.config)
1266
+
1267
+ -- Cache for various operations
1268
+ H.cache = {}
1269
+
1270
+ -- Module's namespaces
1271
+ H.ns_id = {
1272
+ -- Track user input
1273
+ input = vim.api.nvim_create_namespace('MiniAlignInput'),
1274
+ }
1275
+
1276
+ -- Pad functions for supported justify directions
1277
+ -- Allow to not add trailing whitespace
1278
+ H.pad_functions = {
1279
+ left = function(x, n_spaces, no_trailing)
1280
+ if no_trailing or H.is_infinite(n_spaces) then return x end
1281
+ return string.format('%s%s', x, string.rep(' ', n_spaces))
1282
+ end,
1283
+ center = function(x, n_spaces, no_trailing)
1284
+ local n_left = math.floor(0.5 * n_spaces)
1285
+ return H.pad_functions.right(H.pad_functions.left(x, n_left, no_trailing), n_spaces - n_left, no_trailing)
1286
+ end,
1287
+ right = function(x, n_spaces, no_trailing)
1288
+ if (no_trailing and H.is_whitespace(x)) or H.is_infinite(n_spaces) then return x end
1289
+ return string.format('%s%s', string.rep(' ', n_spaces), x)
1290
+ end,
1291
+ none = function(x, _, _) return x end,
1292
+ }
1293
+
1294
+ -- Trim functions
1295
+ H.trim_functions = {
1296
+ both = function(x) return H.trim_functions.left(H.trim_functions.right(x)) end,
1297
+ left = function(x) return string.gsub(x, '^%s*', '') end,
1298
+ right = function(x) return string.gsub(x, '%s*$', '') end,
1299
+ none = function(x) return x end,
1300
+ }
1301
+
1302
+ -- Indentation functions
1303
+ H.indent_functions = {
1304
+ keep = function(indent_arr) return indent_arr end,
1305
+ high = function(indent_arr)
1306
+ local max_indent = indent_arr[1]
1307
+ for i = 2, #indent_arr do
1308
+ max_indent = (max_indent:len() < indent_arr[i]:len()) and indent_arr[i] or max_indent
1309
+ end
1310
+ return vim.tbl_map(function() return max_indent end, indent_arr)
1311
+ end,
1312
+ low = function(indent_arr)
1313
+ local min_indent = indent_arr[1]
1314
+ for i = 2, #indent_arr do
1315
+ min_indent = (indent_arr[i]:len() < min_indent:len()) and indent_arr[i] or min_indent
1316
+ end
1317
+ return vim.tbl_map(function() return min_indent end, indent_arr)
1318
+ end,
1319
+ remove = function(indent_arr)
1320
+ return vim.tbl_map(function() return '' end, indent_arr)
1321
+ end,
1322
+ }
1323
+
1324
+ -- Helper functionality =======================================================
1325
+ -- Settings -------------------------------------------------------------------
1326
+ H.setup_config = function(config)
1327
+ H.check_type('config', config, 'table', true)
1328
+ config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
1329
+
1330
+ H.check_type('mappings', config.mappings, 'table')
1331
+ H.check_type('mappings.start', config.mappings.start, 'string')
1332
+ H.check_type('mappings.start_with_preview', config.mappings.start_with_preview, 'string')
1333
+
1334
+ H.check_type('modifiers', config.modifiers, 'table')
1335
+ for k, v in pairs(config.modifiers) do
1336
+ if not vim.is_callable(v) then H.error(string.format('`modifiers[%s]` should be callable.', vim.inspect(k))) end
1337
+ end
1338
+
1339
+ H.validate_steps(config.steps, 'steps')
1340
+ H.check_type('options', config.options, 'table')
1341
+ H.check_type('silent', config.silent, 'boolean')
1342
+
1343
+ return config
1344
+ end
1345
+
1346
+ H.apply_config = function(config)
1347
+ MiniAlign.config = config
1348
+
1349
+ --stylua: ignore start
1350
+ H.map('n', config.mappings.start, H.make_action_normal(false), { expr = true, desc = 'Align' })
1351
+ H.map('x', config.mappings.start, H.make_action_visual(false), { desc = 'Align' })
1352
+
1353
+ H.map('n', config.mappings.start_with_preview, H.make_action_normal(true), { expr = true, desc = 'Align with preview' })
1354
+ H.map('x', config.mappings.start_with_preview, H.make_action_visual(true), { desc = 'Align with preview' })
1355
+ --stylua: ignore end
1356
+ end
1357
+
1358
+ H.is_disabled = function() return vim.g.minialign_disable == true or vim.b.minialign_disable == true end
1359
+
1360
+ H.get_config = function()
1361
+ -- Using `tbl_deep_extend()` works even in presence of `steps.pre_*` arrays
1362
+ -- because default ones are empty.
1363
+ return vim.tbl_deep_extend('force', MiniAlign.config, vim.b.minialign_config or {})
1364
+ end
1365
+
1366
+ -- Mappings -------------------------------------------------------------------
1367
+ H.make_action_normal = function(with_preview)
1368
+ return function()
1369
+ if H.is_disabled() then return end
1370
+
1371
+ H.cache = { with_preview = with_preview }
1372
+
1373
+ -- Set 'operatorfunc' which will be later called with appropriate marks set
1374
+ vim.o.operatorfunc = 'v:lua.MiniAlign.align_user'
1375
+ return 'g@'
1376
+ end
1377
+ end
1378
+
1379
+ H.make_action_visual = function(with_preview)
1380
+ return function()
1381
+ if H.is_disabled() then return end
1382
+
1383
+ H.cache = { with_preview = with_preview }
1384
+
1385
+ -- Perform action and exit Visual mode
1386
+ local mode = ({ ['v'] = 'char', ['V'] = 'line', ['\22'] = 'block' })[vim.fn.mode(1)]
1387
+ MiniAlign.align_user(mode)
1388
+ vim.cmd('normal! \27')
1389
+ end
1390
+ end
1391
+
1392
+ -- Work with steps and options ------------------------------------------------
1393
+ H.is_valid_steps = function(x, x_name)
1394
+ x_name = x_name or 'config.steps'
1395
+
1396
+ if type(x) ~= 'table' then return false, string.format('`%s` should be table.', x_name) end
1397
+
1398
+ -- Validators
1399
+ local is_steps_array = function(y) return H.is_array_of(y, H.is_step) end
1400
+ local steps_array_msg = 'should be array of steps (see `:h MiniAlign.new_step()`).'
1401
+
1402
+ local is_maybe_step = function(y) return y == nil or H.is_step(y) end
1403
+ local step_msg = 'should be step (see `:h MiniAlign.new_step()`).'
1404
+
1405
+ -- Actual checks
1406
+ if not is_steps_array(x.pre_split) then return false, H.msg_bad_steps(x_name, 'pre_split', steps_array_msg) end
1407
+
1408
+ if not is_maybe_step(x.split) then return false, H.msg_bad_steps(x_name, 'split', step_msg) end
1409
+
1410
+ if not is_steps_array(x.pre_justify) then return false, H.msg_bad_steps(x_name, 'pre_justify', steps_array_msg) end
1411
+
1412
+ if not is_maybe_step(x.justify) then return false, H.msg_bad_steps(x_name, 'justify', step_msg) end
1413
+
1414
+ if not is_steps_array(x.pre_merge) then return false, H.msg_bad_steps(x_name, 'pre_merge', steps_array_msg) end
1415
+
1416
+ if not is_maybe_step(x.merge) then return false, H.msg_bad_steps(x_name, 'merge', step_msg) end
1417
+
1418
+ return true
1419
+ end
1420
+
1421
+ H.validate_steps = function(x, x_name)
1422
+ local is_valid, msg = H.is_valid_steps(x, x_name)
1423
+ if not is_valid then H.error(msg) end
1424
+ end
1425
+
1426
+ H.normalize_steps = function(steps, steps_name)
1427
+ -- Infer all defaults from module config
1428
+ -- NOTE: Don't use `tbl_deep_extend` to prefer full input arrays (if present)
1429
+ local res = vim.tbl_extend('force', H.get_config().steps, steps or {})
1430
+
1431
+ H.validate_steps(res, steps_name)
1432
+
1433
+ -- Possibly fill in default main steps
1434
+ res.split = res.split or MiniAlign.gen_step.default_split()
1435
+ res.justify = res.justify or MiniAlign.gen_step.default_justify()
1436
+ res.merge = res.merge or MiniAlign.gen_step.default_merge()
1437
+
1438
+ -- Deep copy to ensure that table values will not be affected (because if a
1439
+ -- table value is present only in one input, it is taken as is).
1440
+ return vim.deepcopy(res)
1441
+ end
1442
+
1443
+ H.normalize_opts = function(opts)
1444
+ local res = vim.tbl_deep_extend('force', H.get_config().options, opts or {})
1445
+ return vim.deepcopy(res)
1446
+ end
1447
+
1448
+ H.msg_bad_steps = function(steps_name, key, msg) return string.format('`%s.%s` %s', steps_name, key, msg) end
1449
+
1450
+ H.apply_step = function(step, arr, opts, step_container_name)
1451
+ local arr_name, predicate, suggest = 'parts', H.is_parts, ' See `:h MiniAlign.as_parts()`.'
1452
+ if not H.is_parts(arr) then
1453
+ arr_name = 'strings'
1454
+ predicate = function(x) return H.is_array_of(x, H.is_string) end
1455
+ suggest = ''
1456
+ end
1457
+
1458
+ local res = step.action(arr, opts)
1459
+
1460
+ if not predicate(arr) then
1461
+ --stylua: ignore
1462
+ local msg = string.format(
1463
+ 'Step `%s` of `%s` should preserve structure of `%s`.%s',
1464
+ step.name, step_container_name, arr_name, suggest
1465
+ )
1466
+ H.error(msg)
1467
+ end
1468
+
1469
+ return res
1470
+ end
1471
+
1472
+ -- Work with default actions --------------------------------------------------
1473
+ H.default_action_split = function(string_array, opts)
1474
+ -- Prepare options
1475
+ local pattern = opts.split_pattern
1476
+ if not (H.is_string(pattern) or H.is_array_of(pattern, H.is_string)) then
1477
+ H.error('Option `split_pattern` should be string or array of strings.')
1478
+ end
1479
+ if type(pattern) == 'string' then pattern = { pattern } end
1480
+
1481
+ local exclude_patterns = opts.split_exclude_patterns or {}
1482
+ if not H.is_array_of(exclude_patterns, H.is_string) then
1483
+ H.error('Option `split_exclude_patterns` should be array of strings.')
1484
+ end
1485
+
1486
+ local capture_exclude_regions = vim.tbl_map(function(x)
1487
+ local patt = x
1488
+ patt = x:sub(1, 1) == '^' and ('^()' .. patt:sub(2)) or ('()' .. patt)
1489
+ patt = x:sub(-1, -1) == '$' and (patt:sub(1, -2) .. '()$') or (patt .. '()')
1490
+ return patt
1491
+ end, exclude_patterns)
1492
+
1493
+ local forbidden_spans = {}
1494
+ local add_to_forbidden = function(l, r) table.insert(forbidden_spans, { l, r - 1 }) end
1495
+ local make_forbidden_spans = function(s)
1496
+ forbidden_spans = {}
1497
+ for _, capture_pat in ipairs(capture_exclude_regions) do
1498
+ s:gsub(capture_pat, add_to_forbidden)
1499
+ end
1500
+ return forbidden_spans
1501
+ end
1502
+
1503
+ -- Make splits excluding matches inside forbidden regions
1504
+ local res = vim.tbl_map(
1505
+ function(s) return H.default_action_split_string(s, pattern, make_forbidden_spans) end,
1506
+ string_array
1507
+ )
1508
+ return MiniAlign.as_parts(res)
1509
+ end
1510
+
1511
+ H.default_action_split_string = function(s, pattern_arr, make_forbidden_spans)
1512
+ -- Construct forbidden spans for string
1513
+ local forbidden_spans = make_forbidden_spans(s)
1514
+
1515
+ -- Split by recycled `pattern_arr`
1516
+ local res = {}
1517
+ local n_total, n_latest_add, n_find = s:len(), 0, 0
1518
+ local n_pair = 1
1519
+
1520
+ while true do
1521
+ local cur_split = H.slice_mod(pattern_arr, n_pair)
1522
+ local sep_left, sep_right = H.string_find(s, cur_split, n_find)
1523
+
1524
+ if sep_left == nil then
1525
+ -- Avoid adding empty string to non-empty input because it does nothing
1526
+ -- but confuses "don't add trailspace" logic
1527
+ local rest = s:sub(n_latest_add, n_total)
1528
+ if not (rest == '' and #res > 0) then table.insert(res, rest) end
1529
+ break
1530
+ end
1531
+
1532
+ local is_good = #forbidden_spans == 0
1533
+ or not H.is_any_point_inside_any_span({ sep_left, sep_right }, forbidden_spans)
1534
+ if is_good then
1535
+ table.insert(res, s:sub(n_latest_add, sep_left - 1))
1536
+ table.insert(res, s:sub(sep_left, sep_right))
1537
+ n_latest_add = sep_right + 1
1538
+ n_pair = n_pair + 1
1539
+ end
1540
+
1541
+ if (sep_right + 1) <= n_find then
1542
+ H.error(string.format('Pattern %s can not advance search.', vim.inspect(cur_split)))
1543
+ end
1544
+ n_find = sep_right + 1
1545
+ end
1546
+
1547
+ return res
1548
+ end
1549
+
1550
+ H.default_action_justify = function(parts, opts)
1551
+ -- Prepare options
1552
+ local side = opts.justify_side
1553
+ if not (H.is_justify_side(side) or H.is_array_of(side, H.is_justify_side)) then
1554
+ H.error([[Option `justify_side` should be one of 'left', 'center', 'right', 'none', or array of those.]])
1555
+ end
1556
+ if type(side) == 'string' then side = { side } end
1557
+
1558
+ local offsets = opts.justify_offsets or H.tbl_repeat(0, #parts)
1559
+
1560
+ -- Recycle `justify` array and precompute padding functions
1561
+ local dims = parts.get_dims()
1562
+ local pad_funs, side_arr = {}, {}
1563
+ for j = 1, dims.col do
1564
+ local s = H.slice_mod(side, j)
1565
+ side_arr[j] = s
1566
+ pad_funs[j] = H.pad_functions[s]
1567
+ end
1568
+
1569
+ -- Compute cell width and maximum column widths (adjusting for offsets)
1570
+ local width_col = {}
1571
+ for j = 1, dims.col do
1572
+ width_col[j] = 0
1573
+ end
1574
+
1575
+ local width = {}
1576
+ for i, row in ipairs(parts) do
1577
+ width[i] = {}
1578
+ for j, s in ipairs(row) do
1579
+ local w = vim.fn.strdisplaywidth(s)
1580
+ width[i][j] = w
1581
+
1582
+ -- Compute offset
1583
+ local off = j == 1 and offsets[i] or 0
1584
+
1585
+ -- Don't use last column in row to compute column width in case of left
1586
+ -- justification (it won't be padded so shouldn't contribute to column)
1587
+ if not (j == #row and side_arr[j] == 'left') then width_col[j] = math.max(off + w, width_col[j]) end
1588
+ end
1589
+ end
1590
+
1591
+ -- Pad cells to have same width across columns (adjusting for offsets)
1592
+ for i, row in ipairs(parts) do
1593
+ for j, s in ipairs(row) do
1594
+ local off = j == 1 and offsets[i] or 0
1595
+ local n_space = width_col[j] - width[i][j] - off
1596
+ -- Don't add trailing whitespace for last column
1597
+ parts[i][j] = pad_funs[j](s, n_space, j == #row)
1598
+ end
1599
+ end
1600
+ end
1601
+
1602
+ H.default_action_merge = function(parts, opts)
1603
+ -- Prepare options
1604
+ local delimiter = opts.merge_delimiter
1605
+ if not (H.is_string(delimiter) or H.is_array_of(delimiter, H.is_string)) then
1606
+ H.error('Option `merge_delimiter` should be string or array of strings.')
1607
+ end
1608
+ if type(delimiter) == 'string' then delimiter = { delimiter } end
1609
+
1610
+ -- Precompute combination strings (recycle `merge` array)
1611
+ local dims = parts.get_dims()
1612
+ local delim_arr = {}
1613
+ for j = 1, dims.col - 1 do
1614
+ delim_arr[j] = H.slice_mod(delimiter, j)
1615
+ end
1616
+
1617
+ -- Do not change indentation
1618
+ local first_parts_are_indent = true
1619
+ for i = 1, dims.row do
1620
+ first_parts_are_indent = first_parts_are_indent and H.is_whitespace(parts[i][1])
1621
+ end
1622
+ delim_arr[1] = (first_parts_are_indent and delim_arr[1] ~= nil) and delim_arr[1]:gsub('^%s*', '') or delim_arr[1]
1623
+
1624
+ return vim.tbl_map(function(row) return H.concat_array(row, delim_arr) end, parts)
1625
+ end
1626
+
1627
+ -- Work with modifiers --------------------------------------------------------
1628
+ H.make_filter_action = function(expr)
1629
+ if expr == nil then return nil end
1630
+ if expr == '' then expr = 'true' end
1631
+
1632
+ local is_loaded, f = pcall(function() return assert(loadstring('return ' .. expr)) end)
1633
+ if not (is_loaded and vim.is_callable(f)) then H.error(vim.inspect(expr) .. ' is not a valid filter expression.') end
1634
+
1635
+ local predicate = function(data)
1636
+ local context = setmetatable(data, { __index = _G })
1637
+ debug.setfenv(f, context)
1638
+ return f()
1639
+ end
1640
+
1641
+ return function(parts, _)
1642
+ local mask = {}
1643
+ local data = { ROW = #parts }
1644
+ for i, row in ipairs(parts) do
1645
+ data.row = i
1646
+ mask[i] = {}
1647
+ for j, s in ipairs(row) do
1648
+ data.col, data.COL = j, #row
1649
+ data.s = s
1650
+
1651
+ -- Current and total number of pairs
1652
+ data.n = math.ceil(0.5 * j)
1653
+ data.N = math.ceil(0.5 * #row)
1654
+
1655
+ mask[i][j] = predicate(data)
1656
+ end
1657
+ end
1658
+
1659
+ parts.group(mask)
1660
+ end
1661
+ end
1662
+
1663
+ -- Work with regions ----------------------------------------------------------
1664
+ ---@return boolean Whether some lines were actually set.
1665
+ ---@private
1666
+ H.process_current_region = function(lines_were_changed, mode, opts, steps)
1667
+ -- Cache current options and steps for dot-repeat
1668
+ H.cache.opts, H.cache.steps = opts, steps
1669
+
1670
+ -- Undo previously set lines
1671
+ if lines_were_changed then H.undo() end
1672
+
1673
+ -- Get current region. NOTE: use cached value to ensure that the same region
1674
+ -- is processed during preview. Otherwise there might be problems with
1675
+ -- getting "current" regions in Normal mode as necessary marks (`[` and `]`)
1676
+ -- can be not valid.
1677
+ local region = H.cache.region or H.get_current_region()
1678
+ H.cache.region = region
1679
+
1680
+ -- Enrich options
1681
+ opts.region = region
1682
+ opts.mode = mode
1683
+ opts.justify_offsets = H.tbl_repeat(0, region.to.line - region.from.line + 1)
1684
+ if mode == 'char' then
1685
+ -- Compute offset of first line for charwise selection
1686
+ local prefix = vim.fn.getline(region.from.line):sub(1, region.from.col - 1)
1687
+ opts.justify_offsets[1] = vim.fn.strdisplaywidth(prefix)
1688
+ end
1689
+
1690
+ -- Actually process region
1691
+ local strings = H.region_get_text(region, mode)
1692
+ local strings_aligned = MiniAlign.align_strings(strings, opts, steps)
1693
+ H.region_set_text(region, mode, strings_aligned)
1694
+
1695
+ -- Make sure that latest changes are shown
1696
+ vim.cmd('redraw')
1697
+
1698
+ -- Confirm that lines were actually set
1699
+ return table.concat(strings) ~= table.concat(strings_aligned)
1700
+ end
1701
+
1702
+ H.get_current_region = function()
1703
+ local from_expr, to_expr = "'[", "']"
1704
+ if H.is_visual_mode() then
1705
+ from_expr, to_expr = '.', 'v'
1706
+ end
1707
+
1708
+ -- Add offset (*_pos[4]) to allow position go past end of line
1709
+ local from_pos = vim.fn.getpos(from_expr)
1710
+ local from = { line = from_pos[2], col = from_pos[3] + from_pos[4] }
1711
+ local to_pos = vim.fn.getpos(to_expr)
1712
+ local to = { line = to_pos[2], col = to_pos[3] + to_pos[4] }
1713
+
1714
+ -- Ensure correct order
1715
+ if to.line < from.line or (to.line == from.line and to.col < from.col) then
1716
+ from, to = to, from
1717
+ end
1718
+
1719
+ return { from = from, to = to }
1720
+ end
1721
+
1722
+ H.region_get_text = function(region, mode)
1723
+ local from, to = region.from, region.to
1724
+
1725
+ if mode == 'char' then
1726
+ local to_col_offset = vim.o.selection == 'exclusive' and 1 or 0
1727
+ return H.get_text(from.line - 1, from.col - 1, to.line - 1, to.col - to_col_offset)
1728
+ end
1729
+
1730
+ if mode == 'line' then return H.get_lines(from.line - 1, to.line) end
1731
+
1732
+ if mode == 'block' then
1733
+ -- Use virtual columns to respect multibyte/wide/composing characters
1734
+ local left_virtcol, right_virtcol = H.region_virtcols(region)
1735
+
1736
+ local res, lines = {}, H.get_lines(from.line - 1, to.line)
1737
+ for i, l in ipairs(lines) do
1738
+ local lnum = from.line + i - 1
1739
+ local left_col = H.virtcol2col(lnum, left_virtcol)
1740
+ local right_col = H.virtcol2col(lnum, right_virtcol)
1741
+ right_col = right_col + H.str_utf_end(l, right_col)
1742
+
1743
+ table.insert(res, l:sub(left_col, right_col))
1744
+ end
1745
+ return res
1746
+ end
1747
+ end
1748
+
1749
+ H.region_set_text = function(region, mode, text)
1750
+ local from, to = region.from, region.to
1751
+
1752
+ if mode == 'char' then
1753
+ -- Ensure not going past last column (can happen with `$` in Visual mode)
1754
+ local to_line_n_cols = vim.fn.col({ to.line, '$' }) - 1
1755
+ local to_col = math.min(to.col, to_line_n_cols)
1756
+ local to_col_offset = vim.o.selection == 'exclusive' and 1 or 0
1757
+ H.set_text(from.line - 1, from.col - 1, to.line - 1, to_col - to_col_offset, text)
1758
+ end
1759
+
1760
+ if mode == 'line' then H.set_lines(from.line - 1, to.line, text) end
1761
+
1762
+ if mode == 'block' then
1763
+ if #text ~= (to.line - from.line + 1) then
1764
+ H.error('Number of replacement lines should fit the region in blockwise mode')
1765
+ end
1766
+
1767
+ -- Use virtual columns to respect multibyte characters
1768
+ local left_virtcol, right_virtcol = H.region_virtcols(region)
1769
+
1770
+ local lines = H.get_lines(from.line - 1, to.line)
1771
+ for i, l in ipairs(lines) do
1772
+ local lnum = from.line + i - 1
1773
+ local left_col = H.virtcol2col(lnum, left_virtcol)
1774
+ local right_col = H.virtcol2col(lnum, right_virtcol)
1775
+ right_col = right_col + H.str_utf_end(l, right_col)
1776
+
1777
+ -- Adjust columns to not go outside of line
1778
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
1779
+ left_col, right_col = math.max(left_col - 1, 0), math.min(right_col, l:len())
1780
+ H.set_text(lnum - 1, left_col, lnum - 1, right_col, { text[i] })
1781
+ end
1782
+ end
1783
+ end
1784
+
1785
+ H.region_virtcols = function(region)
1786
+ -- Account for multibyte characters and position past the line end
1787
+ local from_virtcol = H.pos_to_virtcol(region.from)
1788
+ local to_virtcol = H.pos_to_virtcol(region.to)
1789
+
1790
+ local left_virtcol, right_virtcol = math.min(from_virtcol, to_virtcol), math.max(from_virtcol, to_virtcol)
1791
+ right_virtcol = right_virtcol - (vim.o.selection == 'exclusive' and 1 or 0)
1792
+
1793
+ return left_virtcol, right_virtcol
1794
+ end
1795
+
1796
+ H.pos_to_virtcol = function(pos)
1797
+ -- Account for position past line end
1798
+ local eol_col = vim.fn.col({ pos.line, '$' })
1799
+ if eol_col < pos.col then return vim.fn.virtcol({ pos.line, '$' }) + pos.col - eol_col end
1800
+
1801
+ return vim.fn.virtcol({ pos.line, pos.col })
1802
+ end
1803
+
1804
+ -- Work with user interaction -------------------------------------------------
1805
+ H.user_modifier = function(with_preview, msg_chunks)
1806
+ -- Get from user single character modifier
1807
+ local needs_show_state = true
1808
+ local delay = (H.cache.msg_shown or with_preview) and 0 or 1000
1809
+ vim.defer_fn(function()
1810
+ if not needs_show_state then return end
1811
+
1812
+ table.insert(msg_chunks, { ' Press modifier' })
1813
+ H.echo(msg_chunks)
1814
+ H.cache.msg_shown = true
1815
+ end, delay)
1816
+ local ok, char = pcall(vim.fn.getcharstr)
1817
+ needs_show_state = false
1818
+
1819
+ -- Terminate if couldn't get input (like with <C-c>) or on `<Esc>`
1820
+ if not ok or char == '' or char == '\3' or char == '\27' then return nil end
1821
+ return char
1822
+ end
1823
+
1824
+ H.user_input = function(prompt, text)
1825
+ prompt = '(mini.align) ' .. prompt
1826
+ if _G.MiniInput ~= nil then return MiniInput.get({ prompt = prompt, scope = 'editor', init_keys = { text } }) end
1827
+
1828
+ -- Use `on_key` to distinguish cancel with `<Esc>` and immediate `<CR>`
1829
+ local was_cancelled = false
1830
+ vim.on_key(function(key) was_cancelled = was_cancelled or key == '\27' end, H.ns_id.input)
1831
+
1832
+ -- Ask for input. Use `pcall` to allow `<C-c>` to cancel user input
1833
+ vim.cmd('echohl Question')
1834
+ local ok, res = pcall(vim.fn.input, { prompt = prompt .. ': ', default = text or '' })
1835
+ vim.cmd('echohl None | echo "" | redraw')
1836
+
1837
+ vim.on_key(nil, H.ns_id.input)
1838
+ return (ok and not was_cancelled) and res or nil
1839
+ end
1840
+
1841
+ H.make_status_msg_chunks = function(opts, steps)
1842
+ local single_to_string = function(pre_steps, opts_value)
1843
+ local steps_str = ''
1844
+ if #pre_steps > 0 then
1845
+ local pre_names = vim.tbl_map(function(x) return x.name end, pre_steps)
1846
+ steps_str = string.format('(%s) ', table.concat(pre_names, ', '))
1847
+ end
1848
+ return steps_str .. vim.inspect(opts_value)
1849
+ end
1850
+
1851
+ return {
1852
+ { 'Split: ', 'ModeMsg' },
1853
+ { single_to_string(steps.pre_split, opts.split_pattern) },
1854
+ { ' | ', 'Question' },
1855
+ { 'Justify: ', 'ModeMsg' },
1856
+ { single_to_string(steps.pre_justify, opts.justify_side) },
1857
+ { ' | ', 'Question' },
1858
+ { 'Merge: ', 'ModeMsg' },
1859
+ { single_to_string(steps.pre_merge, opts.merge_delimiter) },
1860
+ { ' |', 'Question' },
1861
+ }
1862
+ end
1863
+
1864
+ -- Predicates -----------------------------------------------------------------
1865
+ H.is_array_of = function(x, predicate)
1866
+ if not H.islist(x) then return false end
1867
+ for _, v in ipairs(x) do
1868
+ if not predicate(v) then return false end
1869
+ end
1870
+ return true
1871
+ end
1872
+
1873
+ H.is_step = function(x) return type(x) == 'table' and type(x.name) == 'string' and vim.is_callable(x.action) end
1874
+
1875
+ H.is_string = function(v) return type(v) == 'string' end
1876
+
1877
+ H.is_justify_side = function(x) return x == 'left' or x == 'center' or x == 'right' or x == 'none' end
1878
+
1879
+ H.is_parts = function(x) return H.can_be_parts(x) and (getmetatable(x) or {}).class == 'parts' end
1880
+
1881
+ H.can_be_parts = function(x)
1882
+ if type(x) ~= 'table' then return false, 'should be table' end
1883
+ for i = 1, #x do
1884
+ if not H.is_array_of(x[i], H.is_string) then return false, 'values should be an array of strings' end
1885
+ end
1886
+ return true
1887
+ end
1888
+
1889
+ H.is_infinite = function(x) return x == math.huge or x == -math.huge end
1890
+
1891
+ H.is_visual_mode = function() return vim.tbl_contains({ 'v', 'V', '\22' }, vim.fn.mode(1)) end
1892
+
1893
+ H.is_whitespace = function(x) return type(x) == 'string' and x:find('^%s*$') ~= nil end
1894
+
1895
+ -- Work with get/set text -----------------------------------------------------
1896
+ H.get_text = function(start_row, start_col, end_row, end_col)
1897
+ return vim.api.nvim_buf_get_text(0, start_row, start_col, end_row, end_col, {})
1898
+ end
1899
+
1900
+ H.get_lines = function(start_row, end_row) return vim.api.nvim_buf_get_lines(0, start_row, end_row, true) end
1901
+
1902
+ --- Set text in current buffer without affecting marks
1903
+ ---@private
1904
+ H.set_text = function(start_row, start_col, end_row, end_col, replacement)
1905
+ --stylua: ignore
1906
+ local cmd = string.format(
1907
+ 'lockmarks lua vim.api.nvim_buf_set_text(0, %d, %d, %d, %d, %s)',
1908
+ start_row, start_col, end_row, end_col, vim.inspect(replacement)
1909
+ )
1910
+ vim.cmd(cmd)
1911
+ end
1912
+
1913
+ --- Set lines in current buffer without affecting marks
1914
+ ---@private
1915
+ H.set_lines = function(start_row, end_row, replacement)
1916
+ --stylua: ignore
1917
+ local cmd = string.format(
1918
+ 'lockmarks lua vim.api.nvim_buf_set_lines(0, %d, %d, true, %s)',
1919
+ start_row, end_row, vim.inspect(replacement)
1920
+ )
1921
+ vim.cmd(cmd)
1922
+ end
1923
+
1924
+ -- Utilities ------------------------------------------------------------------
1925
+ H.error = function(msg) error('(mini.align) ' .. msg, 0) end
1926
+
1927
+ H.check_type = function(name, val, ref, allow_nil)
1928
+ if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
1929
+ H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
1930
+ end
1931
+
1932
+ H.echo = function(msg, add_to_history)
1933
+ if H.get_config().silent then return end
1934
+
1935
+ -- Construct message chunks
1936
+ msg = type(msg) == 'string' and { { msg } } or msg
1937
+ table.insert(msg, 1, { '(mini.align) ', 'WarningMsg' })
1938
+
1939
+ -- Avoid hit-enter-prompt
1940
+ local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace
1941
+ local chunks, tot_width = {}, 0
1942
+ for _, ch in ipairs(msg) do
1943
+ local new_ch = { vim.fn.strcharpart(ch[1], 0, max_width - tot_width), ch[2] }
1944
+ table.insert(chunks, new_ch)
1945
+ tot_width = tot_width + vim.fn.strdisplaywidth(new_ch[1])
1946
+ if tot_width >= max_width then break end
1947
+ end
1948
+
1949
+ -- Echo. Force redraw to ensure that it is effective (`:h echo-redraw`)
1950
+ vim.cmd([[echo '' | redraw]])
1951
+ vim.api.nvim_echo(chunks, add_to_history, {})
1952
+ end
1953
+
1954
+ H.unecho = function()
1955
+ if H.cache.msg_shown then vim.cmd([[echo '' | redraw]]) end
1956
+ end
1957
+
1958
+ H.map = function(mode, lhs, rhs, opts)
1959
+ if lhs == '' then return end
1960
+ opts = vim.tbl_deep_extend('force', { silent = true }, opts or {})
1961
+ vim.keymap.set(mode, lhs, rhs, opts)
1962
+ end
1963
+
1964
+ H.slice_mod = function(x, i) return x[((i - 1) % #x) + 1] end
1965
+
1966
+ H.tbl_repeat = function(val, n)
1967
+ local res = {}
1968
+ for i = 1, n do
1969
+ res[i] = val
1970
+ end
1971
+ return res
1972
+ end
1973
+
1974
+ H.group_by_mask = function(arr, mask, direction)
1975
+ local res, cur_group = {}, {}
1976
+
1977
+ -- Construct actors based on direction
1978
+ local from, to, by = 1, #arr, 1
1979
+ local insert = function(t, v) table.insert(t, v) end
1980
+ if direction == 'right' then
1981
+ from, to, by = to, from, -1
1982
+ insert = function(t, v) table.insert(t, 1, v) end
1983
+ end
1984
+
1985
+ -- Group
1986
+ for i = from, to, by do
1987
+ insert(cur_group, arr[i])
1988
+ if mask[i] or i == to then
1989
+ insert(res, cur_group)
1990
+ cur_group = {}
1991
+ end
1992
+ end
1993
+
1994
+ return res
1995
+ end
1996
+
1997
+ H.concat_array = function(target_arr, concat_arr)
1998
+ local ext_arr = {}
1999
+ for i = 1, #target_arr - 1 do
2000
+ -- Concat non-empty cells (empty cells at this point add only extra merge)
2001
+ if target_arr[i] ~= '' then
2002
+ table.insert(ext_arr, target_arr[i])
2003
+ table.insert(ext_arr, concat_arr[i])
2004
+ end
2005
+ end
2006
+ table.insert(ext_arr, target_arr[#target_arr])
2007
+ return table.concat(ext_arr, '')
2008
+ end
2009
+
2010
+ H.string_find = function(s, pattern, init)
2011
+ init = init or 1
2012
+
2013
+ -- Match only start of full string if pattern says so.
2014
+ -- This is needed because `string.find()` doesn't do this.
2015
+ -- Example: `string.find('(aaa)', '^.*$', 4)` returns `4, 5`
2016
+ if pattern:sub(1, 1) == '^' and init > 1 then return nil end
2017
+
2018
+ -- Treat `''` as if nothing is found (treats it as "reset split"). If not
2019
+ -- altered, results in infinite loop.
2020
+ if pattern == '' then return nil end
2021
+
2022
+ return string.find(s, pattern, init)
2023
+ end
2024
+
2025
+ H.virtcol2col = function(lnum, col) return vim.fn.virtcol2col(0, lnum, col) end
2026
+ if vim.fn.has('nvim-0.10') == 0 then
2027
+ -- Neovim<0.10 has `virtcol2col` returning cell's last column instead of
2028
+ -- cell's first column in Neovim>=0.10
2029
+ H.virtcol2col = function(lnum, col)
2030
+ if vim.fn.virtcol2col(0, lnum, col) == 0 then return 0 end
2031
+ return vim.fn.virtcol2col(0, lnum, col - 1) + 1
2032
+ end
2033
+ end
2034
+
2035
+ H.str_utfindex = function(s, i) return vim.str_utfindex(s, 'utf-32', i) end
2036
+ if vim.fn.has('nvim-0.11') == 0 then H.str_utfindex = function(s, i) return (vim.str_utfindex(s, i)) end end
2037
+
2038
+ H.str_utf_end = function(s, n) return n >= s:len() and 0 or vim.str_utf_end(s, n) end
2039
+ if vim.fn.has('nvim-0.10') == 0 then
2040
+ H.str_utf_end = function(s, n) return n >= s:len() and 0 or (vim.str_byteindex(s, H.str_utfindex(s, n)) - n) end
2041
+ end
2042
+
2043
+ H.is_any_point_inside_any_span = function(points, spans)
2044
+ for _, point in ipairs(points) do
2045
+ for _, span in ipairs(spans) do
2046
+ if span[1] <= point and point <= span[2] then return true end
2047
+ end
2048
+ end
2049
+ return false
2050
+ end
2051
+
2052
+ H.undo = function()
2053
+ if H.is_visual_mode() then
2054
+ -- Can't use `u` in Visual mode because it makes all selection lowercase
2055
+ local cur_mode = vim.fn.mode(1)
2056
+ vim.cmd('silent! normal! \27')
2057
+
2058
+ -- Undo
2059
+ vim.cmd('silent! lockmarks undo')
2060
+
2061
+ -- Manually restore selection. There are issues with using restoring marks
2062
+ -- via `gv` (couldn't figure out how to reliably preserve visual mode).
2063
+ -- As this is called only if lines were set, region is cached.
2064
+ local region = H.cache.region
2065
+ vim.api.nvim_win_set_cursor(0, { region.from.line, region.from.col - 1 })
2066
+ vim.cmd('silent! normal!' .. cur_mode)
2067
+ vim.api.nvim_win_set_cursor(0, { region.to.line, region.to.col - 1 })
2068
+ else
2069
+ vim.cmd('silent! lockmarks normal! u')
2070
+ end
2071
+ end
2072
+
2073
+ -- TODO: Remove after compatibility with Neovim=0.9 is dropped
2074
+ H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
2075
+
2076
+ return MiniAlign