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,1371 @@
|
|
|
1
|
+
--- *mini.doc* Generate Neovim help files
|
|
2
|
+
---
|
|
3
|
+
--- MIT License Copyright (c) 2022 Evgeni Chasnovski
|
|
4
|
+
|
|
5
|
+
--- Key design ideas:
|
|
6
|
+
--- - Keep documentation next to code by writing EmmyLua-like annotation
|
|
7
|
+
--- comments. They will be parsed as is, so formatting should follow built-in
|
|
8
|
+
--- guide in |help-writing|. However, custom hooks are allowed at many
|
|
9
|
+
--- generation stages for more granular management of output help file.
|
|
10
|
+
---
|
|
11
|
+
--- - Generation is done by processing a set of ordered files line by line.
|
|
12
|
+
--- Each line can either be considered as a part of documentation block (if
|
|
13
|
+
--- it matches certain configurable pattern) or not (considered to be an
|
|
14
|
+
--- "afterline" of documentation block). See |MiniDoc.generate()| for more
|
|
15
|
+
--- details.
|
|
16
|
+
---
|
|
17
|
+
--- - Processing is done by using nested data structures (section, block, file,
|
|
18
|
+
--- doc) describing certain parts of help file. See |MiniDoc-data-structures|
|
|
19
|
+
--- for more details.
|
|
20
|
+
---
|
|
21
|
+
--- - Project specific script can be written as plain Lua file with
|
|
22
|
+
--- configuratble path. See |MiniDoc.generate()| for more details.
|
|
23
|
+
---
|
|
24
|
+
--- What it doesn't do:
|
|
25
|
+
--- - It doesn't support markdown or other markup language inside annotations.
|
|
26
|
+
--- - It doesn't use treesitter in favor of Lua string manipulation for basic
|
|
27
|
+
--- tasks (parsing annotations, formatting, auto-generating tags, etc.). This
|
|
28
|
+
--- is done to manage complexity and be dependency free.
|
|
29
|
+
---
|
|
30
|
+
--- # Setup ~
|
|
31
|
+
---
|
|
32
|
+
--- This module needs a setup with `require('mini.doc').setup({})` (replace
|
|
33
|
+
--- `{}` with your `config` table). It will create global Lua table `MiniDoc`
|
|
34
|
+
--- which you can use for scripting or manually (with `:lua MiniDoc.*`).
|
|
35
|
+
---
|
|
36
|
+
--- See |MiniDoc.config| for available config settings.
|
|
37
|
+
---
|
|
38
|
+
--- You can override runtime config settings locally to buffer inside
|
|
39
|
+
--- `vim.b.minidoc_config` which should have same structure as `MiniDoc.config`.
|
|
40
|
+
--- See |mini.nvim-buffer-local-config| for more details.
|
|
41
|
+
---
|
|
42
|
+
--- To stop module from showing non-error feedback, set `config.silent = true`.
|
|
43
|
+
---
|
|
44
|
+
--- # Tips ~
|
|
45
|
+
---
|
|
46
|
+
--- - Some settings tips that might make writing annotation comments easier:
|
|
47
|
+
--- - Set up appropriate |'comments'| for `lua` file type to respect
|
|
48
|
+
--- EmmyLua-like's `---` comment leader. Value `:---,:--` seems to work.
|
|
49
|
+
--- - Set up appropriate |'formatoptions'| (see also |fo-table|). Consider
|
|
50
|
+
--- adding `j`, `n`, `q`, and `r` flags.
|
|
51
|
+
--- - Set up appropriate |'formatlistpat'| to help auto-formatting lists (if
|
|
52
|
+
--- `n` flag is added to |'formatoptions'|). One suggestion (not entirely
|
|
53
|
+
--- ideal) is a value `^\s*[0-9\-\+\*]\+[\.\)]*\s\+`. This reads as "at
|
|
54
|
+
--- least one special character (digit, `-`, `+`, `*`) possibly followed
|
|
55
|
+
--- by some punctuation (`.` or `)`) followed by at least one space is a
|
|
56
|
+
--- start of list item".
|
|
57
|
+
--- - Probably one of the most reliable resources for what is considered to be
|
|
58
|
+
--- best practice when using this module is this whole plugin. Look at source
|
|
59
|
+
--- code for the reference.
|
|
60
|
+
---
|
|
61
|
+
--- # Comparisons ~
|
|
62
|
+
---
|
|
63
|
+
--- - [tjdevries/tree-sitter-lua](https://github.com/tjdevries/tree-sitter-lua):
|
|
64
|
+
--- - Its key design is to use treesitter grammar to parse both Lua code
|
|
65
|
+
--- and annotation comments. This makes it not easy to install,
|
|
66
|
+
--- customize, and support.
|
|
67
|
+
--- - It takes more care about automating output formatting (like auto
|
|
68
|
+
--- indentation and line width fit). This plugin leans more to manual
|
|
69
|
+
--- formatting with option to supply customized post-processing hooks.
|
|
70
|
+
---@tag MiniDoc
|
|
71
|
+
|
|
72
|
+
--- Data structures
|
|
73
|
+
---
|
|
74
|
+
--- Data structures are basically arrays of other structures accompanied with
|
|
75
|
+
--- some fields (keys with data values) and methods (keys with function
|
|
76
|
+
--- values):
|
|
77
|
+
--- - `Section structure` is an array of string lines describing one aspect
|
|
78
|
+
--- (determined by section id like `@param`, `@return`, `@text`) of an
|
|
79
|
+
--- annotation subject. All lines will be used directly in help file.
|
|
80
|
+
--- - `Block structure` is an array of sections describing one annotation
|
|
81
|
+
--- subject like function, table, concept.
|
|
82
|
+
--- - `File structure` is an array of blocks describing certain file on disk.
|
|
83
|
+
--- Basically, file is split into consecutive blocks: annotation lines go
|
|
84
|
+
--- inside block, non-annotation - inside `block_afterlines` element of info.
|
|
85
|
+
--- - `Doc structure` is an array of files describing a final help file. Each
|
|
86
|
+
--- string line from section (when traversed in depth-first fashion) goes
|
|
87
|
+
--- directly into output file.
|
|
88
|
+
---
|
|
89
|
+
--- All structures have these keys:
|
|
90
|
+
--- - Fields:
|
|
91
|
+
--- - `info` - contains additional information about current structure.
|
|
92
|
+
--- For more details see next section.
|
|
93
|
+
--- - `parent` - table of parent structure (if exists).
|
|
94
|
+
--- - `parent_index` - index of this structure in its parent's array. Useful
|
|
95
|
+
--- for adding to parent another structure near current one.
|
|
96
|
+
--- - `type` - string with structure type (section, block, file, doc).
|
|
97
|
+
--- - Methods (use them as `x:method(args)`):
|
|
98
|
+
--- - `insert(self, [index,] child)` - insert `child` to `self` at position
|
|
99
|
+
--- `index` (optional; if not supplied, child will be appended to end).
|
|
100
|
+
--- Basically, a `table.insert()`, but adds `parent` and `parent_index`
|
|
101
|
+
--- fields to `child` while properly updating `self`.
|
|
102
|
+
--- - `remove(self [,index])` - remove from `self` element at position
|
|
103
|
+
--- `index`. Basically, a `table.remove()`, but properly updates `self`.
|
|
104
|
+
--- - `has_descendant(self, predicate)` - whether there is a descendant
|
|
105
|
+
--- (structure or string) for which `predicate` returns `true`. In case of
|
|
106
|
+
--- success also returns the first such descendant as second value.
|
|
107
|
+
--- - `has_lines(self)` - whether structure has any lines (even empty ones)
|
|
108
|
+
--- to be put in output file. For section structures this is equivalent to
|
|
109
|
+
--- `#self`, but more useful for higher order structures.
|
|
110
|
+
--- - `clear_lines(self)` - remove all lines from structure. As a result,
|
|
111
|
+
--- this structure won't contribute to output help file.
|
|
112
|
+
---
|
|
113
|
+
--- Description of `info` fields per structure type:
|
|
114
|
+
--- - `Section`:
|
|
115
|
+
--- - `id` - captured section identifier. Can be empty string meaning no
|
|
116
|
+
--- identifier is captured.
|
|
117
|
+
--- - `line_begin` - line number inside file at which section begins (-1 if
|
|
118
|
+
--- not generated from file).
|
|
119
|
+
--- - `line_end` - line number inside file at which section ends (-1 if not
|
|
120
|
+
--- generated from file).
|
|
121
|
+
--- - `Block`:
|
|
122
|
+
--- - `afterlines` - array of strings which were parsed from file after
|
|
123
|
+
--- this annotation block (up until the next block or end of file).
|
|
124
|
+
--- Useful for making automated decisions about what is being documented.
|
|
125
|
+
--- - `line_begin` - line number inside file at which block begins (-1 if
|
|
126
|
+
--- not generated from file).
|
|
127
|
+
--- - `line_end` - line number inside file at which block ends (-1 if not
|
|
128
|
+
--- generated from file).
|
|
129
|
+
--- - `File`:
|
|
130
|
+
--- - `path` - absolute path to a file (`''` if not generated from file).
|
|
131
|
+
--- - `Doc`:
|
|
132
|
+
--- - `input` - array of input file paths (as in |MiniDoc.generate()|).
|
|
133
|
+
--- - `output` - output path (as in |MiniDoc.generate()|).
|
|
134
|
+
--- - `config` - configuration used (as in |MiniDoc.generate()|).
|
|
135
|
+
---@tag MiniDoc-data-structures
|
|
136
|
+
|
|
137
|
+
-- Module definition ==========================================================
|
|
138
|
+
local MiniDoc = {}
|
|
139
|
+
local H = {}
|
|
140
|
+
|
|
141
|
+
--- Module setup
|
|
142
|
+
---
|
|
143
|
+
---@param config table|nil Module config table. See |MiniDoc.config|.
|
|
144
|
+
---
|
|
145
|
+
---@usage >lua
|
|
146
|
+
--- require('mini.doc').setup() -- use default config
|
|
147
|
+
--- -- OR
|
|
148
|
+
--- require('mini.doc').setup({}) -- replace {} with your config table
|
|
149
|
+
--- <
|
|
150
|
+
MiniDoc.setup = function(config)
|
|
151
|
+
-- TODO: Remove after Neovim=0.9 support is dropped
|
|
152
|
+
if vim.fn.has('nvim-0.10') == 0 then
|
|
153
|
+
vim.notify(
|
|
154
|
+
'(mini.doc) Neovim<0.10 is soft deprecated (module works but is not supported).'
|
|
155
|
+
.. " It will be deprecated after the next 'mini.nvim' release (module might not work)."
|
|
156
|
+
.. ' Please update your Neovim version.'
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
-- Export module
|
|
161
|
+
_G.MiniDoc = MiniDoc
|
|
162
|
+
|
|
163
|
+
-- Setup config
|
|
164
|
+
config = H.setup_config(config)
|
|
165
|
+
|
|
166
|
+
-- Apply config
|
|
167
|
+
H.apply_config(config)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
--- Defaults ~
|
|
171
|
+
---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
172
|
+
---@text # Notes ~
|
|
173
|
+
---
|
|
174
|
+
--- - `annotation_extractor` takes single string line as input. Output
|
|
175
|
+
--- describes what makes an input to be an annotation (if anything). It
|
|
176
|
+
--- should be similar to `string.find` with one capture group: start and end
|
|
177
|
+
--- of annotation indicator (whole part will be removed from help line) with
|
|
178
|
+
--- third value being string of section id (if input describes first line of
|
|
179
|
+
--- section; `nil` or empty string otherwise). Output should be `nil` if line
|
|
180
|
+
--- is not part of annotation.
|
|
181
|
+
--- Default value means that annotation line should:
|
|
182
|
+
--- - Start with `---` at first column.
|
|
183
|
+
--- - Any non-whitespace after `---` will be treated as new section id.
|
|
184
|
+
--- - Single whitespace at the start of main text will be ignored.
|
|
185
|
+
--- - Hooks are expected to be functions. Their default values might do many
|
|
186
|
+
--- things which might change over time, so for more information please look
|
|
187
|
+
--- at source code. Some more information can be found in
|
|
188
|
+
--- |MiniDoc.default_hooks|.
|
|
189
|
+
MiniDoc.config = {
|
|
190
|
+
-- Function which extracts part of line used to denote annotation.
|
|
191
|
+
-- For more information see 'Notes' in |MiniDoc.config|.
|
|
192
|
+
annotation_extractor = function(l) return string.find(l, '^%-%-%-(%S*) ?') end,
|
|
193
|
+
|
|
194
|
+
-- Identifier of block annotation lines until first captured identifier
|
|
195
|
+
default_section_id = '@text',
|
|
196
|
+
|
|
197
|
+
-- Hooks to be applied at certain stage of document life cycle. Should
|
|
198
|
+
-- modify its input in place (and not return new one).
|
|
199
|
+
hooks = {
|
|
200
|
+
-- Applied to block before anything else
|
|
201
|
+
--minidoc_replace_start block_pre = --<function: infers header sections (tag and/or signature)>,
|
|
202
|
+
block_pre = function(b)
|
|
203
|
+
-- Infer metadata based on afterlines
|
|
204
|
+
if b:has_lines() and #b.info.afterlines > 0 then H.infer_header(b) end
|
|
205
|
+
end,
|
|
206
|
+
--minidoc_replace_end
|
|
207
|
+
|
|
208
|
+
-- Applied to section before anything else
|
|
209
|
+
--minidoc_replace_start section_pre = --<function: replaces current aliases>,
|
|
210
|
+
section_pre = function(s) H.alias_replace(s) end,
|
|
211
|
+
--minidoc_replace_end
|
|
212
|
+
|
|
213
|
+
-- Applied if section has specified captured id
|
|
214
|
+
sections = {
|
|
215
|
+
--minidoc_replace_start ['@alias'] = --<function: registers alias in MiniDoc.current.aliases>,
|
|
216
|
+
['@alias'] = function(s)
|
|
217
|
+
H.alias_register(s)
|
|
218
|
+
-- NOTE: don't use `s.parent:remove(s.parent_index)` here because it
|
|
219
|
+
-- disrupts iteration over block's section during hook application
|
|
220
|
+
-- (skips next section).
|
|
221
|
+
s:clear_lines()
|
|
222
|
+
end,
|
|
223
|
+
--minidoc_replace_end
|
|
224
|
+
--minidoc_replace_start ['@class'] = --<function>,
|
|
225
|
+
['@class'] = function(s)
|
|
226
|
+
H.enclose_var_name(s)
|
|
227
|
+
H.add_section_heading(s, 'Class')
|
|
228
|
+
end,
|
|
229
|
+
--minidoc_replace_end
|
|
230
|
+
--minidoc_replace_start ['@diagnostic'] = --<function: ignores any section content>,
|
|
231
|
+
['@diagnostic'] = function(s) s:clear_lines() end,
|
|
232
|
+
--minidoc_replace_end
|
|
233
|
+
-- For most typical usage see |MiniDoc.afterlines_to_code|
|
|
234
|
+
--minidoc_replace_start ['@eval'] = --<function: evaluates lines; replaces with their return>,
|
|
235
|
+
['@eval'] = function(s)
|
|
236
|
+
local src = table.concat(s, '\n')
|
|
237
|
+
local is_loaded, code = pcall(function() return assert(loadstring(src)) end)
|
|
238
|
+
local output
|
|
239
|
+
if is_loaded then
|
|
240
|
+
MiniDoc.current.eval_section = s
|
|
241
|
+
output = code()
|
|
242
|
+
MiniDoc.current.eval_section = nil
|
|
243
|
+
else
|
|
244
|
+
output = 'MINIDOC ERROR. Parsing Lua code gave the following error:\n' .. code
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
s:clear_lines()
|
|
248
|
+
|
|
249
|
+
if output == nil then return end
|
|
250
|
+
if type(output) == 'string' then output = vim.split(output, '\n') end
|
|
251
|
+
if type(output) ~= 'table' then
|
|
252
|
+
s[1] = 'MINIDOC ERROR. Returned value should be `nil`, `string`, or `table`.'
|
|
253
|
+
return
|
|
254
|
+
end
|
|
255
|
+
for _, x in ipairs(output) do
|
|
256
|
+
s:insert(x)
|
|
257
|
+
end
|
|
258
|
+
end,
|
|
259
|
+
--minidoc_replace_end
|
|
260
|
+
--minidoc_replace_start ['@field'] = --<function>,
|
|
261
|
+
['@field'] = function(s)
|
|
262
|
+
H.mark_optional(s)
|
|
263
|
+
H.enclose_var_name(s)
|
|
264
|
+
local col_past_var_name = s[1]:match('^%s*%S+%s+`%(optional%)`()') or s[1]:match('^%s*%S+()') or 1
|
|
265
|
+
H.enclose_type(s, col_past_var_name)
|
|
266
|
+
end,
|
|
267
|
+
--minidoc_replace_end
|
|
268
|
+
--minidoc_replace_start ['@overload'] = --<function>,
|
|
269
|
+
['@overload'] = function(s)
|
|
270
|
+
s[1] = '`' .. s[1] .. '`'
|
|
271
|
+
H.add_section_heading(s, 'Overload')
|
|
272
|
+
end,
|
|
273
|
+
--minidoc_replace_end
|
|
274
|
+
--minidoc_replace_start ['@param'] = --<function>,
|
|
275
|
+
['@param'] = function(s)
|
|
276
|
+
H.mark_optional(s)
|
|
277
|
+
H.enclose_var_name(s)
|
|
278
|
+
local col_past_var_name = s[1]:match('^%s*%S+%s+`%(optional%)`()') or s[1]:match('^%s*%S+()') or 1
|
|
279
|
+
H.enclose_type(s, col_past_var_name)
|
|
280
|
+
end,
|
|
281
|
+
--minidoc_replace_end
|
|
282
|
+
--minidoc_replace_start ['@private'] = --<function: registers block for removal>,
|
|
283
|
+
['@private'] = function(s) s.parent:clear_lines() end,
|
|
284
|
+
--minidoc_replace_end
|
|
285
|
+
--minidoc_replace_start ['@return'] = --<function>,
|
|
286
|
+
['@return'] = function(s)
|
|
287
|
+
H.mark_optional(s)
|
|
288
|
+
H.enclose_type(s, 1)
|
|
289
|
+
H.add_section_heading(s, 'Return')
|
|
290
|
+
end,
|
|
291
|
+
--minidoc_replace_end
|
|
292
|
+
--minidoc_replace_start ['@seealso'] = --<function>,
|
|
293
|
+
['@seealso'] = function(s) H.add_section_heading(s, 'See also') end,
|
|
294
|
+
--minidoc_replace_end
|
|
295
|
+
--minidoc_replace_start ['@signature'] = --<function: formats signature of documented object>,
|
|
296
|
+
['@signature'] = function(s)
|
|
297
|
+
for i, _ in ipairs(s) do
|
|
298
|
+
-- Add extra formatting to make it stand out
|
|
299
|
+
s[i] = H.format_signature(s[i])
|
|
300
|
+
|
|
301
|
+
-- Align accounting for concealed characters
|
|
302
|
+
s[i] = H.align_text(s[i], 78, 'center')
|
|
303
|
+
end
|
|
304
|
+
end,
|
|
305
|
+
--minidoc_replace_end
|
|
306
|
+
--minidoc_replace_start ['@tag'] = --<function: turns its line in proper tag lines>,
|
|
307
|
+
['@tag'] = function(s)
|
|
308
|
+
for i, _ in ipairs(s) do
|
|
309
|
+
-- Enclose every word in `*`
|
|
310
|
+
s[i] = s[i]:gsub('(%S+)', '%*%1%*')
|
|
311
|
+
|
|
312
|
+
-- Align to right edge accounting for concealed characters
|
|
313
|
+
s[i] = H.align_text(s[i], 78, 'right')
|
|
314
|
+
end
|
|
315
|
+
end,
|
|
316
|
+
--minidoc_replace_end
|
|
317
|
+
--minidoc_replace_start ['@text'] = --<function: purposefully does nothing>,
|
|
318
|
+
['@text'] = function() end,
|
|
319
|
+
--minidoc_replace_end
|
|
320
|
+
--minidoc_replace_start ['@toc'] = --<function: clears all section lines>,
|
|
321
|
+
['@toc'] = function(s) s:clear_lines() end,
|
|
322
|
+
--minidoc_replace_end
|
|
323
|
+
--minidoc_replace_start ['@toc_entry'] = --<function: registers lines for table of contents>,
|
|
324
|
+
['@toc_entry'] = function(s) H.toc_register(s) end,
|
|
325
|
+
--minidoc_replace_end
|
|
326
|
+
--minidoc_replace_start ['@type'] = --<function>,
|
|
327
|
+
['@type'] = function(s)
|
|
328
|
+
H.enclose_type(s, 1)
|
|
329
|
+
H.add_section_heading(s, 'Type')
|
|
330
|
+
end,
|
|
331
|
+
--minidoc_replace_end
|
|
332
|
+
--minidoc_replace_start ['@usage'] = --<function>,
|
|
333
|
+
['@usage'] = function(s) H.add_section_heading(s, 'Usage') end,
|
|
334
|
+
--minidoc_replace_end
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
-- Applied to section after all previous steps
|
|
338
|
+
--minidoc_replace_start section_post = --<function: currently does nothing>,
|
|
339
|
+
section_post = function(s) end,
|
|
340
|
+
--minidoc_replace_end
|
|
341
|
+
|
|
342
|
+
-- Applied to block after all previous steps
|
|
343
|
+
--minidoc_replace_start block_post = --<function: does many things>,
|
|
344
|
+
block_post = function(b)
|
|
345
|
+
if not b:has_lines() then return end
|
|
346
|
+
|
|
347
|
+
local found_param, found_field = false, false
|
|
348
|
+
local n_tag_sections, last_line = 0, nil
|
|
349
|
+
H.apply_recursively(function(x)
|
|
350
|
+
if not (type(x) == 'table' and x.type == 'section') then return end
|
|
351
|
+
|
|
352
|
+
-- Add headings before first occurrence of a section which type usually
|
|
353
|
+
-- appear several times
|
|
354
|
+
if not found_param and x.info.id == '@param' then
|
|
355
|
+
H.add_section_heading(x, 'Parameters')
|
|
356
|
+
found_param = true
|
|
357
|
+
end
|
|
358
|
+
if not found_field and x.info.id == '@field' then
|
|
359
|
+
H.add_section_heading(x, 'Fields')
|
|
360
|
+
found_field = true
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
if x.info.id == '@tag' then
|
|
364
|
+
x.parent:remove(x.parent_index)
|
|
365
|
+
n_tag_sections = n_tag_sections + 1
|
|
366
|
+
x.parent:insert(n_tag_sections, x)
|
|
367
|
+
elseif type(x[#x]) == 'string' then
|
|
368
|
+
last_line = x[#x]
|
|
369
|
+
end
|
|
370
|
+
end, b)
|
|
371
|
+
|
|
372
|
+
b:insert(1, H.as_struct({ string.rep('-', 78) }, 'section'))
|
|
373
|
+
-- Append empty line only if last line is not visibly blank (closing code
|
|
374
|
+
-- block with "<" is concealed)
|
|
375
|
+
if string.find(last_line, '^<?%s*$') == nil then b:insert(H.as_struct({ '' }, 'section')) end
|
|
376
|
+
end,
|
|
377
|
+
--minidoc_replace_end
|
|
378
|
+
|
|
379
|
+
-- Applied to file after all previous steps
|
|
380
|
+
--minidoc_replace_start file = --<function: adds separator>,
|
|
381
|
+
file = function(f)
|
|
382
|
+
if not f:has_lines() then return end
|
|
383
|
+
|
|
384
|
+
f:insert(1, H.as_struct({ H.as_struct({ string.rep('=', 78) }, 'section') }, 'block'))
|
|
385
|
+
f:insert(H.as_struct({ H.as_struct({ '' }, 'section') }, 'block'))
|
|
386
|
+
end,
|
|
387
|
+
--minidoc_replace_end
|
|
388
|
+
|
|
389
|
+
-- Applied to doc after all previous steps
|
|
390
|
+
--minidoc_replace_start doc = --<function: adds modeline>,
|
|
391
|
+
doc = function(d)
|
|
392
|
+
-- Render table of contents
|
|
393
|
+
H.apply_recursively(function(x)
|
|
394
|
+
if not (type(x) == 'table' and x.type == 'section' and x.info.id == '@toc') then return end
|
|
395
|
+
H.toc_insert(x)
|
|
396
|
+
end, d)
|
|
397
|
+
|
|
398
|
+
-- Insert modeline
|
|
399
|
+
d:insert(
|
|
400
|
+
H.as_struct(
|
|
401
|
+
{ H.as_struct({ H.as_struct({ ' vim:tw=78:ts=8:noet:ft=help:norl:' }, 'section') }, 'block') },
|
|
402
|
+
'file'
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
end,
|
|
406
|
+
--minidoc_replace_end
|
|
407
|
+
|
|
408
|
+
-- Applied before output file is written. Takes lines array as argument.
|
|
409
|
+
--minidoc_replace_start write_pre = --<function: removes delimiters at the top>,
|
|
410
|
+
write_pre = function(l)
|
|
411
|
+
-- Remove first two lines with `======` and `------` delimiters to comply
|
|
412
|
+
-- with `:h local-additions` template
|
|
413
|
+
if l[1]:find('^=+$') ~= nil then table.remove(l, 1) end
|
|
414
|
+
if l[1]:find('^-+$') ~= nil then table.remove(l, 1) end
|
|
415
|
+
return l
|
|
416
|
+
end,
|
|
417
|
+
--minidoc_replace_end
|
|
418
|
+
|
|
419
|
+
-- Applied after output help file is written. Takes doc as argument.
|
|
420
|
+
--minidoc_replace_start write_post = --<function: various convenience actions>,
|
|
421
|
+
write_post = function(d)
|
|
422
|
+
local output = d.info.output
|
|
423
|
+
|
|
424
|
+
-- Generate help tags for directory of output file
|
|
425
|
+
vim.cmd('helptags ' .. vim.fn.fnamemodify(output, ':h'))
|
|
426
|
+
|
|
427
|
+
-- Reload buffer with output file (helps during writing annotations)
|
|
428
|
+
local output_path = H.full_path(output)
|
|
429
|
+
for _, buf_id in ipairs(vim.api.nvim_list_bufs()) do
|
|
430
|
+
local buf_path = H.full_path(vim.api.nvim_buf_get_name(buf_id))
|
|
431
|
+
if buf_path == output_path then
|
|
432
|
+
vim.api.nvim_buf_call(buf_id, function() vim.cmd('noautocmd silent edit | set ft=help') end)
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
-- Notify
|
|
437
|
+
local msg = ('Help file %s is successfully generated.'):format(vim.inspect(output))
|
|
438
|
+
vim.notify(msg, vim.log.levels.INFO)
|
|
439
|
+
end,
|
|
440
|
+
--minidoc_replace_end
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
-- Path (relative to current directory) to script which handles project
|
|
444
|
+
-- specific help file generation (like custom input files, hooks, etc.).
|
|
445
|
+
script_path = 'scripts/minidoc.lua',
|
|
446
|
+
|
|
447
|
+
-- Whether to disable showing non-error feedback
|
|
448
|
+
silent = false,
|
|
449
|
+
}
|
|
450
|
+
--minidoc_afterlines_end
|
|
451
|
+
|
|
452
|
+
-- Module data ================================================================
|
|
453
|
+
--- Table with information about current state of auto-generation
|
|
454
|
+
---
|
|
455
|
+
--- It is reset at the beginning and end of `MiniDoc.generate()`.
|
|
456
|
+
---
|
|
457
|
+
--- At least these keys are supported:
|
|
458
|
+
--- - {aliases} - table with keys being alias name and values - alias
|
|
459
|
+
--- description and single string (using `\n` to separate lines).
|
|
460
|
+
--- - {eval_section} - input section of `@eval` section hook. Can be used for
|
|
461
|
+
--- information about current block, etc.
|
|
462
|
+
--- - {toc} - array with table of contents entries. Each entry is a whole
|
|
463
|
+
--- `@toc_entry` section.
|
|
464
|
+
MiniDoc.current = { aliases = {}, toc = {} }
|
|
465
|
+
|
|
466
|
+
--- Default hooks
|
|
467
|
+
---
|
|
468
|
+
--- This is default value of `MiniDoc.config.hooks`. Use it if only a little
|
|
469
|
+
--- tweak is needed.
|
|
470
|
+
---
|
|
471
|
+
--- Some more insight about their behavior:
|
|
472
|
+
--- - Default inference of documented object metadata (tag and object signature
|
|
473
|
+
--- at the moment) is done in `block_pre`. Inference is based on string
|
|
474
|
+
--- pattern matching, so can lead to false results, although works in most
|
|
475
|
+
--- cases. It intentionally works only if first line after block has no
|
|
476
|
+
--- indentation and contains all necessary information to determine if
|
|
477
|
+
--- inference should happen.
|
|
478
|
+
--- - Hooks for sections describing some "variable-like" object (`@class`,
|
|
479
|
+
--- `@field`, `@param`) automatically enclose first word in `{}`.
|
|
480
|
+
--- - Hooks for sections which supposed to have "type-like" data (`@field`,
|
|
481
|
+
--- `@param`, `@return`, `@type`) automatically enclose FIRST FOUND type-like
|
|
482
|
+
--- word and its neighbor characters in `(<type>)` (expect false positives).
|
|
483
|
+
--- Algorithm is far from being 100% correct, but seems to work with present
|
|
484
|
+
--- allowed type annotation. For allowed types see
|
|
485
|
+
--- https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#types-and-type
|
|
486
|
+
--- or, better yet, look in source code of this module.
|
|
487
|
+
--- - Automated creation of table of contents (TOC) is done in the following way:
|
|
488
|
+
--- - Put section with `@toc_entry` id in the annotation block. Section's
|
|
489
|
+
--- lines will be registered as TOC entry.
|
|
490
|
+
--- - Put `@toc` section where you want to insert rendered table of
|
|
491
|
+
--- contents. TOC entries will be inserted on the left, references for
|
|
492
|
+
--- their respective tag section (only first, if present) on the right.
|
|
493
|
+
--- Render is done in default `doc` hook (because it should be done after
|
|
494
|
+
--- processing all files).
|
|
495
|
+
--- - The `write_post` hook executes some actions convenient for iterative
|
|
496
|
+
--- annotations writing:
|
|
497
|
+
--- - Generate `:helptags` for directory containing output file.
|
|
498
|
+
--- - Silently reload buffer containing output file (if such exists).
|
|
499
|
+
--- - Display notification message about result.
|
|
500
|
+
MiniDoc.default_hooks = MiniDoc.config.hooks
|
|
501
|
+
|
|
502
|
+
-- Module functionality =======================================================
|
|
503
|
+
--- Generate help file
|
|
504
|
+
---
|
|
505
|
+
--- # Algorithm ~
|
|
506
|
+
---
|
|
507
|
+
--- - Main parameters for help generation are an array of input file paths and
|
|
508
|
+
--- path to output help file.
|
|
509
|
+
--- - Parse all inputs:
|
|
510
|
+
--- - For each file, lines are processed top to bottom in order to create an
|
|
511
|
+
--- array of documentation blocks. Each line is tested whether it is an
|
|
512
|
+
--- annotation by applying `MiniDoc.config.annotation_extractor`: if
|
|
513
|
+
--- anything is extracted, it is considered to be an annotation. Annotation
|
|
514
|
+
--- line goes to "current block" after removing extracted annotation
|
|
515
|
+
--- indicator, otherwise - to afterlines of "current block".
|
|
516
|
+
--- - Each block's annotation lines are processed top to bottom. If line had
|
|
517
|
+
--- captured section id, it is a first line of "current section" (first
|
|
518
|
+
--- block lines are allowed to not specify section id; by default it is
|
|
519
|
+
--- `@text`). All subsequent lines without captured section id go into
|
|
520
|
+
--- "current section".
|
|
521
|
+
--- - Apply structure hooks (they should modify its input in place, which is
|
|
522
|
+
--- possible due to "table nature" of all inputs):
|
|
523
|
+
--- - Each block is processed by `MiniDoc.config.hooks.block_pre`. This is a
|
|
524
|
+
--- designated step for auto-generation of sections from described
|
|
525
|
+
--- annotation subject (like sections with id `@tag`, `@type`).
|
|
526
|
+
--- - Each section is processed by `MiniDoc.config.hooks.section_pre`.
|
|
527
|
+
--- - Each section is processed by corresponding
|
|
528
|
+
--- `MiniDoc.config.hooks.sections` function (table key equals to section
|
|
529
|
+
--- id). This is a step where most of formatting should happen (like
|
|
530
|
+
--- wrap first word of `@param` section with `{` and `}`, append empty
|
|
531
|
+
--- line to section, etc.).
|
|
532
|
+
--- - Each section is processed by `MiniDoc.config.hooks.section_post`.
|
|
533
|
+
--- - Each block is processed by `MiniDoc.config.hooks.block_post`. This is
|
|
534
|
+
--- a step for processing block after formatting is done (like add first
|
|
535
|
+
--- line with `----` delimiter).
|
|
536
|
+
--- - Each file is processed by `MiniDoc.config.hooks.file`. This is a step
|
|
537
|
+
--- for adding any file-related data (like add first line with `====`
|
|
538
|
+
--- delimiter).
|
|
539
|
+
--- - Doc is processed by `MiniDoc.config.hooks.doc`. This is a step for
|
|
540
|
+
--- adding any helpfile-related data (maybe like table of contents).
|
|
541
|
+
--- - Collect all strings from sections in depth-first fashion (equivalent to
|
|
542
|
+
--- nested "for all files -> for all blocks -> for all sections -> for all
|
|
543
|
+
--- strings -> add string to output"). Strings can have `\n` character
|
|
544
|
+
--- indicating start of new line.
|
|
545
|
+
--- - Modify collected strings with `MiniDoc.config.write_pre`. Takes strings
|
|
546
|
+
--- from previous step as input and should return array of strings.
|
|
547
|
+
--- - Write modified strings to output file.
|
|
548
|
+
--- - Execute `MiniDoc.config.write_post` hook. This is useful for showing some
|
|
549
|
+
--- feedback and making actions involving newly updated help file (like
|
|
550
|
+
--- generate tags, etc.).
|
|
551
|
+
---
|
|
552
|
+
--- # Project specific script ~
|
|
553
|
+
---
|
|
554
|
+
--- If all arguments have default `nil` values, first there is an attempt to
|
|
555
|
+
--- source project specific script. This is basically a
|
|
556
|
+
--- `luafile <MiniDoc.config.script_path>` with current Lua runtime while caching
|
|
557
|
+
--- and restoring current `MiniDoc.config`. Its successful execution stops any
|
|
558
|
+
--- further generation actions while error means proceeding generation as if no
|
|
559
|
+
--- script was found.
|
|
560
|
+
---
|
|
561
|
+
--- Typical script content might include definition of custom hooks, input and
|
|
562
|
+
--- output files with eventual call to `require('mini.doc').generate()` (with
|
|
563
|
+
--- or without arguments).
|
|
564
|
+
---
|
|
565
|
+
---@param input table|nil Array of file paths which will be processed in supplied
|
|
566
|
+
--- order. Default: all `.lua` files from current directory following by all
|
|
567
|
+
--- such files in these subdirectories: `lua/`, `after/`, `colors/`. Note:
|
|
568
|
+
--- any `init.lua` file is placed before other files from the same directory.
|
|
569
|
+
---@param output string|nil Path for output help file. Default:
|
|
570
|
+
--- `doc/<current-directory>.txt` (designed to be used for generating help
|
|
571
|
+
--- file for plugin).
|
|
572
|
+
---@param config table|nil Configuration overriding parts of |MiniDoc.config|.
|
|
573
|
+
---
|
|
574
|
+
---@return table Document structure which was generated and used for output
|
|
575
|
+
--- help file. In case `MiniDoc.config.script_path` was successfully used,
|
|
576
|
+
--- this is a return from the latest call of this function.
|
|
577
|
+
MiniDoc.generate = function(input, output, config)
|
|
578
|
+
-- Try sourcing project specific script first
|
|
579
|
+
local success = H.execute_project_script(input, output, config)
|
|
580
|
+
if success then return H.generate_recent_output end
|
|
581
|
+
|
|
582
|
+
input = input or H.default_input()
|
|
583
|
+
output = output or H.default_output()
|
|
584
|
+
config = H.get_config(config)
|
|
585
|
+
|
|
586
|
+
-- Prepare table for current information
|
|
587
|
+
MiniDoc.current = {}
|
|
588
|
+
|
|
589
|
+
-- Parse input files
|
|
590
|
+
local doc = H.new_struct('doc', { input = input, output = output, config = config })
|
|
591
|
+
for _, path in ipairs(input) do
|
|
592
|
+
local lines = H.file_read(path)
|
|
593
|
+
local block_arr = H.lines_to_block_arr(lines, config)
|
|
594
|
+
local file = H.as_struct(block_arr, 'file', { path = path })
|
|
595
|
+
|
|
596
|
+
doc:insert(file)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
-- Apply hooks
|
|
600
|
+
H.apply_structure_hooks(doc, config.hooks)
|
|
601
|
+
|
|
602
|
+
-- Gather string lines in depth-first fashion
|
|
603
|
+
local help_lines = H.collect_strings(doc)
|
|
604
|
+
|
|
605
|
+
-- Execute pre-write hook
|
|
606
|
+
help_lines = config.hooks.write_pre(help_lines)
|
|
607
|
+
if not H.is_array_of(help_lines, H.is_string) then H.error('Output of `write_pre` should be array of strings.') end
|
|
608
|
+
|
|
609
|
+
-- Write helpfile
|
|
610
|
+
H.file_write(output, help_lines)
|
|
611
|
+
|
|
612
|
+
-- Execute post-write hook
|
|
613
|
+
config.hooks.write_post(doc)
|
|
614
|
+
|
|
615
|
+
-- Clear current information
|
|
616
|
+
MiniDoc.current = {}
|
|
617
|
+
|
|
618
|
+
-- Stash output to allow returning value even when called from script
|
|
619
|
+
H.generate_recent_output = doc
|
|
620
|
+
|
|
621
|
+
return doc
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
--- Convert afterlines to code
|
|
625
|
+
---
|
|
626
|
+
--- This function is designed to be used together with `@eval` section to
|
|
627
|
+
--- automate documentation of certain values (notably default values of a
|
|
628
|
+
--- table). It processes afterlines based on certain directives and makes
|
|
629
|
+
--- output look like a Lua code block.
|
|
630
|
+
---
|
|
631
|
+
--- Most common usage is by adding the following section in your annotation: >
|
|
632
|
+
---
|
|
633
|
+
--- ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
|
|
634
|
+
--- <
|
|
635
|
+
--- # Directives ~
|
|
636
|
+
---
|
|
637
|
+
--- Directives are special comments that are processed using Lua string pattern
|
|
638
|
+
--- capabilities (so beware of false positives). Each directive should be put
|
|
639
|
+
--- on its separate line. Supported directives:
|
|
640
|
+
--- - `--minidoc_afterlines_end` denotes a line at afterlines end. Only all
|
|
641
|
+
--- lines before it will be considered as afterlines. Useful if there is
|
|
642
|
+
--- extra code in afterlines which shouldn't be used.
|
|
643
|
+
--- - `--minidoc_replace_start <replacement>` and `--minidoc_replace_end`
|
|
644
|
+
--- denote lines between them which should be replaced with `<replacement>`.
|
|
645
|
+
--- Useful for manually changing what should be placed in output like in case
|
|
646
|
+
--- of replacing function body with something else.
|
|
647
|
+
---
|
|
648
|
+
--- Here is an example. Suppose having these afterlines: >lua
|
|
649
|
+
---
|
|
650
|
+
--- --minidoc_replace_start {
|
|
651
|
+
--- M.config = {
|
|
652
|
+
--- --minidoc_replace_end
|
|
653
|
+
--- param_one = 1,
|
|
654
|
+
--- --minidoc_replace_start param_fun = --<function>
|
|
655
|
+
--- param_fun = function(x)
|
|
656
|
+
--- return x + 1
|
|
657
|
+
--- end
|
|
658
|
+
--- --minidoc_replace_end
|
|
659
|
+
--- }
|
|
660
|
+
--- --minidoc_afterlines_end
|
|
661
|
+
---
|
|
662
|
+
--- return M
|
|
663
|
+
--- <
|
|
664
|
+
--- After adding `@eval` section those will be formatted as: >
|
|
665
|
+
---
|
|
666
|
+
--- {
|
|
667
|
+
--- param_one = 1,
|
|
668
|
+
--- param_fun = --<function>
|
|
669
|
+
--- }
|
|
670
|
+
--- <
|
|
671
|
+
---@param struct table Block or section structure which after lines will be
|
|
672
|
+
--- converted to code.
|
|
673
|
+
---
|
|
674
|
+
---@return string|nil Single string (using `\n` to separate lines) describing
|
|
675
|
+
--- afterlines as Lua code block in help file. If `nil`, input is not valid.
|
|
676
|
+
MiniDoc.afterlines_to_code = function(struct)
|
|
677
|
+
if not (type(struct) == 'table' and (struct.type == 'section' or struct.type == 'block')) then
|
|
678
|
+
vim.notify('Input to `MiniDoc.afterlines_to_code()` should be either section or block.', vim.log.levels.WARN)
|
|
679
|
+
return
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
if struct.type == 'section' then struct = struct.parent end
|
|
683
|
+
local src = table.concat(struct.info.afterlines, '\n')
|
|
684
|
+
|
|
685
|
+
-- Process directives
|
|
686
|
+
-- Try to extract afterlines
|
|
687
|
+
src = src:match('^(.-)\n%s*%-%-minidoc_afterlines_end') or src
|
|
688
|
+
|
|
689
|
+
-- Make replacements
|
|
690
|
+
src = src:gsub('%-%-minidoc_replace_start ?(.-)\n.-%-%-minidoc_replace_end', '%1')
|
|
691
|
+
|
|
692
|
+
-- Convert to a standalone code. NOTE: indent is needed because of how `>`
|
|
693
|
+
-- and `<` work (any line starting in column 1 stops code block).
|
|
694
|
+
src = H.ensure_indent(src, 2)
|
|
695
|
+
return '>lua\n' .. src .. '\n<'
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
-- Helper data ================================================================
|
|
699
|
+
-- Module default config
|
|
700
|
+
H.default_config = vim.deepcopy(MiniDoc.config)
|
|
701
|
+
|
|
702
|
+
-- Alias registry. Keys are alias name, values - single string of alias
|
|
703
|
+
-- description with '\n' separating output lines.
|
|
704
|
+
H.alias_registry = {}
|
|
705
|
+
|
|
706
|
+
--stylua: ignore start
|
|
707
|
+
H.pattern_sets = {
|
|
708
|
+
-- Patterns for working with afterlines. At the moment deliberately crafted
|
|
709
|
+
-- to work only on first line without indent.
|
|
710
|
+
|
|
711
|
+
-- Determine if line is a function definition. Captures function name and
|
|
712
|
+
-- arguments. For reference see '2.5.9 – Function Definitions' in Lua manual.
|
|
713
|
+
afterline_fundef = {
|
|
714
|
+
'^function%s+(%S-)(%b())', -- Regular definition
|
|
715
|
+
'^local%s+function%s+(%S-)(%b())', -- Local definition
|
|
716
|
+
'^(%S+)%s*=%s*function(%b())', -- Regular assignment
|
|
717
|
+
'^local%s+(%S+)%s*=%s*function(%b())', -- Local assignment
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
-- Determine if line is a general assignment
|
|
721
|
+
afterline_assign = {
|
|
722
|
+
'^(%S-)%s*=', -- General assignment
|
|
723
|
+
'^local%s+(%S-)%s*=', -- Local assignment
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
-- Patterns to work with type descriptions
|
|
727
|
+
-- (see https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#types-and-type)
|
|
728
|
+
types = {
|
|
729
|
+
'%b()', -- Allow union type
|
|
730
|
+
'%b[]',
|
|
731
|
+
'%b{}',
|
|
732
|
+
'table%b<>',
|
|
733
|
+
'fun%b():%s*%b()', 'fun%b():%s*%b[]', 'fun%b():%s*%b{}', 'fun%b():%s*table%b<>', 'fun%b():%s*%S+', 'fun%b()',
|
|
734
|
+
'nil', 'any', 'boolean', 'string', 'number', 'integer', 'function', 'table', 'thread', 'userdata', 'lightuserdata',
|
|
735
|
+
'%.%.%.',
|
|
736
|
+
'[%a][%w_%.]*', -- Allow any class as a type
|
|
737
|
+
},
|
|
738
|
+
}
|
|
739
|
+
--stylua: ignore end
|
|
740
|
+
|
|
741
|
+
-- Helper functionality =======================================================
|
|
742
|
+
-- Settings -------------------------------------------------------------------
|
|
743
|
+
H.setup_config = function(config)
|
|
744
|
+
H.check_type('config', config, 'table', true)
|
|
745
|
+
config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {})
|
|
746
|
+
|
|
747
|
+
H.check_type('annotation_extractor', config.annotation_extractor, 'function')
|
|
748
|
+
H.check_type('default_section_id', config.default_section_id, 'string')
|
|
749
|
+
H.check_type('hooks', config.hooks, 'table')
|
|
750
|
+
|
|
751
|
+
H.check_type('hooks.block_pre', config.hooks.block_pre, 'function')
|
|
752
|
+
H.check_type('hooks.section_pre', config.hooks.section_pre, 'function')
|
|
753
|
+
|
|
754
|
+
H.check_type('hooks.sections', config.hooks.sections, 'table')
|
|
755
|
+
H.check_type('hooks.sections.@alias', config.hooks.sections['@alias'], 'function')
|
|
756
|
+
H.check_type('hooks.sections.@class', config.hooks.sections['@class'], 'function')
|
|
757
|
+
H.check_type('hooks.sections.@diagnostic', config.hooks.sections['@diagnostic'], 'function')
|
|
758
|
+
H.check_type('hooks.sections.@eval', config.hooks.sections['@eval'], 'function')
|
|
759
|
+
H.check_type('hooks.sections.@field', config.hooks.sections['@field'], 'function')
|
|
760
|
+
H.check_type('hooks.sections.@overload', config.hooks.sections['@overload'], 'function')
|
|
761
|
+
H.check_type('hooks.sections.@param', config.hooks.sections['@param'], 'function')
|
|
762
|
+
H.check_type('hooks.sections.@private', config.hooks.sections['@private'], 'function')
|
|
763
|
+
H.check_type('hooks.sections.@return', config.hooks.sections['@return'], 'function')
|
|
764
|
+
H.check_type('hooks.sections.@seealso', config.hooks.sections['@seealso'], 'function')
|
|
765
|
+
H.check_type('hooks.sections.@signature', config.hooks.sections['@signature'], 'function')
|
|
766
|
+
H.check_type('hooks.sections.@tag', config.hooks.sections['@tag'], 'function')
|
|
767
|
+
H.check_type('hooks.sections.@text', config.hooks.sections['@text'], 'function')
|
|
768
|
+
H.check_type('hooks.sections.@toc', config.hooks.sections['@toc'], 'function')
|
|
769
|
+
H.check_type('hooks.sections.@toc_entry', config.hooks.sections['@toc_entry'], 'function')
|
|
770
|
+
H.check_type('hooks.sections.@type', config.hooks.sections['@type'], 'function')
|
|
771
|
+
H.check_type('hooks.sections.@usage', config.hooks.sections['@usage'], 'function')
|
|
772
|
+
|
|
773
|
+
H.check_type('hooks.section_post', config.hooks.section_post, 'function')
|
|
774
|
+
H.check_type('hooks.block_post', config.hooks.block_post, 'function')
|
|
775
|
+
H.check_type('hooks.file', config.hooks.file, 'function')
|
|
776
|
+
H.check_type('hooks.doc', config.hooks.doc, 'function')
|
|
777
|
+
H.check_type('hooks.write_pre', config.hooks.write_pre, 'function')
|
|
778
|
+
H.check_type('hooks.write_post', config.hooks.write_post, 'function')
|
|
779
|
+
|
|
780
|
+
H.check_type('script_path', config.script_path, 'string')
|
|
781
|
+
H.check_type('silent', config.silent, 'boolean')
|
|
782
|
+
|
|
783
|
+
return config
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
H.apply_config = function(config) MiniDoc.config = config end
|
|
787
|
+
|
|
788
|
+
H.get_config = function(config)
|
|
789
|
+
return vim.tbl_deep_extend('force', MiniDoc.config, vim.b.minidoc_config or {}, config or {})
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
-- Work with project specific script ==========================================
|
|
793
|
+
H.execute_project_script = function(input, output, config)
|
|
794
|
+
-- Don't process script if there are more than one active `generate` calls
|
|
795
|
+
if H.generate_is_active then return end
|
|
796
|
+
|
|
797
|
+
-- Don't process script if at least one argument is not default
|
|
798
|
+
if not (input == nil and output == nil and config == nil) then return end
|
|
799
|
+
|
|
800
|
+
-- Store information
|
|
801
|
+
local global_config_cache = vim.deepcopy(MiniDoc.config)
|
|
802
|
+
local local_config_cache = vim.b.minidoc_config
|
|
803
|
+
|
|
804
|
+
-- Pass information to a possible `generate()` call inside script
|
|
805
|
+
H.generate_is_active = true
|
|
806
|
+
H.generate_recent_output = nil
|
|
807
|
+
|
|
808
|
+
-- Execute script
|
|
809
|
+
local success = pcall(vim.cmd, 'luafile ' .. H.get_config(config).script_path)
|
|
810
|
+
|
|
811
|
+
-- Restore information
|
|
812
|
+
MiniDoc.config = global_config_cache
|
|
813
|
+
vim.b.minidoc_config = local_config_cache
|
|
814
|
+
H.generate_is_active = nil
|
|
815
|
+
|
|
816
|
+
return success
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
-- Default documentation targets ----------------------------------------------
|
|
820
|
+
H.default_input = function()
|
|
821
|
+
-- Search in current and recursively in other directories for Lua files
|
|
822
|
+
local res = {}
|
|
823
|
+
for _, dir in ipairs({ '.', 'lua', 'after', 'colors' }) do
|
|
824
|
+
local glob = (dir == '.' and '' or '**/') .. '*.lua'
|
|
825
|
+
local files = vim.fn.globpath(dir, glob, false, true)
|
|
826
|
+
|
|
827
|
+
-- Use full paths
|
|
828
|
+
files = vim.tbl_map(function(x) return vim.fn.fnamemodify(x, ':p') end, files)
|
|
829
|
+
|
|
830
|
+
-- Ensure consistent order
|
|
831
|
+
table.sort(files, function(a, b)
|
|
832
|
+
local a_dir, b_dir = vim.fn.fnamemodify(a, ':h'), vim.fn.fnamemodify(b, ':h')
|
|
833
|
+
|
|
834
|
+
-- Put 'init.lua' first among files from same directory
|
|
835
|
+
if a_dir == b_dir then
|
|
836
|
+
local a_basename, b_basename = vim.fn.fnamemodify(a, ':t'), vim.fn.fnamemodify(b, ':t')
|
|
837
|
+
if a_basename == 'init.lua' then return true end
|
|
838
|
+
if b_basename == 'init.lua' then return false end
|
|
839
|
+
return a_basename < b_basename
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
return a_dir < b_dir
|
|
843
|
+
end)
|
|
844
|
+
table.insert(res, files)
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
return H.tbl_flatten(res)
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
H.default_output = function()
|
|
851
|
+
local cur_dir = vim.fn.fnamemodify(vim.loop.cwd(), ':t:r')
|
|
852
|
+
return ('doc/%s.txt'):format(cur_dir)
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
-- Parsing --------------------------------------------------------------------
|
|
856
|
+
H.lines_to_block_arr = function(lines, config)
|
|
857
|
+
local matched_prev, matched_cur
|
|
858
|
+
|
|
859
|
+
local res = {}
|
|
860
|
+
local block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = 1 }
|
|
861
|
+
|
|
862
|
+
for i, l in ipairs(lines) do
|
|
863
|
+
local from, to, section_id = config.annotation_extractor(l)
|
|
864
|
+
matched_prev, matched_cur = matched_cur, from ~= nil
|
|
865
|
+
|
|
866
|
+
if matched_cur then
|
|
867
|
+
if not matched_prev then
|
|
868
|
+
-- Finish current block
|
|
869
|
+
block_raw.line_end = i - 1
|
|
870
|
+
table.insert(res, H.raw_block_to_block(block_raw, config))
|
|
871
|
+
|
|
872
|
+
-- Start new block
|
|
873
|
+
block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = i }
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
-- Add annotation line without matched annotation pattern
|
|
877
|
+
table.insert(block_raw.annotation, ('%s%s'):format(l:sub(0, from - 1), l:sub(to + 1)))
|
|
878
|
+
|
|
879
|
+
-- Add section id (it is empty string in case of no section id capture)
|
|
880
|
+
table.insert(block_raw.section_id, section_id or '')
|
|
881
|
+
else
|
|
882
|
+
-- Add afterline
|
|
883
|
+
table.insert(block_raw.afterlines, l)
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
block_raw.line_end = #lines
|
|
887
|
+
table.insert(res, H.raw_block_to_block(block_raw, config))
|
|
888
|
+
|
|
889
|
+
return res
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
-- Raw block structure is an intermediate step added for convenience. It is
|
|
893
|
+
-- a table with the following keys:
|
|
894
|
+
-- - `annotation` - lines (after removing matched annotation pattern) that were
|
|
895
|
+
-- parsed as annotation.
|
|
896
|
+
-- - `section_id` - array with length equal to `annotation` length with strings
|
|
897
|
+
-- captured as section id. Empty string of no section id was captured.
|
|
898
|
+
-- - Everything else is used as block info (like `afterlines`, etc.).
|
|
899
|
+
H.raw_block_to_block = function(block_raw, config)
|
|
900
|
+
if #block_raw.annotation == 0 and #block_raw.afterlines == 0 then return nil end
|
|
901
|
+
|
|
902
|
+
local block = H.new_struct('block', {
|
|
903
|
+
afterlines = block_raw.afterlines,
|
|
904
|
+
line_begin = block_raw.line_begin,
|
|
905
|
+
line_end = block_raw.line_end,
|
|
906
|
+
})
|
|
907
|
+
local block_begin = block.info.line_begin
|
|
908
|
+
|
|
909
|
+
-- Parse raw block annotation lines from top to bottom. New section starts
|
|
910
|
+
-- when section id is detected in that line.
|
|
911
|
+
local section_cur = H.new_struct('section', { id = config.default_section_id, line_begin = block_begin })
|
|
912
|
+
|
|
913
|
+
for i, annotation_line in ipairs(block_raw.annotation) do
|
|
914
|
+
local id = block_raw.section_id[i]
|
|
915
|
+
if id ~= '' then
|
|
916
|
+
-- Finish current section
|
|
917
|
+
if #section_cur > 0 then
|
|
918
|
+
section_cur.info.line_end = block_begin + i - 2
|
|
919
|
+
block:insert(section_cur)
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
-- Start new section
|
|
923
|
+
section_cur = H.new_struct('section', { id = id, line_begin = block_begin + i - 1 })
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
section_cur:insert(annotation_line)
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
if #section_cur > 0 then
|
|
930
|
+
section_cur.info.line_end = block_begin + #block_raw.annotation - 1
|
|
931
|
+
block:insert(section_cur)
|
|
932
|
+
end
|
|
933
|
+
|
|
934
|
+
return block
|
|
935
|
+
end
|
|
936
|
+
|
|
937
|
+
-- Hooks ----------------------------------------------------------------------
|
|
938
|
+
H.apply_structure_hooks = function(doc, hooks)
|
|
939
|
+
for _, file in ipairs(doc) do
|
|
940
|
+
for _, block in ipairs(file) do
|
|
941
|
+
hooks.block_pre(block)
|
|
942
|
+
|
|
943
|
+
for _, section in ipairs(block) do
|
|
944
|
+
-- NOTE: Section can be empty if previous hook used `clear_lines()` on
|
|
945
|
+
-- the whole block (like default `@private`).
|
|
946
|
+
if #section > 0 then
|
|
947
|
+
hooks.section_pre(section)
|
|
948
|
+
|
|
949
|
+
local hook = hooks.sections[section.info.id]
|
|
950
|
+
if hook ~= nil then hook(section) end
|
|
951
|
+
|
|
952
|
+
hooks.section_post(section)
|
|
953
|
+
end
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
hooks.block_post(block)
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
hooks.file(file)
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
hooks.doc(doc)
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
H.alias_register = function(s)
|
|
966
|
+
if #s == 0 then return end
|
|
967
|
+
|
|
968
|
+
-- Remove first word (with bits of surrounding whitespace) while capturing it
|
|
969
|
+
local alias_name
|
|
970
|
+
s[1] = s[1]:gsub('%s*(%S+) ?', function(x)
|
|
971
|
+
alias_name = x
|
|
972
|
+
return ''
|
|
973
|
+
end, 1)
|
|
974
|
+
if alias_name == nil then return end
|
|
975
|
+
|
|
976
|
+
MiniDoc.current.aliases = MiniDoc.current.aliases or {}
|
|
977
|
+
MiniDoc.current.aliases[alias_name] = table.concat(s, '\n')
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
H.alias_replace = function(s)
|
|
981
|
+
if MiniDoc.current.aliases == nil then return end
|
|
982
|
+
|
|
983
|
+
local s_type = s.info.id
|
|
984
|
+
local has_special_first_word = s_type == '@param' or s_type == '@field' or s_type == '@class'
|
|
985
|
+
local has_special_type = s_type == '@tag' or s_type == '@toc_entry'
|
|
986
|
+
for alias_name, alias_desc in pairs(MiniDoc.current.aliases) do
|
|
987
|
+
-- Escape special characters. This is done here and not while registering
|
|
988
|
+
-- alias to allow user to refer to aliases by its original name.
|
|
989
|
+
local name_escaped = vim.pesc(alias_name)
|
|
990
|
+
local desc_is_union = alias_desc:find('|') ~= nil
|
|
991
|
+
for i, _ in ipairs(s) do
|
|
992
|
+
-- Try to be accurate in which matches to replace. This avoids cases like
|
|
993
|
+
-- `@alias aaa AAA` with `aaaBBB->AAABBB` replacements or replacing
|
|
994
|
+
-- inside special places (like parameter/field/tag names, etc.)
|
|
995
|
+
s[i] = s[i]:gsub('(.?)(' .. name_escaped .. ')(.?)', function(before, match, after)
|
|
996
|
+
local before_is_empty, after_is_empty = before == '', after == ''
|
|
997
|
+
local before_is_space, after_is_space = before:find('%s') == 1, after:find('%s') == 1
|
|
998
|
+
-- Allow match to be preceded/followed by special characters that can
|
|
999
|
+
-- be used inside EmmyLua/LuaCATS annotations.
|
|
1000
|
+
-- Source: https://luals.github.io/wiki/annotations/#documenting-types
|
|
1001
|
+
local before_is_special, after_is_special = before:find('[|,%[%(<:]') == 1, after:find('[|,%[%])>}%?]') == 1
|
|
1002
|
+
|
|
1003
|
+
local is_fixed_name = i == 1 and has_special_first_word and before_is_empty
|
|
1004
|
+
local before_is_valid = before_is_empty or before_is_space or before_is_special
|
|
1005
|
+
local after_is_valid = after_is_empty or after_is_space or after_is_special
|
|
1006
|
+
local is_valid = before_is_valid and after_is_valid
|
|
1007
|
+
if not is_valid or is_fixed_name or has_special_type then return before .. match .. after end
|
|
1008
|
+
|
|
1009
|
+
local should_enclose = desc_is_union and (before_is_special or after_is_special)
|
|
1010
|
+
return before .. (should_enclose and ('(' .. alias_desc .. ')') or alias_desc) .. after
|
|
1011
|
+
end)
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
|
|
1016
|
+
H.toc_register = function(s)
|
|
1017
|
+
MiniDoc.current.toc = MiniDoc.current.toc or {}
|
|
1018
|
+
table.insert(MiniDoc.current.toc, s)
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
H.toc_insert = function(s)
|
|
1022
|
+
if MiniDoc.current.toc == nil then return end
|
|
1023
|
+
|
|
1024
|
+
-- Render table of contents
|
|
1025
|
+
local toc_lines = {}
|
|
1026
|
+
for _, toc_entry in ipairs(MiniDoc.current.toc) do
|
|
1027
|
+
local _, tag_section = toc_entry.parent:has_descendant(
|
|
1028
|
+
function(x) return type(x) == 'table' and x.type == 'section' and x.info.id == '@tag' end
|
|
1029
|
+
)
|
|
1030
|
+
tag_section = tag_section or {}
|
|
1031
|
+
|
|
1032
|
+
local lines = {}
|
|
1033
|
+
for i = 1, math.max(#toc_entry, #tag_section) do
|
|
1034
|
+
local left = toc_entry[i] or ''
|
|
1035
|
+
-- Use tag reference instead of tag enclosure
|
|
1036
|
+
local right = vim.trim((tag_section[i] or ''):gsub('%*', '|'))
|
|
1037
|
+
-- Add helper line of dots in first entry (without new trailing space)
|
|
1038
|
+
local filler = right == '' and '' or ' '
|
|
1039
|
+
if i == 1 then
|
|
1040
|
+
-- Ensure parts are padded for proper conceal
|
|
1041
|
+
filler, left, right = '.', (left:gsub('(%S)$', '%1 ')), (right:gsub('^(%S)', ' %1'))
|
|
1042
|
+
end
|
|
1043
|
+
-- Make padding of 2 spaces at both left and right
|
|
1044
|
+
local n_filler = math.max(74 - H.visual_text_width(left) - H.visual_text_width(right), 3)
|
|
1045
|
+
table.insert(lines, (' %s%s%s'):format(left, filler:rep(n_filler), right))
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
table.insert(toc_lines, lines)
|
|
1049
|
+
|
|
1050
|
+
-- Don't show `toc_entry` lines in output
|
|
1051
|
+
toc_entry:clear_lines()
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
for _, l in ipairs(H.tbl_flatten(toc_lines)) do
|
|
1055
|
+
s:insert(l)
|
|
1056
|
+
end
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
H.add_section_heading = function(s, heading)
|
|
1060
|
+
-- Add heading
|
|
1061
|
+
s:insert(1, ('%s ~'):format(heading))
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
H.mark_optional = function(s)
|
|
1065
|
+
-- Treat question mark at end of first word as "optional" indicator. See:
|
|
1066
|
+
-- https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#optional-params
|
|
1067
|
+
s[1] = s[1]:gsub('^(%s-%S-)%?', '%1 `(optional)`', 1)
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
H.enclose_var_name = function(s) s[1] = s[1]:gsub('(%S+)', '{%1}', 1) end
|
|
1071
|
+
|
|
1072
|
+
---@param init number Start of searching for first "type-like" string. It is
|
|
1073
|
+
--- needed to not detect type early. Like in `@param a_function function`.
|
|
1074
|
+
---@private
|
|
1075
|
+
H.enclose_type = function(s, init)
|
|
1076
|
+
if #s == 0 or s.type ~= 'section' then return end
|
|
1077
|
+
init = init or 1
|
|
1078
|
+
|
|
1079
|
+
local type_pattern_set = H.pattern_sets['types']
|
|
1080
|
+
local type_pattern = H.find_pattern_with_first_match(s[1], type_pattern_set, init)
|
|
1081
|
+
if type_pattern == nil then return end
|
|
1082
|
+
|
|
1083
|
+
-- Find range representing type. It can be a match for type pattern (plain,
|
|
1084
|
+
-- array `[]`, or optional `?`), possibly in a union (`|`).
|
|
1085
|
+
local from, to = s[1]:find(type_pattern, init)
|
|
1086
|
+
for _ = 1, s[1]:len() do
|
|
1087
|
+
if s[1]:sub(to + 1, to + 2) == '[]' then to = to + 2 end
|
|
1088
|
+
if s[1]:sub(to + 1, to + 1) == '?' then to = to + 1 end
|
|
1089
|
+
|
|
1090
|
+
local new_to = s[1]:sub(to + 1):match('^%s*|%s*()')
|
|
1091
|
+
if new_to == nil then break end
|
|
1092
|
+
to = to + new_to - 1
|
|
1093
|
+
local next_type_pattern = H.find_pattern_with_first_match(s[1], type_pattern_set, to + 1)
|
|
1094
|
+
if next_type_pattern == nil then break end
|
|
1095
|
+
to = s[1]:match(next_type_pattern .. '()', to + 1) - 1
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
-- Avoid replacing match before `init` and avoid unnecessary () enclosing
|
|
1099
|
+
local avoid_brackets = s[1]:sub(from, to):find('^%b()$') ~= nil
|
|
1100
|
+
local left = avoid_brackets and '`' or '`('
|
|
1101
|
+
local right = avoid_brackets and '`' or ')`'
|
|
1102
|
+
|
|
1103
|
+
s[1] = s[1]:sub(1, from - 1) .. left .. s[1]:sub(from, to) .. right .. s[1]:sub(to + 1)
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
-- Infer data from afterlines -------------------------------------------------
|
|
1107
|
+
H.infer_header = function(b)
|
|
1108
|
+
local has_signature = b:has_descendant(
|
|
1109
|
+
function(x) return type(x) == 'table' and x.type == 'section' and x.info.id == '@signature' end
|
|
1110
|
+
)
|
|
1111
|
+
local has_tag = b:has_descendant(
|
|
1112
|
+
function(x) return type(x) == 'table' and x.type == 'section' and x.info.id == '@tag' end
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
if has_signature and has_tag then return end
|
|
1116
|
+
|
|
1117
|
+
local l_all = table.concat(b.info.afterlines, ' ')
|
|
1118
|
+
local tag, signature
|
|
1119
|
+
|
|
1120
|
+
-- Try function definition
|
|
1121
|
+
local fun_pattern = H.find_pattern_with_first_match(l_all, H.pattern_sets['afterline_fundef'])
|
|
1122
|
+
if fun_pattern ~= nil then
|
|
1123
|
+
local fun_name, fun_args = l_all:match(fun_pattern)
|
|
1124
|
+
tag = tag or (fun_name .. '()')
|
|
1125
|
+
signature = signature or (fun_name .. fun_args)
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
-- Try general assignment
|
|
1129
|
+
local assign_pattern = H.find_pattern_with_first_match(l_all, H.pattern_sets['afterline_assign'])
|
|
1130
|
+
if assign_pattern ~= nil then
|
|
1131
|
+
local obj_name = l_all:match(assign_pattern)
|
|
1132
|
+
tag = tag or obj_name
|
|
1133
|
+
signature = signature or obj_name
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
if tag ~= nil then
|
|
1137
|
+
-- First insert signature (so that it will appear after tag section)
|
|
1138
|
+
if not has_signature then b:insert(1, H.as_struct({ signature }, 'section', { id = '@signature' })) end
|
|
1139
|
+
|
|
1140
|
+
-- Insert tag
|
|
1141
|
+
if not has_tag then b:insert(1, H.as_struct({ tag }, 'section', { id = '@tag' })) end
|
|
1142
|
+
end
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
H.format_signature = function(line)
|
|
1146
|
+
-- Try capture function signature
|
|
1147
|
+
local name, args = line:match('(%S-)(%b())')
|
|
1148
|
+
-- Otherwise pick first word
|
|
1149
|
+
name = name or line:match('(%S+)')
|
|
1150
|
+
|
|
1151
|
+
if not name then return '' end
|
|
1152
|
+
|
|
1153
|
+
-- Tidy arguments
|
|
1154
|
+
if args and args ~= '()' then
|
|
1155
|
+
local arg_parts = vim.split(args:sub(2, -2), ',')
|
|
1156
|
+
local arg_list = {}
|
|
1157
|
+
for _, a in ipairs(arg_parts) do
|
|
1158
|
+
-- Enclose argument in `{}` while controlling whitespace
|
|
1159
|
+
table.insert(arg_list, ('{%s}'):format(vim.trim(a)))
|
|
1160
|
+
end
|
|
1161
|
+
args = ('(%s)'):format(table.concat(arg_list, ', '))
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
return ('`%s`%s'):format(name, args or '')
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
-- Work with structures -------------------------------------------------------
|
|
1168
|
+
-- Constructor
|
|
1169
|
+
H.new_struct = function(struct_type, info)
|
|
1170
|
+
local output = {
|
|
1171
|
+
info = info or {},
|
|
1172
|
+
type = struct_type,
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
output.insert = function(self, index, child)
|
|
1176
|
+
-- Allow both `x:insert(child)` and `x:insert(1, child)`
|
|
1177
|
+
if child == nil then
|
|
1178
|
+
child, index = index, #self + 1
|
|
1179
|
+
end
|
|
1180
|
+
|
|
1181
|
+
if type(child) == 'table' then
|
|
1182
|
+
child.parent = self
|
|
1183
|
+
child.parent_index = index
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
table.insert(self, index, child)
|
|
1187
|
+
|
|
1188
|
+
H.sync_parent_index(self)
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1191
|
+
output.remove = function(self, index)
|
|
1192
|
+
index = index or #self
|
|
1193
|
+
table.remove(self, index)
|
|
1194
|
+
|
|
1195
|
+
H.sync_parent_index(self)
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
output.has_descendant = function(self, predicate)
|
|
1199
|
+
local bool_res, descendant = false, nil
|
|
1200
|
+
H.apply_recursively(function(x)
|
|
1201
|
+
if not bool_res and predicate(x) then
|
|
1202
|
+
bool_res = true
|
|
1203
|
+
descendant = x
|
|
1204
|
+
end
|
|
1205
|
+
end, self)
|
|
1206
|
+
return bool_res, descendant
|
|
1207
|
+
end
|
|
1208
|
+
|
|
1209
|
+
output.has_lines = function(self)
|
|
1210
|
+
return self:has_descendant(function(x) return type(x) == 'string' end)
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
output.clear_lines = function(self)
|
|
1214
|
+
for i, x in ipairs(self) do
|
|
1215
|
+
if type(x) == 'string' then
|
|
1216
|
+
self[i] = nil
|
|
1217
|
+
else
|
|
1218
|
+
x:clear_lines()
|
|
1219
|
+
end
|
|
1220
|
+
end
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
return output
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
H.sync_parent_index = function(x)
|
|
1227
|
+
for i, _ in ipairs(x) do
|
|
1228
|
+
if type(x[i]) == 'table' then x[i].parent_index = i end
|
|
1229
|
+
end
|
|
1230
|
+
return x
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
-- Converter (this ensures that children have proper parent-related data)
|
|
1234
|
+
H.as_struct = function(array, struct_type, info)
|
|
1235
|
+
-- Make default info `info` for cases when structure is created manually
|
|
1236
|
+
local default_info = ({
|
|
1237
|
+
section = { id = '@text', line_begin = -1, line_end = -1 },
|
|
1238
|
+
block = { afterlines = {}, line_begin = -1, line_end = -1 },
|
|
1239
|
+
file = { path = '' },
|
|
1240
|
+
doc = { input = {}, output = '', config = H.get_config() },
|
|
1241
|
+
})[struct_type]
|
|
1242
|
+
info = vim.tbl_deep_extend('force', default_info, info or {})
|
|
1243
|
+
|
|
1244
|
+
local res = H.new_struct(struct_type, info)
|
|
1245
|
+
for _, x in ipairs(array) do
|
|
1246
|
+
res:insert(x)
|
|
1247
|
+
end
|
|
1248
|
+
return res
|
|
1249
|
+
end
|
|
1250
|
+
|
|
1251
|
+
-- Work with text -------------------------------------------------------------
|
|
1252
|
+
H.ensure_indent = function(text, n_indent_target)
|
|
1253
|
+
local lines = vim.split(text, '\n')
|
|
1254
|
+
local n_indent, n_indent_cur = math.huge, math.huge
|
|
1255
|
+
|
|
1256
|
+
-- Find number of characters in indent
|
|
1257
|
+
for _, l in ipairs(lines) do
|
|
1258
|
+
-- Update lines indent: minimum of all indents except empty lines
|
|
1259
|
+
if n_indent > 0 then
|
|
1260
|
+
_, n_indent_cur = l:find('^%s*')
|
|
1261
|
+
-- Condition "current n-indent equals line length" detects empty line
|
|
1262
|
+
if (n_indent_cur < n_indent) and (n_indent_cur < l:len()) then n_indent = n_indent_cur end
|
|
1263
|
+
end
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
-- Ensure indent
|
|
1267
|
+
local indent = string.rep(' ', n_indent_target)
|
|
1268
|
+
for i, l in ipairs(lines) do
|
|
1269
|
+
if l ~= '' then lines[i] = indent .. l:sub(n_indent + 1) end
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
return table.concat(lines, '\n')
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
H.align_text = function(text, width, direction)
|
|
1276
|
+
if type(text) ~= 'string' then return end
|
|
1277
|
+
text = vim.trim(text)
|
|
1278
|
+
width = width or 78
|
|
1279
|
+
direction = direction or 'left'
|
|
1280
|
+
|
|
1281
|
+
-- Don't do anything if aligning left or line is a whitespace
|
|
1282
|
+
if direction == 'left' or text:find('^%s*$') then return text end
|
|
1283
|
+
|
|
1284
|
+
local n_left = math.max(0, 78 - H.visual_text_width(text))
|
|
1285
|
+
if direction == 'center' then n_left = math.floor(0.5 * n_left) end
|
|
1286
|
+
|
|
1287
|
+
return (' '):rep(n_left) .. text
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
H.visual_text_width = function(text)
|
|
1291
|
+
-- Ignore concealed characters (usually "invisible" in 'help' filetype)
|
|
1292
|
+
local _, n_concealed_chars = text:gsub('([*|`])', '%1')
|
|
1293
|
+
return vim.fn.strdisplaywidth(text) - n_concealed_chars
|
|
1294
|
+
end
|
|
1295
|
+
|
|
1296
|
+
H.find_pattern_with_first_match = function(text, pattern_set, init)
|
|
1297
|
+
local min_start, first_pat = math.huge, nil
|
|
1298
|
+
for _, pat in ipairs(pattern_set) do
|
|
1299
|
+
local from = text:find(pat, init)
|
|
1300
|
+
if from ~= nil and from < min_start then
|
|
1301
|
+
min_start, first_pat = from, pat
|
|
1302
|
+
end
|
|
1303
|
+
end
|
|
1304
|
+
return first_pat
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
-- Utilities ------------------------------------------------------------------
|
|
1308
|
+
H.error = function(msg) error('(mini.doc) ' .. msg, 0) end
|
|
1309
|
+
|
|
1310
|
+
H.check_type = function(name, val, ref, allow_nil)
|
|
1311
|
+
if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end
|
|
1312
|
+
H.error(string.format('`%s` should be %s, not %s', name, ref, type(val)))
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
H.apply_recursively = function(f, x)
|
|
1316
|
+
f(x)
|
|
1317
|
+
|
|
1318
|
+
if type(x) == 'table' then
|
|
1319
|
+
for _, t in ipairs(x) do
|
|
1320
|
+
H.apply_recursively(f, t)
|
|
1321
|
+
end
|
|
1322
|
+
end
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
H.collect_strings = function(x)
|
|
1326
|
+
local res = {}
|
|
1327
|
+
H.apply_recursively(function(y)
|
|
1328
|
+
if type(y) == 'string' then
|
|
1329
|
+
-- Allow `\n` in strings
|
|
1330
|
+
table.insert(res, vim.split(y, '\n'))
|
|
1331
|
+
end
|
|
1332
|
+
end, x)
|
|
1333
|
+
-- Flatten to only have strings and not table of strings (from `vim.split`)
|
|
1334
|
+
return H.tbl_flatten(res)
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
H.file_read = function(path)
|
|
1338
|
+
local file = assert(io.open(path))
|
|
1339
|
+
local contents = file:read('*all')
|
|
1340
|
+
file:close()
|
|
1341
|
+
|
|
1342
|
+
return vim.split(contents, '\n')
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
H.file_write = function(path, lines)
|
|
1346
|
+
-- Ensure target directory exists
|
|
1347
|
+
local dir = vim.fn.fnamemodify(path, ':h')
|
|
1348
|
+
vim.fn.mkdir(dir, 'p')
|
|
1349
|
+
|
|
1350
|
+
-- Write to file
|
|
1351
|
+
vim.fn.writefile(lines, path, 'b')
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
H.full_path = function(path) return vim.fn.resolve(vim.fn.fnamemodify(path, ':p')) end
|
|
1355
|
+
|
|
1356
|
+
H.is_array_of = function(x, predicate)
|
|
1357
|
+
if not H.islist(x) then return false end
|
|
1358
|
+
for _, v in ipairs(x) do
|
|
1359
|
+
if not predicate(v) then return false end
|
|
1360
|
+
end
|
|
1361
|
+
return true
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
H.is_string = function(x) return type(x) == 'string' end
|
|
1365
|
+
|
|
1366
|
+
-- TODO: Remove after compatibility with Neovim=0.9 is dropped
|
|
1367
|
+
H.islist = vim.fn.has('nvim-0.10') == 1 and vim.islist or vim.tbl_islist
|
|
1368
|
+
H.tbl_flatten = vim.fn.has('nvim-0.10') == 1 and function(x) return vim.iter(x):flatten(math.huge):totable() end
|
|
1369
|
+
or vim.tbl_flatten
|
|
1370
|
+
|
|
1371
|
+
return MiniDoc
|