retrovim 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.conda/retrovim.yaml +259 -0
- package/.conda/terax.icns +0 -0
- package/.conda/terax.ico +0 -0
- package/.conda/terax.png +0 -0
- package/.github/workflows/release.yml +95 -0
- package/.gitmodules +3 -0
- package/.npm/kanata_touchcursor.js +28 -0
- package/.npm/nvim.js +26 -0
- package/.npm/yazi.js +26 -0
- package/.npm/zsh.js +17 -0
- package/.pip/kanata_touchcursor/__init__.py +28 -0
- package/.pip/nvim/__init__.py +31 -0
- package/.pip/yazi/__init__.py +31 -0
- package/.pip/zsh/__init__.py +22 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +674 -0
- package/README.md +336 -0
- package/kanata/touchcursor.kbd +164 -0
- package/nvim/init.lua +813 -0
- package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
- package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
- package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
- package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
- package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
- package/nvim/plugins/mini.nvim/.styluaignore +1 -0
- package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
- package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
- package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
- package/nvim/plugins/mini.nvim/LICENSE +21 -0
- package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
- package/nvim/plugins/mini.nvim/Makefile +51 -0
- package/nvim/plugins/mini.nvim/README.md +261 -0
- package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
- package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
- package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
- package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
- package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
- package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
- package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
- package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
- package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
- package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
- package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
- package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
- package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
- package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
- package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
- package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
- package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
- package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
- package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
- package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
- package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
- package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
- package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
- package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
- package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
- package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
- package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
- package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
- package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
- package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
- package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
- package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
- package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
- package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
- package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
- package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
- package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
- package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
- package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
- package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
- package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
- package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
- package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
- package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
- package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
- package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
- package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
- package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
- package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
- package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
- package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
- package/nvim/plugins/mini.nvim/logo.png +0 -0
- package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
- package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
- package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
- package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
- package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
- package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
- package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
- package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
- package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
- package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
- package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
- package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
- package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
- package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
- package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
- package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
- package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
- package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
- package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
- package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
- package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
- package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
- package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
- package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
- package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
- package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
- package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
- package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
- package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
- package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
- package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
- package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
- package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
- package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
- package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
- package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
- package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
- package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
- package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
- package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
- package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
- package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
- package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
- package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
- package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
- package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
- package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
- package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
- package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
- package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
- package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
- package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
- package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
- package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
- package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
- package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
- package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
- package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
- package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
- package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
- package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
- package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
- package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
- package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
- package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
- package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
- package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
- package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
- package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
- package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
- package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
- package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
- package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
- package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
- package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
- package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
- package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
- package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
- package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
- package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
- package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
- package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
- package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
- package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
- package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
- package/package.json +16 -0
- package/pyproject.toml +43 -0
- package/yazi/init.lua +12 -0
- package/yazi/keymap.toml +114 -0
- package/yazi/lesskey +5 -0
- package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
- package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
- package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
- package/yazi/theme.toml +26 -0
- package/yazi/yazi.toml +15 -0
- package/zsh/.zshrc +145 -0
- package/zsh/starship.toml +20 -0
- package/zsh/zsh-patina.toml +2 -0
|
@@ -0,0 +1,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
|