retrovim 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. package/zsh/zsh-patina.toml +2 -0
@@ -0,0 +1,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