32 | config.cmdline = {}
33 |
34 | ---Set configuration for global.
35 | ---@param c cmp.ConfigSchema
36 | config.set_global = function(c)
37 | config.global = misc.merge(c, config.global)
38 | config.global.revision = config.global.revision or 1
39 | config.global.revision = config.global.revision + 1
40 | end
41 |
42 | ---Set configuration for buffer
43 | ---@param c cmp.ConfigSchema
44 | ---@param bufnr number|nil
45 | config.set_buffer = function(c, bufnr)
46 | local revision = (config.buffers[bufnr] or {}).revision or 1
47 | config.buffers[bufnr] = c
48 | config.buffers[bufnr].revision = revision + 1
49 | end
50 |
51 | ---Set configuration for cmdline
52 | config.set_cmdline = function(c, type)
53 | local revision = (config.cmdline[type] or {}).revision or 1
54 | config.cmdline[type] = c
55 | config.cmdline[type].revision = revision + 1
56 | end
57 |
58 | ---@return cmp.ConfigSchema
59 | config.get = function()
60 | local global = config.global
61 | if api.is_cmdline_mode() then
62 | local type = vim.fn.getcmdtype()
63 | local cmdline = config.cmdline[type] or { revision = 1, sources = {} }
64 | return config.cache:ensure({ "get_cmdline", type, global.revision or 0, cmdline.revision or 0 }, function()
65 | return misc.merge(config.normalize(cmdline), config.normalize(global))
66 | end)
67 | else
68 | local bufnr = vim.api.nvim_get_current_buf()
69 | local buffer = config.buffers[bufnr] or { revision = 1 }
70 | return config.cache:ensure({ "get_buffer", bufnr, global.revision or 0, buffer.revision or 0 }, function()
71 | return misc.merge(config.normalize(buffer), config.normalize(global))
72 | end)
73 | end
74 | end
75 |
76 | ---Return cmp is enabled or not.
77 | config.enabled = function()
78 | local enabled = config.get().enabled
79 | if type(enabled) == "function" then
80 | enabled = enabled()
81 | end
82 | return enabled and api.is_suitable_mode()
83 | end
84 |
85 | ---Return source config
86 | ---@param name string
87 | ---@return cmp.SourceConfig
88 | config.get_source_config = function(name)
89 | local bufnr = vim.api.nvim_get_current_buf()
90 | local global = config.global
91 | local buffer = config.buffers[bufnr] or { revision = 1 }
92 | return config.cache:ensure(
93 | { "get_source_config", bufnr, global.revision or 0, buffer.revision or 0, name },
94 | function()
95 | local c = config.get()
96 | for _, s in ipairs(c.sources) do
97 | if s.name == name then
98 | if type(s.opts) ~= "table" then
99 | s.opts = {}
100 | end
101 | return s
102 | end
103 | end
104 | return nil
105 | end
106 | )
107 | end
108 |
109 | ---Normalize mapping key
110 | ---@param c cmp.ConfigSchema
111 | ---@return cmp.ConfigSchema
112 | config.normalize = function(c)
113 | if c.mapping then
114 | for k, v in pairs(c.mapping) do
115 | c.mapping[keymap.normalize(k)] = v
116 | end
117 | end
118 | return c
119 | end
120 |
121 | return config
122 |
--------------------------------------------------------------------------------
/queries/lua/highlights.scm:
--------------------------------------------------------------------------------
1 | ;;; Highlighting for lua
2 |
3 | ;;; Builtins
4 | ;; Keywords
5 |
6 | [(if_start)
7 | (if_then)
8 | (if_elseif)
9 | (if_else)
10 | (if_end)] @keyword.conditional
11 |
12 | [(for_start)
13 | (for_in)
14 | (for_do)
15 | (for_end)] @keyword.repeat
16 |
17 | [(while_start)
18 | (while_do)
19 | (while_end)] @keyword.repeat
20 |
21 | [(repeat_start)
22 | (repeat_until)] @keyword.repeat
23 |
24 | (break_statement) @keyword.repeat
25 |
26 | [(return_statement)
27 | (module_return_statement)] @keyword.return
28 |
29 | [(do_start)
30 | (do_end)] @keyword
31 |
32 | ; [
33 | ; "goto"
34 | ; ] @keyword
35 |
36 | ;; Operators
37 |
38 | ; TODO: I think I've made a bunch of these nodes.
39 | ; we might be able to just use those!
40 |
41 | [
42 | "not"
43 | "and"
44 | "or"] @keyword.operator
45 |
46 | ["="
47 | "~="
48 | "=="
49 | "<="
50 | ">="
51 | "<"
52 | ">"
53 | "+"
54 | "-"
55 | "%"
56 | "/"
57 | "//"
58 | "*"
59 | "^"
60 | "&"
61 | "~"
62 | "|"
63 | ">>"
64 | "<<"
65 | ".."
66 | "#"] @operator
67 |
68 |
69 |
70 | ;; Punctuation
71 | ["," "."] @punctuation.delimiter
72 |
73 | ;; Brackets
74 | [(left_paren)
75 | (right_paren)
76 | "["
77 | "]"
78 | "{"
79 | "}"] @punctuation.bracket
80 |
81 | ;; Variables
82 | (identifier) @variable
83 | (
84 | (identifier) @variable.builtin
85 | (#match? @variable.builtin "self"))
86 |
87 | ; (preproc_call
88 | ; directive: (preproc_directive) @_u
89 | ; argument: (_) @constant
90 | ; (#eq? @_u "#undef"))
91 |
92 | ;; Constants
93 | (boolean) @boolean
94 | (nil) @constant.builtin
95 | (ellipsis) @constant ;; "..."
96 | (local) @keyword
97 |
98 | ;; Functions
99 | (function_call_paren) @function.bracket
100 |
101 | [
102 | (function_start)
103 | (function_end)] @keyword.function
104 |
105 | (emmy_type) @type
106 | (emmy_literal) @string
107 | (emmy_parameter
108 | (identifier) @parameter
109 | description: (_)? @comment) @comment
110 |
111 | (emmy_class) @comment
112 | (emmy_field name: (_) @property) @comment
113 | (emmy_function_parameter
114 | name: (_) @parameter)
115 |
116 | (emmy_type_dictionary_value key: (identifier) @property)
117 |
118 | (emmy_note) @comment
119 | (emmy_see) @comment
120 |
121 | ; TODO: Make the container so we can still highlight the beginning of the line
122 | ; (emmy_eval_container) @comment
123 | ; (_emmy_eval_container) @comment
124 |
125 | (emmy_return) @comment
126 |
127 | ; TODO: returns
128 |
129 | (emmy_header) @comment
130 | (emmy_ignore) @comment
131 | (documentation_brief) @comment
132 |
133 | (documentation_command) @comment
134 |
135 | (function_call
136 | [
137 | ((identifier)+ @identifier . (identifier) @function.call . (function_call_paren))
138 | ((identifier) @function.call.lua . (function_call_paren))])
139 |
140 | (function_call
141 | prefix: (identifier) @function.call.lua
142 | args: (string_argument) @string)
143 |
144 | (function_call
145 | prefix: (identifier) @function.call.lua
146 | args: (table_argument))
147 |
148 | ; (function [(function_name) (identifier)] @function)
149 | ; (function ["function" "end"] @keyword.function)
150 | ; (local_function [(function_name) (identifier)] @function)
151 | ; (local_function ["function" "end"] @keyword.function)
152 | ; (function_definition ["function" "end"] @keyword.function)
153 |
154 | ; TODO: Do I have replacements for these.
155 | ; (property_identifier) @property
156 | ; (method) @method
157 |
158 | ; (function_call (identifier) @function . (arguments))
159 | ; (function_call (field (property_identifier) @function) . (arguments))
160 |
161 | ;; Parameters
162 | ; (parameters (identifier) @parameter)
163 |
164 | ;; Nodes
165 | ; (table ["{" "}"] @constructor)
166 | (comment) @comment
167 | (string) @string
168 | (number) @number
169 | ; (label_statement) @label
170 |
171 | ;; Error
172 | (ERROR) @error
173 |
--------------------------------------------------------------------------------
/lua/tests/docgen/brief_spec.lua:
--------------------------------------------------------------------------------
1 | local docgen = require "docgen"
2 | local docgen_help = require "docgen.help"
3 |
4 | local eq = assert.are.same
5 |
6 | local dedent = require("plenary.strings").dedent
7 |
8 | local dedent_trim = function(x)
9 | return vim.trim(dedent(x))
10 | end
11 |
12 | local help_block = function(x)
13 | return "================================================================================\n"
14 | .. dedent_trim((x:gsub("\n%s*$", "")))
15 | end
16 |
17 | local get_dedented_nodes = function(source)
18 | return docgen.get_nodes(dedent(source))
19 | end
20 |
21 | describe("brief", function()
22 | local check_brief_nodes = function(input, brief_node)
23 | local nodes = get_dedented_nodes(input)
24 |
25 | eq({
26 | brief = brief_node,
27 | }, nodes)
28 | end
29 |
30 | it("should generate a brief", function()
31 | check_brief_nodes(
32 | [=[
33 | ---@brief [[
34 | --- Hello world
35 | ---@brief ]]
36 | ]=],
37 | { "Hello world" }
38 | )
39 | end)
40 |
41 | it("should generate a multi-line brief", function()
42 | check_brief_nodes(
43 | [=[
44 | ---@brief [[
45 | --- Hello world
46 | --- Yup again
47 | ---@brief ]]
48 | ]=],
49 | { "Hello world", "Yup again" }
50 | )
51 | end)
52 |
53 | it("keeps empty strings for empty lines", function()
54 | check_brief_nodes(
55 | [=[
56 | ---@brief [[
57 | --- Hello world
58 | ---
59 | --- Yup again
60 | ---@brief ]]
61 | ]=],
62 | { "Hello world", "", "Yup again" }
63 | )
64 | end)
65 |
66 | it("should keep indents in the inner strings", function()
67 | check_brief_nodes(
68 | [=[
69 | ---@brief [[
70 | --- Hello world:
71 | --- - This is indented
72 | --- - And this is some more
73 | --- - Not as indented
74 | ---@brief ]]
75 | ]=],
76 | {
77 | "Hello world:",
78 | " - This is indented",
79 | " - And this is some more",
80 | " - Not as indented",
81 | }
82 | )
83 | end)
84 |
85 | describe("help output", function()
86 | local function check_brief_output(input, output)
87 | local nodes = require("docgen").get_nodes(input)
88 | local result = docgen_help.format(nodes)
89 |
90 | eq(help_block(output), vim.trim(result))
91 | end
92 |
93 | it("should not wrap lines, if
", function()
94 | check_brief_output(
95 | [=[
96 | ---@brief [[
97 | --- Hello world
98 | --- Yup again
99 | ---@brief ]]
100 | ]=],
101 | [[
102 | Hello world
103 | Yup again
104 | ]]
105 | )
106 | end)
107 |
108 | it("should wrap lines", function()
109 | check_brief_output(
110 | [=[
111 | ---@brief [[
112 | --- Hello world
113 | --- Yup again
114 | ---@brief ]]
115 | ]=],
116 | [[
117 | Hello world Yup again
118 | ]]
119 | )
120 | end)
121 |
122 | it("should keep empty lines", function()
123 | check_brief_output(
124 | [=[
125 | ---@brief [[
126 | --- Hello world
127 | ---
128 | --- Yup again
129 | ---@brief ]]
130 | ]=],
131 | [[
132 | Hello world
133 |
134 | Yup again
135 | ]]
136 | )
137 | end)
138 |
139 | it("should keep indenting working", function()
140 | check_brief_output(
141 | [=[
142 | ---@brief [[
143 | --- Hello world:
144 | --- - This is indented
145 | --- - And this is some more
146 | --- - Not as indented
147 | ---@brief ]]
148 | ]=],
149 | [[
150 | Hello world:
151 | - This is indented
152 | - And this is some more
153 | - Not as indented
154 | ]]
155 | )
156 | end)
157 | end)
158 | end)
159 |
--------------------------------------------------------------------------------
/scratch/emmy_lua_grammar.lua:
--------------------------------------------------------------------------------
1 | local _ = [[
2 | EmmyLua <- ({} '---' EmmyBody {} ShortComment)
3 | -> EmmyLua
4 | EmmySp <- (!'---@' !'---' Comment / %s / %nl)*
5 | EmmyComments <- (EmmyComment (%nl EmmyComMulti / %nl EmmyComSingle)*)
6 | EmmyComment <- EmmySp %s* {(!%nl .)*}
7 | EmmyComMulti <- EmmySp '---|' {} -> en {(!%nl .)*}
8 | EmmyComSingle <- EmmySp '---' !'@' %s* {} -> ' ' {(!%nl .)*}
9 | EmmyBody <- '@class' %s+ EmmyClass -> EmmyClass
10 | / '@type' %s+ EmmyType -> EmmyType
11 | / '@alias' %s+ EmmyAlias -> EmmyAlias
12 | / '@param' %s+ EmmyParam -> EmmyParam
13 | / '@return' %s+ EmmyReturn -> EmmyReturn
14 | / '@field' %s+ EmmyField -> EmmyField
15 | / '@generic' %s+ EmmyGeneric -> EmmyGeneric
16 | / '@vararg' %s+ EmmyVararg -> EmmyVararg
17 | / '@language' %s+ EmmyLanguage -> EmmyLanguage
18 | / '@see' %s+ EmmySee -> EmmySee
19 | / '@overload' %s+ EmmyOverLoad -> EmmyOverLoad
20 | / %s* EmmyComments -> EmmyComment
21 | / EmmyIncomplete
22 | EmmyName <- ({} {[a-zA-Z_] [a-zA-Z0-9_]*})
23 | -> EmmyName
24 | MustEmmyName <- EmmyName / DirtyEmmyName
25 | DirtyEmmyName <- {} -> DirtyEmmyName
26 | EmmyLongName <- ({} {(!%nl .)+})
27 | -> EmmyName
28 | EmmyIncomplete <- MustEmmyName
29 | -> EmmyIncomplete
30 | EmmyClass <- (MustEmmyName EmmyParentClass?)
31 | EmmyParentClass <- %s* {} ':' %s* MustEmmyName
32 | EmmyType <- EmmyTypeUnits EmmyTypeEnums
33 | EmmyTypeUnits <- {|
34 | EmmyTypeUnit?
35 | (%s* '|' %s* !String EmmyTypeUnit)*
36 | |}
37 | EmmyTypeEnums <- {| EmmyTypeEnum* |}
38 | EmmyTypeUnit <- EmmyFunctionType
39 | / EmmyTableType
40 | / EmmyArrayType
41 | / EmmyCommonType
42 | EmmyCommonType <- EmmyName
43 | -> EmmyCommonType
44 | EmmyTypeEnum <- %s* (%nl %s* '---')? '|'? EmmyEnum
45 | -> EmmyTypeEnum
46 | EmmyEnum <- %s* {'>'?} %s* String (EmmyEnumComment / (!%nl !'|' .)*)
47 | EmmyEnumComment <- %s* '#' %s* {(!%nl .)*}
48 | EmmyAlias <- MustEmmyName %s* EmmyType EmmyTypeEnum*
49 | EmmyParam <- MustEmmyName %s* EmmyType %s* EmmyOption %s* EmmyTypeEnum*
50 | EmmyOption <- Table?
51 | -> EmmyOption
52 | EmmyReturn <- {} %nil {} Table -> EmmyOption
53 | / {} EmmyType {} EmmyOption
54 | EmmyField <- (EmmyFieldAccess MustEmmyName %s* EmmyType)
55 | EmmyFieldAccess <- ({'public'} Cut %s*)
56 | / ({'protected'} Cut %s*)
57 | / ({'private'} Cut %s*)
58 | / {} -> 'public'
59 | EmmyGeneric <- EmmyGenericBlock
60 | (%s* ',' %s* EmmyGenericBlock)*
61 | EmmyGenericBlock<- (MustEmmyName %s* (':' %s* EmmyType)?)
62 | -> EmmyGenericBlock
63 | EmmyVararg <- EmmyType
64 | EmmyLanguage <- MustEmmyName
65 | EmmyArrayType <- ({} MustEmmyName -> EmmyCommonType {} '[' DirtyBR)
66 | -> EmmyArrayType
67 | / ({} PL EmmyCommonType DirtyPR '[' DirtyBR)
68 | -> EmmyArrayType
69 | EmmyTableType <- ({} 'table' Cut '<' %s* EmmyType %s* ',' %s* EmmyType %s* '>' {})
70 | -> EmmyTableType
71 | EmmyFunctionType<- ({} 'fun' Cut %s* EmmyFunctionArgs %s* EmmyFunctionRtns {})
72 | -> EmmyFunctionType
73 | EmmyFunctionArgs<- ('(' %s* EmmyFunctionArg %s* (',' %s* EmmyFunctionArg %s*)* DirtyPR)
74 | -> EmmyFunctionArgs
75 | / '(' %nil DirtyPR -> None
76 | / %nil
77 | EmmyFunctionRtns<- (':' %s* EmmyType (%s* ',' %s* EmmyType)*)
78 | -> EmmyFunctionRtns
79 | / %nil
80 | EmmyFunctionArg <- MustEmmyName %s* ':' %s* EmmyType
81 | EmmySee <- {} MustEmmyName %s* '#' %s* MustEmmyName {}
82 | EmmyOverLoad <- EmmyFunctionType
83 | ]]
84 |
--------------------------------------------------------------------------------
/scratch/gh_issue_005.lua:
--------------------------------------------------------------------------------
1 | local m = {}
2 |
3 | --- Test Header 1
4 | function m.a01()
5 | return 0
6 | end
7 |
8 | --- Duh docs
9 | function m.asdf()
10 | return 5
11 | end
12 |
13 | --- Test Header 2
14 | function m.a02()
15 | return 0
16 | end
17 |
18 | --- Test Header 3
19 | function m.a03()
20 | return 0
21 | end
22 |
23 | --- Test Header 5
24 | function m.a05()
25 | return 0
26 | end
27 |
28 | --- Test Header 6
29 | function m.a06()
30 | return 0
31 | end
32 |
33 | --- Test Header 7
34 | function m.a07()
35 | return 0
36 | end
37 |
38 | --- Test Header 8
39 | function m.a08()
40 | return 0
41 | end
42 |
43 | --- Test Header 9
44 | function m.a09()
45 | return 0
46 | end
47 |
48 | --- Test Header 10
49 | function m.a10()
50 | return 0
51 | end
52 |
53 | --- Test Header 11
54 | function m.a11()
55 | return 0
56 | end
57 |
58 | --- Test Header 12
59 | function m.a12()
60 | return 0
61 | end
62 |
63 | --- Test Header 13
64 | function m.a13()
65 | return 0
66 | end
67 |
68 | --- Test Header 14
69 | function m.a14()
70 | return 0
71 | end
72 |
73 | --- Test Header 15
74 | function m.a15()
75 | return 0
76 | end
77 |
78 | --- Test Header 16
79 | function m.a16()
80 | return 0
81 | end
82 |
83 | --- Test Header 17
84 | function m.a17()
85 | return 0
86 | end
87 |
88 | --- Test Header 17
89 | function m.a17()
90 | return 0
91 | end
92 | --- Test Header 17
93 | function m.a17()
94 | return 0
95 | end
96 | --- Test Header 17
97 | function m.a17()
98 | return 0
99 | end
100 | --- Test Header 17
101 | function m.a17()
102 | return 0
103 | end
104 | --- Test Header 17
105 | function m.a17()
106 | return 0
107 | end
108 | --- Test Header 17
109 | function m.a17()
110 | return 0
111 | end
112 | --- Test Header 17
113 | function m.a17()
114 | return 0
115 | end
116 | --- Test Header 17
117 | function m.a17()
118 | return 0
119 | end
120 | --- Test Header 17
121 | function m.a17()
122 | return 0
123 | end
124 | --- Test Header 17
125 | function m.a17()
126 | return 0
127 | end
128 | --- Test Header 17
129 | function m.a17()
130 | return 0
131 | end
132 | --- Test Header 17
133 | function m.a17()
134 | return 0
135 | end
136 | --- Test Header 17
137 | function m.a17()
138 | return 0
139 | end
140 | --- Test Header 17
141 | function m.a17()
142 | return 0
143 | end
144 | --- Test Header 17
145 | function m.a17()
146 | return 0
147 | end
148 | --- Test Header 17
149 | function m.a17()
150 | return 0
151 | end
152 | --- Test Header 17
153 | function m.a17()
154 | return 0
155 | end
156 | --- Test Header 17
157 | function m.a17()
158 | return 0
159 | end
160 | --- Test Header 17
161 | function m.a17()
162 | return 0
163 | end
164 | --- Test Header 17
165 | function m.a17()
166 | return 0
167 | end
168 | --- Test Header 17
169 | function m.a17()
170 | return 0
171 | end
172 | --- Test Header 17
173 | function m.a17()
174 | return 0
175 | end
176 | --- Test Header 17
177 | function m.a17()
178 | return 0
179 | end
180 | --- Test Header 17
181 | function m.a17()
182 | return 0
183 | end
184 | --- Test Header 17
185 | function m.a17()
186 | return 0
187 | end
188 | --- Test Header 17
189 | function m.a17()
190 | return 0
191 | end
192 | --- Test Header 17
193 | function m.a17()
194 | return 0
195 | end
196 | --- Test Header 17
197 | function m.a17()
198 | return 0
199 | end
200 | --- Test Header 17
201 | function m.a17()
202 | return 0
203 | end
204 | --- Test Header 17
205 | function m.a17()
206 | return 0
207 | end
208 | --- Test Header 17
209 | function m.a17()
210 | return 0
211 | end
212 | --- Test Header 17
213 | function m.a17()
214 | return 0
215 | end
216 | --- Test Header 17
217 | function m.a17()
218 | return 0
219 | end
220 | --- Test Header 17
221 | function m.a17()
222 | return 0
223 | end
224 | --- Test Header 17
225 | function m.a17()
226 | return 0
227 | end
228 | --- Test Header 17
229 | function m.a17()
230 | return 0
231 | end
232 | --- Test Header 17
233 | function m.a17()
234 | return 0
235 | end
236 | --- Test Header 17
237 | function m.a17()
238 | return 0
239 | end
240 | --- Test Header 17
241 | function m.a17()
242 | return 0
243 | end
244 | --- Test Header 17
245 | function m.a17()
246 | return 0
247 | end
248 | --- Test Header 17
249 | function m.a17()
250 | return 0
251 | end
252 | --- Test Header 17
253 | function m.a17()
254 | return 0
255 | end
256 |
257 | return m
258 |
--------------------------------------------------------------------------------
/lua/tests/nlsp/text_document_sync_spec.lua:
--------------------------------------------------------------------------------
1 | if true then
2 | return
3 | end
4 |
5 | local methods = require "nlsp.methods"
6 | local state = require "nlsp.state"
7 | -- local structures = require('nlsp.structures')
8 |
9 | local query = require "vim.treesitter.query"
10 |
11 | describe("text_document_sync", function()
12 | before_each(function()
13 | -- Clear the state between executions.
14 | state._clear()
15 | end)
16 |
17 | it("should support opening a textDocument", function()
18 | local uri = vim.uri_from_fname "/home/fake.lua"
19 | local item = {
20 | uri = uri,
21 | text = "local hello = 'world'",
22 | }
23 |
24 | methods["textDocument/didOpen"] {
25 | textDocument = item,
26 | }
27 |
28 | local saved_item = state.get_text_document_item(uri)
29 | assert.are.same(saved_item.text, item.text)
30 | end)
31 |
32 | it("should support changing a textDocument", function()
33 | local uri = vim.uri_from_fname "/home/fake.lua"
34 | local item = {
35 | uri = uri,
36 | text = "local hello = 'world'",
37 | }
38 |
39 | local new_text = "local hello = 'goodbye'"
40 |
41 | methods["textDocument/didOpen"] {
42 | textDocument = vim.deepcopy(item),
43 | }
44 |
45 | methods["textDocument/didChange"] {
46 | textDocument = {
47 | uri = uri,
48 | },
49 | contentChanges = {
50 | { text = new_text },
51 | },
52 | }
53 |
54 | local saved_item = state.get_text_document_item(uri)
55 | assert.are.same(saved_item.text, new_text)
56 | assert.are_not.same(item.text, new_text)
57 | end)
58 |
59 | it("should not allow calling didChange before didOpen", function()
60 | local uri = vim.uri_from_fname "/home/fake.lua"
61 |
62 | local ok = pcall(methods["textDocument/didChange"], {
63 | textDocument = {
64 | uri = uri,
65 | },
66 | contentChanges = {
67 | { text = "this should not matter" },
68 | },
69 | })
70 |
71 | assert(not ok)
72 | end)
73 |
74 | it("should support saving", function()
75 | local uri = vim.uri_from_fname "/home/fake.lua"
76 | local item = {
77 | uri = uri,
78 | text = "local hello = 'world'",
79 | }
80 |
81 | local new_text = "local hello = 'goodbye'"
82 |
83 | methods["textDocument/didOpen"] {
84 | textDocument = vim.deepcopy(item),
85 | }
86 |
87 | methods["textDocument/didSave"] {
88 | textDocument = {
89 | uri = uri,
90 | },
91 | text = new_text,
92 | }
93 |
94 | local saved_item = state.get_text_document_item(uri)
95 | assert.are.same(saved_item.text, new_text)
96 | assert.are_not.same(item.text, new_text)
97 | end)
98 |
99 | it("should not allow calling didSave before didOpen", function()
100 | local uri = vim.uri_from_fname "/home/fake.lua"
101 |
102 | local ok = pcall(methods["textDocument/didSave"], {
103 | textDocument = {
104 | uri = uri,
105 | },
106 | text = "this should not matter",
107 | })
108 |
109 | assert(not ok)
110 | end)
111 |
112 | it("should allow closing documents", function()
113 | local uri = vim.uri_from_fname "/home/fake.lua"
114 | local item = {
115 | uri = uri,
116 | text = "local hello = 'world'",
117 | }
118 |
119 | methods["textDocument/didOpen"] {
120 | textDocument = vim.deepcopy(item),
121 | }
122 |
123 | methods["textDocument/didClose"] {
124 | textDocument = {
125 | uri = uri,
126 | },
127 | }
128 |
129 | local saved_item = state.get_text_document_item(uri)
130 | assert(saved_item == nil)
131 | end)
132 |
133 | describe("parser:parse()", function()
134 | it("should create a parser on open", function()
135 | local uri = vim.uri_from_fname "/home/fake.lua"
136 | local item = {
137 | uri = uri,
138 | text = "local hello = 'world'",
139 | }
140 |
141 | methods["textDocument/didOpen"] {
142 | textDocument = item,
143 | }
144 |
145 | local parser = state.get_ts_parser(uri)
146 | assert.are_not.same(parser, nil)
147 |
148 | parser = parser:parse()[1]
149 | local root = parser:root()
150 | assert.are.same(vim.treesitter.get_node_text(root, item.text), item.text)
151 | assert.are.same(root:type(), "program")
152 | end)
153 | end)
154 | end)
155 |
--------------------------------------------------------------------------------
/lua/docgen/log.lua:
--------------------------------------------------------------------------------
1 | -- log.lua
2 | --
3 | -- Inspired by rxi/log.lua
4 | -- Modified by tjdevries and can be found at github.com/tjdevries/vlog.nvim
5 | --
6 | -- This library is free software; you can redistribute it and/or modify it
7 | -- under the terms of the MIT license. See LICENSE for details.
8 |
9 | -- User configuration section
10 | local default_config = {
11 | -- Name of the plugin. Prepended to log messages
12 | plugin = "docgen.nvim",
13 |
14 | -- Should print the output to neovim while running
15 | use_console = true,
16 |
17 | -- Should highlighting be used in console (using echohl)
18 | highlights = true,
19 |
20 | -- Should write to a file
21 | use_file = true,
22 |
23 | -- Any messages above this level will be logged.
24 | level = "info",
25 |
26 | -- Level configuration
27 | modes = {
28 | { name = "trace", hl = "Comment" },
29 | { name = "debug", hl = "Comment" },
30 | { name = "info", hl = "None" },
31 | { name = "warn", hl = "WarningMsg" },
32 | { name = "error", hl = "ErrorMsg" },
33 | { name = "fatal", hl = "ErrorMsg" },
34 | },
35 |
36 | -- Can limit the number of decimals displayed for floats
37 | float_precision = 0.01,
38 | }
39 |
40 | -- {{{ NO NEED TO CHANGE
41 | local log = {}
42 |
43 | local unpack = unpack or table.unpack
44 |
45 | log.new = function(config, standalone)
46 | config = vim.tbl_deep_extend("force", default_config, config)
47 |
48 | local outfile = string.format("%s/%s.log", vim.api.nvim_call_function("stdpath", { "data" }), config.plugin)
49 |
50 | local obj
51 | if standalone then
52 | obj = log
53 | else
54 | obj = {}
55 | end
56 |
57 | local levels = {}
58 | for i, v in ipairs(config.modes) do
59 | levels[v.name] = i
60 | end
61 |
62 | local round = function(x, increment)
63 | increment = increment or 1
64 | x = x / increment
65 | return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
66 | end
67 |
68 | local make_string = function(...)
69 | local t = {}
70 | for i = 1, select("#", ...) do
71 | local x = select(i, ...)
72 |
73 | if type(x) == "number" and config.float_precision then
74 | x = tostring(round(x, config.float_precision))
75 | elseif type(x) == "table" then
76 | x = vim.inspect(x)
77 | else
78 | x = tostring(x)
79 | end
80 |
81 | t[#t + 1] = x
82 | end
83 | return table.concat(t, " ")
84 | end
85 |
86 | local log_at_level = function(level, level_config, message_maker, ...)
87 | -- Return early if we're below the config.level
88 | if level < levels[config.level] then
89 | return
90 | end
91 | local nameupper = level_config.name:upper()
92 |
93 | local msg = message_maker(...)
94 | local info = debug.getinfo(2, "Sl")
95 | local lineinfo = info.short_src .. ":" .. info.currentline
96 |
97 | -- Output to console
98 | if config.use_console then
99 | local console_string = string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg)
100 |
101 | if config.highlights and level_config.hl then
102 | vim.cmd(string.format("echohl %s", level_config.hl))
103 | end
104 |
105 | local split_console = vim.split(console_string, "\n")
106 | for _, v in ipairs(split_console) do
107 | vim.cmd(string.format([[echom "[%s] %s"]], config.plugin, vim.fn.escape(v, '"')))
108 | end
109 |
110 | if config.highlights and level_config.hl then
111 | vim.cmd "echohl NONE"
112 | end
113 | end
114 |
115 | -- Output to log file
116 | if config.use_file then
117 | local fp = io.open(outfile, "a")
118 | local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg)
119 | fp:write(str)
120 | fp:close()
121 | end
122 | end
123 |
124 | for i, x in ipairs(config.modes) do
125 | obj[x.name] = function(...)
126 | return log_at_level(i, x, make_string, ...)
127 | end
128 |
129 | obj[("fmt_%s"):format(x.name)] = function()
130 | return log_at_level(i, x, function(...)
131 | local passed = { ... }
132 | local fmt = table.remove(passed, 1)
133 | local inspected = {}
134 | for _, v in ipairs(passed) do
135 | table.insert(inspected, vim.inspect(v))
136 | end
137 | return string.format(fmt, unpack(inspected))
138 | end)
139 | end
140 | end
141 | end
142 |
143 | log.new(default_config, true)
144 | -- }}}
145 |
146 | return log
147 |
--------------------------------------------------------------------------------
/test/corpus/blocks.txt:
--------------------------------------------------------------------------------
1 |
2 | ==================
3 | Can do simple do block
4 | ==================
5 |
6 | do x = 1 end
7 |
8 | ---
9 |
10 | (program
11 | (do_statement
12 | (do_start)
13 | (variable_declaration
14 | (variable_declarator (identifier))
15 | (number))
16 | (do_end)))
17 |
18 | ==================
19 | Can have do blocks with more than one thing
20 | ==================
21 |
22 | do
23 | x = 1
24 | my_func()
25 | end
26 |
27 | ---
28 |
29 | (program
30 | (do_statement
31 | (do_start)
32 | (variable_declaration
33 | (variable_declarator (identifier))
34 | (number))
35 | (function_call
36 | (identifier) (function_call_paren) (function_call_paren))
37 | (do_end)))
38 |
39 | ==================
40 | While blocks
41 | ==================
42 |
43 | while false do x = x + 1 end
44 |
45 | ---
46 |
47 | (program
48 | (while_statement
49 | (while_start)
50 | (boolean)
51 | (while_do)
52 | (variable_declaration
53 | (variable_declarator (identifier))
54 | (binary_operation (identifier) (number)))
55 | (while_end)))
56 |
57 | ==================
58 | Repeat block
59 | ==================
60 |
61 | repeat x = x + 1 until true
62 |
63 | ---
64 |
65 | (program
66 | (repeat_statement
67 | (repeat_start)
68 | (variable_declaration
69 | (variable_declarator (identifier))
70 | (binary_operation (identifier) (number)))
71 | (repeat_until)
72 | (boolean)))
73 |
74 | ==================
75 | If blocks
76 | ==================
77 |
78 | if x then return y end
79 |
80 | ---
81 |
82 | (program
83 | (if_statement
84 | (if_start)
85 | (identifier)
86 | (if_then)
87 | (return_statement (identifier))
88 | (if_end)))
89 |
90 | ==================
91 | If elseif else blocks
92 | ==================
93 |
94 | if x == 0 then return y elseif x == nil then return 7 else return "str" end
95 |
96 | ---
97 |
98 | (program
99 | (if_statement
100 | (if_start)
101 | (binary_operation (identifier) (number))
102 | (if_then)
103 | (return_statement (identifier))
104 | (if_elseif)
105 | (binary_operation (identifier) (nil))
106 | (if_then)
107 | (return_statement (number))
108 | (if_else)
109 | (return_statement (string))
110 | (if_end)))
111 |
112 | ==================
113 | For loop, identifier style
114 | ==================
115 |
116 | for x = 1, 10 do print(x) end
117 |
118 | ---
119 |
120 | (program
121 | (for_statement
122 | (for_start)
123 | (for_numeric
124 | var: (identifier)
125 | start: (number)
126 | finish: (number))
127 | (for_do)
128 | (function_call
129 | prefix: (identifier)
130 | (function_call_paren)
131 | args: (function_arguments (identifier))
132 | (function_call_paren))
133 | (for_end)))
134 |
135 | ==================
136 | For loop, identifier style
137 | ==================
138 |
139 | for x = 1, 10, z do print(x) end
140 |
141 | ---
142 |
143 | (program
144 | (for_statement
145 | (for_start)
146 | (for_numeric
147 | var: (identifier)
148 | start: (number)
149 | finish: (number)
150 | step: (identifier))
151 | (for_do)
152 | (function_call
153 | prefix: (identifier)
154 | (function_call_paren)
155 | args: (function_arguments (identifier))
156 | (function_call_paren))
157 | (for_end)))
158 |
159 | ==================
160 | For loop, identifier style
161 | ==================
162 |
163 | for k, v in ipairs(x) do print(k, v) end
164 |
165 | ---
166 |
167 | (program
168 | (for_statement
169 | (for_start)
170 | (for_generic
171 | identifier_list: (identifier_list (identifier) (identifier))
172 | (for_in)
173 | expression_list: (function_call
174 | prefix: (identifier)
175 | (function_call_paren)
176 | args: (function_arguments (identifier))
177 | (function_call_paren)))
178 | (for_do)
179 | (function_call
180 | prefix: (identifier)
181 | (function_call_paren)
182 | args: (function_arguments (identifier) (identifier))
183 | (function_call_paren))
184 | (for_end)))
185 |
186 | ==================
187 | Can handle for loops with no body
188 | ==================
189 |
190 | for _, x in ipairs(mylist) do
191 | end
192 |
193 | ---
194 |
195 | (program
196 | (for_statement
197 | (for_start)
198 | (for_generic
199 | (identifier_list
200 | (identifier)
201 | (identifier))
202 | (for_in)
203 | (function_call
204 | (identifier)
205 | (function_call_paren)
206 | (function_arguments
207 | (identifier))
208 | (function_call_paren)))
209 | (for_do)
210 | (for_end)))
211 |
212 | ==================
213 | Returns from if statement with no values
214 | ==================
215 |
216 | if not diagnostics then return end
217 |
218 | ---
219 |
220 | (program
221 | (if_statement
222 | (if_start)
223 | (unary_operation (identifier))
224 | (if_then)
225 | (return_statement)
226 | (if_end)))
227 |
--------------------------------------------------------------------------------
/test/corpus/functions.txt:
--------------------------------------------------------------------------------
1 |
2 | ==================
3 | Declare global function
4 | ==================
5 |
6 | function x() return 5 end
7 |
8 | ---
9 |
10 | (program
11 | (function_statement
12 | (function_start)
13 | name: (function_name (identifier))
14 | (function_body_paren)
15 | (function_body_paren)
16 | (function_body (return_statement (number)))
17 | (function_end)))
18 |
19 | ==================
20 | Declare table function
21 | ==================
22 |
23 | function t.x() return 5 end
24 |
25 | ---
26 |
27 | (program
28 | (function_statement
29 | (function_start)
30 | name: (function_name (identifier) (table_dot) (identifier))
31 | (function_body_paren)
32 | (function_body_paren)
33 | (function_body (return_statement (number)))
34 | (function_end)))
35 |
36 | ==================
37 | Declare table function
38 | ==================
39 |
40 | function t:x() return 5 end
41 |
42 | ---
43 |
44 | (program
45 | (function_statement
46 | (function_start)
47 | name: (function_name (identifier) (table_colon) (identifier))
48 | (function_body_paren)
49 | (function_body_paren)
50 | (function_body (return_statement (number)))
51 | (function_end)))
52 |
53 | ==================
54 | Declare local function
55 | ==================
56 |
57 | local function f() print("hi"); return 5 end
58 |
59 | ---
60 |
61 | (program
62 | (function_statement
63 | (local)
64 | (function_start)
65 | name: (identifier)
66 | (function_body_paren)
67 | (function_body_paren)
68 | (function_body
69 | (function_call
70 | prefix: (identifier)
71 | (function_call_paren)
72 | args: (function_arguments (string))
73 | (function_call_paren))
74 | (return_statement (number)))
75 | (function_end)))
76 |
77 | ==================
78 | Declare local function, error
79 | ==================
80 |
81 | local function t.x() return 5 end
82 |
83 | ---
84 |
85 | (program
86 | (function_statement
87 | (local)
88 | (function_start)
89 | (identifier)
90 | (ERROR (UNEXPECTED 'x'))
91 | (function_body_paren)
92 | (function_body_paren)
93 | (function_body (return_statement (number)))
94 | (function_end)))
95 |
96 | ==================
97 | Declare function with an argument
98 | ==================
99 |
100 | function f(x) end
101 |
102 | ---
103 |
104 | (program
105 | (function_statement
106 | (function_start)
107 | (function_name (identifier))
108 | (function_body_paren)
109 | (parameter_list (identifier))
110 | (function_body_paren)
111 | (function_end)))
112 |
113 | ==================
114 | No trailing commas in function declaration
115 | ==================
116 |
117 | function f(x,) end
118 |
119 | ---
120 |
121 | (program
122 | (function_statement
123 | (function_start)
124 | (function_name (identifier))
125 | (function_body_paren)
126 | (parameter_list (identifier))
127 | (ERROR)
128 | (function_body_paren)
129 | (function_end)))
130 |
131 | ==================
132 | Declare function with two arguments
133 | ==================
134 |
135 | function f(wow, two_vars) end
136 |
137 | ---
138 |
139 | (program
140 | (function_statement
141 | (function_start)
142 | (function_name (identifier))
143 | (function_body_paren)
144 | (parameter_list (identifier) (identifier))
145 | (function_body_paren)
146 | (function_end)))
147 |
148 | ==================
149 | Declare function with ellipsis
150 | ==================
151 |
152 | function f(...) end
153 |
154 | ---
155 |
156 | (program
157 | (function_statement
158 | (function_start)
159 | (function_name (identifier))
160 | (function_body_paren)
161 | (parameter_list (ellipsis))
162 | (function_body_paren)
163 | (function_end)))
164 |
165 | ==================
166 | Declare function with one arg and ellipsis
167 | ==================
168 |
169 | function f(x, ...) end
170 |
171 | ---
172 |
173 | (program
174 | (function_statement
175 | (function_start)
176 | (function_name (identifier))
177 | (function_body_paren)
178 | (parameter_list (identifier) (ellipsis))
179 | (function_body_paren)
180 | (function_end)))
181 |
182 | ==================
183 | Declare function with multiple args and ellipsis
184 | ==================
185 |
186 | function f(x, y, z, ...) end
187 |
188 | ---
189 |
190 | (program
191 | (function_statement
192 | (function_start)
193 | (function_name (identifier))
194 | (function_body_paren)
195 | (parameter_list (identifier) (identifier) (identifier) (ellipsis))
196 | (function_body_paren)
197 | (function_end)))
198 |
199 | ==================
200 | Declare a function with documentation ahead of it
201 | ==================
202 |
203 | ---
204 | function f() end
205 |
206 | ---
207 |
208 | (program
209 | (function_statement
210 | (emmy_documentation
211 | (emmy_header))
212 | (function_start)
213 | (function_name
214 | (identifier))
215 | (function_body_paren)
216 | (function_body_paren)
217 | (function_end)))
218 |
--------------------------------------------------------------------------------
/lua/docgen/init.lua:
--------------------------------------------------------------------------------
1 | vim.cmd [[runtime plugin/ts_lua.lua]]
2 |
3 | local call_transformer = require "docgen.transformers"
4 | local log = require "docgen.log"
5 | local utils = require "docgen.utils"
6 |
7 | local read = utils.read
8 |
9 | ---@brief [[
10 | --- Public API for all associated docgen procedures.
11 | ---
12 | --- Supported tags:
13 | --- - @brief:
14 | --- - Usage:
15 | ---
16 | --- ---@brief [[
17 | --- --- You can put things you want to say about the project here.
18 | --- --- It gets put at the top of the help file.
19 | --- ---@brief ]]
20 |
21 | ---@brief ]]
22 |
23 | ---@tag docgen
24 |
25 | -- Load up our build parser.
26 |
27 | local docgen = {}
28 |
29 | docgen.debug = false
30 |
31 | function docgen._get_query_text(query_name)
32 | return read(vim.api.nvim_get_runtime_file(string.format("query/lua/%s.scm", query_name), false)[1])
33 | end
34 |
35 | --- Get the query for a tree sitter query, loaded from query directory.
36 | ---@param query_name string: The name of the query file (without .scm)
37 | function docgen.get_ts_query(query_name)
38 | return vim.treesitter.query.parse("lua", docgen._get_query_text(query_name))
39 | end
40 |
41 | --- Get the string parser for some contents
42 | function docgen.get_parser(contents)
43 | return vim.treesitter.get_string_parser(contents, "lua")
44 | end
45 |
46 | --- Run {cb} on each node from contents and query
47 | ---@param contents string: Contents to pass to string parser
48 | ---@param query_name string: Name of the query to search for
49 | ---@param cb function: Function to call on captures with (id, node)
50 | function docgen.foreach_node(contents, query_name, cb)
51 | local parser = docgen.get_parser(contents)
52 | local query = docgen.get_ts_query(query_name)
53 |
54 | local tree = parser:parse()[1]
55 |
56 | for id, node in query:iter_captures(tree:root(), contents, 0, -1) do
57 | log.debug(id, node:type())
58 |
59 | cb(id, node)
60 | end
61 | end
62 |
63 | function docgen.transform_nodes(contents, query_name, toplevel_types, return_module)
64 | local t = {}
65 |
66 | t.return_module = return_module
67 |
68 | docgen.foreach_node(contents, query_name, function(id, node)
69 | if toplevel_types[node:type()] then
70 | local ok, result = pcall(call_transformer, t, contents, node, return_module)
71 | if not ok then
72 | print("ERROR:", id, node, result)
73 | end
74 | end
75 | end)
76 |
77 | return t
78 | end
79 |
80 | local function find_return_module(contents)
81 | local parser = docgen.get_parser(contents)
82 | -- Its better to just have one query here. That way i know as soon as i found the
83 | -- module return statement i am done. Its bad for performance that i am parsing the
84 | -- file twice now but its no performance critical thing, so nvm
85 | local query = vim.treesitter.query.parse("lua", "(module_return_statement (identifier) @exported)")
86 | local tree = parser:parse()[1]
87 |
88 | for _, node in query:iter_captures(tree:root(), contents, 0, -1) do -- luacheck: ignore
89 | return vim.treesitter.get_node_text(node, contents)
90 | end
91 | end
92 |
93 | function docgen.get_nodes(contents)
94 | local query_name = "documentation"
95 | local toplevel_types = {
96 | variable_declaration = true,
97 | function_statement = true,
98 | documentation_brief = true,
99 | documentation_tag = true,
100 | documentation_config = true,
101 | documentation_class = true,
102 | documentation_command = true,
103 | }
104 |
105 | -- Can be nil here. That way it still works if the file only has a brief.
106 | -- I do the nil check in the transformer _function
107 | local return_module = find_return_module(contents)
108 |
109 | return docgen.transform_nodes(contents, query_name, toplevel_types, return_module)
110 | end
111 |
112 | function docgen.write(input_file, output_file_handle)
113 | log.trace("Transforming:", input_file)
114 | local contents = read(input_file)
115 | local resulting_nodes = docgen.get_nodes(contents)
116 |
117 | if docgen.debug then
118 | print("Resulting Nodes:", vim.inspect(resulting_nodes))
119 | end
120 |
121 | local result = require("docgen.help").format(resulting_nodes)
122 | output_file_handle:write(result)
123 | end
124 |
125 | function docgen.test()
126 | local input_dir = "./lua/docgen/"
127 | local input_files = vim.fn.globpath(input_dir, "**/*.lua", false, true)
128 |
129 | -- Always put init.lua first, then you can do other stuff.
130 | table.sort(input_files, function(a, b)
131 | if string.find(a, "init.lua") then
132 | return true
133 | elseif string.find(b, "init.lua") then
134 | return false
135 | else
136 | return a < b
137 | end
138 | end)
139 |
140 | local output_file = "./scratch/docgen_output.txt"
141 | local output_file_handle = io.open(output_file, "w")
142 |
143 | for _, input_file in ipairs(input_files) do
144 | docgen.write(input_file, output_file_handle)
145 | end
146 |
147 | output_file_handle:write " vim:tw=78:ts=8:ft=help:norl:"
148 | output_file_handle:close()
149 | vim.cmd [[checktime]]
150 | end
151 |
152 | --[[
153 | ["M.example"] = {
154 | name = "M.example",
155 | description = "--- Example function",
156 | parameters = {
157 | a = {
158 | description = { "This is a number" },
159 | name = "a",
160 | type = "number"
161 | },
162 | b = {
163 | description = { "Also a number" },
164 | name = "b",
165 | type = "number"
166 | }
167 | }
168 | }
169 | --]]
170 |
171 | return docgen
172 |
--------------------------------------------------------------------------------
/scratch/gen_howto.lua:
--------------------------------------------------------------------------------
1 | local docgen = require "docgen"
2 | local docgen_help = require "docgen.help"
3 | local docgen_util = require "docgen.utils"
4 |
5 | local dedent = require("plenary.strings").dedent
6 |
7 | local is_empty = function(content)
8 | local lines = vim.split(content, "\n")
9 | for i = 2, #lines do
10 | if lines[i] ~= "" then
11 | return false
12 | end
13 | end
14 | return true
15 | end
16 |
17 | local docs = {}
18 |
19 | docs.test = function()
20 | -- Filepaths that should generate docs
21 | local input_files = {
22 | {
23 | head = "## Brief",
24 | pre_desc = "Brief is used to describe a module. This is an example input:",
25 | input = "./example/brief.lua",
26 | post_desc = "",
27 | },
28 | {
29 | head = "## Tag",
30 | pre_desc = "Add a tag to your module. This is suggested:",
31 | input = "./example/tag.lua",
32 | post_desc = "",
33 | },
34 | {
35 | head = "## Config",
36 | pre_desc = "You can configure docgen on file basis. For example you can define how `functions` or `classes` are sorted.",
37 | input = "./example/config.lua",
38 | post_desc = dedent [[
39 | Available keys value pairs are:
40 | - `function_order`:
41 | - `file_order` (default)
42 | - `ascending`
43 | - `descending`
44 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
45 | - If you have a typo it will do `file_order` sorting
46 | - `class_order`:
47 | - `file_order` (default)
48 | - `ascending`
49 | - `descending`
50 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
51 | - If you have a typo it will do `file_order` sorting
52 | - `field_order`:
53 | - `file_order` (default)
54 | - `ascending`
55 | - `descending`
56 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
57 | - If you have a typo it will do `file_order` sorting]],
58 | },
59 | {
60 | head = "## Function Header",
61 | pre_desc = dedent [[
62 | You can describe your functions.
63 |
64 | Note: We will only generate documentation for functions that are exported with the module.]],
65 | input = "./example/function.lua",
66 | post_desc = "",
67 | },
68 | {
69 | head = "## Parameter",
70 | pre_desc = "You can specify parameters and document them with `---@param name type: desc`",
71 | input = "./example/parameter.lua",
72 | post_desc = "",
73 | },
74 | {
75 | head = "## Field",
76 | pre_desc = "Can be used to describe a parameter table.",
77 | input = "./example/field.lua",
78 | post_desc = "",
79 | },
80 | {
81 | head = "## Return",
82 | pre_desc = "You can specify a return parameter with `---@return type: desc`",
83 | input = "./example/return.lua",
84 | post_desc = "",
85 | },
86 | {
87 | head = "## See",
88 | pre_desc = "Reference something else.",
89 | input = "./example/see.lua",
90 | post_desc = "",
91 | },
92 | {
93 | head = "## Class",
94 | pre_desc = dedent [[
95 | You can define your own classes and types to give a better sense of the Input or Ouput of a function.
96 | Another good usecase for this are structs defined by ffi.
97 |
98 | This is a more complete (not functional) example where we define the documentation of the c struct
99 | `passwd` and return this struct with a function.]],
100 | input = "./example/class.lua",
101 | post_desc = "",
102 | },
103 | {
104 | head = "## Eval",
105 | pre_desc = dedent [[
106 | You can evaluate arbitrary code. For example if you have a static table you can
107 | do generate a table that will be part of the `description` output.
108 | ]],
109 | input = "./example/eval.lua",
110 | -- TODO(conni2461):
111 | -- Hard code eval because currently it doesn't create something.
112 | -- Your module doesn't exist. We can tackle this when we can have multiline
113 | -- eval
114 | post_desc = dedent [[
115 |
116 | Output:
117 |
118 | ```
119 | ================================================================================
120 | m.actual_func() *m.actual_func()*
121 | The documentation for this function will be generated. The markdown
122 | renderer will be used again.
123 | With the same set of features.
124 |
125 | Static Values: ~
126 | a
127 | b
128 | c
129 | d
130 | ```]],
131 | },
132 | }
133 |
134 | local output_file_handle = io.open("HOWTO.md", "w")
135 | output_file_handle:write "# How to write emmy documentation\n\n"
136 | for _, file in pairs(input_files) do
137 | output_file_handle:write(file.head .. "\n\n")
138 | if file.pre_desc ~= "" then
139 | output_file_handle:write(file.pre_desc .. "\n\n")
140 | end
141 |
142 | local content = docgen_util.read(file.input)
143 | output_file_handle:write("```lua\n" .. content .. "\n```\n")
144 |
145 | local output = docgen_help.format(docgen.get_nodes(content))
146 | if not is_empty(output) then
147 | output_file_handle:write("\nOutput:\n\n```\n" .. output .. "\n```\n\n")
148 | end
149 |
150 | if file.post_desc ~= "" then
151 | output_file_handle:write(file.post_desc .. "\n\n")
152 | end
153 | end
154 | output_file_handle:close()
155 | end
156 |
157 | docs.test()
158 |
159 | return docs
160 |
--------------------------------------------------------------------------------
/src/scanner.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | enum TokenType {
6 | BLOCK_COMMENT_START,
7 | BLOCK_COMMENT_CONTENT,
8 | BLOCK_COMMENT_END,
9 |
10 | STRING_START,
11 | STRING_CONTENT,
12 | STRING_END,
13 | };
14 |
15 | static inline void consume(TSLexer *lexer) { lexer->advance(lexer, false); }
16 | static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
17 |
18 | static inline bool consume_char(char c, TSLexer *lexer) {
19 | if (lexer->lookahead != c) {
20 | return false;
21 | }
22 |
23 | consume(lexer);
24 | return true;
25 | }
26 |
27 | static inline uint8_t consume_and_count_char(char c, TSLexer *lexer) {
28 | uint8_t count = 0;
29 | while (lexer->lookahead == c) {
30 | ++count;
31 | consume(lexer);
32 | }
33 | return count;
34 | }
35 |
36 | static inline void skip_whitespaces(TSLexer *lexer) {
37 | while (iswspace(lexer->lookahead)) {
38 | skip(lexer);
39 | }
40 | }
41 |
42 | void *tree_sitter_lua_external_scanner_create() { return NULL; }
43 | void tree_sitter_lua_external_scanner_destroy(void *payload) {}
44 |
45 | char ending_char = 0;
46 | uint8_t level_count = 0;
47 |
48 | static inline void reset_state() {
49 | ending_char = 0;
50 | level_count = 0;
51 | }
52 |
53 | unsigned tree_sitter_lua_external_scanner_serialize(void *payload, char *buffer) {
54 | buffer[0] = ending_char;
55 | buffer[1] = level_count;
56 | return 2;
57 | }
58 |
59 | void tree_sitter_lua_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {
60 | if (length == 0) return;
61 | ending_char = buffer[0];
62 | if (length == 1) return;
63 | level_count = buffer[1];
64 | }
65 |
66 | static bool scan_block_start(TSLexer *lexer) {
67 | if (consume_char('[', lexer)) {
68 | uint8_t level = consume_and_count_char('=', lexer);
69 |
70 | if (consume_char('[', lexer)) {
71 | level_count = level;
72 | return true;
73 | }
74 | }
75 |
76 | return false;
77 | }
78 |
79 | static bool scan_block_end(TSLexer *lexer) {
80 | if (consume_char(']', lexer)) {
81 | uint8_t level = consume_and_count_char('=', lexer);
82 |
83 | if (level_count == level && consume_char(']', lexer)) {
84 | return true;
85 | }
86 | }
87 |
88 | return false;
89 | }
90 |
91 | static bool scan_block_content(TSLexer *lexer) {
92 | while (lexer->lookahead != 0) {
93 | if (lexer->lookahead == ']') {
94 | lexer->mark_end(lexer);
95 |
96 | if (scan_block_end(lexer)) {
97 | return true;
98 | }
99 | } else {
100 | consume(lexer);
101 | }
102 | }
103 |
104 | return false;
105 | }
106 |
107 | static bool scan_comment_start(TSLexer *lexer) {
108 | if (consume_char('-', lexer) && consume_char('-', lexer)) {
109 | lexer->mark_end(lexer);
110 |
111 | if (scan_block_start(lexer)) {
112 | lexer->mark_end(lexer);
113 | lexer->result_symbol = BLOCK_COMMENT_START;
114 | return true;
115 | }
116 | }
117 |
118 | return false;
119 | }
120 |
121 | static bool scan_comment_content(TSLexer *lexer) {
122 | if (ending_char == 0) { // block comment
123 | if (scan_block_content(lexer)) {
124 | lexer->result_symbol = BLOCK_COMMENT_CONTENT;
125 | return true;
126 | }
127 |
128 | return false;
129 | }
130 |
131 | while (lexer->lookahead != 0) {
132 | if (lexer->lookahead == ending_char) {
133 | reset_state();
134 | lexer->result_symbol = BLOCK_COMMENT_CONTENT;
135 | return true;
136 | }
137 |
138 | consume(lexer);
139 | }
140 |
141 | return false;
142 | }
143 |
144 | static bool scan_string_start(TSLexer *lexer) {
145 | if (lexer->lookahead == '"' || lexer->lookahead == '\'') {
146 | ending_char = lexer->lookahead;
147 | consume(lexer);
148 | return true;
149 | }
150 |
151 | if (scan_block_start(lexer)) {
152 | return true;
153 | }
154 |
155 | return false;
156 | }
157 |
158 | static bool scan_string_end(TSLexer *lexer) {
159 | if (ending_char == 0) { // block string
160 | return scan_block_end(lexer);
161 | }
162 |
163 | if (consume_char(ending_char, lexer)) {
164 | return true;
165 | }
166 |
167 | return false;
168 | }
169 |
170 | static bool scan_string_content(TSLexer *lexer) {
171 | if (ending_char == 0) { // block string
172 | return scan_block_content(lexer);
173 | }
174 |
175 | while (lexer->lookahead != '\n' && lexer->lookahead != 0 && lexer->lookahead != ending_char) {
176 | if (consume_char('\\', lexer) && consume_char('z', lexer)) {
177 | while (iswspace(lexer->lookahead)) {
178 | consume(lexer);
179 | }
180 | continue;
181 | };
182 |
183 | if (lexer->lookahead == 0) {
184 | return true;
185 | }
186 |
187 | consume(lexer);
188 | }
189 |
190 | return true;
191 | }
192 |
193 | bool tree_sitter_lua_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
194 | if (valid_symbols[STRING_END] && scan_string_end(lexer)) {
195 | reset_state();
196 | lexer->result_symbol = STRING_END;
197 | return true;
198 | }
199 |
200 | if (valid_symbols[STRING_CONTENT] && scan_string_content(lexer)) {
201 | lexer->result_symbol = STRING_CONTENT;
202 | return true;
203 | }
204 |
205 | if (valid_symbols[BLOCK_COMMENT_END] && ending_char == 0 && scan_block_end(lexer)) {
206 | reset_state();
207 | lexer->result_symbol = BLOCK_COMMENT_END;
208 | return true;
209 | }
210 |
211 | if (valid_symbols[BLOCK_COMMENT_CONTENT] && scan_comment_content(lexer)) {
212 | return true;
213 | }
214 |
215 | skip_whitespaces(lexer);
216 |
217 | if (valid_symbols[STRING_START] && scan_string_start(lexer)) {
218 | lexer->result_symbol = STRING_START;
219 | return true;
220 | }
221 |
222 | if (valid_symbols[BLOCK_COMMENT_START]) {
223 | if (scan_comment_start(lexer)) {
224 | return true;
225 | }
226 | }
227 |
228 | return false;
229 | }
230 |
--------------------------------------------------------------------------------
/src/tree_sitter/parser.h:
--------------------------------------------------------------------------------
1 | #ifndef TREE_SITTER_PARSER_H_
2 | #define TREE_SITTER_PARSER_H_
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | #define ts_builtin_sym_error ((TSSymbol)-1)
13 | #define ts_builtin_sym_end 0
14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
15 |
16 | #ifndef TREE_SITTER_API_H_
17 | typedef uint16_t TSStateId;
18 | typedef uint16_t TSSymbol;
19 | typedef uint16_t TSFieldId;
20 | typedef struct TSLanguage TSLanguage;
21 | #endif
22 |
23 | typedef struct {
24 | TSFieldId field_id;
25 | uint8_t child_index;
26 | bool inherited;
27 | } TSFieldMapEntry;
28 |
29 | typedef struct {
30 | uint16_t index;
31 | uint16_t length;
32 | } TSFieldMapSlice;
33 |
34 | typedef struct {
35 | bool visible;
36 | bool named;
37 | bool supertype;
38 | } TSSymbolMetadata;
39 |
40 | typedef struct TSLexer TSLexer;
41 |
42 | struct TSLexer {
43 | int32_t lookahead;
44 | TSSymbol result_symbol;
45 | void (*advance)(TSLexer *, bool);
46 | void (*mark_end)(TSLexer *);
47 | uint32_t (*get_column)(TSLexer *);
48 | bool (*is_at_included_range_start)(const TSLexer *);
49 | bool (*eof)(const TSLexer *);
50 | void (*log)(const TSLexer *, const char *, ...);
51 | };
52 |
53 | typedef enum {
54 | TSParseActionTypeShift,
55 | TSParseActionTypeReduce,
56 | TSParseActionTypeAccept,
57 | TSParseActionTypeRecover,
58 | } TSParseActionType;
59 |
60 | typedef union {
61 | struct {
62 | uint8_t type;
63 | TSStateId state;
64 | bool extra;
65 | bool repetition;
66 | } shift;
67 | struct {
68 | uint8_t type;
69 | uint8_t child_count;
70 | TSSymbol symbol;
71 | int16_t dynamic_precedence;
72 | uint16_t production_id;
73 | } reduce;
74 | uint8_t type;
75 | } TSParseAction;
76 |
77 | typedef struct {
78 | uint16_t lex_state;
79 | uint16_t external_lex_state;
80 | } TSLexMode;
81 |
82 | typedef union {
83 | TSParseAction action;
84 | struct {
85 | uint8_t count;
86 | bool reusable;
87 | } entry;
88 | } TSParseActionEntry;
89 |
90 | typedef struct {
91 | int32_t start;
92 | int32_t end;
93 | } TSCharacterRange;
94 |
95 | struct TSLanguage {
96 | uint32_t version;
97 | uint32_t symbol_count;
98 | uint32_t alias_count;
99 | uint32_t token_count;
100 | uint32_t external_token_count;
101 | uint32_t state_count;
102 | uint32_t large_state_count;
103 | uint32_t production_id_count;
104 | uint32_t field_count;
105 | uint16_t max_alias_sequence_length;
106 | const uint16_t *parse_table;
107 | const uint16_t *small_parse_table;
108 | const uint32_t *small_parse_table_map;
109 | const TSParseActionEntry *parse_actions;
110 | const char * const *symbol_names;
111 | const char * const *field_names;
112 | const TSFieldMapSlice *field_map_slices;
113 | const TSFieldMapEntry *field_map_entries;
114 | const TSSymbolMetadata *symbol_metadata;
115 | const TSSymbol *public_symbol_map;
116 | const uint16_t *alias_map;
117 | const TSSymbol *alias_sequences;
118 | const TSLexMode *lex_modes;
119 | bool (*lex_fn)(TSLexer *, TSStateId);
120 | bool (*keyword_lex_fn)(TSLexer *, TSStateId);
121 | TSSymbol keyword_capture_token;
122 | struct {
123 | const bool *states;
124 | const TSSymbol *symbol_map;
125 | void *(*create)(void);
126 | void (*destroy)(void *);
127 | bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
128 | unsigned (*serialize)(void *, char *);
129 | void (*deserialize)(void *, const char *, unsigned);
130 | } external_scanner;
131 | const TSStateId *primary_state_ids;
132 | };
133 |
134 | static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
135 | uint32_t index = 0;
136 | uint32_t size = len - index;
137 | while (size > 1) {
138 | uint32_t half_size = size / 2;
139 | uint32_t mid_index = index + half_size;
140 | TSCharacterRange *range = &ranges[mid_index];
141 | if (lookahead >= range->start && lookahead <= range->end) {
142 | return true;
143 | } else if (lookahead > range->end) {
144 | index = mid_index;
145 | }
146 | size -= half_size;
147 | }
148 | TSCharacterRange *range = &ranges[index];
149 | return (lookahead >= range->start && lookahead <= range->end);
150 | }
151 |
152 | /*
153 | * Lexer Macros
154 | */
155 |
156 | #ifdef _MSC_VER
157 | #define UNUSED __pragma(warning(suppress : 4101))
158 | #else
159 | #define UNUSED __attribute__((unused))
160 | #endif
161 |
162 | #define START_LEXER() \
163 | bool result = false; \
164 | bool skip = false; \
165 | UNUSED \
166 | bool eof = false; \
167 | int32_t lookahead; \
168 | goto start; \
169 | next_state: \
170 | lexer->advance(lexer, skip); \
171 | start: \
172 | skip = false; \
173 | lookahead = lexer->lookahead;
174 |
175 | #define ADVANCE(state_value) \
176 | { \
177 | state = state_value; \
178 | goto next_state; \
179 | }
180 |
181 | #define ADVANCE_MAP(...) \
182 | { \
183 | static const uint16_t map[] = { __VA_ARGS__ }; \
184 | for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
185 | if (map[i] == lookahead) { \
186 | state = map[i + 1]; \
187 | goto next_state; \
188 | } \
189 | } \
190 | }
191 |
192 | #define SKIP(state_value) \
193 | { \
194 | skip = true; \
195 | state = state_value; \
196 | goto next_state; \
197 | }
198 |
199 | #define ACCEPT_TOKEN(symbol_value) \
200 | result = true; \
201 | lexer->result_symbol = symbol_value; \
202 | lexer->mark_end(lexer);
203 |
204 | #define END_STATE() return result;
205 |
206 | /*
207 | * Parse Table Macros
208 | */
209 |
210 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
211 |
212 | #define STATE(id) id
213 |
214 | #define ACTIONS(id) id
215 |
216 | #define SHIFT(state_value) \
217 | {{ \
218 | .shift = { \
219 | .type = TSParseActionTypeShift, \
220 | .state = (state_value) \
221 | } \
222 | }}
223 |
224 | #define SHIFT_REPEAT(state_value) \
225 | {{ \
226 | .shift = { \
227 | .type = TSParseActionTypeShift, \
228 | .state = (state_value), \
229 | .repetition = true \
230 | } \
231 | }}
232 |
233 | #define SHIFT_EXTRA() \
234 | {{ \
235 | .shift = { \
236 | .type = TSParseActionTypeShift, \
237 | .extra = true \
238 | } \
239 | }}
240 |
241 | #define REDUCE(symbol_name, children, precedence, prod_id) \
242 | {{ \
243 | .reduce = { \
244 | .type = TSParseActionTypeReduce, \
245 | .symbol = symbol_name, \
246 | .child_count = children, \
247 | .dynamic_precedence = precedence, \
248 | .production_id = prod_id \
249 | }, \
250 | }}
251 |
252 | #define RECOVER() \
253 | {{ \
254 | .type = TSParseActionTypeRecover \
255 | }}
256 |
257 | #define ACCEPT_INPUT() \
258 | {{ \
259 | .type = TSParseActionTypeAccept \
260 | }}
261 |
262 | #ifdef __cplusplus
263 | }
264 | #endif
265 |
266 | #endif // TREE_SITTER_PARSER_H_
267 |
--------------------------------------------------------------------------------
/test/corpus/gh_issue_005.txt:
--------------------------------------------------------------------------------
1 | ==================
2 | Can do a large lua file
3 | ==================
4 |
5 | local m = {}
6 |
7 | --- Test Header 1
8 | function m.a01()
9 | return 0
10 | end
11 |
12 | return m
13 |
14 | ---
15 |
16 | (program
17 | (variable_declaration
18 | (local)
19 | (variable_declarator
20 | (identifier))
21 | (tableconstructor))
22 | (function_statement
23 | (emmy_documentation
24 | (emmy_header))
25 | (function_start)
26 | (function_name
27 | (identifier)
28 | (table_dot)
29 | (identifier))
30 | (function_body_paren)
31 | (function_body_paren)
32 | (function_body
33 | (return_statement
34 | (number)))
35 | (function_end))
36 | (module_return_statement
37 | (identifier)))
38 |
39 | ==================
40 | Can do a large lua file
41 | ==================
42 |
43 | local m = {}
44 |
45 | --- Test Header 1
46 | function m.a01()
47 | return 0
48 | end
49 |
50 | --- Test Header 2
51 | function m.a02()
52 | return 0
53 | end
54 |
55 | --- Test Header 3
56 | function m.a03()
57 | return 0
58 | end
59 |
60 | --- Test Header 4
61 | function m.a04()
62 | return 0
63 | end
64 |
65 | --- Test Header 5
66 | function m.a05()
67 | return 0
68 | end
69 |
70 | --- Test Header 6
71 | function m.a06()
72 | return 0
73 | end
74 |
75 | --- Test Header 7
76 | function m.a07()
77 | return 0
78 | end
79 |
80 | --- Test Header 8
81 | function m.a08()
82 | return 0
83 | end
84 |
85 | --- Test Header 9
86 | function m.a09()
87 | return 0
88 | end
89 |
90 | --- Test Header 10
91 | function m.a10()
92 | return 0
93 | end
94 |
95 | --- Test Header 11
96 | function m.a11()
97 | return 0
98 | end
99 |
100 | --- Test Header 12
101 | function m.a12()
102 | return 0
103 | end
104 |
105 | --- Test Header 13
106 | function m.a13()
107 | return 0
108 | end
109 |
110 | --- Test Header 14
111 | function m.a14()
112 | return 0
113 | end
114 |
115 | --- Test Header 15
116 | function m.a15()
117 | return 0
118 | end
119 |
120 | --- Test Header 16
121 | function m.a16()
122 | return 0
123 | end
124 | --- Test Header 17
125 | function m.a17()
126 | return 0
127 | end
128 |
129 | return m
130 |
131 | ---
132 |
133 | (program
134 | (variable_declaration
135 | (local)
136 | (variable_declarator
137 | (identifier))
138 | (tableconstructor))
139 | (function_statement
140 | (emmy_documentation
141 | (emmy_header))
142 | (function_start)
143 | (function_name
144 | (identifier)
145 | (table_dot)
146 | (identifier))
147 | (function_body_paren)
148 | (function_body_paren)
149 | (function_body
150 | (return_statement
151 | (number)))
152 | (function_end))
153 | (function_statement
154 | (emmy_documentation
155 | (emmy_header))
156 | (function_start)
157 | (function_name
158 | (identifier)
159 | (table_dot)
160 | (identifier))
161 | (function_body_paren)
162 | (function_body_paren)
163 | (function_body
164 | (return_statement
165 | (number)))
166 | (function_end))
167 | (function_statement
168 | (emmy_documentation
169 | (emmy_header))
170 | (function_start)
171 | (function_name
172 | (identifier)
173 | (table_dot)
174 | (identifier))
175 | (function_body_paren)
176 | (function_body_paren)
177 | (function_body
178 | (return_statement
179 | (number)))
180 | (function_end))
181 | (function_statement
182 | (emmy_documentation
183 | (emmy_header))
184 | (function_start)
185 | (function_name
186 | (identifier)
187 | (table_dot)
188 | (identifier))
189 | (function_body_paren)
190 | (function_body_paren)
191 | (function_body
192 | (return_statement
193 | (number)))
194 | (function_end))
195 | (function_statement
196 | (emmy_documentation
197 | (emmy_header))
198 | (function_start)
199 | (function_name
200 | (identifier)
201 | (table_dot)
202 | (identifier))
203 | (function_body_paren)
204 | (function_body_paren)
205 | (function_body
206 | (return_statement
207 | (number)))
208 | (function_end))
209 | (function_statement
210 | (emmy_documentation
211 | (emmy_header))
212 | (function_start)
213 | (function_name
214 | (identifier)
215 | (table_dot)
216 | (identifier))
217 | (function_body_paren)
218 | (function_body_paren)
219 | (function_body
220 | (return_statement
221 | (number)))
222 | (function_end))
223 | (function_statement
224 | (emmy_documentation
225 | (emmy_header))
226 | (function_start)
227 | (function_name
228 | (identifier)
229 | (table_dot)
230 | (identifier))
231 | (function_body_paren)
232 | (function_body_paren)
233 | (function_body
234 | (return_statement
235 | (number)))
236 | (function_end))
237 | (function_statement
238 | (emmy_documentation
239 | (emmy_header))
240 | (function_start)
241 | (function_name
242 | (identifier)
243 | (table_dot)
244 | (identifier))
245 | (function_body_paren)
246 | (function_body_paren)
247 | (function_body
248 | (return_statement
249 | (number)))
250 | (function_end))
251 | (function_statement
252 | (emmy_documentation
253 | (emmy_header))
254 | (function_start)
255 | (function_name
256 | (identifier)
257 | (table_dot)
258 | (identifier))
259 | (function_body_paren)
260 | (function_body_paren)
261 | (function_body
262 | (return_statement
263 | (number)))
264 | (function_end))
265 | (function_statement
266 | (emmy_documentation
267 | (emmy_header))
268 | (function_start)
269 | (function_name
270 | (identifier)
271 | (table_dot)
272 | (identifier))
273 | (function_body_paren)
274 | (function_body_paren)
275 | (function_body
276 | (return_statement
277 | (number)))
278 | (function_end))
279 | (function_statement
280 | (emmy_documentation
281 | (emmy_header))
282 | (function_start)
283 | (function_name
284 | (identifier)
285 | (table_dot)
286 | (identifier))
287 | (function_body_paren)
288 | (function_body_paren)
289 | (function_body
290 | (return_statement
291 | (number)))
292 | (function_end))
293 | (function_statement
294 | (emmy_documentation
295 | (emmy_header))
296 | (function_start)
297 | (function_name
298 | (identifier)
299 | (table_dot)
300 | (identifier))
301 | (function_body_paren)
302 | (function_body_paren)
303 | (function_body
304 | (return_statement
305 | (number)))
306 | (function_end))
307 | (function_statement
308 | (emmy_documentation
309 | (emmy_header))
310 | (function_start)
311 | (function_name
312 | (identifier)
313 | (table_dot)
314 | (identifier))
315 | (function_body_paren)
316 | (function_body_paren)
317 | (function_body
318 | (return_statement
319 | (number)))
320 | (function_end))
321 | (function_statement
322 | (emmy_documentation
323 | (emmy_header))
324 | (function_start)
325 | (function_name
326 | (identifier)
327 | (table_dot)
328 | (identifier))
329 | (function_body_paren)
330 | (function_body_paren)
331 | (function_body
332 | (return_statement
333 | (number)))
334 | (function_end))
335 | (function_statement
336 | (emmy_documentation
337 | (emmy_header))
338 | (function_start)
339 | (function_name
340 | (identifier)
341 | (table_dot)
342 | (identifier))
343 | (function_body_paren)
344 | (function_body_paren)
345 | (function_body
346 | (return_statement
347 | (number)))
348 | (function_end))
349 | (function_statement
350 | (emmy_documentation
351 | (emmy_header))
352 | (function_start)
353 | (function_name
354 | (identifier)
355 | (table_dot)
356 | (identifier))
357 | (function_body_paren)
358 | (function_body_paren)
359 | (function_body
360 | (return_statement
361 | (number)))
362 | (function_end))
363 | (function_statement
364 | (emmy_documentation
365 | (emmy_header))
366 | (function_start)
367 | (function_name
368 | (identifier)
369 | (table_dot)
370 | (identifier))
371 | (function_body_paren)
372 | (function_body_paren)
373 | (function_body
374 | (return_statement
375 | (number)))
376 | (function_end))
377 | (module_return_statement
378 | (identifier)))
379 |
--------------------------------------------------------------------------------
/lua/docgen/transformers.lua:
--------------------------------------------------------------------------------
1 | local get_node_text = vim.treesitter.get_node_text
2 |
3 | local log = require "docgen.log"
4 |
5 | local for_each_child = function(node, cb)
6 | local named_children_count = node:named_child_count()
7 | for child = 0, named_children_count - 1 do
8 | local child_node = node:named_child(child)
9 | cb(child_node)
10 | end
11 | end
12 |
13 | ---@brief [[
14 | --- Transforms generated tree from tree sitter -> metadata nodes that we can use for the project.
15 | --- Structure of a program is: (TODO)
16 | ---@brief ]]
17 | ---@tag docgen-transformers
18 | local transformers = {}
19 |
20 | --- Takes any node and recursively transforms its children into the corresponding metadata required by |docgen|.
21 | local call_transformer = function(accumulator, str, node, return_module)
22 | if transformers[node:type()] then
23 | return transformers[node:type()](accumulator, str, node, return_module)
24 | end
25 | end
26 |
27 | transformers._function = function(accumulator, str, node, return_module)
28 | if not return_module then
29 | return
30 | end
31 |
32 | local name_node = node:field("name")[1]
33 | local documentation_node = node:field("documentation")[1]
34 |
35 | assert(documentation_node, "Documentation must exist for this variable")
36 | assert(name_node, "Variable must have a name")
37 |
38 | local name = vim.trim(get_node_text(name_node, str))
39 |
40 | if not accumulator.functions then
41 | accumulator.functions = {}
42 | end
43 | if not accumulator.function_list then
44 | accumulator.function_list = {}
45 | end
46 |
47 | if not name:match(return_module .. "[.:].*") then
48 | return
49 | end
50 |
51 | -- If we already have the function skip
52 | -- Can happen now with function_statement and variable_declaration
53 | -- (but we still need to match both queries)
54 | if accumulator.functions[name] == nil then
55 | accumulator.functions[name] = {
56 | name = name,
57 | format = "function",
58 | }
59 | table.insert(accumulator.function_list, name)
60 | call_transformer(accumulator.functions[name], str, documentation_node)
61 | end
62 | end
63 |
64 | --- Transform briefs into the accumulator.brief
65 | transformers.documentation_brief = function(accumulator, str, node)
66 | if not accumulator.brief then
67 | accumulator.brief = {}
68 | end
69 |
70 | local result = get_node_text(node, str)
71 | if result:sub(1, 1) == " " then
72 | result = result:sub(2)
73 | end
74 |
75 | table.insert(accumulator.brief, result)
76 | end
77 |
78 | transformers.documentation_class = function(accumulator, str, node)
79 | if not accumulator.classes then
80 | accumulator.classes = {}
81 | end
82 | if not accumulator.class_list then
83 | accumulator.class_list = {}
84 | end
85 |
86 | local class_node = node:named_child(0)
87 |
88 | local type_node = class_node:named_child(0)
89 | local parent_or_desc = class_node:named_child(1)
90 | local desc_node = class_node:named_child(2)
91 |
92 | local class = {}
93 | local name = get_node_text(type_node, str)
94 | class.name = name
95 |
96 | if parent_or_desc ~= nil then
97 | if desc_node == nil then
98 | class.desc = { get_node_text(parent_or_desc, str) }
99 | else
100 | class.parent = get_node_text(parent_or_desc, str)
101 | class.desc = { get_node_text(desc_node, str) }
102 | end
103 | else
104 | class.desc = {}
105 | end
106 |
107 | class.fields = {}
108 | class.field_list = {}
109 |
110 | local named_children_count = node:named_child_count()
111 | for child = 1, named_children_count - 1 do
112 | transformers.emmy_field(class, str, node:named_child(child))
113 | end
114 |
115 | accumulator.classes[name] = class
116 | table.insert(accumulator.class_list, name)
117 | end
118 |
119 | transformers.documentation_tag = function(accumulator, str, node)
120 | accumulator.tag = get_node_text(node, str)
121 | end
122 |
123 | transformers.function_statement = transformers._function
124 | transformers.variable_declaration = transformers._function
125 |
126 | transformers.documentation_command = function(accumulator, str, node)
127 | accumulator.commands = accumulator.commands or {}
128 |
129 | local usage = vim.trim(get_node_text(node:field("usage")[1], str))
130 | usage = usage:gsub("^:", "")
131 |
132 | local name = vim.split(usage, " ")[1]
133 | table.insert(accumulator.commands, {
134 | name = name,
135 | usage = ":" .. usage,
136 | documentation = vim.tbl_map(function(child)
137 | return get_node_text(child, str)
138 | end, node:field "documentation"),
139 | })
140 | end
141 |
142 | transformers.emmy_documentation = function(accumulator, str, node)
143 | accumulator.class = {}
144 |
145 | accumulator.fields = {}
146 | accumulator.field_list = {}
147 |
148 | accumulator.parameters = {}
149 | accumulator.parameter_list = {}
150 |
151 | log.trace("Accumulator:", accumulator)
152 |
153 | for_each_child(node, function(child_node)
154 | call_transformer(accumulator, str, child_node)
155 | end)
156 | end
157 |
158 | transformers.emmy_header = function(accumulator, str, node)
159 | return transformers.emmy_comment(accumulator, str, node)
160 | end
161 |
162 | transformers.emmy_comment = function(accumulator, str, node)
163 | -- TODO: Make this not ugly
164 | local text = get_node_text(node, str)
165 |
166 | local raw_lines = vim.split(text, "\n")
167 | if raw_lines[1] == "" then
168 | table.remove(raw_lines, 1)
169 | end
170 |
171 | if not accumulator.description then
172 | accumulator.description = {}
173 | end
174 |
175 | for _, line in ipairs(raw_lines) do
176 | local start, finish = line:find "^%s*---"
177 | if start then
178 | line = line:sub(finish + 3)
179 | end
180 |
181 | if line:sub(1, 1) == " " then
182 | line = line:sub(2)
183 | end
184 |
185 | table.insert(accumulator.description, line)
186 | end
187 | end
188 |
189 | transformers.emmy_class = function(accumulator, str, node)
190 | local type_node = node:named_child(0)
191 | local parent_or_desc = node:named_child(1)
192 | local desc_node = node:named_child(2)
193 |
194 | local name = get_node_text(type_node, str)
195 | accumulator.class.name = name
196 |
197 | if desc_node == nil then
198 | accumulator.class.desc = { get_node_text(parent_or_desc, str) }
199 | else
200 | accumulator.class.parent = get_node_text(parent_or_desc, str)
201 | accumulator.class.desc = { get_node_text(desc_node, str) }
202 | end
203 | end
204 |
205 | transformers.emmy_field = function(accumulator, str, node)
206 | local name_node = node:field("name")[1]
207 | assert(name_node, "Field must have a name")
208 |
209 | local types = {}
210 | local desc
211 | for _, child in ipairs(node:field "type") do
212 | local child_type = child:type()
213 | if child_type == "emmy_type" or child_type == "identifier" then
214 | table.insert(types, get_node_text(child, str))
215 | elseif child_type == "|" then
216 | -- do nothing
217 | else
218 | print(string.format("[docgen] [Error]: We should not be here // emmy_field #2 (%s)", child:type()))
219 | end
220 | end
221 |
222 | for _, child in ipairs(node:field "description") do
223 | if desc ~= nil then
224 | print "[docgen] [Error]: We should not be here // emmy_field #1"
225 | else
226 | desc = get_node_text(child, str)
227 | end
228 | end
229 |
230 | local name = get_node_text(name_node, str)
231 |
232 | accumulator.fields[name] = {
233 | name = name,
234 | type = types,
235 | description = { desc },
236 | }
237 |
238 | if not vim.tbl_contains(accumulator.field_list, name) then
239 | table.insert(accumulator.field_list, name)
240 | end
241 | end
242 |
243 | transformers.emmy_parameter = function(accumulator, str, node)
244 | local name_node = node:named_child(0)
245 | assert(name_node, "Parameters must have a name")
246 |
247 | local types = {}
248 | local desc
249 | for i = 1, node:named_child_count() - 1 do
250 | if node:named_child(i):type() == "emmy_type" then
251 | table.insert(types, get_node_text(node:named_child(i), str))
252 | elseif node:named_child(i):type() == "parameter_description" then
253 | if desc ~= nil then
254 | print "[docgen] [Error]: We should not be here // emmy_parameter #1"
255 | else
256 | desc = get_node_text(node:named_child(i), str)
257 | end
258 | else
259 | print "[docgen] [Error]: We should not be here // emmy_parazn #2"
260 | end
261 | end
262 |
263 | local name = get_node_text(name_node, str)
264 |
265 | accumulator.parameters[name] = {
266 | name = name,
267 | type = types,
268 | description = { desc },
269 | }
270 |
271 | if not vim.tbl_contains(accumulator.parameter_list, name) then
272 | table.insert(accumulator.parameter_list, name)
273 | end
274 | end
275 |
276 | local create_emmy_type_function = function(identifier)
277 | return function(accumulator, str, node)
278 | if not accumulator[identifier] then
279 | accumulator[identifier] = {}
280 | end
281 |
282 | local text = vim.trim(get_node_text(node, str))
283 | text = text:gsub(string.format("---@%s ", identifier), "")
284 |
285 | table.insert(accumulator[identifier], text)
286 | end
287 | end
288 |
289 | transformers.emmy_return = create_emmy_type_function "return"
290 | transformers.emmy_see = create_emmy_type_function "see"
291 | transformers.emmy_todo = create_emmy_type_function "todo"
292 | transformers.emmy_usage = create_emmy_type_function "usage"
293 | transformers.emmy_varargs = create_emmy_type_function "varargs"
294 |
295 | transformers.emmy_eval = function(accumulator, str, node)
296 | local ok, result = pcall(loadstring("return " .. get_node_text(node, str)))
297 |
298 | if ok then
299 | if type(result) == "table" then
300 | for k, v in pairs(result) do
301 | -- assert(type(v) == 'string', "Not implemented to be nested tables yet." .. vim.inspect(accumulator))
302 | -- local current_accumulator = accumulator
303 | -- if type(v) == 'table' then
304 | -- -- curre
305 | -- end
306 |
307 | if not accumulator[k] then
308 | accumulator[k] = {}
309 | end
310 |
311 | table.insert(accumulator[k], v)
312 | end
313 | end
314 | else
315 | print("ERR:", result)
316 | end
317 | end
318 |
319 | transformers.documentation_config = function(accumulator, str, node)
320 | local ok, result = pcall(loadstring("return " .. get_node_text(node, str)))
321 |
322 | if ok then
323 | if type(result) == "table" then
324 | if not accumulator then
325 | accumulator = {}
326 | end
327 |
328 | accumulator["config"] = result
329 | end
330 | else
331 | print("ERR:", result)
332 | end
333 | end
334 |
335 | return call_transformer
336 |
--------------------------------------------------------------------------------
/test/corpus/statements.txt:
--------------------------------------------------------------------------------
1 |
2 | ==================
3 | Nil is handled
4 | ==================
5 |
6 | x = nil
7 |
8 | ---
9 |
10 | (program
11 | (variable_declaration
12 | (variable_declarator (identifier))
13 | (nil)))
14 |
15 | ==================
16 | true is handled
17 | ==================
18 |
19 | x = true
20 |
21 | ---
22 |
23 | (program
24 | (variable_declaration
25 | (variable_declarator (identifier))
26 | (boolean)))
27 |
28 | ==================
29 | false is handled
30 | ==================
31 |
32 | x = false
33 |
34 | ---
35 |
36 | (program
37 | (variable_declaration
38 | (variable_declarator (identifier))
39 | (boolean)))
40 |
41 | ==================
42 | Simple assignment
43 | ==================
44 |
45 | x = 1
46 |
47 | ---
48 |
49 | (program
50 | (variable_declaration
51 | (variable_declarator (identifier))
52 | (number)))
53 |
54 | ==================
55 | Simple assignment from variable
56 | ==================
57 |
58 | x = y
59 |
60 | ---
61 |
62 | (program
63 | (variable_declaration
64 | (variable_declarator (identifier))
65 | (identifier)))
66 |
67 | ==================
68 | Accepts addition
69 | ==================
70 |
71 | x = 1 + 2
72 |
73 | ---
74 |
75 | (program
76 | (variable_declaration
77 | (variable_declarator (identifier))
78 | (binary_operation (number) (number))))
79 |
80 | ==================
81 | Accepts addition of variables
82 | ==================
83 |
84 | x = y + z
85 |
86 | ---
87 |
88 | (program
89 | (variable_declaration
90 | (variable_declarator (identifier))
91 | (binary_operation (identifier) (identifier))))
92 |
93 | ==================
94 | Can make local variables
95 | ==================
96 |
97 | local x = 2
98 |
99 | ---
100 |
101 | (program
102 | (variable_declaration
103 | (local)
104 | (variable_declarator (identifier))
105 | (number)))
106 |
107 | ==================
108 | Can do multiple sets
109 | ==================
110 |
111 | x, y, z = 1, 2, 3
112 |
113 | ---
114 |
115 | (program
116 | (variable_declaration
117 | (variable_declarator (identifier))
118 | (variable_declarator (identifier))
119 | (variable_declarator (identifier))
120 | (number)
121 | (number)
122 | (number)))
123 |
124 | ==================
125 | Can make an empty table
126 | ==================
127 |
128 | t = {}
129 |
130 | ---
131 |
132 | (program
133 | (variable_declaration
134 | (variable_declarator (identifier))
135 | (tableconstructor)))
136 |
137 | ==================
138 | Can make a table with a number
139 | ==================
140 |
141 | t = { 1 }
142 |
143 | ---
144 |
145 | (program
146 | (variable_declaration
147 | name: (variable_declarator (identifier))
148 | value: (tableconstructor
149 | (fieldlist (field value: (number))))))
150 |
151 | ==================
152 | Can make a table with a list of numbers
153 | ==================
154 |
155 | t = { 1, 2, 3 }
156 |
157 | ---
158 |
159 | (program
160 | (variable_declaration
161 | name: (variable_declarator (identifier))
162 | value: (tableconstructor
163 | (fieldlist
164 | (field value: (number))
165 | (field value: (number))
166 | (field value: (number))))))
167 |
168 | ==================
169 | Can make a table with keys
170 | ==================
171 |
172 | t = { x = 1, y = 2 }
173 |
174 | ---
175 |
176 | (program
177 | (variable_declaration
178 | name: (variable_declarator (identifier))
179 | value: (tableconstructor
180 | (fieldlist
181 | (field
182 | name: (identifier)
183 | value: (number))
184 | (field
185 | name: (identifier)
186 | value: (number))))))
187 |
188 | ==================
189 | Can make a table with expression keys
190 | ==================
191 |
192 | t = { [x] = 1, ["y"] = 2 }
193 |
194 | ---
195 |
196 | (program
197 | (variable_declaration
198 | name: (variable_declarator (identifier))
199 | value: (tableconstructor
200 | (fieldlist
201 | (field
202 | field_left_bracket: (field_left_bracket)
203 | key: (identifier)
204 | field_right_bracket: (field_right_bracket)
205 | value: (number))
206 | (field
207 | field_left_bracket: (field_left_bracket)
208 | key: (string)
209 | field_right_bracket: (field_right_bracket)
210 | value: (number))))))
211 |
212 |
213 | ==================
214 | Can make a table with some expressions, some nothing and some keys
215 | ==================
216 |
217 | t = { 1, 2, x = 1, ["y"] = 2 }
218 |
219 | ---
220 |
221 | (program
222 | (variable_declaration
223 | name: (variable_declarator (identifier))
224 | value: (tableconstructor
225 | (fieldlist
226 | (field value: (number))
227 | (field value: (number))
228 | (field
229 | name: (identifier)
230 | value: (number))
231 | (field
232 | field_left_bracket: (field_left_bracket)
233 | key: (string)
234 | field_right_bracket: (field_right_bracket)
235 | value: (number))))))
236 |
237 |
238 | ==================
239 | Can assign a function result
240 | ==================
241 |
242 | foo = my_func()
243 |
244 | ---
245 |
246 | (program
247 | (variable_declaration
248 | name: (variable_declarator (identifier))
249 | value: (function_call
250 | prefix: (identifier)
251 | (function_call_paren)
252 | (function_call_paren))))
253 |
254 |
255 | ==================
256 | Can assign a function with params
257 | ==================
258 |
259 | foo = my_func(x, 2, "3")
260 |
261 | ---
262 |
263 | (program
264 | (variable_declaration
265 | name: (variable_declarator (identifier))
266 | value: (function_call
267 | prefix: (identifier)
268 | (function_call_paren)
269 | args: (function_arguments
270 | (identifier)
271 | (number)
272 | (string))
273 | (function_call_paren))))
274 |
275 |
276 | ==================
277 | Can call a function with a string value
278 | ==================
279 |
280 | foo = my_func "hello world"
281 |
282 | ---
283 |
284 | (program
285 | (variable_declaration
286 | name: (variable_declarator (identifier))
287 | value: (function_call
288 | prefix: (identifier)
289 | args: (string_argument))))
290 |
291 | ==================
292 | Can call a function with a table value
293 | ==================
294 |
295 | foo = my_func {}
296 |
297 | ---
298 |
299 | (program
300 | (variable_declaration
301 | name: (variable_declarator (identifier))
302 | value: (function_call
303 | prefix: (identifier)
304 | args: (table_argument))))
305 |
306 | ==================
307 | Can call a function returned by a function
308 | ==================
309 |
310 | foo = my_func()()
311 |
312 | ---
313 |
314 | (program
315 | (variable_declaration
316 | name: (variable_declarator (identifier))
317 | value: (function_call
318 | prefix: (function_call
319 | prefix: (identifier)
320 | (function_call_paren)
321 | (function_call_paren))
322 | (function_call_paren)
323 | (function_call_paren))))
324 |
325 | ==================
326 | Can call a table function // TODO: Decide if it makes sense with identifiers
327 | ==================
328 |
329 | foo = my_table.func()
330 |
331 | ---
332 |
333 | (program
334 | (variable_declaration
335 | (variable_declarator (identifier))
336 | (function_call
337 | (identifier) (identifier)
338 | (function_call_paren)
339 | (function_call_paren))))
340 |
341 |
342 | ==================
343 | Can call a table function with lots of ids
344 | ==================
345 |
346 | foo = my_table.func.x.y()
347 |
348 | ---
349 |
350 | (program
351 | (variable_declaration
352 | (variable_declarator (identifier))
353 | (function_call
354 | (identifier) (identifier) (identifier) (identifier)
355 | (function_call_paren)
356 | (function_call_paren))))
357 |
358 |
359 | ==================
360 | Can set namelist
361 | ==================
362 |
363 | local x, y, z = 1, 2, 3
364 |
365 | ---
366 |
367 | (program
368 | (variable_declaration
369 | (local)
370 | name: (variable_declarator (identifier))
371 | name: (variable_declarator (identifier))
372 | name: (variable_declarator (identifier))
373 | value: (number)
374 | value: (number)
375 | value: (number)))
376 |
377 | ==================
378 | Can do comments
379 | ==================
380 |
381 | -- Comment
382 |
383 |
384 | ---
385 |
386 | (program
387 | (comment))
388 |
389 | ==================
390 | Multi lines
391 | ==================
392 |
393 | x = 1
394 | y = 2
395 |
396 | ---
397 |
398 | (program
399 | (variable_declaration (variable_declarator (identifier)) (number))
400 | (variable_declaration (variable_declarator (identifier)) (number)))
401 |
402 |
403 | ==================
404 | Can do comments at the end of a line
405 | ==================
406 |
407 | x = nil -- Comment
408 |
409 | ---
410 |
411 | (program
412 | (variable_declaration
413 | (variable_declarator (identifier))
414 | (nil))
415 | (comment))
416 |
417 | ==================
418 | Can handle parens around an expression
419 | ==================
420 |
421 | x = (1)
422 |
423 | ---
424 |
425 | (program
426 | (variable_declaration (variable_declarator (identifier)) (left_paren) (number) (right_paren)))
427 |
428 | ==================
429 | Multi declaration
430 | ==================
431 |
432 | local x, y = 1, 2
433 |
434 | ---
435 |
436 | (program
437 | (variable_declaration
438 | (local)
439 | (variable_declarator
440 | (identifier))
441 | (variable_declarator
442 | (identifier))
443 | (number)
444 | (number)))
445 |
446 | ==================
447 | Can do multi-line local with nothing else happening
448 | ==================
449 |
450 | local documentation_node, name_node
451 | local x = 1
452 |
453 | ---
454 |
455 | (program
456 | (variable_declaration
457 | (local)
458 | (variable_declarator
459 | (identifier))
460 | (variable_declarator
461 | (identifier)))
462 | (variable_declaration
463 | (local)
464 | (variable_declarator
465 | (identifier))
466 | (number)))
467 |
468 | ==================
469 | Can do single statments
470 | ==================
471 |
472 | local x
473 | x = {}
474 |
475 | ---
476 |
477 | (program
478 | (variable_declaration
479 | (local)
480 | (variable_declarator
481 | (identifier)))
482 | (variable_declaration
483 | (variable_declarator
484 | (identifier))
485 | (tableconstructor)))
486 |
487 | ==================
488 | Can do floats
489 | ==================
490 |
491 | local x = 0.8
492 |
493 | ---
494 |
495 | (program (variable_declaration (local) (variable_declarator (identifier)) (number)))
496 |
--------------------------------------------------------------------------------
/src/tree_sitter/array.h:
--------------------------------------------------------------------------------
1 | #ifndef TREE_SITTER_ARRAY_H_
2 | #define TREE_SITTER_ARRAY_H_
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include "./alloc.h"
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #ifdef _MSC_VER
17 | #pragma warning(disable : 4101)
18 | #elif defined(__GNUC__) || defined(__clang__)
19 | #pragma GCC diagnostic push
20 | #pragma GCC diagnostic ignored "-Wunused-variable"
21 | #endif
22 |
23 | #define Array(T) \
24 | struct { \
25 | T *contents; \
26 | uint32_t size; \
27 | uint32_t capacity; \
28 | }
29 |
30 | /// Initialize an array.
31 | #define array_init(self) \
32 | ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
33 |
34 | /// Create an empty array.
35 | #define array_new() \
36 | { NULL, 0, 0 }
37 |
38 | /// Get a pointer to the element at a given `index` in the array.
39 | #define array_get(self, _index) \
40 | (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
41 |
42 | /// Get a pointer to the first element in the array.
43 | #define array_front(self) array_get(self, 0)
44 |
45 | /// Get a pointer to the last element in the array.
46 | #define array_back(self) array_get(self, (self)->size - 1)
47 |
48 | /// Clear the array, setting its size to zero. Note that this does not free any
49 | /// memory allocated for the array's contents.
50 | #define array_clear(self) ((self)->size = 0)
51 |
52 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
53 | /// less than the array's current capacity, this function has no effect.
54 | #define array_reserve(self, new_capacity) \
55 | _array__reserve((Array *)(self), array_elem_size(self), new_capacity)
56 |
57 | /// Free any memory allocated for this array. Note that this does not free any
58 | /// memory allocated for the array's contents.
59 | #define array_delete(self) _array__delete((Array *)(self))
60 |
61 | /// Push a new `element` onto the end of the array.
62 | #define array_push(self, element) \
63 | (_array__grow((Array *)(self), 1, array_elem_size(self)), \
64 | (self)->contents[(self)->size++] = (element))
65 |
66 | /// Increase the array's size by `count` elements.
67 | /// New elements are zero-initialized.
68 | #define array_grow_by(self, count) \
69 | do { \
70 | if ((count) == 0) break; \
71 | _array__grow((Array *)(self), count, array_elem_size(self)); \
72 | memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
73 | (self)->size += (count); \
74 | } while (0)
75 |
76 | /// Append all elements from one array to the end of another.
77 | #define array_push_all(self, other) \
78 | array_extend((self), (other)->size, (other)->contents)
79 |
80 | /// Append `count` elements to the end of the array, reading their values from the
81 | /// `contents` pointer.
82 | #define array_extend(self, count, contents) \
83 | _array__splice( \
84 | (Array *)(self), array_elem_size(self), (self)->size, \
85 | 0, count, contents \
86 | )
87 |
88 | /// Remove `old_count` elements from the array starting at the given `index`. At
89 | /// the same index, insert `new_count` new elements, reading their values from the
90 | /// `new_contents` pointer.
91 | #define array_splice(self, _index, old_count, new_count, new_contents) \
92 | _array__splice( \
93 | (Array *)(self), array_elem_size(self), _index, \
94 | old_count, new_count, new_contents \
95 | )
96 |
97 | /// Insert one `element` into the array at the given `index`.
98 | #define array_insert(self, _index, element) \
99 | _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
100 |
101 | /// Remove one element from the array at the given `index`.
102 | #define array_erase(self, _index) \
103 | _array__erase((Array *)(self), array_elem_size(self), _index)
104 |
105 | /// Pop the last element off the array, returning the element by value.
106 | #define array_pop(self) ((self)->contents[--(self)->size])
107 |
108 | /// Assign the contents of one array to another, reallocating if necessary.
109 | #define array_assign(self, other) \
110 | _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
111 |
112 | /// Swap one array with another
113 | #define array_swap(self, other) \
114 | _array__swap((Array *)(self), (Array *)(other))
115 |
116 | /// Get the size of the array contents
117 | #define array_elem_size(self) (sizeof *(self)->contents)
118 |
119 | /// Search a sorted array for a given `needle` value, using the given `compare`
120 | /// callback to determine the order.
121 | ///
122 | /// If an existing element is found to be equal to `needle`, then the `index`
123 | /// out-parameter is set to the existing value's index, and the `exists`
124 | /// out-parameter is set to true. Otherwise, `index` is set to an index where
125 | /// `needle` should be inserted in order to preserve the sorting, and `exists`
126 | /// is set to false.
127 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \
128 | _array__search_sorted(self, 0, compare, , needle, _index, _exists)
129 |
130 | /// Search a sorted array for a given `needle` value, using integer comparisons
131 | /// of a given struct field (specified with a leading dot) to determine the order.
132 | ///
133 | /// See also `array_search_sorted_with`.
134 | #define array_search_sorted_by(self, field, needle, _index, _exists) \
135 | _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
136 |
137 | /// Insert a given `value` into a sorted array, using the given `compare`
138 | /// callback to determine the order.
139 | #define array_insert_sorted_with(self, compare, value) \
140 | do { \
141 | unsigned _index, _exists; \
142 | array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
143 | if (!_exists) array_insert(self, _index, value); \
144 | } while (0)
145 |
146 | /// Insert a given `value` into a sorted array, using integer comparisons of
147 | /// a given struct field (specified with a leading dot) to determine the order.
148 | ///
149 | /// See also `array_search_sorted_by`.
150 | #define array_insert_sorted_by(self, field, value) \
151 | do { \
152 | unsigned _index, _exists; \
153 | array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
154 | if (!_exists) array_insert(self, _index, value); \
155 | } while (0)
156 |
157 | // Private
158 |
159 | typedef Array(void) Array;
160 |
161 | /// This is not what you're looking for, see `array_delete`.
162 | static inline void _array__delete(Array *self) {
163 | if (self->contents) {
164 | ts_free(self->contents);
165 | self->contents = NULL;
166 | self->size = 0;
167 | self->capacity = 0;
168 | }
169 | }
170 |
171 | /// This is not what you're looking for, see `array_erase`.
172 | static inline void _array__erase(Array *self, size_t element_size,
173 | uint32_t index) {
174 | assert(index < self->size);
175 | char *contents = (char *)self->contents;
176 | memmove(contents + index * element_size, contents + (index + 1) * element_size,
177 | (self->size - index - 1) * element_size);
178 | self->size--;
179 | }
180 |
181 | /// This is not what you're looking for, see `array_reserve`.
182 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
183 | if (new_capacity > self->capacity) {
184 | if (self->contents) {
185 | self->contents = ts_realloc(self->contents, new_capacity * element_size);
186 | } else {
187 | self->contents = ts_malloc(new_capacity * element_size);
188 | }
189 | self->capacity = new_capacity;
190 | }
191 | }
192 |
193 | /// This is not what you're looking for, see `array_assign`.
194 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
195 | _array__reserve(self, element_size, other->size);
196 | self->size = other->size;
197 | memcpy(self->contents, other->contents, self->size * element_size);
198 | }
199 |
200 | /// This is not what you're looking for, see `array_swap`.
201 | static inline void _array__swap(Array *self, Array *other) {
202 | Array swap = *other;
203 | *other = *self;
204 | *self = swap;
205 | }
206 |
207 | /// This is not what you're looking for, see `array_push` or `array_grow_by`.
208 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
209 | uint32_t new_size = self->size + count;
210 | if (new_size > self->capacity) {
211 | uint32_t new_capacity = self->capacity * 2;
212 | if (new_capacity < 8) new_capacity = 8;
213 | if (new_capacity < new_size) new_capacity = new_size;
214 | _array__reserve(self, element_size, new_capacity);
215 | }
216 | }
217 |
218 | /// This is not what you're looking for, see `array_splice`.
219 | static inline void _array__splice(Array *self, size_t element_size,
220 | uint32_t index, uint32_t old_count,
221 | uint32_t new_count, const void *elements) {
222 | uint32_t new_size = self->size + new_count - old_count;
223 | uint32_t old_end = index + old_count;
224 | uint32_t new_end = index + new_count;
225 | assert(old_end <= self->size);
226 |
227 | _array__reserve(self, element_size, new_size);
228 |
229 | char *contents = (char *)self->contents;
230 | if (self->size > old_end) {
231 | memmove(
232 | contents + new_end * element_size,
233 | contents + old_end * element_size,
234 | (self->size - old_end) * element_size
235 | );
236 | }
237 | if (new_count > 0) {
238 | if (elements) {
239 | memcpy(
240 | (contents + index * element_size),
241 | elements,
242 | new_count * element_size
243 | );
244 | } else {
245 | memset(
246 | (contents + index * element_size),
247 | 0,
248 | new_count * element_size
249 | );
250 | }
251 | }
252 | self->size += new_count - old_count;
253 | }
254 |
255 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`.
256 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
257 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
258 | do { \
259 | *(_index) = start; \
260 | *(_exists) = false; \
261 | uint32_t size = (self)->size - *(_index); \
262 | if (size == 0) break; \
263 | int comparison; \
264 | while (size > 1) { \
265 | uint32_t half_size = size / 2; \
266 | uint32_t mid_index = *(_index) + half_size; \
267 | comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
268 | if (comparison <= 0) *(_index) = mid_index; \
269 | size -= half_size; \
270 | } \
271 | comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
272 | if (comparison == 0) *(_exists) = true; \
273 | else if (comparison < 0) *(_index) += 1; \
274 | } while (0)
275 |
276 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
277 | /// parameter by reference in order to work with the generic sorting function above.
278 | #define _compare_int(a, b) ((int)*(a) - (int)(b))
279 |
280 | #ifdef _MSC_VER
281 | #pragma warning(default : 4101)
282 | #elif defined(__GNUC__) || defined(__clang__)
283 | #pragma GCC diagnostic pop
284 | #endif
285 |
286 | #ifdef __cplusplus
287 | }
288 | #endif
289 |
290 | #endif // TREE_SITTER_ARRAY_H_
291 |
--------------------------------------------------------------------------------
/lua/nlsp/rpc.lua:
--------------------------------------------------------------------------------
1 | local vim = vim
2 | local uv = vim.loop
3 | local log = require "nlsp.log"
4 | local protocol = require "vim.lsp.protocol"
5 | local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
6 |
7 | -- TODO replace with a better implementation.
8 |
9 | local M = {}
10 |
11 | --@private
12 | --- Encodes to JSON.
13 | ---
14 | --@param data (table) Data to encode
15 | --@returns (string) Encoded object
16 | local function json_encode(data)
17 | local status, result = pcall(vim.fn.json_encode, data)
18 | if status then
19 | return true, result
20 | else
21 | return nil, result
22 | end
23 | end
24 | --@private
25 | --- Decodes from JSON.
26 | ---
27 | --@param data (string) Data to decode
28 | --@returns (table) Decoded JSON object
29 | local function json_decode(data)
30 | local status, result = pcall(vim.fn.json_decode, data)
31 | if status then
32 | return true, result
33 | else
34 | return nil, result
35 | end
36 | end
37 |
38 | local function format_message_with_content_length(encoded_message)
39 | local message = table.concat {
40 | "Content-Length: ",
41 | tostring(#encoded_message),
42 | "\r\n\r\n",
43 | encoded_message,
44 | }
45 |
46 | log.trace(message)
47 | return message
48 | end
49 |
50 | function M.read_message()
51 | local line = io.read "*l"
52 | local length = line:lower():match "content%-length:%s*(%d+)"
53 | return json_decode(io.read(2 + length):sub(2))
54 | end
55 |
56 | function M.send_message(payload, pipe)
57 | if not pipe then
58 | pipe = io.stdout
59 | end
60 |
61 | log.debug("rpc.send.payload", payload)
62 | local ok, encoded = json_encode(payload)
63 | if ok then
64 | pipe:write(format_message_with_content_length(encoded))
65 | else
66 | error("Could not encode:" .. payload)
67 | end
68 | end
69 |
70 | function M.respond(id, err, result)
71 | assert(type(id) == "number", "id must be a number")
72 | M.send_message { jsonrpc = "2.0", id = id, error = err, result = result }
73 | end
74 |
75 | function M.notify(method, params)
76 | assert(type(method) == "string", "method must be a string")
77 | M.send_message { jsonrpc = "2.0", method = method, params = params or {} }
78 | end
79 |
80 | --@private
81 | --- Parses an LSP Message's header
82 | ---
83 | --@param header: The header to parse.
84 | --@returns Parsed headers
85 | local function parse_headers(header)
86 | if type(header) ~= "string" then
87 | return nil
88 | end
89 | local headers = {}
90 | for line in vim.gsplit(header, "\r\n", true) do
91 | if line == "" then
92 | break
93 | end
94 | local key, value = line:match "^%s*(%S+)%s*:%s*(.+)%s*$"
95 | if key then
96 | key = key:lower():gsub("%-", "_")
97 | headers[key] = value
98 | else
99 | local _ = log.error() and log.error("invalid header line %q", line)
100 | error(string.format("invalid header line %q", line))
101 | end
102 | end
103 | headers.content_length = tonumber(headers.content_length)
104 | or error(string.format("Content-Length not found in headers. %q", header))
105 | return headers
106 | end
107 |
108 | -- This is the start of any possible header patterns. The gsub converts it to a
109 | -- case insensitive pattern.
110 | local header_start_pattern = ("content"):gsub("%w", function(c)
111 | return "[" .. c .. c:upper() .. "]"
112 | end)
113 |
114 | --@private
115 | --- The actual workhorse.
116 | local function request_parser_loop()
117 | local buffer = ""
118 | while true do
119 | -- A message can only be complete if it has a double CRLF and also the full
120 | -- payload, so first let's check for the CRLFs
121 | local start, finish = buffer:find("\r\n\r\n", 1, true)
122 | -- Start parsing the headers
123 | if start then
124 | -- This is a workaround for servers sending initial garbage before
125 | -- sending headers, such as if a bash script sends stdout. It assumes
126 | -- that we know all of the headers ahead of time. At this moment, the
127 | -- only valid headers start with "Content-*", so that's the thing we will
128 | -- be searching for.
129 | -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
130 | local buffer_start = buffer:find(header_start_pattern)
131 | local headers = parse_headers(buffer:sub(buffer_start, start - 1))
132 | buffer = buffer:sub(finish + 1)
133 | local content_length = headers.content_length
134 | -- Keep waiting for data until we have enough.
135 | while #buffer < content_length do
136 | buffer = buffer .. (coroutine.yield() or error "Expected more data for the body. The server may have died.") -- TODO hmm.
137 | end
138 | local body = buffer:sub(1, content_length)
139 | buffer = buffer:sub(content_length + 1)
140 | -- Yield our data.
141 | buffer = buffer
142 | .. (coroutine.yield(headers, body) or error "Expected more data for the body. The server may have died.") -- TODO hmm.
143 | else
144 | -- Get more data since we don't have enough.
145 | buffer = buffer .. (coroutine.yield() or error "Expected more data for the header. The server may have died.") -- TODO hmm.
146 | end
147 | end
148 | end
149 |
150 | local client_errors = vim.tbl_add_reverse_lookup {
151 | INVALID_SERVER_MESSAGE = 1,
152 | INVALID_SERVER_JSON = 2,
153 | NO_RESULT_CALLBACK_FOUND = 3,
154 | READ_ERROR = 4,
155 | NOTIFICATION_HANDLER_ERROR = 5,
156 | SERVER_REQUEST_HANDLER_ERROR = 6,
157 | SERVER_RESULT_CALLBACK_ERROR = 7,
158 | }
159 |
160 | --- Constructs an error message from an LSP error object.
161 | ---
162 | --@param err (table) The error object
163 | --@returns (string) The formatted error message
164 | local function format_rpc_error(err)
165 | validate {
166 | err = { err, "t" },
167 | }
168 |
169 | -- There is ErrorCodes in the LSP specification,
170 | -- but in ResponseError.code it is not used and the actual type is number.
171 | local code
172 | if protocol.ErrorCodes[err.code] then
173 | code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
174 | else
175 | code = string.format("code_name = unknown, code = %s,", err.code)
176 | end
177 |
178 | local message_parts = { "RPC[Error]", code }
179 | if err.message then
180 | table.insert(message_parts, "message =")
181 | table.insert(message_parts, string.format("%q", err.message))
182 | end
183 | if err.data then
184 | table.insert(message_parts, "data =")
185 | table.insert(message_parts, vim.inspect(err.data))
186 | end
187 | return table.concat(message_parts, " ")
188 | end
189 |
190 | --- Creates an RPC response object/table.
191 | ---
192 | --@param code RPC error code defined in `vim.lsp.protocol.ErrorCodes`
193 | --@param message (optional) arbitrary message to send to server
194 | --@param data (optional) arbitrary data to send to server
195 | local function rpc_response_error(code, message, data)
196 | -- TODO should this error or just pick a sane error (like InternalError)?
197 | local code_name = assert(protocol.ErrorCodes[code], "Invalid RPC error code")
198 | return setmetatable({
199 | code = code,
200 | message = message or code_name,
201 | data = data,
202 | }, {
203 | __tostring = format_rpc_error,
204 | })
205 | end
206 |
207 | local default_handlers = {}
208 | --@private
209 | --- Default handler for notifications sent to an LSP server.
210 | ---
211 | --@param method (string) The invoked LSP method
212 | --@param params (table): Parameters for the invoked LSP method
213 | function default_handlers.notification(method, params)
214 | local _ = log.debug() and log.debug("notification", method, params)
215 | end
216 | --@private
217 | --- Default handler for requests sent to an LSP server.
218 | ---
219 | --@param method (string) The invoked LSP method
220 | --@param params (table): Parameters for the invoked LSP method
221 | --@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
222 | function default_handlers.server_request(method, params)
223 | local _ = log.debug() and log.debug("server_request", method, params)
224 | return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
225 | end
226 | --@private
227 | --- Default handler for when a client exits.
228 | ---
229 | --@param code (number): Exit code
230 | --@param signal (number): Number describing the signal used to terminate (if
231 | ---any)
232 | function default_handlers.on_exit(code, signal)
233 | local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
234 | end
235 | --@private
236 | --- Default handler for client errors.
237 | ---
238 | --@param code (number): Error code
239 | --@param err (any): Details about the error
240 | ---any)
241 | function default_handlers.on_error(code, err)
242 | local _ = log.error() and log.error("client_error:", client_errors[code], err)
243 | end
244 |
245 | --- Starts an LSP server process and create an LSP RPC client object to
246 | --- interact with it.
247 | ---
248 | --@param cmd (string) Command to start the LSP server.
249 | --@param cmd_args (table) List of additional string arguments to pass to {cmd}.
250 | --@param handlers (table, optional) Handlers for LSP message types. Valid
251 | ---handler names are:
252 | --- - `"notification"`
253 | --- - `"server_request"`
254 | --- - `"on_error"`
255 | --- - `"on_exit"`
256 | --@param extra_spawn_params (table, optional) Additional context for the LSP
257 | --- server process. May contain:
258 | --- - {cwd} (string) Working directory for the LSP server process
259 | --- - {env} (table) Additional environment variables for LSP server process
260 | --@returns Client RPC object.
261 | ---
262 | --@returns Methods:
263 | --- - `notify()` |vim.lsp.rpc.notify()|
264 | --- - `request()` |vim.lsp.rpc.request()|
265 | ---
266 | --@returns Members:
267 | --- - {pid} (number) The LSP server's PID.
268 | --- - {handle} A handle for low-level interaction with the LSP server process
269 | --- |vim.loop|.
270 | local function start(cmd, cmd_args, handlers, extra_spawn_params)
271 | --@private
272 | local function on_error(errkind, ...)
273 | assert(client_errors[errkind])
274 | -- TODO what to do if this fails?
275 | pcall(handlers.on_error, errkind, ...)
276 | end
277 |
278 | --@private
279 | local function pcall_handler(errkind, status, head, ...)
280 | if not status then
281 | on_error(errkind, head, ...)
282 | return status, head
283 | end
284 | return status, head, ...
285 | end
286 | --@private
287 | local function try_call(errkind, fn, ...)
288 | return pcall_handler(errkind, pcall(fn, ...))
289 | end
290 |
291 | -- TODO periodically check message_callbacks for old requests past a certain
292 | -- time and log them. This would require storing the timestamp. I could call
293 | -- them with an error then, perhaps.
294 |
295 | local request_parser = coroutine.wrap(request_parser_loop)
296 | request_parser()
297 | stdout:read_start(function(err, chunk)
298 | if err then
299 | -- TODO better handling. Can these be intermittent errors?
300 | on_error(client_errors.READ_ERROR, err)
301 | return
302 | end
303 | -- This should signal that we are done reading from the client.
304 | if not chunk then
305 | return
306 | end
307 | -- Flush anything in the parser by looping until we don't get a result
308 | -- anymore.
309 | while true do
310 | local headers, body = request_parser(chunk)
311 | -- If we successfully parsed, then handle the response.
312 | if headers then
313 | handle_body(body)
314 | -- Set chunk to empty so that we can call request_parser to get
315 | -- anything existing in the parser to flush.
316 | chunk = ""
317 | else
318 | break
319 | end
320 | end
321 | end)
322 | end
323 |
324 | return M
325 | -- vim:sw=2 ts=2 et
326 |
--------------------------------------------------------------------------------
/lua/docgen/help.lua:
--------------------------------------------------------------------------------
1 | local render = require("docgen.renderer").render
2 | local render_without_first_line_prefix = require("docgen.renderer").render_without_first_line_prefix
3 |
4 | ---@brief [[
5 | --- All help formatting related utilties. Used to transform output from |docgen| into vim style documentation.
6 | --- Other documentation styles are possible, but have not yet been implemented.
7 | ---@brief ]]
8 |
9 | ---@tag docgen-help-formatter
10 | local help = {}
11 |
12 | local trim_trailing = function(str)
13 | return str:gsub("%s*$", "")
14 | end
15 |
16 | local align_text = function(left, right, width)
17 | left = left or ""
18 | right = right or ""
19 |
20 | local remaining = width - #left - #right
21 |
22 | return string.format("%s%s%s", left, string.rep(" ", remaining), right)
23 | end
24 |
25 | --- Format an entire generated metadata from |docgen|
26 | ---@param metadata table: The metadata from docgen
27 | help.format = function(metadata)
28 | if vim.tbl_isempty(metadata) then
29 | return ""
30 | end
31 |
32 | local formatted = ""
33 |
34 | local add = function(text, no_nl)
35 | formatted = string.format("%s%s%s", formatted, (text or ""), (no_nl and "" or "\n"))
36 | end
37 |
38 | -- TODO: Make top level
39 |
40 | add(string.rep("=", 80))
41 | if metadata.tag then
42 | -- Support multiple tags
43 | local tags = vim.tbl_map(function(x)
44 | return string.format("*%s*", x)
45 | end, vim.split(metadata.tag, "%s+"))
46 |
47 | local left = (function()
48 | if metadata.config and metadata.config.name and type(metadata.config.name) == "string" then
49 | return metadata.config.name:upper()
50 | else
51 | local ret = vim.split(metadata.tag, "%s+")
52 | if ret and ret[1] then
53 | ret = vim.split(ret[1], "%.")
54 | ret = ret[#ret]
55 | end
56 | return ret:upper()
57 | end
58 | end)()
59 | add(align_text(left, table.concat(tags, " "), 80))
60 | add()
61 | end
62 |
63 | -- Make brief
64 | if metadata.brief then
65 | local result = help.format_brief(metadata.brief)
66 |
67 | if not result then
68 | error "Missing result"
69 | end
70 |
71 | add(result)
72 | add()
73 | end
74 |
75 | -- Make commands
76 | local commands = metadata.commands or {}
77 | if not vim.tbl_isempty(commands) then
78 | add(help.format_commands(commands, metadata.config))
79 | add()
80 | end
81 |
82 | -- Make classes
83 | local metadata_classes = vim.deepcopy(metadata.class_list or {})
84 |
85 | if metadata.config then
86 | if type(metadata.config.class_order) == "function" then
87 | metadata.config.class_order(metadata_classes)
88 | elseif metadata.config.class_order == "ascending" then
89 | table.sort(metadata_classes)
90 | elseif metadata.config.class_order == "descending" then
91 | table.sort(metadata_classes, function(a, b)
92 | return a > b
93 | end)
94 | end
95 | end
96 | for _, class_name in ipairs(metadata_classes) do
97 | local v = metadata.classes[class_name]
98 |
99 | local result = help.format_class_metadata(v, metadata.config)
100 | if not result then
101 | error "Missing result"
102 | end
103 |
104 | add(result)
105 | add()
106 | end
107 |
108 | -- Make functions
109 | local metadata_keys = vim.deepcopy(metadata.function_list or {})
110 |
111 | if metadata.config then
112 | if type(metadata.config.function_order) == "function" then
113 | metadata.config.function_order(metadata_keys)
114 | elseif metadata.config.function_order == "ascending" then
115 | table.sort(metadata_keys)
116 | elseif metadata.config.function_order == "descending" then
117 | table.sort(metadata_keys, function(a, b)
118 | return a > b
119 | end)
120 | end
121 |
122 | -- config.module:
123 | -- Replace the module return name with this.
124 | if metadata.config.module then
125 | if type(metadata.return_module) == "string" then
126 | metadata.config.transform_name = function(_, name)
127 | return (string.gsub(name, metadata.return_module, metadata.config.module, 1))
128 | end
129 | end
130 | end
131 | end
132 |
133 | metadata_keys = vim.tbl_filter(function(func_name)
134 | if string.find(func_name, ".__", 1, true) then
135 | return false
136 | end
137 |
138 | if string.find(func_name, ":__", 1, true) then
139 | return false
140 | end
141 |
142 | if func_name:sub(1, 2) == "__" then
143 | return false
144 | end
145 |
146 | return func_name:sub(1, 2) ~= "__"
147 | end, metadata_keys)
148 |
149 | for _, func_name in ipairs(metadata_keys) do
150 | local v = metadata.functions[func_name]
151 |
152 | local result = help.format_function_metadata(v, metadata.config)
153 | if not result then
154 | error "Missing result"
155 | end
156 |
157 | add(result)
158 | add()
159 | end
160 |
161 | add()
162 |
163 | return formatted
164 | end
165 |
166 | help.format_brief = function(brief_metadata)
167 | return render(brief_metadata, "", 79)
168 | end
169 |
170 | -- TODO(conni2461): Do we want some configuration for alignment?!
171 | help.__left_side_parameter_field = function(input, max_name_width, space_prefix)
172 | local name = string.format("%s%s{%s} ", space_prefix, space_prefix, input.name)
173 | local diff = max_name_width - #input.name
174 |
175 | return string.format("%s%s(%s) ", name, string.rep(" ", diff), table.concat(input.type, "|"))
176 | end
177 |
178 | help.format_parameter_field = function(input, space_prefix, max_name_width, align_width)
179 | local left_side = help.__left_side_parameter_field(input, max_name_width, space_prefix)
180 |
181 | local width = math.max(align_width, 78)
182 | local right_side = render_without_first_line_prefix(input.description, string.rep(" ", align_width), width)
183 | if right_side == "" then
184 | return string.format("%s\n", trim_trailing(left_side))
185 | end
186 |
187 | local diff = align_width - #left_side
188 | assert(diff >= 0, "Otherwise we have a big error somewhere in docgen")
189 |
190 | return string.format("%s%s%s\n", left_side, string.rep(" ", diff), right_side)
191 | end
192 |
193 | help.iter_parameter_field = function(input, list, name, space_prefix)
194 | local output = ""
195 | if list and table.getn(list) > 0 then
196 | output = string.format("%s\n%s%s: ~\n", output, space_prefix, name)
197 | local max_name_width = 0
198 | for _, e in ipairs(list) do
199 | local width = #input[e].name
200 | if width > max_name_width then
201 | max_name_width = width
202 | end
203 | end
204 |
205 | local left_width = 0
206 | for _, e in ipairs(list) do
207 | local width = #(help.__left_side_parameter_field(input[e], max_name_width, space_prefix))
208 | if width > left_width then
209 | left_width = width
210 | end
211 | end
212 |
213 | for _, e in ipairs(list) do
214 | output =
215 | string.format("%s%s", output, help.format_parameter_field(input[e], space_prefix, max_name_width, left_width))
216 | end
217 | end
218 | return output
219 | end
220 |
221 | help.format_class_metadata = function(class, config)
222 | config = config or {}
223 | local space_prefix = string.rep(" ", config.space_prefix or 4)
224 |
225 | local doc = ""
226 | local left_side = class.parent and string.format("%s : %s", class.name, class.parent) or class.name
227 |
228 | local header = align_text(left_side, string.format("*%s*", class.name), 78)
229 | doc = string.format("%s%s\n", doc, header)
230 |
231 | local description = render(class.desc, space_prefix, 79)
232 | doc = string.format("%s%s\n", doc, description)
233 |
234 | if class.parent then
235 | doc = string.format("%s\n%sParents: ~\n%s%s|%s|\n", doc, space_prefix, space_prefix, space_prefix, class.parent)
236 | end
237 |
238 | if type(config.field_order) == "function" then
239 | config.field_order(class.field_list)
240 | elseif config.field_order == "ascending" then
241 | table.sort(class.field_list)
242 | elseif config.field_order == "descending" then
243 | table.sort(class.field_list, function(a, b)
244 | return a > b
245 | end)
246 | end
247 | doc = string.format(
248 | "%s%s",
249 | doc,
250 | help.iter_parameter_field(class.fields, class.field_list, config.field_heading or "Fields", space_prefix)
251 | )
252 |
253 | return doc
254 | end
255 |
256 | ---@class DocgenCommand
257 | ---@field name string
258 | ---@field usage string
259 | ---@field documentation string[]
260 |
261 | --- Format commands
262 | ---@param command_metadata DocgenCommand[]
263 | ---@param config any
264 | help.format_commands = function(command_metadata, config)
265 | local doc = ""
266 |
267 | for _, command in ipairs(command_metadata) do
268 | config = config or {}
269 |
270 | local right_side = string.format("*:%s*", command.name)
271 | local header = align_text("", right_side, 78)
272 |
273 | doc = doc .. header .. "\n"
274 | doc = doc .. command.usage .. " ~\n"
275 | doc = doc .. render(command.documentation, " ", 79)
276 | doc = doc .. "\n\n"
277 | end
278 |
279 | return doc
280 | end
281 |
282 | help.format_function_metadata = function(function_metadata, config)
283 | config = config or {}
284 | local space_prefix = string.rep(" ", 4)
285 |
286 | local name = function_metadata.name
287 | local tag = config.transform_name and config.transform_name(function_metadata, name) or name
288 |
289 | local left_side = string.format(
290 | "%s(%s)",
291 | name,
292 | table.concat(
293 | vim.tbl_map(function(val)
294 | return string.format("{%s}", function_metadata.parameters[val].name)
295 | end, function_metadata.parameter_list),
296 | ", "
297 | )
298 | )
299 |
300 | -- Add single whitespace on the left to ensure that it reads as help tag
301 | local right_side = string.format(" *%s()*", tag)
302 |
303 | -- TODO(conni2461): LONG function names break this thing
304 | local header = align_text(left_side, right_side, 78)
305 |
306 | local doc = ""
307 | doc = string.format("%s%s\n", doc, header)
308 |
309 | local description = render(function_metadata.description or {}, space_prefix, 79)
310 | doc = string.format("%s%s\n", doc, description)
311 |
312 | -- TODO(conni2461): CLASS
313 |
314 | -- Handles parameter if used
315 | doc = string.format(
316 | "%s%s",
317 | doc,
318 | help.iter_parameter_field(
319 | function_metadata.parameters,
320 | function_metadata.parameter_list,
321 | "Parameters",
322 | space_prefix
323 | )
324 | )
325 |
326 | if type(config.field_order) == "function" then
327 | config.field_order(function_metadata.field_list)
328 | elseif config.field_order == "ascending" then
329 | table.sort(function_metadata.field_list)
330 | elseif config.field_order == "descending" then
331 | table.sort(function_metadata.field_list, function(a, b)
332 | return a > b
333 | end)
334 | end
335 | -- Handle fields if used
336 | doc = string.format(
337 | "%s%s",
338 | doc,
339 | help.iter_parameter_field(
340 | function_metadata.fields,
341 | function_metadata.field_list,
342 | config.field_heading or "Fields",
343 | space_prefix
344 | )
345 | )
346 |
347 | local gen_misc_doc = function(identification, ins)
348 | if function_metadata[identification] then
349 | local title = string.format("%s%s", identification:sub(1, 1):upper(), identification:sub(2, -1))
350 |
351 | if doc:sub(#doc, #doc) ~= "\n" then
352 | doc = string.format("%s\n", doc)
353 | end
354 | doc = string.format("%s\n%s%s: ~\n", doc, space_prefix, title)
355 | for _, x in ipairs(function_metadata[identification]) do
356 | doc = string.format("%s%s\n", doc, render({ string.format(ins, x) }, string.rep(space_prefix, 2), 78))
357 | end
358 | end
359 | end
360 |
361 | gen_misc_doc("varargs", "%s")
362 | gen_misc_doc("return", "%s")
363 | gen_misc_doc("usage", "%s")
364 | gen_misc_doc("see", "|%s()|")
365 |
366 | return doc
367 | end
368 |
369 | return help
370 |
--------------------------------------------------------------------------------
/HOWTO.md:
--------------------------------------------------------------------------------
1 | # How to write emmy documentation
2 |
3 | ## Brief
4 |
5 | Brief is used to describe a module. This is an example input:
6 |
7 | ```lua
8 | ---@brief [[
9 | --- This will document a module and will be found at the top of each file. It uses an internal markdown renderer
10 | --- so you don't need to worry about formatting. It will wrap the lines into one paragraph and
11 | --- will make sure that the max line width is < 80.
12 | ---
13 | --- To start a new paragraph with a newline.
14 | ---
15 | --- To explicitly do a breakline do a `
` at the end.
16 | --- This is useful sometimes
17 | ---
18 | --- We also support itemize and enumerate
19 | --- - Item 1
20 | --- - Item 1.1 This item will be wrapped as well and the result will be as expected. This is really handy.
21 | --- - Item 1.1.1
22 | --- - Item 1.2
23 | --- - Item 2
24 | ---
25 | --- 1. Item
26 | --- 1.1. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna
27 | --- aliquyam erat, sed diam voluptua.
28 | --- 1.1.1. Item
29 | --- 1.2. Item
30 | --- 2. Item
31 | ---
32 | ---
33 | --- You can disable formatting with a
34 | --- pre block.
35 | --- This is useful if you want to draw a table or write some code
36 | ---
37 | ---
38 | ---@brief ]]
39 |
40 | ```
41 |
42 | Output:
43 |
44 | ```
45 | ================================================================================
46 | This will document a module and will be found at the top of each file. It uses
47 | an internal markdown renderer so you don't need to worry about formatting. It
48 | will wrap the lines into one paragraph and will make sure that the max line
49 | width is < 80.
50 |
51 | To start a new paragraph with a newline.
52 |
53 | To explicitly do a breakline do a `
` at the end.
54 | This is useful sometimes
55 |
56 | We also support itemize and enumerate
57 | - Item 1
58 | - Item 1.1 This item will be wrapped as well and the result will be as
59 | expected. This is really handy.
60 | - Item 1.1.1
61 | - Item 1.2
62 | - Item 2
63 |
64 | 1. Item
65 | 1.1. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
66 | eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
67 | voluptua.
68 | 1.1.1. Item
69 | 1.2. Item
70 | 2. Item
71 |
72 | You can disable formatting with a
73 | pre block.
74 | This is useful if you want to draw a table or write some code
75 |
76 |
77 |
78 |
79 | ```
80 |
81 | ## Tag
82 |
83 | Add a tag to your module. This is suggested:
84 |
85 | ```lua
86 | ---@tag your_module
87 |
88 | ```
89 |
90 | Output:
91 |
92 | ```
93 | ================================================================================
94 | *your_module*
95 |
96 |
97 |
98 | ```
99 |
100 | ## Config
101 |
102 | You can configure docgen on file basis. For example you can define how `functions` or `classes` are sorted.
103 |
104 | ```lua
105 | ---@config { ['function_order'] = 'ascending', ['class_order'] = 'descending' }
106 |
107 | ```
108 | Available keys value pairs are:
109 | - `function_order`:
110 | - `file_order` (default)
111 | - `ascending`
112 | - `descending`
113 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
114 | - If you have a typo it will do `file_order` sorting
115 | - `class_order`:
116 | - `file_order` (default)
117 | - `ascending`
118 | - `descending`
119 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
120 | - If you have a typo it will do `file_order` sorting
121 | - `field_order`:
122 | - `file_order` (default)
123 | - `ascending`
124 | - `descending`
125 | - or it can accept a function. example: `function(tbl) table.sort(tbl, function(a, b) return a > b end) end`
126 | - If you have a typo it will do `file_order` sorting
127 |
128 | ## Function Header
129 |
130 | You can describe your functions.
131 |
132 | Note: We will only generate documentation for functions that are exported with the module.
133 |
134 | ```lua
135 | local m = {}
136 |
137 | --- We will not generate documentation for this function
138 | local some_func = function()
139 | return 5
140 | end
141 |
142 | --- We will not generate documentation for this function
143 | --- because it has `__` as prefix. This is the one exception
144 | m.__hidden = function()
145 | return 5
146 | end
147 |
148 | --- The documentation for this function will be generated.
149 | --- The markdown renderer will be used again.
150 | --- With the same set of features
151 | m.actual_func = function()
152 | return 5
153 | end
154 |
155 | return m
156 |
157 | ```
158 |
159 | Output:
160 |
161 | ```
162 | ================================================================================
163 | m.actual_func() *m.actual_func()*
164 | The documentation for this function will be generated. The markdown
165 | renderer will be used again.
166 | With the same set of features
167 |
168 |
169 |
170 |
171 |
172 | ```
173 |
174 | ## Parameter
175 |
176 | You can specify parameters and document them with `---@param name type: desc`
177 |
178 | ```lua
179 | local math = {}
180 |
181 | --- Will return the bigger number
182 | ---@param a number: first number
183 | ---@param b number: second number
184 | math.max = function(a, b)
185 | if a > b then
186 | return a
187 | end
188 | return b
189 | end
190 |
191 | return math
192 |
193 | ```
194 |
195 | Output:
196 |
197 | ```
198 | ================================================================================
199 | math.max({a}, {b}) *math.max()*
200 | Will return the bigger number
201 |
202 |
203 | Parameters: ~
204 | {a} (number) first number
205 | {b} (number) second number
206 |
207 |
208 |
209 |
210 | ```
211 |
212 | ## Field
213 |
214 | Can be used to describe a parameter table.
215 |
216 | ```lua
217 | local x = {}
218 |
219 | --- This function has documentation
220 | ---@param t table: some input table
221 | ---@field k1 number: first key of input table
222 | ---@field key function: second key of input table
223 | ---@field key3 table: third key of input table
224 | function x.hello(t)
225 | return 0
226 | end
227 |
228 | return x
229 |
230 | ```
231 |
232 | Output:
233 |
234 | ```
235 | ================================================================================
236 | x.hello({t}) *x.hello()*
237 | This function has documentation
238 |
239 |
240 | Parameters: ~
241 | {t} (table) some input table
242 |
243 | Fields: ~
244 | {k1} (number) first key of input table
245 | {key} (function) second key of input table
246 | {key3} (table) third key of input table
247 |
248 |
249 |
250 |
251 | ```
252 |
253 | ## Return
254 |
255 | You can specify a return parameter with `---@return type: desc`
256 |
257 | ```lua
258 | local math = {}
259 |
260 | --- Will return the bigger number
261 | ---@param a number: first number
262 | ---@param b number: second number
263 | ---@return number: bigger number
264 | function math.max = function(a, b)
265 | if a > b then
266 | return a
267 | end
268 | return b
269 | end
270 |
271 | return math
272 |
273 | ```
274 |
275 | Output:
276 |
277 | ```
278 | ================================================================================
279 | math.max({a}, {b}) *math.max()*
280 | Will return the bigger number
281 |
282 |
283 | Parameters: ~
284 | {a} (number) first number
285 | {b} (number) second number
286 |
287 | Return: ~
288 | number: bigger number
289 |
290 |
291 |
292 |
293 | ```
294 |
295 | ## See
296 |
297 | Reference something else.
298 |
299 | ```lua
300 | local math = {}
301 |
302 | --- Will return the smaller number
303 | ---@param a number: first number
304 | ---@param b number: second number
305 | ---@return number: smaller number
306 | ---@see math.max
307 | function math.min(a, b)
308 | if a < b then
309 | return a
310 | end
311 | return b
312 | end
313 |
314 | --- Will return the bigger number
315 | ---@param a number: first number
316 | ---@param b number: second number
317 | ---@return number: bigger number
318 | ---@see math.min
319 | function math.max(a, b)
320 | if a > b then
321 | return a
322 | end
323 | return b
324 | end
325 |
326 | return math
327 |
328 | ```
329 |
330 | Output:
331 |
332 | ```
333 | ================================================================================
334 | math.min({a}, {b}) *math.min()*
335 | Will return the smaller number
336 |
337 |
338 | Parameters: ~
339 | {a} (number) first number
340 | {b} (number) second number
341 |
342 | Return: ~
343 | number: smaller number
344 |
345 | See: ~
346 | |math.max()|
347 |
348 |
349 | math.max({a}, {b}) *math.max()*
350 | Will return the bigger number
351 |
352 |
353 | Parameters: ~
354 | {a} (number) first number
355 | {b} (number) second number
356 |
357 | Return: ~
358 | number: bigger number
359 |
360 | See: ~
361 | |math.min()|
362 |
363 |
364 |
365 |
366 | ```
367 |
368 | ## Class
369 |
370 | You can define your own classes and types to give a better sense of the Input or Ouput of a function.
371 | Another good usecase for this are structs defined by ffi.
372 |
373 | This is a more complete (not functional) example where we define the documentation of the c struct
374 | `passwd` and return this struct with a function.
375 |
376 | ```lua
377 | local m = {}
378 |
379 | ---@class passwd @The passwd c struct
380 | ---@field pw_name string: username
381 | ---@field pw_passwd string: user password
382 | ---@field pw_uid number: user id
383 | ---@field pw_gid number: groupd id
384 | ---@field pw_gecos string: user information
385 | ---@field pw_dir string: user home directory
386 | ---@field pw_shell string: user default shell
387 |
388 | --- Get user by id
389 | ---@param id number: user id
390 | ---@return passwd: returns a password table
391 | function m.get_user(id)
392 | return ffi.C.getpwuid(id)
393 | end
394 |
395 | return m
396 |
397 | ```
398 |
399 | Output:
400 |
401 | ```
402 | ================================================================================
403 | passwd *passwd*
404 | The passwd c struct
405 |
406 | Fields: ~
407 | {pw_name} (string) username
408 | {pw_passwd} (string) user password
409 | {pw_uid} (number) user id
410 | {pw_gid} (number) groupd id
411 | {pw_gecos} (string) user information
412 | {pw_dir} (string) user home directory
413 | {pw_shell} (string) user default shell
414 |
415 |
416 | m.get_user({id}) *m.get_user()*
417 | Get user by id
418 |
419 |
420 | Parameters: ~
421 | {id} (number) user id
422 |
423 | Return: ~
424 | passwd: returns a password table
425 |
426 |
427 |
428 |
429 | ```
430 |
431 | ## Eval
432 |
433 | You can evaluate arbitrary code. For example if you have a static table you can
434 | do generate a table that will be part of the `description` output.
435 |
436 |
437 | ```lua
438 | local m = {}
439 |
440 | --- The documentation for this function will be generated.
441 | --- The markdown renderer will be used again.
442 | --- With the same set of features
443 | ---@eval { ['description'] = require('your_module').__format_keys() }
444 | m.actual_func = function()
445 | return 5
446 | end
447 |
448 | local static_values = {
449 | 'a',
450 | 'b',
451 | 'c',
452 | 'd',
453 | }
454 |
455 | m.__format_keys = function()
456 | -- we want to do formatting
457 | local table = { '', 'Static Values: ~' }
458 |
459 | for _, v in ipairs(static_values) do
460 | table.insert(table, ' ' .. v)
461 | end
462 |
463 | table.insert(table, '
')
464 | return table
465 | end
466 |
467 | return m
468 |
469 | ```
470 |
471 | Output:
472 |
473 | ```
474 | ================================================================================
475 | m.actual_func() *m.actual_func()*
476 | The documentation for this function will be generated. The markdown
477 | renderer will be used again.
478 | With the same set of features.
479 |
480 | Static Values: ~
481 | a
482 | b
483 | c
484 | d
485 | ```
486 |
487 |
--------------------------------------------------------------------------------