forked from nvim-neorg/neorg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdocgen.lua
885 lines (758 loc) · 32 KB
/
docgen.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
local neorg = require("neorg.core")
local docgen = {}
-- Create the directory if it does not exist
docgen.output_dir = "../wiki"
docgen.static_dir = "../res/wiki/static"
pcall(vim.fn.mkdir, docgen.output_dir)
-- Copy static wiki resources into the wiki
vim.loop.fs_scandir(docgen.static_dir, function(err, handle)
assert(handle, err) -- will not kill docgen on fail, because it is within async callback
local name, type = vim.loop.fs_scandir_next(handle)
while name do
if type == "file" then
assert(vim.loop.fs_copyfile(docgen.static_dir .. "/" .. name, docgen.output_dir .. "/" .. name))
end
name, type = vim.loop.fs_scandir_next(handle)
end
end)
require("neorg").setup({
load = {
["core.defaults"] = {},
["core.integrations.treesitter"] = {
config = {
configure_parsers = false,
},
},
},
})
local lib, modules, utils, log = neorg.lib, neorg.modules, neorg.utils, neorg.log
-- Start neorg
neorg.org_file_entered(false)
-- Extract treesitter utility functions provided by Neorg and nvim-treesitter.ts_utils
---@type core.integrations.treesitter
local ts = modules.get_module("core.integrations.treesitter")
assert(ts, "treesitter not available")
--- Aggregates all the available modules.
---@return table #A list of paths to every module's `module.lua` file
docgen.aggregate_module_files = function()
return vim.fs.find("module.lua", {
path = "..",
type = "file",
limit = math.huge,
})
end
--- Opens a file from a given path in a new buffer
---@param path string #The path of the file to open
---@return number #The buffer ID of the opened file
docgen.open_file = function(path)
local uri = vim.uri_from_fname(path)
local buf = vim.uri_to_bufnr(uri)
vim.fn.bufload(buf)
return buf
end
--- Get the first comment (at line 0) from a module and get it's content
--- @param buf number #The buffer number to read from
--- @return table? #A table of lines
docgen.get_module_top_comment = function(buf)
local node = ts.get_first_node_recursive("comment", { buf = buf, ft = "lua" })
if not node then
print("no comment node")
return
end
-- Verify if it's the first line
local start_row = node:range()
if start_row ~= 0 then
print("doens't start of the first line")
return
end
local comment = vim.split(ts.get_node_text(node, buf), "\n")
-- Stops execution if it's not a multiline comment
if comment[1] ~= [[--[[]] or comment[#comment] ~= "--]]" then
print("not a block comment")
return
end
-- Removes first and last braces
table.remove(comment, 1)
table.remove(comment, #comment)
return comment
end
---@alias TopComment { file: string, title: string, summary: string, description: string, embed: string, markdown: string[], internal: boolean }
--- Parses the top comment
---@param comment string[] #The comment
---@return TopComment #The parsed comment
docgen.parse_top_comment = function(comment)
---@type TopComment
local result = {
-- file = "",
-- title = "",
-- summary = "",
markdown = {},
}
local can_have_options = true
for _, line in ipairs(comment) do
if line:match("^%s*%-%-%-%s*$") then
can_have_options = false
else
local option_name, value = line:match("^%s*(%w+):%s*(.+)")
if vim.tbl_contains({ "true", "false" }, value) then
value = (value == "true")
end
if option_name and can_have_options then
result[option_name:lower()] = value
else
table.insert(result.markdown, line)
end
end
end
return result
end
--- Ensures that the top comment of each Neorg module follows a certain set of rules.
---@param top_comment TopComment #The comment to check for errors
---@return string|TopComment #An error string or the comment itself
docgen.check_top_comment_integrity = function(top_comment)
local tc = vim.tbl_deep_extend("keep", top_comment, {
title = "",
summary = "",
markdown = {},
})
if not tc.file then
return "no `File:` field provided."
elseif tc.summary:sub(1, 1):upper() ~= tc.summary:sub(1, 1) then
return "summary does not begin with a capital letter."
elseif tc.summary:sub(tc.summary:len()) ~= "." then
return "summary does not end with a full stop."
elseif tc.title:find("neorg") then
return "`neorg` written with lowercase letter. Use uppercase instead."
-- elseif vim.tbl_isempty(tc.markdown) then
-- return "no overview provided."
end
return top_comment
end
--- Retrieves the TS node corresponding to the `module.config.public` treesitter node
---@param buffer number #Buffer ID
---@param root TSNode #The root node
---@return TSNode? #The `module.config.public` node
docgen.get_module_config_node = function(buffer, root)
local query = utils.ts_parse_query(
"lua",
[[
(assignment_statement
(variable_list) @_name
(#eq? @_name "module.config.public")) @declaration
]]
)
local _, declaration_node = query:iter_captures(root, buffer)()
return declaration_node and declaration_node:named_child(1):named_child(0) or nil
end
---@alias ConfigOptionData { node: TSNode, name: string, value: userdata, parents: string[] }
--- Recursively maps over each item in the `module.config.public` table,
-- invoking a callback on each run. Also descends down recursive tables.
---@param buffer number #Buffer ID
---@param start_node table #The node to start parsing
---@param callback fun(ConfigOptionData, table) #Invoked on each node with the corresponding data
---@param parents string[]? #Used internally to track nesting levels
docgen.map_config = function(buffer, start_node, callback, parents)
parents = parents or {}
---@type string[]
local comments = {}
local index = 1
for node in start_node:iter_children() do
if node:type() == "comment" then
table.insert(comments, ts.get_node_text(node, buffer))
elseif node:type() == "field" then
local name_node = node:field("name")[1]
-- If a node does not have an associated name, then
-- it's part of a list
if name_node and name_node:type() == "string" then
name_node = name_node:field("content")[1]
end
local name = name_node and ts.get_node_text(name_node, buffer) or nil
local value = node:field("value")[1]
-- If the right hand side of the expression is a table
-- then go down it recursively
if value:type() == "table_constructor" then
callback({
node = node,
name = name,
value = value,
parents = parents,
}, comments)
-- The deepcopy is necessary or else
-- the parents table would be overwritten in-place
local copy = vim.deepcopy(parents)
table.insert(copy, name or index)
docgen.map_config(buffer, value, callback, copy)
else
callback({
node = node,
name = name,
value = value,
parents = parents,
}, comments)
end
comments = {}
index = index + 1
else
comments = {}
end
end
end
--- Goes through a table and evaluates all functions in that table, merging the
-- return values back into the original table.
---@param tbl table #Input table
---@return table #The new table
docgen.evaluate_functions = function(tbl)
local new = {}
lib.map(tbl, function(_, value)
if type(value) == "function" then
vim.list_extend(new, value())
else
table.insert(new, value)
end
end)
return new
end
---@alias Module { top_comment_data: TopComment, buffer: number, parsed: table }
---@alias Modules { [string]: Module }
--- Returns a function which itself returns a table of links to modules
-- in a markdown-like unordered list.
---@param mods Modules #A table of modules to enumerate
---@param predicate fun(Module):boolean #A predicate that determines whether or not to render this object.
--- If the predicate returns false, then the object is dismissed.
---@return fun():string[] #An array of markdown strings with the enumerated modules
local function list_modules_with_predicate(mods, predicate)
local sorted = lib.unroll(mods)
table.sort(sorted, function(x, y)
return x[1] < y[1]
end)
return function()
local res = {}
for _, kv_pair in ipairs(sorted) do
local mod = kv_pair[1]
local data = kv_pair[2]
if predicate and predicate(data) then
local insert
if data.top_comment_data.file then
insert = "- [`"
.. data.parsed.name
.. "`](https://github.com/nvim-neorg/neorg/wiki/"
.. data.top_comment_data.file
.. ")"
else
insert = "- `" .. mod .. "`"
end
if data.top_comment_data.summary then
insert = insert .. " - " .. data.top_comment_data.summary
else
insert = insert .. " - undocumented module"
end
table.insert(res, insert)
end
end
return res
end
end
docgen.generators = {
--- Generates the Home.md file
---@param mods Modules #A table of modules
homepage = function(mods)
local core_defaults = mods["core.defaults"]
assert(core_defaults, "core.defaults module not loaded!")
local structure = {
'<div align="center">',
"",
"# Welcome to the Neorg wiki!",
"Want to know how to properly use Neorg? Your answers are contained here.",
"",
"</div>",
"",
"# Kickstart",
"",
"If you would like a Neovim setup that has Neorg configured out of the box look no further than the [kickstart guide](https://github.com/nvim-neorg/neorg/wiki/Kickstart)!",
"",
"# Using Neorg",
"",
"Neorg depends on a number of other technologies, all of which have to be correctly configured to keep Neorg running smoothly.",
"For some help on understanding how your terminal, Neovim, colourschemes, tree-sitter and more come together to produce your Neorg experience (or Neorg problems), see [this document on understanding Neorg dependencies](Dependencies).",
"",
"At first configuring Neorg might be rather scary. I have to define what modules I want to use in the `require('neorg').setup()` function?",
"I don't even know what the default available values are!",
"Don't worry, there are guides you are free to check out. The [tutorial](https://github.com/nvim-neorg/neorg/wiki/Tutorial) guides you through what Neorg is and its basics.",
"Afterwards, feel free to check out the [configuration guide](https://github.com/nvim-neorg/neorg/wiki/Setup-Guide) as well as the [cookbook](https://github.com/nvim-neorg/neorg/wiki/Cookbook).",
"",
"# Broken Installation",
"",
"Having issues when installing Neorg, specifically past the `8.0.0` version? Check out the [following page](https://github.com/pysan3/Norg-Tutorial/blob/main/MIGRATION-v8.md) where you can troubleshoot your issue from start to finish.",
"",
"# Contributing to Neorg",
"",
"Neorg is a very big and powerful tool behind the scenes - way bigger than it may initially seem.",
"Modules are its core foundation, and building modules is like building lego bricks to form a massive structure.",
"There's an in-the-works tutorial dedicated to making modules [right here](https://github.com/andreadev-it/neorg-module-tutorials/blob/main/introduction.md)!",
"",
"# Module naming convention",
"",
"Neorg provides default modules, and users can extend Neorg by creating community modules.",
"We agreed on a module naming convention, and it should be used as is.",
"This convention should help users know at a glance what function the module serves in the grand scheme of things.",
"- Core modules: `core.*`",
"- Integrations with 3rd party software that are embedded in neorg: `core.integrations.*`",
"- External modules: `external.*`",
"- Integrations with 3rd party software that aren't embedded in neorg: `external.integrations.*`",
"",
"# Default Modules",
"",
function()
local link = "[`core.defaults`](https://github.com/nvim-neorg/neorg/wiki/"
.. core_defaults.top_comment_data.file
.. ")"
return {
"Neorg comes with some default modules that will be automatically loaded if you require the "
.. link
.. " module:",
}
end,
"",
list_modules_with_predicate(mods, function(data)
return vim.tbl_contains(core_defaults.parsed.config.public.enable, data.parsed.name)
and not data.top_comment_data.internal
end),
"",
"# Other Modules",
"",
"Some modules are not included by default as they require some manual configuration or are merely extra bells and whistles",
"and are not critical to editing `.norg` files. Below is a list of all modules that are not required by default:",
"",
list_modules_with_predicate(mods, function(data)
return not data.parsed.extension
and not vim.tbl_contains(core_defaults.parsed.config.public.enable, data.parsed.name)
and not data.top_comment_data.internal
end),
"",
"# Developer modules",
"",
"These are modules that are only meant for developers. They are generally required in other modules:",
"",
list_modules_with_predicate(mods, function(data)
return not data.parsed.extension and data.top_comment_data.internal
end),
}
return docgen.evaluate_functions(structure)
end,
--- Generates the _Sidebar.md file
---@param mods Modules #A table of modules
sidebar = function(mods)
local structure = {
"<div align='center'>",
"",
"# :star2: Neorg",
"</div>",
"",
"",
"- [Setup Guide](https://github.com/nvim-neorg/neorg/wiki/Setup-Guide)",
"- [Tutorial](https://github.com/nvim-neorg/neorg/wiki/Tutorial)",
"- [Default Keybinds](https://github.com/nvim-neorg/neorg/wiki/Default-Keybinds)",
"",
"<details>",
"<summary><h3>Inbuilt modules:</h3></summary>",
"",
function()
local res = {}
local names = {}
for n, data in pairs(mods) do
if data.parsed.extension ~= true then
table.insert(names, n)
end
end
table.sort(names)
for _, name in ipairs(names) do
local data = mods[name]
if not data.parsed.internal then
local insert = ""
if data.top_comment_data.file then
insert = insert
.. "- [`"
.. data.parsed.name
.. "`](https://github.com/nvim-neorg/neorg/wiki/"
.. data.top_comment_data.file
.. ")"
else
insert = insert .. "- `" .. name .. "`"
end
table.insert(res, insert)
end
end
return res
end,
"",
"</details>",
}
return docgen.evaluate_functions(structure)
end,
--- Generates the page for any Neorg module
---@param mods Modules #The list of currently loaded modules
---@param module Module #The module we want to generate the page for
---@param configuration string[] #An array of markdown strings detailing the configuration options for the module
---@return string[] #A table of markdown strings representing the page
module = function(mods, module, configuration)
local structure = {
'<div align="center">',
"",
"# `" .. module.parsed.name .. "`",
"",
"### " .. (module.top_comment_data.title or ""),
"",
module.top_comment_data.description or "",
"",
module.top_comment_data.embed and ("") or "",
"",
"</div>",
"",
function()
if module.top_comment_data.markdown and not vim.tbl_isempty(module.top_comment_data.markdown) then
return vim.list_extend({
"# Overview",
"",
}, module.top_comment_data.markdown)
end
return {}
end,
"",
"# Configuration",
"",
function()
if vim.tbl_isempty(configuration) then
return {
"This module provides no configuration options!",
}
else
return configuration
end
end,
"",
function()
local required_modules = module.parsed.setup().requires or {}
if vim.tbl_isempty(required_modules) then
return {}
end
local module_list = {}
for _, module_name in ipairs(required_modules) do
module_list[module_name] = mods[module_name]
end
return docgen.evaluate_functions({
"# Dependencies",
"",
list_modules_with_predicate(module_list, function()
return true
end),
})
end,
"",
function()
local required_by = {}
for mod, data in pairs(mods) do
local required_modules = data.parsed.setup().requires or {}
if vim.tbl_contains(required_modules, module.parsed.name) then
required_by[mod] = data
end
end
if vim.tbl_isempty(required_by) then
return {}
end
return docgen.evaluate_functions({
"# Required By",
"",
list_modules_with_predicate(required_by, function()
return true
end),
})
end,
}
return docgen.evaluate_functions(structure)
end,
keybinds = function(mods, buffer)
local keybind_data = docgen.parse_keybind_data(buffer)
local layout = {
'<div align="center">',
"",
"# :keyboard: Neorg Keybinds :keyboard:",
"A comprehensive list of all keys available in Neorg.",
"",
"</div>",
"",
"### Further Reading",
"",
docgen.lookup_modules(
mods,
"To find out how to rebind the available keys consult the [`core.keybinds`](@core.keybinds) wiki entry."
),
"",
}
for preset_name, preset_data in vim.spairs(keybind_data) do
for neorg_mode_name, neorg_mode_data in vim.spairs(preset_data) do
if neorg_mode_name == "all" then
table.insert(layout, string.format("## Preset `%s` // All Files", preset_name))
table.insert(layout, "")
elseif neorg_mode_name == "norg" then
table.insert(layout, string.format("## Preset `%s` // Norg Only", preset_name))
table.insert(layout, "")
end
for mode_name, mode_data in vim.spairs(neorg_mode_data) do
mode_name = lib.match(mode_name)({
n = "Normal Mode",
i = "Insert Mode",
v = "Visual Mode",
})
table.insert(layout, "### " .. mode_name)
table.insert(layout, "")
for key, data in vim.spairs(mode_data) do
if not vim.tbl_isempty(data.comments) then
local comments = vim.iter(data.comments)
:map(function(comment)
return (comment:gsub("^%s*%-%-%s*", ""))
end)
:totable()
local mnemonic = docgen.extract_mnemonic(comments)
local summary = comments[1]:sub(1, 1):lower() .. comments[1]:sub(2)
local description = vim.list_slice(comments, 2)
local err = docgen.check_comment_integrity(summary)
if err then
log.error("Invalid keybind description:", err)
end
table.insert(layout, "<details>")
table.insert(layout, "<summary>")
table.insert(layout, "")
table.insert(layout, string.format("#### `%s` - %s", key, summary))
table.insert(layout, "")
table.insert(layout, "</summary>")
table.insert(layout, "")
vim.list_extend(layout, description)
table.insert(layout, string.format("- Maps to: `%s`", data.rhs))
if mnemonic then
table.insert(
layout,
string.format("- Mnemonic: <code>%s</code>", docgen.format_mnemonic(mnemonic))
)
end
table.insert(layout, "")
table.insert(layout, "</details>")
table.insert(layout, "")
end
end
end
end
end
return layout
end,
}
--- Check the integrity of the description comments found in configuration blocks
---@param comment string #The comment to check the integrity of
---@return nil|string #`nil` for success, `string` if there was an error
docgen.check_comment_integrity = function(comment)
if comment:match("^%s*%-%-+%s*") then
return "found leading `--` comment text."
elseif comment:sub(1, 1):upper() ~= comment:sub(1, 1) then
return "comment does not begin with a capital letter."
elseif comment:find(" neorg ") then
return "`neorg` written with lowercase letter. Use uppercase instead."
end
end
--- Replaces all instances of a module reference (e.g. `@core.concealer`) with a link in the wiki
---@param mods Modules #The list of loaded modules
---@param str string #The string to perform the lookup in
---@return string #The original `str` parameter with all `@` references replaced with links
docgen.lookup_modules = function(mods, str)
return (
str:gsub("@([%-%.%w]+)", function(target_module_name)
if not mods[target_module_name] then
return table.concat({ "@", target_module_name })
else
return table.concat({
"https://github.com/nvim-neorg/neorg/wiki/",
mods[target_module_name].top_comment_data.file,
})
end
end)
)
end
--- Renders a treesitter node to a lua object
---@param node userdata #The node to render
---@param chunkname string? #The custom name to give to the chunk
---@return any #The converted object
docgen.to_lua_object = function(module, buffer, node, chunkname)
local loaded = loadstring(table.concat({ "return ", ts.get_node_text(node, buffer) }), chunkname)
if loaded then
return setfenv(loaded, vim.tbl_extend("force", getfenv(0), { module = module }))()
end
end
---@alias ConfigOptionArray { [string|number]: ConfigOptionArray, self: ConfigOption }
---@alias ConfigOption { buffer: number, data: ConfigOptionData, comments: string[], object: any }
--- Converts a ConfigOptionData struct to a html node in the resulting HTML document
---@param configuration_option ConfigOptionArray #The config option to render
---@param open boolean? #Whether to auto-open the generated `<details>` tag. Defaults to false.
---@return string[] #A list of markdown tables corresponding to the rendered element.
docgen.render = function(configuration_option, open)
open = open or false
local self = configuration_option.self
local type_of_object = (function()
local t = type(self.object)
if t == "table" then
return (vim.tbl_isempty(self.object) and "empty " or "")
.. (vim.tbl_islist(self.object) and "list" or "table")
else
return t
end
end)()
local basis = {
"* <details" .. (open and " open>" or ">"),
"",
((self.data.name or ""):match("^%s*$") and "<summary>" or table.concat({
"<summary><h6><code>",
self.data.name,
"</h6></code>",
})) .. " (" .. type_of_object .. ")</summary>",
"",
}
if not vim.tbl_isempty(self.comments) then
vim.list_extend(basis, {
"<div>",
"",
})
vim.list_extend(basis, self.comments)
vim.list_extend(basis, {
"",
"</div>",
"",
})
else
vim.list_extend(basis, {
"<br>",
"",
})
end
vim.list_extend(basis, docgen.htmlify(configuration_option))
vim.list_extend(basis, {
"",
"</details>",
})
for i, str in ipairs(basis) do
basis[i] = string.rep(" ", 2 - (i == 1 and 2 or 0)) .. str
end
return basis
end
--- Converts an object directly into HTML, with no extra fluff.
---@param configuration_option ConfigOptionArray
---@return string[] #An array of markdown strings with the rendered HTML inside
docgen.htmlify = function(configuration_option)
local self = configuration_option.self
local result = {}
local code_block = true
lib.match(self.data.value:type())({
string = function()
table.insert(result, table.concat({ '"', self.object, '"' }))
end,
table_constructor = function()
table.insert(result, "")
local unrolled = lib.unroll(self.object)
table.sort(unrolled, function(x, y)
return tostring(x[1]) < tostring(y[1])
end)
for _, data in ipairs(unrolled) do
---@type number|string
local name_or_index = data[1]
local subitem = configuration_option[name_or_index]
if subitem then
vim.list_extend(result, docgen.render(subitem))
end
end
table.insert(result, "")
code_block = false
end,
function_definition = function()
local text = ts.get_node_text(self.data.value, self.buffer):match("^function%s*(%b())")
if not text then
log.error(string.format("Unable to parse function, perhaps some wrong formatting?"))
table.insert(result, "<error: incorrect formatting>")
return
end
table.insert(result, "function" .. text)
end,
_ = function()
table.insert(result, ts.get_node_text(self.data.value, self.buffer))
end,
})
if code_block then
table.insert(result, 1, "```lua")
table.insert(result, "```")
end
return result
end
--- Parses keybind data and returns it in a readable format.
---@param buffer number The buffer ID to extract information from.
---@return table<string, table>
docgen.parse_keybind_data = function(buffer)
local query = utils.ts_parse_query(
"lua",
[[
(field
name: (identifier) @_ident
(#eq? @_ident "presets")) @presets
]]
)
local root = assert(vim.treesitter.get_parser(buffer, "lua"):parse()[1]:root(), "unable to parse keybinds!")
local _, presets = query:iter_captures(root, buffer)()
assert(presets, "could not find presets")
local available_keys = neorg.modules.loaded_modules["core.keybinds"].private.presets
local output = vim.defaulttable()
for preset in presets:named_child(1):iter_children() do
if preset:type() == "field" then
local preset_name, preset_data =
vim.treesitter.get_node_text(assert(preset:named_child(0)), buffer), preset:named_child(1)
for neorg_mode in assert(preset_data):iter_children() do
if neorg_mode:type() == "field" then
local neorg_mode_name, neorg_mode_data =
vim.treesitter.get_node_text(assert(neorg_mode:named_child(0)), buffer),
neorg_mode:named_child(1)
for neovim_mode in assert(neorg_mode_data):iter_children() do
if neovim_mode:type() == "field" then
local mode_name, mode_data =
vim.treesitter.get_node_text(assert(neovim_mode:named_child(0)), buffer),
neovim_mode:named_child(1)
local comments = {}
local i, keybind_data
for comment_or_data in assert(mode_data):iter_children() do
if comment_or_data:type() == "comment" then
table.insert(
comments,
vim.trim(vim.treesitter.get_node_text(comment_or_data, buffer))
)
elseif comment_or_data:type() == "field" then
i, keybind_data = next(available_keys[preset_name][neorg_mode_name][mode_name], i)
output[preset_name][neorg_mode_name][mode_name][keybind_data[1]] = {
comments = comments,
rhs = keybind_data[2],
}
comments = {}
end
end
end
end
end
end
end
end
return output
end
docgen.format_mnemonic = function(str)
return str:gsub("([A-Z])", "<strong>%1</strong>"):lower()
end
docgen.extract_mnemonic = function(comments)
for i, comment in ipairs(comments) do
local mnemonic = comment:match("^%s*%^(.+)")
if mnemonic then
table.remove(comments, i)
return mnemonic
end
end
end
return docgen