From 93c55c238f4c1088da4dc6ec80103eb3ef4085d2 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sat, 25 May 2024 01:39:06 +0200 Subject: test(lintdoc): check that input list is same length as output list (#28976) --- scripts/gen_help_html.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index cdfb85bde6..e238fee5f8 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1434,12 +1434,10 @@ function M.test_gen(help_dir) help_dir = vim.fn.expand(help_dir or '$VIMRUNTIME/doc') print('doc path = ' .. vim.uv.fs_realpath(help_dir)) - local rv = M.gen( - help_dir, - tmpdir, - -- Because gen() is slow (~30s), this test is limited to a few files. - { 'help.txt', 'index.txt', 'nvim.txt' } - ) + -- Because gen() is slow (~30s), this test is limited to a few files. + local input = { 'help.txt', 'index.txt', 'nvim.txt' } + local rv = M.gen(help_dir, tmpdir, input) + eq(#input, #rv.helpfiles) eq(0, rv.err_count, 'parse errors in :help docs') eq({}, rv.invalid_links, 'invalid tags in :help docs') end -- cgit From 6e8a728e3dad747d0c46dc47a530b76e8997bc08 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 25 May 2024 20:35:37 +0200 Subject: refactor: fix luals type warnings --- scripts/gen_help_html.lua | 4 ++-- scripts/gen_vimdoc.lua | 4 ++-- scripts/luacats_parser.lua | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index e238fee5f8..722efc489f 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1372,7 +1372,7 @@ function M.validate(help_dir, include, parser_path) parser_path = parser_path and vim.fn.expand(parser_path) or nil for _, f in ipairs(helpfiles) do - local helpfile = assert(vim.fs.basename(f)) + local helpfile = vim.fs.basename(f) local rv = validate_one(f, parser_path) print(('validated (%-4s errors): %s'):format(#rv.parse_errors, helpfile)) if #rv.parse_errors > 0 then @@ -1430,7 +1430,7 @@ end --- :help files, we can be precise about the tolerances here. --- @param help_dir? string e.g. '$VIMRUNTIME/doc' or './runtime/doc' function M.test_gen(help_dir) - local tmpdir = assert(vim.fs.dirname(vim.fn.tempname())) + local tmpdir = vim.fs.dirname(vim.fn.tempname()) help_dir = vim.fn.expand(help_dir or '$VIMRUNTIME/doc') print('doc path = ' .. vim.uv.fs_realpath(help_dir)) diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 9c6225efc3..b88bdff99b 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -813,7 +813,7 @@ local function get_script_path() end local script_path = get_script_path() -local base_dir = vim.fs.dirname(assert(vim.fs.dirname(script_path))) +local base_dir = vim.fs.dirname(vim.fs.dirname(script_path)) local function delete_lines_below(doc_file, tokenstr) local lines = {} --- @type string[] @@ -965,7 +965,7 @@ local function gen_target(cfg) end end -- FIXME: Using f_base will confuse `_meta/protocol.lua` with `protocol.lua` - local f_base = assert(vim.fs.basename(f)) + local f_base = vim.fs.basename(f) sections[f_base] = make_section(f_base, cfg, briefs_txt, funs_txt) end diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua index cb301b32e4..66fe8ed616 100644 --- a/scripts/luacats_parser.lua +++ b/scripts/luacats_parser.lua @@ -458,7 +458,7 @@ local function dump_uncommitted(filename, uncommitted) local out_path = 'luacats-uncommited/' .. filename:gsub('/', '%%') .. '.txt' if #uncommitted > 0 then print(string.format('Could not commit %d objects in %s', #uncommitted, filename)) - vim.fn.mkdir(assert(vim.fs.dirname(out_path)), 'p') + vim.fn.mkdir(vim.fs.dirname(out_path), 'p') local f = assert(io.open(out_path, 'w')) for i, x in ipairs(uncommitted) do f:write(i) -- cgit From ff097f2091e7a970e5b12960683b4dade5563040 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 4 Feb 2024 14:13:23 -0800 Subject: feat(lsp): completion side effects --- scripts/gen_vimdoc.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts') diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 9c6225efc3..d85089116e 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -273,6 +273,7 @@ local config = { 'buf.lua', 'diagnostic.lua', 'codelens.lua', + 'completion.lua', 'inlay_hint.lua', 'tagfunc.lua', 'semantic_tokens.lua', -- cgit From d87ecfc8bc3c737e2e7f766d365e67dd08c3b600 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Thu, 30 May 2024 13:11:21 -0400 Subject: docs(luacats): add tuple support --- scripts/luacats_grammar.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index 29f3bda5aa..906f66c18a 100644 --- a/scripts/luacats_grammar.lua +++ b/scripts/luacats_grammar.lua @@ -170,7 +170,7 @@ local grammar = P { ltype = parenOpt(v.ty_union), ty_union = v.ty_opt * rep(Pf('|') * v.ty_opt), - ty = v.ty_fun + ident + v.ty_table + literal + paren(v.ty) + v.ty_generic, + ty = v.ty_fun + ident + v.ty_table + literal + paren(v.ty) + v.ty_generic + v.ty_tuple, ty_param = Pf('<') * comma1(v.ltype) * fill * P('>'), ty_opt = v.ty * opt(v.ty_param) * opt(P('[]')) * opt(P('?')), ty_index = (Pf('[') * (v.ltype + ident + rep1(num)) * fill * P(']')), @@ -180,6 +180,7 @@ local grammar = P { fun_param = lname * opt(colon * v.ltype), ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)), ty_generic = P('`') * letter * P('`'), + ty_tuple = Pf('[') * comma(v.ty_opt) * fill * P(']') } return grammar --[[@as nvim.luacats.grammar]] -- cgit From 217828b20c9fc224c6892ce1b0129850c280f598 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Thu, 30 May 2024 13:25:06 -0400 Subject: fixup! docs(luacats): add tuple support --- scripts/luacats_grammar.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index 906f66c18a..6742eab5e9 100644 --- a/scripts/luacats_grammar.lua +++ b/scripts/luacats_grammar.lua @@ -180,7 +180,7 @@ local grammar = P { fun_param = lname * opt(colon * v.ltype), ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)), ty_generic = P('`') * letter * P('`'), - ty_tuple = Pf('[') * comma(v.ty_opt) * fill * P(']') + ty_tuple = Pf('[') * comma(v.ty_opt) * fill * P(']'), } return grammar --[[@as nvim.luacats.grammar]] -- cgit From d62d181ce065556be51d5eda0425aa42f427cc27 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Fri, 31 May 2024 10:48:05 -0400 Subject: refactor(lsp): use tuple syntax in generated protocol types (#29110) --- scripts/gen_lsp.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua index 04d19f22e6..1706b39864 100644 --- a/scripts/gen_lsp.lua +++ b/scripts/gen_lsp.lua @@ -297,13 +297,13 @@ function M.gen(opt) -- TupleType elseif type.kind == 'tuple' then - local tuple = '{ ' - for i, value in ipairs(type.items) do - tuple = tuple .. '[' .. i .. ']: ' .. parse_type(value, prefix) .. ', ' + local tuple = '[' + for _, value in ipairs(type.items) do + tuple = tuple .. parse_type(value, prefix) .. ', ' end -- remove , at the end tuple = tuple:sub(0, -3) - return tuple .. ' }' + return tuple .. ']' end vim.print('WARNING: Unknown type ', type) -- cgit From 9eb0426002696fba4a7c5b9cadd8799a8ae18e6a Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Fri, 31 May 2024 11:47:32 -0400 Subject: fix(luacats): allow all types inside tuples --- scripts/luacats_grammar.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index 6742eab5e9..9360eb9417 100644 --- a/scripts/luacats_grammar.lua +++ b/scripts/luacats_grammar.lua @@ -180,7 +180,7 @@ local grammar = P { fun_param = lname * opt(colon * v.ltype), ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)), ty_generic = P('`') * letter * P('`'), - ty_tuple = Pf('[') * comma(v.ty_opt) * fill * P(']'), + ty_tuple = Pf('[') * comma(v.ltype) * fill * P(']'), } return grammar --[[@as nvim.luacats.grammar]] -- cgit From 8cbb1f20e557461c8417583a7f69d53aaaef920b Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Tue, 4 Jun 2024 09:06:02 -0400 Subject: refactor(lua): use tuple syntax everywhere #29111 --- scripts/gen_eval_files.lua | 12 ++++++------ scripts/gen_vimdoc.lua | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f1bba5c0a2..76092f8b39 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -6,7 +6,7 @@ local DEP_API_METADATA = 'build/funcs_metadata.mpack' --- @class vim.api.metadata --- @field name string ---- @field parameters {[1]:string,[2]:string}[] +--- @field parameters [string,string][] --- @field return_type string --- @field deprecated_since integer --- @field eval boolean @@ -149,7 +149,7 @@ local function api_type(t) end --- @param f string ---- @param params {[1]:string,[2]:string}[]|true +--- @param params [string,string][]|true --- @return string local function render_fun_sig(f, params) local param_str --- @type string @@ -158,7 +158,7 @@ local function render_fun_sig(f, params) else param_str = table.concat( vim.tbl_map( - --- @param v {[1]:string,[2]:string} + --- @param v [string,string] --- @return string function(v) return v[1] @@ -178,8 +178,8 @@ end --- Uniquify names --- Fix any names that are lua keywords ---- @param params {[1]:string,[2]:string,[3]:string}[] ---- @return {[1]:string,[2]:string,[3]:string}[] +--- @param params [string,string,string][] +--- @return [string,string,string][] local function process_params(params) local seen = {} --- @type table local sfx = 1 @@ -245,7 +245,7 @@ local function get_api_meta() for _, fun in pairs(functions) do local deprecated = fun.deprecated_since ~= nil - local params = {} --- @type {[1]:string,[2]:string}[] + local params = {} --- @type [string,string][] for _, p in ipairs(fun.params) do params[#params + 1] = { p.name, diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index dac6c6f461..507426c8c0 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -935,7 +935,7 @@ local function gen_target(cfg) expand_files(cfg.files) - --- @type table, [2]: nvim.luacats.parser.fun[], [3]: string[]}> + --- @type table, nvim.luacats.parser.fun[], string[]]> local file_results = {} --- @type table -- cgit From 6c7677e5d274da7e477518aa29b0faa862e61627 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Fri, 7 Jun 2024 05:36:14 +0200 Subject: revert(commitlint): stop ignoring "fixup" commits (#29184) This reverts 2875d45e79b80878af45c91702914f4f0d0e3dca. Allowing lintcommit to ignore "fixup" makes it too easy to fixup commits to be merged on master as the CI won't give any indications that something is wrong. Contributors can always squash their pull requests if it annoys them too much. --- scripts/lintcommit.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'scripts') diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 96f6304247..f0e2feaab3 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -41,12 +41,6 @@ end -- Returns nil if the given commit message is valid, or returns a string -- message explaining why it is invalid. local function validate_commit(commit_message) - -- Return nil if the commit message starts with "fixup" as it signifies it's - -- a work in progress and shouldn't be linted yet. - if vim.startswith(commit_message, 'fixup') then - return nil - end - local commit_split = vim.split(commit_message, ':', { plain = true }) -- Return nil if the type is vim-patch since most of the normal rules don't -- apply. @@ -229,9 +223,9 @@ function M._test() ['vim-patch:8.2.3374: Pyret files are not recognized (#15642)'] = true, ['vim-patch:8.1.1195,8.2.{3417,3419}'] = true, ['revert: "ci: use continue-on-error instead of "|| true""'] = true, - ['fixup'] = true, - ['fixup: commit message'] = true, - ['fixup! commit message'] = true, + ['fixup'] = false, + ['fixup: commit message'] = false, + ['fixup! commit message'] = false, [':no type before colon 1'] = false, [' :no type before colon 2'] = false, [' :no type before colon 3'] = false, -- cgit From 5e49ef0af3cb8dba658e5d0dc6a807f8edebf590 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 11 Jun 2024 12:05:18 +0100 Subject: refactor(lua): improve type annotations --- scripts/gen_vimdoc.lua | 9 ++++++--- scripts/luacats_parser.lua | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 507426c8c0..dc384c12f5 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -421,8 +421,11 @@ local function render_type(ty, generics, default) end --- @param p nvim.luacats.parser.param|nvim.luacats.parser.field -local function should_render_param(p) - return not p.access and not contains(p.name, { '_', 'self' }) +local function should_render_field_or_param(p) + return not p.nodoc + and not p.access + and not contains(p.name, { '_', 'self' }) + and not vim.startswith(p.name, '_') end --- @param desc? string @@ -524,7 +527,7 @@ end local function render_fields_or_params(xs, generics, classes, exclude_types) local ret = {} --- @type string[] - xs = vim.tbl_filter(should_render_param, xs) + xs = vim.tbl_filter(should_render_field_or_param, xs) local indent = 0 for _, p in ipairs(xs) do diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua index 66fe8ed616..e73a42111d 100644 --- a/scripts/luacats_parser.lua +++ b/scripts/luacats_parser.lua @@ -46,6 +46,7 @@ local luacats_grammar = require('scripts.luacats_grammar') --- @field type string --- @field desc string --- @field access? 'private'|'package'|'protected' +--- @field nodoc? true --- @class nvim.luacats.parser.class --- @field kind 'class' @@ -270,6 +271,7 @@ local function fun2field(fun) type = table.concat(parts, ''), access = fun.access, desc = fun.desc, + nodoc = fun.nodoc, } end -- cgit From e947f226bebef1310af39ce3d93d7bb87e85d757 Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Sat, 1 Jun 2024 00:18:59 -0700 Subject: fix(types): use vararg return type annotation build(types): allow vararg returns in function types --- scripts/luacats_grammar.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index 9360eb9417..ebb0183fd9 100644 --- a/scripts/luacats_grammar.lua +++ b/scripts/luacats_grammar.lua @@ -178,7 +178,8 @@ local grammar = P { table_elem = v.table_key * colon * v.ltype, ty_table = Pf('{') * comma1(v.table_elem) * fill * P('}'), fun_param = lname * opt(colon * v.ltype), - ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)), + fun_ret = v.ltype + (ident * colon * v.ltype) + (P('...') * opt(colon * v.ltype)), + ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.fun_ret)), ty_generic = P('`') * letter * P('`'), ty_tuple = Pf('[') * comma(v.ltype) * fill * P(']'), } -- cgit From ceea6898a8bdcb6c4cfe06b8dc4739c144e6b1f8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 19 Jun 2024 09:45:40 -0700 Subject: fix(gen_help_html): handle delimiter, heading #29415 Problem: vimdoc grammar added new forms that are not handled in our HTML generator. https://github.com/neovim/tree-sitter-vimdoc/pull/134 Solution: Update `gen_help_html.lua`. Fixes #29277 --- scripts/gen_help_html.lua | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 722efc489f..4584d87bf5 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -490,7 +490,6 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) or nil -- Parent kind (string). local parent = root:parent() and root:parent():type() or nil - local text = '' -- Gets leading whitespace of `node`. local function ws(node) node = node or root @@ -508,6 +507,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) return string.format('%s%s', ws_, vim.treesitter.get_node_text(node, opt.buf)) end + local text = '' local trimmed ---@type string if root:named_child_count() == 0 or node_name == 'ERROR' then text = node_text() @@ -537,12 +537,17 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) if is_noise(text, stats.noise_lines) then return '' -- Discard common "noise" lines. end - -- Remove "===" and tags from ToC text. - local hname = (node_text():gsub('%-%-%-%-+', ''):gsub('%=%=%=%=+', ''):gsub('%*.*%*', '')) + -- Remove tags from ToC text. + local heading_node = first(root, 'heading') + local hname = trim(node_text(heading_node):gsub('%*.*%*', '')) + if not heading_node or hname == '' then + return '' -- Spurious "===" or "---" in the help doc. + end + -- Use the first *tag* node as the heading anchor, if any. - local tagnode = first(root, 'tag') + local tagnode = first(heading_node, 'tag') -- Use the *tag* as the heading anchor id, if possible. - local tagname = tagnode and url_encode(node_text(tagnode:child(1), false)) + local tagname = tagnode and url_encode(trim(node_text(tagnode:child(1), false))) or to_heading_tag(hname) if node_name == 'h1' or #headings == 0 then ---@type nvim.gen_help_html.heading @@ -555,7 +560,9 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) ) end local el = node_name == 'h1' and 'h2' or 'h3' - return ('<%s id="%s" class="help-heading">%s\n'):format(el, tagname, text, el) + return ('<%s id="%s" class="help-heading">%s\n'):format(el, tagname, trimmed, el) + elseif node_name == 'heading' then + return trimmed elseif node_name == 'column_heading' or node_name == 'column_name' then if root:has_error() then return text @@ -695,7 +702,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) return string.format('%s', s) end return s - elseif node_name == 'modeline' then + elseif node_name == 'delimiter' or node_name == 'modeline' then return '' elseif node_name == 'ERROR' then if ignore_parse_error(opt.fname, trimmed) then -- cgit From b923fcbaf06276051372c17177a9970c4adc8e3d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 20 Jun 2024 20:16:53 +0800 Subject: build(vim-patch.sh): don't ignore changes to version*.txt (#29425) Suggest adding them to news.txt instead. Also don't ignore changes to intro.txt and sponsor.txt, as they don't change much these days, and it's necessary to consider whether to include their changes in Nvim's intro.txt. --- scripts/vim-patch.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index e8758c064f..6d426010a7 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -207,7 +207,7 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('"${na_rt}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file" # Remove unwanted Vim doc files. - local na_doc='channel\.txt\|if_cscop\.txt\|netbeans\.txt\|os_\w\+\.txt\|print\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|vim9\.txt\|sponsor\.txt\|intro\.txt\|tags' + local na_doc='channel\.txt\|if_cscop\.txt\|netbeans\.txt\|os_\w\+\.txt\|print\.txt\|term\.txt\|todo\.txt\|vim9\.txt\|tags' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\<\%('"${na_doc}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file" # Remove "Last change ..." changes in doc files. @@ -293,6 +293,14 @@ preprocess_patch() { LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/option\.h/\1\/option_vars.h/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename version*.txt to news.txt + LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/version[0-9]+\.txt/\1\/news.txt/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + + # Rename sponsor.txt to intro.txt + LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/sponsor\.txt/\1\/intro.txt/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename terminal.txt to nvim_terminal_emulator.txt LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/terminal\.txt/\1\/nvim_terminal_emulator.txt/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" -- cgit From 6c3f7e7e27a0ffcf6d58dc1f5ad2fce7e59a2d88 Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Fri, 21 Jun 2024 14:44:40 -0500 Subject: fix(gen_vimdoc): correctly generate function fields --- scripts/luacats_parser.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua index e73a42111d..9a763e4d7b 100644 --- a/scripts/luacats_parser.lua +++ b/scripts/luacats_parser.lua @@ -253,9 +253,12 @@ end --- @return nvim.luacats.parser.field local function fun2field(fun) local parts = { 'fun(' } + + local params = {} ---@type string[] for _, p in ipairs(fun.params or {}) do - parts[#parts + 1] = string.format('%s: %s', p.name, p.type) + params[#params + 1] = string.format('%s: %s', p.name, p.type) end + parts[#parts + 1] = table.concat(params, ', ') parts[#parts + 1] = ')' if fun.returns then parts[#parts + 1] = ': ' -- cgit From 9dc09a4cdde9fad4e7861b9467276260bd9b82a9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 26 Jun 2024 05:08:15 +0800 Subject: ci(lintcommit): allow capitalized letter after colon in description (#29480) --- scripts/lintcommit.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index f0e2feaab3..7cb57de901 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -68,11 +68,12 @@ local function validate_commit(commit_message) if after_idx > vim.tbl_count(commit_split) then return [[Commit message does not include colons.]] end - local after_colon = '' + local after_colon_split = {} while after_idx <= vim.tbl_count(commit_split) do - after_colon = after_colon .. commit_split[after_idx] + table.insert(after_colon_split, commit_split[after_idx]) after_idx = after_idx + 1 end + local after_colon = table.concat(after_colon_split, ':') -- Check if commit introduces a breaking change. if vim.endswith(before_colon, '!') then @@ -247,8 +248,10 @@ function M._test() ['unknown: using unknown type'] = false, ['feat: foo:bar'] = true, ['feat: :foo:bar'] = true, + ['feat: :Foo:Bar'] = true, ['feat(something): foo:bar'] = true, ['feat(something): :foo:bar'] = true, + ['feat(something): :Foo:Bar'] = true, ['feat(:grep): read from pipe'] = true, ['feat(:grep/:make): read from pipe'] = true, ['feat(:grep): foo:bar'] = true, -- cgit From 487f44a6c14f83a4f80a4d03a4a8c16ad690927a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 9 Jul 2024 19:17:50 +0800 Subject: fix(lua): change some vim.fn.expand() to vim.fs.normalize() (#29583) Unlike vim.fn.expand(), vim.fs.normalize() doesn't expand wildcards. --- scripts/gen_help_html.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 4584d87bf5..003f3efa03 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1223,7 +1223,7 @@ end function M._test() tagmap = get_helptags('$VIMRUNTIME/doc') - helpfiles = get_helpfiles(vim.fn.expand('$VIMRUNTIME/doc')) + helpfiles = get_helpfiles(vim.fs.normalize('$VIMRUNTIME/doc')) ok(vim.tbl_count(tagmap) > 3000, '>3000', vim.tbl_count(tagmap)) ok( @@ -1285,7 +1285,7 @@ function M.gen(help_dir, to_dir, include, commit, parser_path) help_dir = { help_dir, function(d) - return vim.fn.isdirectory(vim.fn.expand(d)) == 1 + return vim.fn.isdirectory(vim.fs.normalize(d)) == 1 end, 'valid directory', }, @@ -1295,7 +1295,7 @@ function M.gen(help_dir, to_dir, include, commit, parser_path) parser_path = { parser_path, function(f) - return f == nil or vim.fn.filereadable(vim.fn.expand(f)) == 1 + return f == nil or vim.fn.filereadable(vim.fs.normalize(f)) == 1 end, 'valid vimdoc.{so,dll} filepath', }, @@ -1303,10 +1303,10 @@ function M.gen(help_dir, to_dir, include, commit, parser_path) local err_count = 0 ensure_runtimepath() - tagmap = get_helptags(vim.fn.expand(help_dir)) + tagmap = get_helptags(vim.fs.normalize(help_dir)) helpfiles = get_helpfiles(help_dir, include) - to_dir = vim.fn.expand(to_dir) - parser_path = parser_path and vim.fn.expand(parser_path) or nil + to_dir = vim.fs.normalize(to_dir) + parser_path = parser_path and vim.fs.normalize(parser_path) or nil print(('output dir: %s'):format(to_dir)) vim.fn.mkdir(to_dir, 'p') @@ -1358,7 +1358,7 @@ function M.validate(help_dir, include, parser_path) help_dir = { help_dir, function(d) - return vim.fn.isdirectory(vim.fn.expand(d)) == 1 + return vim.fn.isdirectory(vim.fs.normalize(d)) == 1 end, 'valid directory', }, @@ -1366,7 +1366,7 @@ function M.validate(help_dir, include, parser_path) parser_path = { parser_path, function(f) - return f == nil or vim.fn.filereadable(vim.fn.expand(f)) == 1 + return f == nil or vim.fn.filereadable(vim.fs.normalize(f)) == 1 end, 'valid vimdoc.{so,dll} filepath', }, @@ -1374,9 +1374,9 @@ function M.validate(help_dir, include, parser_path) local err_count = 0 ---@type integer local files_to_errors = {} ---@type table ensure_runtimepath() - tagmap = get_helptags(vim.fn.expand(help_dir)) + tagmap = get_helptags(vim.fs.normalize(help_dir)) helpfiles = get_helpfiles(help_dir, include) - parser_path = parser_path and vim.fn.expand(parser_path) or nil + parser_path = parser_path and vim.fs.normalize(parser_path) or nil for _, f in ipairs(helpfiles) do local helpfile = vim.fs.basename(f) @@ -1411,7 +1411,7 @@ end --- --- @param help_dir? string e.g. '$VIMRUNTIME/doc' or './runtime/doc' function M.run_validate(help_dir) - help_dir = vim.fn.expand(help_dir or '$VIMRUNTIME/doc') + help_dir = vim.fs.normalize(help_dir or '$VIMRUNTIME/doc') print('doc path = ' .. vim.uv.fs_realpath(help_dir)) local rv = M.validate(help_dir) @@ -1438,7 +1438,7 @@ end --- @param help_dir? string e.g. '$VIMRUNTIME/doc' or './runtime/doc' function M.test_gen(help_dir) local tmpdir = vim.fs.dirname(vim.fn.tempname()) - help_dir = vim.fn.expand(help_dir or '$VIMRUNTIME/doc') + help_dir = vim.fs.normalize(help_dir or '$VIMRUNTIME/doc') print('doc path = ' .. vim.uv.fs_realpath(help_dir)) -- Because gen() is slow (~30s), this test is limited to a few files. -- cgit From 545aafbeb80eb52c182ce139800489b392a12d0d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 10 Jul 2024 08:07:16 +0800 Subject: vim-patch:9.1.0547: No way to get the arity of a Vim function (#29638) Problem: No way to get the arity of a Vim function (Austin Ziegler) Solution: Enhance get() Vim script function to return the function argument info using get(func, "arity") (LemonBoy) fixes: vim/vim#15097 closes: vim/vim#15109 https://github.com/vim/vim/commit/48b7d05a4f88c4326bd5d7a73a523f2d953b3e51 Co-authored-by: LemonBoy --- scripts/gen_eval_files.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 76092f8b39..b490e7b480 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -432,14 +432,15 @@ local function render_eval_meta(f, fun, write) end --- @param name string +--- @param name_tag boolean --- @param fun vim.EvalFn --- @param write fun(line: string) -local function render_sig_and_tag(name, fun, write) +local function render_sig_and_tag(name, name_tag, fun, write) if not fun.signature then return end - local tags = { '*' .. name .. '()*' } + local tags = name_tag and { '*' .. name .. '()*' } or {} if fun.tags then for _, t in ipairs(fun.tags) do @@ -447,6 +448,11 @@ local function render_sig_and_tag(name, fun, write) end end + if #tags == 0 then + write(fun.signature) + return + end + local tag = table.concat(tags, ' ') local siglen = #fun.signature local conceal_offset = 2 * (#tags - 1) @@ -472,11 +478,7 @@ local function render_eval_doc(f, fun, write) return end - if f:find('__%d+$') then - write(fun.signature) - else - render_sig_and_tag(fun.name or f, fun, write) - end + render_sig_and_tag(fun.name or f, not f:find('__%d+$'), fun, write) if not fun.desc then return -- cgit From 3146433190803247ce0132fe08a8576f4e50f23d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 1 Aug 2024 20:37:43 +0800 Subject: build(vim-patch.sh): use 7 hex digits for runtime patch file name (#29940) 7 digits are used in commit message, so also using this in patch file name allows its proper deletion on PR creation. --- scripts/vim-patch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 6d426010a7..c02aab327b 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -156,7 +156,7 @@ assign_commit_details() { local munge_commit_line=true else # Interpret parameter as commit hash. - vim_version="${1:0:12}" + vim_version="${1:0:7}" vim_tag= vim_commit_ref="$vim_version" local munge_commit_line=false -- cgit From 0a0962a2e8d9ffa5f03e492fa293e964650a965e Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 2 Aug 2024 13:00:11 +0200 Subject: refactor(lsp): remove freeze() from gen_lsp (#29955) To match the change in https://github.com/neovim/neovim/pull/29283 --- scripts/gen_lsp.lua | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua index 1706b39864..8bd771d97b 100644 --- a/scripts/gen_lsp.lua +++ b/scripts/gen_lsp.lua @@ -109,26 +109,8 @@ local function gen_methods(protocol) end end output[#output + 1] = '}' - output = vim.list_extend( - output, - vim.split( - [[ -local function freeze(t) - return setmetatable({}, { - __index = t, - __newindex = function() - error('cannot modify immutable table') - end, - }) -end -protocol.Methods = freeze(protocol.Methods) - -return protocol -]], - '\n', - { trimempty = true } - ) - ) + output[#output + 1] = '' + output[#output + 1] = 'return protocol' local fname = './runtime/lua/vim/lsp/protocol.lua' local bufnr = vim.fn.bufadd(fname) -- cgit From f926cc32c9262b6254e2843276b951cef9da1afe Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 2 Jul 2024 13:45:50 +0200 Subject: refactor(shada): rework msgpack decoding without msgpack-c This also makes shada reading slightly faster due to avoiding some copying and allocation. Use keysets to drive decoding of msgpack maps for shada entries. --- scripts/gen_eval_files.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index b490e7b480..fc2fadc440 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -376,6 +376,9 @@ end --- @param fun vim.EvalFn --- @param write fun(line: string) local function render_api_keyset_meta(_f, fun, write) + if string.sub(fun.name, 1, 1) == '_' then + return -- not exported + end write('') write('--- @class vim.api.keyset.' .. fun.name) for _, p in ipairs(fun.params) do -- cgit From cc26cf0400286990d553bee993c9b113ca4cbc46 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 6 Aug 2024 21:25:31 +0800 Subject: fix(docs): do not treat indexes as `short_link` --- scripts/text_utils.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'scripts') diff --git a/scripts/text_utils.lua b/scripts/text_utils.lua index 75b3bfedd5..3569f5a5af 100644 --- a/scripts/text_utils.lua +++ b/scripts/text_utils.lua @@ -207,6 +207,8 @@ local function render_md(node, start_indent, indent, text_width, level, is_list) elseif ntype == 'shortcut_link' then if node[1].text:find('^<.*>$') then parts[#parts + 1] = node[1].text + elseif node[1].text:find('^%d+$') then + vim.list_extend(parts, { '[', node[1].text, ']' }) else vim.list_extend(parts, { '|', node[1].text, '|' }) end -- cgit From a901fb875f69ff4e3033f883d5b8665eb608a586 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 16 Aug 2024 08:36:23 -0700 Subject: fix(docs): add missing properties to hl_info #30032 --- scripts/gen_eval_files.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index fc2fadc440..a5f9449049 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -29,11 +29,11 @@ local LUA_API_RETURN_OVERRIDES = { nvim_get_keymap = 'vim.api.keyset.keymap[]', nvim_get_mark = 'vim.api.keyset.get_mark', - -- Can also return table, however we need to + -- Can also return table, however we need to -- pick one to get some benefit. -- REVISIT lewrus01 (26/01/24): we can maybe add - -- @overload fun(ns: integer, {}): table - nvim_get_hl = 'vim.api.keyset.hl_info', + -- @overload fun(ns: integer, {}): table + nvim_get_hl = 'vim.api.keyset.get_hl_info', nvim_get_mode = 'vim.api.keyset.get_mode', nvim_get_namespaces = 'table', -- cgit From 766d5036275e871932893f8dfc8c5bc1eb7a3726 Mon Sep 17 00:00:00 2001 From: Ricardo Casía Date: Tue, 20 Aug 2024 14:52:14 +0200 Subject: docs(lsp): annotate with `vim.lsp.protocol.Methods` enum #29521 Added the enum type annotation `vim.lsp.protocol.Methods` to provide some intellisense support. --- scripts/gen_lsp.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua index 8bd771d97b..c8dcf8c018 100644 --- a/scripts/gen_lsp.lua +++ b/scripts/gen_lsp.lua @@ -60,9 +60,10 @@ end local function gen_methods(protocol) local output = { '-- Generated by gen_lsp.lua, keep at end of file.', - '--- LSP method names.', '---', + '---@enum vim.lsp.protocol.Methods', '---@see https://microsoft.github.io/language-server-protocol/specification/#metaModel', + '--- LSP method names.', 'protocol.Methods = {', } local indent = (' '):rep(2) -- cgit From 8faa369791c9e0e040b1ea7f64269ffcf2a05cf7 Mon Sep 17 00:00:00 2001 From: Rosen Stoyanov Date: Tue, 20 Aug 2024 15:56:37 +0300 Subject: docs(gen_help_html): wrap headings for narrow viewport #29903 Problem: The headings and help tags overlap when browsing the docs in neovim.io/doc/user/ from a mobile phone. Solution: Apply the correct CSS rules so that the headings and help tags wrap nicely below one another. --- scripts/gen_help_html.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 003f3efa03..117e6c27d5 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1146,10 +1146,11 @@ local function gen_css(fname) font-size: smaller; } .help-heading { - overflow: hidden; - white-space: nowrap; + white-space: normal; display: flex; + flex-flow: row wrap; justify-content: space-between; + gap: 0 15px; } /* The (right-aligned) "tags" part of a section heading. */ .help-heading-tags { @@ -1184,8 +1185,7 @@ local function gen_css(fname) pre:last-child { margin-bottom: 0; } - pre:hover, - .help-heading:hover { + pre:hover { overflow: visible; } .generator-stats { -- cgit From b8135a76b71f1af0d708e3dc58ccb58abad59f7c Mon Sep 17 00:00:00 2001 From: JonnyKong Date: Sun, 25 Aug 2024 03:57:02 +0000 Subject: fix(docs): wrong return value annotation for `nvim_buf_get_extmarks` --- scripts/gen_eval_files.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index a5f9449049..93621551ea 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -20,7 +20,7 @@ local DEP_API_METADATA = 'build/funcs_metadata.mpack' local LUA_API_RETURN_OVERRIDES = { nvim_buf_get_command = 'table', - nvim_buf_get_extmark_by_id = 'vim.api.keyset.get_extmark_item', + nvim_buf_get_extmark_by_id = 'vim.api.keyset.get_extmark_item_by_id', nvim_buf_get_extmarks = 'vim.api.keyset.get_extmark_item[]', nvim_buf_get_keymap = 'vim.api.keyset.keymap[]', nvim_get_autocmds = 'vim.api.keyset.get_autocmds.ret[]', -- cgit From 61e9137394fc5229e582a64316c2ffef55d8d7af Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 1 Sep 2024 13:01:24 -0700 Subject: docs: misc #28970 --- scripts/gen_vimdoc.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index dc384c12f5..aa09bc7dc7 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -373,8 +373,8 @@ local config = { section_fmt = function(_name) return 'Checkhealth' end, - helptag_fmt = function(name) - return name:lower() + helptag_fmt = function() + return 'vim.health* *health' -- HACK end, }, } -- cgit From a5bd6665b00a772ef3ccbcb5f2cbc12020634a3d Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 9 Sep 2024 16:01:47 +0200 Subject: fix(scripts): update bundled dependencies in bump_deps --- scripts/bump_deps.lua | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'scripts') diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua index c5294893e0..e332ef475f 100755 --- a/scripts/bump_deps.lua +++ b/scripts/bump_deps.lua @@ -81,6 +81,14 @@ local function get_dependency(dependency_name) repo = 'luvit/luv', symbol = 'LUV', }, + ['unibilium'] = { + repo = 'neovim/unibilium', + symbol = 'UNIBILIUM', + }, + ['utf8proc'] = { + repo = 'JuliaStrings/utf8proc', + symbol = 'UTF8PROC', + }, ['tree-sitter'] = { repo = 'tree-sitter/tree-sitter', symbol = 'TREESITTER', @@ -90,11 +98,11 @@ local function get_dependency(dependency_name) symbol = 'TREESITTER_C', }, ['tree-sitter-lua'] = { - repo = 'MunifTanjim/tree-sitter-lua', + repo = 'tree-sitter-grammars/tree-sitter-lua', symbol = 'TREESITTER_LUA', }, ['tree-sitter-vim'] = { - repo = 'neovim/tree-sitter-vim', + repo = 'tree-sitter-grammars/tree-sitter-vim', symbol = 'TREESITTER_VIM', }, ['tree-sitter-vimdoc'] = { @@ -102,9 +110,21 @@ local function get_dependency(dependency_name) symbol = 'TREESITTER_VIMDOC', }, ['tree-sitter-query'] = { - repo = 'nvim-treesitter/tree-sitter-query', + repo = 'tree-sitter-grammars/tree-sitter-query', symbol = 'TREESITTER_QUERY', }, + ['tree-sitter-markdown'] = { + repo = 'tree-sitter-grammars/tree-sitter-markdown', + symbol = 'TREESITTER_MARKDOWN', + }, + ['wasmtime'] = { + repo = 'bytecodealliance/wasmtime', + symbol = 'WASMTIME', + }, + ['uncrustify'] = { + repo = 'uncrustify/uncrustify', + symbol = 'UNCRUSTIFY', + }, } local dependency = dependency_table[dependency_name] if dependency == nil then -- cgit From b9b408a56c7e607972beaa7214719ff1494e384c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 13 Sep 2024 05:09:11 -0700 Subject: feat(treesitter): start moving get_parser to return nil #30313 **Problem:** `vim.treesitter.get_parser` will throw an error if no parser can be found. - This means the caller is responsible for wrapping it in a `pcall`, which is easy to forget - It also makes it slightly harder to potentially memoize `get_parser` in the future - It's a bit unintuitive since many other `get_*` style functions conventionally return `nil` if no object is found (e.g. `get_node`, `get_lang`, `query.get`, etc.) **Solution:** Return `nil` if no parser can be found or created - This requires a function signature change, and some new assertions in places where the parser will always (or should always) be found. - This commit starts by making this change internally, since it is breaking. Eventually it will be rolled out to the public API. --- scripts/gen_help_html.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 117e6c27d5..b1a6cb546a 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -786,7 +786,7 @@ local function parse_buf(fname, parser_path) if parser_path then vim.treesitter.language.add('vimdoc', { path = parser_path }) end - local lang_tree = vim.treesitter.get_parser(buf) + local lang_tree = assert(vim.treesitter._get_parser(buf), 'vimdoc parser not found.') return lang_tree, buf end -- cgit From 44afd074430a7cb612f548e651df07651019e34c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 18 Sep 2024 00:26:01 -0700 Subject: docs(tui): rename term.txt, nvim_terminal_emulator.txt #30398 Problem: It has long been a convention that references to the builtin terminal UI should mention "tui", not "term", in order to avoid ambiguity vs the builtin `:terminal` feature. The final step was to rename term.txt; let's that step. Solution: - rename term.txt => tui.txt - rename nvim_terminal_emulator.txt => terminal.txt - `gen_help_html.lua`: generate redirects for renamed pages. --- scripts/gen_help_html.lua | 98 +++++++++++++++++++++++++++++++++++++++-------- scripts/vim-patch.sh | 4 -- 2 files changed, 81 insertions(+), 21 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index b1a6cb546a..51048c9803 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -1,6 +1,4 @@ --- Converts Vim :help files to HTML. Validates |tag| links and document syntax (parser errors). --- --- NOTE: :helptags checks for duplicate tags, whereas this script checks _links_ (to tags). +--- Converts Nvim :help files to HTML. Validates |tag| links and document syntax (parser errors). -- -- USAGE (For CI/local testing purposes): Simply `make lintdoc` or `scripts/lintdoc.lua`, which -- basically does the following: @@ -23,6 +21,8 @@ -- 1. nvim -V1 -es +"lua require('scripts.gen_help_html')._test()" +q -- -- NOTES: +-- * This script is used by the automation repo: https://github.com/neovim/doc +-- * :helptags checks for duplicate tags, whereas this script checks _links_ (to tags). -- * gen() and validate() are the primary (programmatic) entrypoints. validate() only exists -- because gen() is too slow (~1 min) to run in per-commit CI. -- * visit_node() is the core function used by gen() to traverse the document tree and produce HTML. @@ -80,6 +80,12 @@ local new_layout = { ['vim_diff.txt'] = true, } +-- Map of new:old pages, to redirect renamed pages. +local redirects = { + ['tui'] = 'term', + ['terminal'] = 'nvim_terminal_emulator', +} + -- TODO: These known invalid |links| require an update to the relevant docs. local exclude_invalid = { ["'string'"] = 'eval.txt', @@ -212,6 +218,7 @@ local function is_noise(line, noise_lines) end --- Creates a github issue URL at neovim/tree-sitter-vimdoc with prefilled content. +--- @return string local function get_bug_url_vimdoc(fname, to_fname, sample_text) local this_url = string.format('https://neovim.io/doc/user/%s', vim.fs.basename(to_fname)) local bug_url = ( @@ -227,6 +234,7 @@ local function get_bug_url_vimdoc(fname, to_fname, sample_text) end --- Creates a github issue URL at neovim/neovim with prefilled content. +--- @return string local function get_bug_url_nvim(fname, to_fname, sample_text, token_name) local this_url = string.format('https://neovim.io/doc/user/%s', vim.fs.basename(to_fname)) local bug_url = ( @@ -255,7 +263,7 @@ local function get_helppage(f) return 'index.html' end - return (f:gsub('%.txt$', '.html')) + return (f:gsub('%.txt$', '')) .. '.html' end --- Counts leading spaces (tab=8) to decide the indent size of multiline text. @@ -767,14 +775,21 @@ local function ensure_runtimepath() end end ---- Opens `fname` in a buffer and gets a treesitter parser for the buffer contents. +--- Opens `fname` (or `text`, if given) in a buffer and gets a treesitter parser for the buffer contents. --- ---- @param fname string help file to parse +--- @param fname string :help file to parse +--- @param text string? :help file contents --- @param parser_path string? path to non-default vimdoc.so --- @return vim.treesitter.LanguageTree, integer (lang_tree, bufnr) -local function parse_buf(fname, parser_path) +local function parse_buf(fname, text, parser_path) local buf ---@type integer - if type(fname) == 'string' then + if text then + vim.cmd('split new') -- Text contents. + vim.api.nvim_put(vim.split(text, '\n'), '', false, false) + vim.cmd('setfiletype help') + -- vim.treesitter.language.add('vimdoc') + buf = vim.api.nvim_get_current_buf() + elseif type(fname) == 'string' then vim.cmd('split ' .. vim.fn.fnameescape(fname)) -- Filename. buf = vim.api.nvim_get_current_buf() else @@ -801,7 +816,7 @@ local function validate_one(fname, parser_path) local stats = { parse_errors = {}, } - local lang_tree, buf = parse_buf(fname, parser_path) + local lang_tree, buf = parse_buf(fname, nil, parser_path) for _, tree in ipairs(lang_tree:trees()) do visit_validate(tree:root(), 0, tree, { buf = buf, fname = fname }, stats) end @@ -812,20 +827,21 @@ end --- Generates HTML from one :help file `fname` and writes the result to `to_fname`. --- ---- @param fname string Source :help file +--- @param fname string Source :help file. +--- @param text string|nil Source :help file contents, or nil to read `fname`. --- @param to_fname string Destination .html file --- @param old boolean Preformat paragraphs (for old :help files which are full of arbitrary whitespace) --- @param parser_path string? path to non-default vimdoc.so --- --- @return string html --- @return table stats -local function gen_one(fname, to_fname, old, commit, parser_path) +local function gen_one(fname, text, to_fname, old, commit, parser_path) local stats = { noise_lines = {}, parse_errors = {}, first_tags = {}, -- Track the first few tags in doc. } - local lang_tree, buf = parse_buf(fname, parser_path) + local lang_tree, buf = parse_buf(fname, text, parser_path) ---@type nvim.gen_help_html.heading[] local headings = {} -- Headings (for ToC). 2-dimensional: h1 contains h2/h3. local title = to_titlecase(basename_noext(fname)) @@ -1302,33 +1318,81 @@ function M.gen(help_dir, to_dir, include, commit, parser_path) } local err_count = 0 + local redirects_count = 0 ensure_runtimepath() tagmap = get_helptags(vim.fs.normalize(help_dir)) helpfiles = get_helpfiles(help_dir, include) to_dir = vim.fs.normalize(to_dir) parser_path = parser_path and vim.fs.normalize(parser_path) or nil - print(('output dir: %s'):format(to_dir)) + print(('output dir: %s\n\n'):format(to_dir)) vim.fn.mkdir(to_dir, 'p') gen_css(('%s/help.css'):format(to_dir)) for _, f in ipairs(helpfiles) do + -- "foo.txt" local helpfile = vim.fs.basename(f) + -- "to/dir/foo.html" local to_fname = ('%s/%s'):format(to_dir, get_helppage(helpfile)) - local html, stats = gen_one(f, to_fname, not new_layout[helpfile], commit or '?', parser_path) + local html, stats = + gen_one(f, nil, to_fname, not new_layout[helpfile], commit or '?', parser_path) tofile(to_fname, html) print( - ('generated (%-4s errors): %-15s => %s'):format( + ('generated (%-2s errors): %-15s => %s'):format( #stats.parse_errors, helpfile, vim.fs.basename(to_fname) ) ) + + -- Generate redirect pages for renamed help files. + local helpfile_tag = (helpfile:gsub('%.txt$', '')) + local redirect_from = redirects[helpfile_tag] + if redirect_from then + local redirect_text = ([[ +*%s* Nvim + +This document moved to: |%s| + +============================================================================== +This document moved to: |%s| + +This document moved to: |%s| + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: + ]]):format( + redirect_from, + helpfile_tag, + helpfile_tag, + helpfile_tag, + helpfile_tag, + helpfile_tag + ) + local redirect_to = ('%s/%s'):format(to_dir, get_helppage(redirect_from)) + local redirect_html, _ = + gen_one(redirect_from, redirect_text, redirect_to, false, commit or '?', parser_path) + assert(redirect_html:find(helpfile_tag)) + tofile(redirect_to, redirect_html) + + print( + ('generated (redirect) : %-15s => %s'):format( + redirect_from .. '.txt', + vim.fs.basename(to_fname) + ) + ) + redirects_count = redirects_count + 1 + end + err_count = err_count + #stats.parse_errors end - print(('generated %d html pages'):format(#helpfiles)) + + print(('\ngenerated %d html pages'):format(#helpfiles + redirects_count)) print(('total errors: %d'):format(err_count)) - print(('invalid tags:\n%s'):format(vim.inspect(invalid_links))) + print(('invalid tags: %s'):format(vim.inspect(invalid_links))) + assert(#(include or {}) > 0 or redirects_count == vim.tbl_count(redirects)) -- sanity check + print(('redirects: %d'):format(redirects_count)) + print('\n') --- @type nvim.gen_help_html.gen_result return { diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index c02aab327b..bfa9f6d99c 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -301,10 +301,6 @@ preprocess_patch() { LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/sponsor\.txt/\1\/intro.txt/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" - # Rename terminal.txt to nvim_terminal_emulator.txt - LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/terminal\.txt/\1\/nvim_terminal_emulator.txt/g' \ - "$file" > "$file".tmp && mv "$file".tmp "$file" - # Rename test_urls.vim to check_urls.vim LC_ALL=C sed -Ee 's/( [ab])\/runtime\/doc\/test(_urls\.vim)/\1\/scripts\/check\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" -- cgit From 22553e1f38addd867ad659b2944d00129141a499 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 18 Sep 2024 01:28:00 -0700 Subject: docs: graduate tui.txt to "flow layout" #30413 --- scripts/gen_help_html.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 51048c9803..f088f706d6 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -76,6 +76,7 @@ local new_layout = { ['news-0.10.txt'] = true, ['nvim.txt'] = true, ['provider.txt'] = true, + ['tui.txt'] = true, ['ui.txt'] = true, ['vim_diff.txt'] = true, } -- cgit From 737f58e23230ea14f1648ac1fc7f442ea0f8563c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 20 Sep 2024 07:34:50 +0200 Subject: refactor(api)!: rename Dictionary => Dict In the api_info() output: :new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val') ... {'return_type': 'ArrayOf(Integer, 2)', 'name': 'nvim_win_get_position', 'method': v:true, 'parameters': [['Window', 'window']], 'since': 1} The `ArrayOf(Integer, 2)` return type didn't break clients when we added it, which is evidence that clients don't use the `return_type` field, thus renaming Dictionary => Dict in api_info() is not (in practice) a breaking change. --- scripts/gen_eval_files.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 93621551ea..35af84ae28 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -112,7 +112,7 @@ local API_TYPES = { String = 'string', Array = 'any[]', LuaRef = 'function', - Dictionary = 'table', + Dict = 'table', Float = 'number', HLGroupID = 'number|string', void = '', @@ -140,7 +140,7 @@ local function api_type(t) return 'vim.api.keyset.' .. d end - local d0 = t:match('^DictionaryOf%((.*)%)') + local d0 = t:match('^DictOf%((.*)%)') if d0 then return 'table' end -- cgit From ce7017b850e0f62b3ebe6ea0d7010ba0291624e5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 25 Sep 2024 02:34:13 -0700 Subject: docs: render @see, @note items in _meta/api.lua #30494 --- scripts/gen_eval_files.lua | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 35af84ae28..6c97893a4a 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -245,6 +245,16 @@ local function get_api_meta() for _, fun in pairs(functions) do local deprecated = fun.deprecated_since ~= nil + local notes = {} --- @type string[] + for _, note in ipairs(fun.notes or {}) do + notes[#notes + 1] = note.desc + end + + local sees = {} --- @type string[] + for _, see in ipairs(fun.see or {}) do + sees[#sees + 1] = see.desc + end + local params = {} --- @type [string,string][] for _, p in ipairs(fun.params) do params[#params + 1] = { @@ -258,6 +268,8 @@ local function get_api_meta() signature = 'NA', name = fun.name, params = params, + notes = notes, + see = sees, returns = api_type(fun.returns[1].type), deprecated = deprecated, } @@ -315,6 +327,26 @@ local function render_api_meta(_f, fun, write) end end + -- LuaLS doesn't support @note. Render @note items as a markdown list. + if fun.notes and #fun.notes > 0 then + write('--- Note:') + for _, note in ipairs(fun.notes) do + -- XXX: abuse md_to_vimdoc() to force-fit the markdown list. Need norm_md()? + note = text_utils.md_to_vimdoc(' - ' .. note, 0, 0, 74) + for _, l in ipairs(split(vim.trim(norm_text(note)))) do + write('--- ' .. l:gsub('\n*$', '')) + end + end + write('---') + end + + for _, see in ipairs(fun.see or {}) do + see = text_utils.md_to_vimdoc('@see ' .. see, 0, 0, 74) + for _, l in ipairs(split(vim.trim(norm_text(see)))) do + write('--- ' .. l:gsub([[\s*$]], '')) + end + end + local param_names = {} --- @type string[] local params = process_params(fun.params) for _, p in ipairs(params) do @@ -336,6 +368,7 @@ local function render_api_meta(_f, fun, write) write('--- @param ' .. p[1] .. ' ' .. p[2]) end end + if fun.returns ~= '' then local ret_desc = fun.returns_desc and ' : ' .. fun.returns_desc or '' ret_desc = text_utils.md_to_vimdoc(ret_desc, 0, 0, 74) -- cgit From 0f067cd34d09b38f9aaf2e1732d825e89b573077 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 14 Sep 2024 12:57:33 -0700 Subject: fix(treesitter): suppress get_parser warnings via opts.error --- scripts/gen_help_html.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index f088f706d6..6751fd7ca7 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -802,7 +802,7 @@ local function parse_buf(fname, text, parser_path) if parser_path then vim.treesitter.language.add('vimdoc', { path = parser_path }) end - local lang_tree = assert(vim.treesitter._get_parser(buf), 'vimdoc parser not found.') + local lang_tree = assert(vim.treesitter.get_parser(buf, nil, { error = false })) return lang_tree, buf end -- cgit From 20251be15a4ad3f6e7016450ca3338d52b2f0951 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 30 Sep 2024 00:41:40 +0200 Subject: docs: graduate editorconfig.txt to "flow layout" fix #25401 --- scripts/gen_help_html.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 6751fd7ca7..fbe2b922b4 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -68,6 +68,7 @@ local new_layout = { ['dev_theme.txt'] = true, ['dev_tools.txt'] = true, ['dev_vimpatch.txt'] = true, + ['editorconfig.txt'] = true, ['faq.txt'] = true, ['lua.txt'] = true, ['luaref.txt'] = true, -- cgit From a0c64fe816f82010dea43d8bbe25475fbabfb562 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 1 Oct 2024 02:52:07 -0700 Subject: docs(gen_help_html.lua): h4 pseudo-heading layout #30609 Problem: The right-aligned tag "pseudo-heading" layout mushes together with the left-aligned text. This is especially messy in a narrow viewport. Solution: Put a `
` on it. This is a hack until tree-sitter-vimdoc recognizes these pseudo-headings. --- scripts/gen_help_html.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index fbe2b922b4..20aeb2791e 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -665,13 +665,13 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) code = ('
%s
'):format(trim(trim_indent(text), 2)) end return code - elseif node_name == 'tag' then -- anchor + elseif node_name == 'tag' then -- anchor, h4 pseudo-heading if root:has_error() then return text end local in_heading = vim.list_contains({ 'h1', 'h2', 'h3' }, parent) - local cssclass = (not in_heading and get_indent(node_text()) > 8) and 'help-tag-right' - or 'help-tag' + local h4 = (not in_heading and get_indent(node_text()) > 8) + local cssclass = h4 and 'help-tag-right' or 'help-tag' local tagname = node_text(root:child(1), false) if vim.tbl_count(stats.first_tags) < 2 then -- Force the first 2 tags in the doc to be anchored at the main heading. @@ -711,7 +711,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) -- End the container for tags in a heading. return string.format('%s', s) end - return s + return s .. (h4 and '
' or '') -- HACK:
avoids h4 pseudo-heading mushing with text. elseif node_name == 'delimiter' or node_name == 'modeline' then return '' elseif node_name == 'ERROR' then @@ -1151,6 +1151,7 @@ local function gen_css(fname) margin-left: auto; margin-right: 0; float: right; + display: block; } .help-tag a, .help-tag-right a { -- cgit From 72892aab066a58524d670777512e9cab45e7310d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 1 Oct 2024 12:51:16 +0200 Subject: docs(gen_help_html.lua): h4 pseudo-heading layout Problem: The
hack in a0c64fe816f8 causes weird layout if a "h4 pseudo-heading" is not the only tag on the line. For example in the help text below, the "*'buflisted'*" tag was treated as h4 and followed by
, which is obviously wrong: *'buflisted'* *'bl'* *'nobuflisted'* *'nobl'* *E85* 'buflisted' 'bl' boolean (default on) Solution: Only treat a tag as "h4 pseudo-heading" if it is the only tag in the line. This is fragile, but in practice seems to get the right balance. --- scripts/gen_help_html.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scripts') diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 20aeb2791e..f6e799508b 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -670,7 +670,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) return text end local in_heading = vim.list_contains({ 'h1', 'h2', 'h3' }, parent) - local h4 = (not in_heading and get_indent(node_text()) > 8) + local h4 = not in_heading and not next_ and get_indent(node_text()) > 8 -- h4 pseudo-heading local cssclass = h4 and 'help-tag-right' or 'help-tag' local tagname = node_text(root:child(1), false) if vim.tbl_count(stats.first_tags) < 2 then -- cgit From 385fbfb3e739b457027b469782678f86eefdf7fc Mon Sep 17 00:00:00 2001 From: James Trew <66286082+jamestrew@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:45:51 +0000 Subject: docs: improve luacats support #30580 Some composite/compound types even as basic as `(string|number)[]` are not currently supported by the luacats LPEG grammar used by gen_vimdoc. It would be parsed & rendered as just `string|number`. Changeset adds better support for these types. --- scripts/luacats_grammar.lua | 110 ++++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 45 deletions(-) (limited to 'scripts') diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index ebb0183fd9..34c1470fea 100644 --- a/scripts/luacats_grammar.lua +++ b/scripts/luacats_grammar.lua @@ -4,7 +4,7 @@ LPEG grammar for LuaCATS local lpeg = vim.lpeg local P, R, S = lpeg.P, lpeg.R, lpeg.S -local Ct, Cg = lpeg.Ct, lpeg.Cg +local C, Ct, Cg = lpeg.C, lpeg.Ct, lpeg.Cg --- @param x vim.lpeg.Pattern local function rep(x) @@ -23,23 +23,20 @@ end local ws = rep1(S(' \t')) local fill = opt(ws) - local any = P(1) -- (consume one character) -local letter = R('az', 'AZ') + S('_$') +local letter = R('az', 'AZ') local num = R('09') -local ident = letter * rep(letter + num + S '-.') -local string_single = P "'" * rep(any - P "'") * P "'" -local string_double = P('"') * rep(any - P('"')) * P('"') - -local literal = (string_single + string_double + (opt(P('-')) * num) + P('false') + P('true')) -local lname = (ident + P('...')) * opt(P('?')) - ---- @param x string +--- @param x string | vim.lpeg.Pattern local function Pf(x) return fill * P(x) * fill end +--- @param x string | vim.lpeg.Pattern +local function Plf(x) + return fill * P(x) +end + --- @param x string local function Sf(x) return fill * S(x) * fill @@ -72,16 +69,6 @@ local v = setmetatable({}, { end, }) -local colon = Pf(':') -local opt_exact = opt(Cg(Pf('(exact)'), 'access')) -local access = P('private') + P('protected') + P('package') -local caccess = Cg(access, 'access') -local desc_delim = Sf '#:' + ws -local desc = Cg(rep(any), 'desc') -local opt_desc = opt(desc_delim * desc) -local cname = Cg(ident, 'name') -local opt_parent = opt(colon * Cg(ident, 'parent')) - --- @class nvim.luacats.Param --- @field kind 'param' --- @field name string @@ -135,21 +122,69 @@ local function annot(nm, pat) return Ct(Cg(P(nm), 'kind')) end +local colon = Pf(':') +local ellipsis = P('...') +local ident_first = P('_') + letter +local ident = ident_first * rep(ident_first + num) +local opt_ident = ident * opt(P('?')) +local ty_ident_sep = S('-._') +local ty_ident = ident * rep(ty_ident_sep * ident) +local string_single = P "'" * rep(any - P "'") * P "'" +local string_double = P('"') * rep(any - P('"')) * P('"') +local generic = P('`') * ty_ident * P('`') +local literal = string_single + string_double + (opt(P('-')) * rep1(num)) + P('false') + P('true') +local ty_prims = ty_ident + literal + generic + +local array_postfix = rep1(Plf('[]')) +local opt_postfix = rep1(Plf('?')) +local rep_array_opt_postfix = rep(array_postfix + opt_postfix) + +local typedef = P({ + 'typedef', + typedef = C(v.type), + + type = v.ty * rep_array_opt_postfix * rep(Pf('|') * v.ty * rep_array_opt_postfix), + ty = v.composite + paren(v.typedef), + composite = (v.types * array_postfix) + (v.types * opt_postfix) + v.types, + types = v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + v.fun + ty_prims, + + tuple = Pf('[') * comma1(v.type) * Plf(']'), + dict = Pf('{') * comma1(Pf('[') * v.type * Pf(']') * colon * v.type) * Plf('}'), + kv_table = Pf('table') * Pf('<') * v.type * Pf(',') * v.type * Plf('>'), + table_literal = Pf('{') * comma1(opt_ident * Pf(':') * v.type) * Plf('}'), + fun_param = (opt_ident + ellipsis) * opt(colon * v.type), + fun_ret = v.type + (ellipsis * opt(colon * v.type)), + fun = Pf('fun') * paren(comma(v.fun_param)) * opt(Pf(':') * comma1(v.fun_ret)), + generics = P(ty_ident) * Pf('<') * comma1(v.type) * Plf('>'), +}) / function(match) + return vim.trim(match):gsub('^%((.*)%)$', '%1'):gsub('%?+', '?') +end + +local opt_exact = opt(Cg(Pf('(exact)'), 'access')) +local access = P('private') + P('protected') + P('package') +local caccess = Cg(access, 'access') +local desc_delim = Sf '#:' + ws +local desc = Cg(rep(any), 'desc') +local opt_desc = opt(desc_delim * desc) +local ty_name = Cg(ty_ident, 'name') +local opt_parent = opt(colon * Cg(ty_ident, 'parent')) +local lname = (ident + ellipsis) * opt(P('?')) + local grammar = P { rep1(P('@') * (v.ats + v.ext_ats)), ats = annot('param', Cg(lname, 'name') * ws * v.ctype * opt_desc) - + annot('return', comma1(Ct(v.ctype * opt(ws * cname))) * opt_desc) + + annot('return', comma1(Ct(v.ctype * opt(ws * (ty_name + Cg(ellipsis, 'name'))))) * opt_desc) + annot('type', comma1(Ct(v.ctype)) * opt_desc) - + annot('cast', cname * ws * opt(Sf('+-')) * v.ctype) - + annot('generic', cname * opt(colon * v.ctype)) - + annot('class', opt_exact * opt(paren(caccess)) * fill * cname * opt_parent) + + annot('cast', ty_name * ws * opt(Sf('+-')) * v.ctype) + + annot('generic', ty_name * opt(colon * v.ctype)) + + annot('class', opt_exact * opt(paren(caccess)) * fill * ty_name * opt_parent) + annot('field', opt(caccess * ws) * v.field_name * ws * v.ctype * opt_desc) - + annot('operator', cname * opt(paren(Cg(v.ltype, 'argtype'))) * colon * v.ctype) + + annot('operator', ty_name * opt(paren(Cg(v.ctype, 'argtype'))) * colon * v.ctype) + annot(access) + annot('deprecated') - + annot('alias', cname * opt(ws * v.ctype)) - + annot('enum', cname) + + annot('alias', ty_name * opt(ws * v.ctype)) + + annot('enum', ty_name) + annot('overload', v.ctype) + annot('see', opt(desc_delim) * desc) + annot('diagnostic', opt(desc_delim) * desc) @@ -165,23 +200,8 @@ local grammar = P { ), field_name = Cg(lname + (v.ty_index * opt(P('?'))), 'name'), - - ctype = parenOpt(Cg(v.ltype, 'type')), - ltype = parenOpt(v.ty_union), - - ty_union = v.ty_opt * rep(Pf('|') * v.ty_opt), - ty = v.ty_fun + ident + v.ty_table + literal + paren(v.ty) + v.ty_generic + v.ty_tuple, - ty_param = Pf('<') * comma1(v.ltype) * fill * P('>'), - ty_opt = v.ty * opt(v.ty_param) * opt(P('[]')) * opt(P('?')), - ty_index = (Pf('[') * (v.ltype + ident + rep1(num)) * fill * P(']')), - table_key = v.ty_index + lname, - table_elem = v.table_key * colon * v.ltype, - ty_table = Pf('{') * comma1(v.table_elem) * fill * P('}'), - fun_param = lname * opt(colon * v.ltype), - fun_ret = v.ltype + (ident * colon * v.ltype) + (P('...') * opt(colon * v.ltype)), - ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.fun_ret)), - ty_generic = P('`') * letter * P('`'), - ty_tuple = Pf('[') * comma(v.ltype) * fill * P(']'), + ty_index = C(Pf('[') * typedef * fill * P(']')), + ctype = Cg(typedef, 'type'), } return grammar --[[@as nvim.luacats.grammar]] -- cgit From f62728cd80a9c458b1c0ef7c5c1251e55fe91090 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Thu, 3 Oct 2024 16:57:19 -0700 Subject: docs(treesitter): generate TSNode, TSTree docs #30643 **Problem:** The documentation for `TSNode` and `TSTree` methods is incomplete from the LSP perspective. This is because they are written directly to the vimdoc, rather than in Lua and generated to vimdoc. **Solution:** Migrate the docs to Lua and generate them into the vimdoc. This requires breaking up the `treesitter/_meta.lua` file into a directory with a few different modules. This commit also makes the vimdoc generator slightly more robust with regard to sections that have multiple help tags (e.g. `*one* *two*`) --- scripts/gen_vimdoc.lua | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index aa09bc7dc7..66b72e0c40 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -50,7 +50,7 @@ local INDENTATION = 4 --- For generated section names. --- @field section_fmt fun(name: string): string --- ---- @field helptag_fmt fun(name: string): string +--- @field helptag_fmt fun(name: string): string|string[] --- --- Per-function helptag. --- @field fn_helptag_fmt? fun(fun: nvim.luacats.parser.fun): string @@ -319,6 +319,8 @@ local config = { treesitter = { filename = 'treesitter.txt', section_order = { + 'tstree.lua', + 'tsnode.lua', 'treesitter.lua', 'language.lua', 'query.lua', @@ -327,18 +329,27 @@ local config = { 'dev.lua', }, files = { + 'runtime/lua/vim/treesitter/_meta/', 'runtime/lua/vim/treesitter.lua', 'runtime/lua/vim/treesitter/', }, section_fmt = function(name) if name:lower() == 'treesitter' then return 'Lua module: vim.treesitter' + elseif name:lower() == 'tstree' then + return 'TREESITTER TREES' + elseif name:lower() == 'tsnode' then + return 'TREESITTER NODES' end return 'Lua module: vim.treesitter.' .. name:lower() end, helptag_fmt = function(name) if name:lower() == 'treesitter' then return 'lua-treesitter-core' + elseif name:lower() == 'tstree' then + return { 'treesitter-tree', 'TSTree' } + elseif name:lower() == 'tsnode' then + return { 'treesitter-node', 'TSNode' } end return 'lua-treesitter-' .. name:lower() end, @@ -374,7 +385,7 @@ local config = { return 'Checkhealth' end, helptag_fmt = function() - return 'vim.health* *health' -- HACK + return { 'vim.health', 'health' } end, }, } @@ -869,7 +880,11 @@ local function make_section(filename, cfg, section_docs, funs_txt) local sectname = cfg.section_name and cfg.section_name[filename] or mktitle(name) -- section tag: e.g., "*api-autocmd*" - local help_tag = '*' .. cfg.helptag_fmt(sectname) .. '*' + local help_labels = cfg.helptag_fmt(sectname) + if type(help_labels) == 'table' then + help_labels = table.concat(help_labels, '* *') + end + local help_tags = '*' .. help_labels .. '*' if funs_txt == '' and #section_docs == 0 then return @@ -878,7 +893,7 @@ local function make_section(filename, cfg, section_docs, funs_txt) return { name = sectname, title = cfg.section_fmt(sectname), - help_tag = help_tag, + help_tag = help_tags, funs_txt = funs_txt, doc = section_docs, } -- cgit From b45c50f3140e7ece593f2126840900f5cc3d39ea Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 4 Oct 2024 02:13:31 -0700 Subject: docs: render `@since` versions, 0 means experimental #30649 An implication of this current approach is that `NVIM_API_LEVEL` should be bumped when a new Lua function is added. TODO(future): add a lint check which requires `@since` on all new functions. ref #25416 --- scripts/gen_eval_files.lua | 12 +- scripts/gen_vimdoc.lua | 28 ++-- scripts/text_utils.lua | 365 ------------------------------------------ scripts/util.lua | 384 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 407 insertions(+), 382 deletions(-) delete mode 100644 scripts/text_utils.lua create mode 100644 scripts/util.lua (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 6c97893a4a..234028b972 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -309,7 +309,7 @@ end local function render_api_meta(_f, fun, write) write('') - local text_utils = require('scripts.text_utils') + local util = require('scripts.util') if vim.startswith(fun.name, 'nvim__') then write('--- @private') @@ -321,7 +321,7 @@ local function render_api_meta(_f, fun, write) local desc = fun.desc if desc then - desc = text_utils.md_to_vimdoc(desc, 0, 0, 74) + desc = util.md_to_vimdoc(desc, 0, 0, 74) for _, l in ipairs(split(norm_text(desc))) do write('--- ' .. l) end @@ -332,7 +332,7 @@ local function render_api_meta(_f, fun, write) write('--- Note:') for _, note in ipairs(fun.notes) do -- XXX: abuse md_to_vimdoc() to force-fit the markdown list. Need norm_md()? - note = text_utils.md_to_vimdoc(' - ' .. note, 0, 0, 74) + note = util.md_to_vimdoc(' - ' .. note, 0, 0, 74) for _, l in ipairs(split(vim.trim(norm_text(note)))) do write('--- ' .. l:gsub('\n*$', '')) end @@ -341,7 +341,7 @@ local function render_api_meta(_f, fun, write) end for _, see in ipairs(fun.see or {}) do - see = text_utils.md_to_vimdoc('@see ' .. see, 0, 0, 74) + see = util.md_to_vimdoc('@see ' .. see, 0, 0, 74) for _, l in ipairs(split(vim.trim(norm_text(see)))) do write('--- ' .. l:gsub([[\s*$]], '')) end @@ -355,7 +355,7 @@ local function render_api_meta(_f, fun, write) if pdesc then local s = '--- @param ' .. p[1] .. ' ' .. p[2] .. ' ' local indent = #('@param ' .. p[1] .. ' ') - pdesc = text_utils.md_to_vimdoc(pdesc, #s, indent, 74, true) + pdesc = util.md_to_vimdoc(pdesc, #s, indent, 74, true) local pdesc_a = split(vim.trim(norm_text(pdesc))) write(s .. pdesc_a[1]) for i = 2, #pdesc_a do @@ -371,7 +371,7 @@ local function render_api_meta(_f, fun, write) if fun.returns ~= '' then local ret_desc = fun.returns_desc and ' : ' .. fun.returns_desc or '' - ret_desc = text_utils.md_to_vimdoc(ret_desc, 0, 0, 74) + ret_desc = util.md_to_vimdoc(ret_desc, 0, 0, 74) local ret = LUA_API_RETURN_OVERRIDES[fun.name] or fun.returns write('--- @return ' .. ret .. ret_desc) end diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 66b72e0c40..8908097397 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -18,12 +18,12 @@ local luacats_parser = require('scripts.luacats_parser') local cdoc_parser = require('scripts.cdoc_parser') -local text_utils = require('scripts.text_utils') +local util = require('scripts.util') local fmt = string.format -local wrap = text_utils.wrap -local md_to_vimdoc = text_utils.md_to_vimdoc +local wrap = util.wrap +local md_to_vimdoc = util.md_to_vimdoc local TEXT_WIDTH = 78 local INDENTATION = 4 @@ -730,19 +730,25 @@ local function render_fun(fun, classes, cfg) table.insert(ret, render_fun_header(fun, cfg)) table.insert(ret, '\n') - if fun.desc then - table.insert(ret, md_to_vimdoc(fun.desc, INDENTATION, INDENTATION, TEXT_WIDTH)) - end - if fun.since then - local since = tonumber(fun.since) + local since = assert(tonumber(fun.since), 'invalid @since on ' .. fun.name) local info = nvim_api_info() - if since and (since > info.level or since == info.level and info.prerelease) then - fun.notes = fun.notes or {} - table.insert(fun.notes, { desc = 'This API is pre-release (unstable).' }) + if since == 0 or (info.prerelease and since == info.level) then + -- Experimental = (since==0 or current prerelease) + local s = 'WARNING: This feature is experimental/unstable.' + table.insert(ret, md_to_vimdoc(s, INDENTATION, INDENTATION, TEXT_WIDTH)) + table.insert(ret, '\n') + else + local v = assert(util.version_level[since], 'invalid @since on ' .. fun.name) + fun.attrs = fun.attrs or {} + table.insert(fun.attrs, ('Since: %s'):format(v)) end end + if fun.desc then + table.insert(ret, md_to_vimdoc(fun.desc, INDENTATION, INDENTATION, TEXT_WIDTH)) + end + if fun.notes then table.insert(ret, '\n Note: ~\n') for _, p in ipairs(fun.notes) do diff --git a/scripts/text_utils.lua b/scripts/text_utils.lua deleted file mode 100644 index 3569f5a5af..0000000000 --- a/scripts/text_utils.lua +++ /dev/null @@ -1,365 +0,0 @@ -local fmt = string.format - ---- @class nvim.text_utils.MDNode ---- @field [integer] nvim.text_utils.MDNode ---- @field type string ---- @field text? string - -local INDENTATION = 4 - -local NBSP = string.char(160) - -local M = {} - -local function contains(t, xs) - return vim.tbl_contains(xs, t) -end - ---- @param txt string ---- @param srow integer ---- @param scol integer ---- @param erow? integer ---- @param ecol? integer ---- @return string -local function slice_text(txt, srow, scol, erow, ecol) - local lines = vim.split(txt, '\n') - - if srow == erow then - return lines[srow + 1]:sub(scol + 1, ecol) - end - - if erow then - -- Trim the end - for _ = erow + 2, #lines do - table.remove(lines, #lines) - end - end - - -- Trim the start - for _ = 1, srow do - table.remove(lines, 1) - end - - lines[1] = lines[1]:sub(scol + 1) - lines[#lines] = lines[#lines]:sub(1, ecol) - - return table.concat(lines, '\n') -end - ---- @param text string ---- @return nvim.text_utils.MDNode -local function parse_md_inline(text) - local parser = vim.treesitter.languagetree.new(text, 'markdown_inline') - local root = parser:parse(true)[1]:root() - - --- @param node TSNode - --- @return nvim.text_utils.MDNode? - local function extract(node) - local ntype = node:type() - - if ntype:match('^%p$') then - return - end - - --- @type table - local ret = { type = ntype } - ret.text = vim.treesitter.get_node_text(node, text) - - local row, col = 0, 0 - - for child, child_field in node:iter_children() do - local e = extract(child) - if e and ntype == 'inline' then - local srow, scol = child:start() - if (srow == row and scol > col) or srow > row then - local t = slice_text(ret.text, row, col, srow, scol) - if t and t ~= '' then - table.insert(ret, { type = 'text', j = true, text = t }) - end - end - row, col = child:end_() - end - - if child_field then - ret[child_field] = e - else - table.insert(ret, e) - end - end - - if ntype == 'inline' and (row > 0 or col > 0) then - local t = slice_text(ret.text, row, col) - if t and t ~= '' then - table.insert(ret, { type = 'text', text = t }) - end - end - - return ret - end - - return extract(root) or {} -end - ---- @param text string ---- @return nvim.text_utils.MDNode -local function parse_md(text) - local parser = vim.treesitter.languagetree.new(text, 'markdown', { - injections = { markdown = '' }, - }) - - local root = parser:parse(true)[1]:root() - - local EXCLUDE_TEXT_TYPE = { - list = true, - list_item = true, - section = true, - document = true, - fenced_code_block = true, - fenced_code_block_delimiter = true, - } - - --- @param node TSNode - --- @return nvim.text_utils.MDNode? - local function extract(node) - local ntype = node:type() - - if ntype:match('^%p$') or contains(ntype, { 'block_continuation' }) then - return - end - - --- @type table - local ret = { type = ntype } - - if not EXCLUDE_TEXT_TYPE[ntype] then - ret.text = vim.treesitter.get_node_text(node, text) - end - - if ntype == 'inline' then - ret = parse_md_inline(ret.text) - end - - for child, child_field in node:iter_children() do - local e = extract(child) - if child_field then - ret[child_field] = e - else - table.insert(ret, e) - end - end - - return ret - end - - return extract(root) or {} -end - ---- @param x string ---- @param start_indent integer ---- @param indent integer ---- @param text_width integer ---- @return string -function M.wrap(x, start_indent, indent, text_width) - local words = vim.split(vim.trim(x), '%s+') - local parts = { string.rep(' ', start_indent) } --- @type string[] - local count = indent - - for i, w in ipairs(words) do - if count > indent and count + #w > text_width - 1 then - parts[#parts + 1] = '\n' - parts[#parts + 1] = string.rep(' ', indent) - count = indent - elseif i ~= 1 then - parts[#parts + 1] = ' ' - count = count + 1 - end - count = count + #w - parts[#parts + 1] = w - end - - return (table.concat(parts):gsub('%s+\n', '\n'):gsub('\n+$', '')) -end - ---- @param node nvim.text_utils.MDNode ---- @param start_indent integer ---- @param indent integer ---- @param text_width integer ---- @param level integer ---- @return string[] -local function render_md(node, start_indent, indent, text_width, level, is_list) - local parts = {} --- @type string[] - - -- For debugging - local add_tag = false - -- local add_tag = true - - local ntype = node.type - - if add_tag then - parts[#parts + 1] = '<' .. ntype .. '>' - end - - if ntype == 'text' then - parts[#parts + 1] = node.text - elseif ntype == 'html_tag' then - error('html_tag: ' .. node.text) - elseif ntype == 'inline_link' then - vim.list_extend(parts, { '*', node[1].text, '*' }) - elseif ntype == 'shortcut_link' then - if node[1].text:find('^<.*>$') then - parts[#parts + 1] = node[1].text - elseif node[1].text:find('^%d+$') then - vim.list_extend(parts, { '[', node[1].text, ']' }) - else - vim.list_extend(parts, { '|', node[1].text, '|' }) - end - elseif ntype == 'backslash_escape' then - parts[#parts + 1] = node.text - elseif ntype == 'emphasis' then - parts[#parts + 1] = node.text:sub(2, -2) - elseif ntype == 'code_span' then - vim.list_extend(parts, { '`', node.text:sub(2, -2):gsub(' ', NBSP), '`' }) - elseif ntype == 'inline' then - if #node == 0 then - local text = assert(node.text) - parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width) - else - for _, child in ipairs(node) do - vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1)) - end - end - elseif ntype == 'paragraph' then - local pparts = {} - for _, child in ipairs(node) do - vim.list_extend(pparts, render_md(child, start_indent, indent, text_width, level + 1)) - end - parts[#parts + 1] = M.wrap(table.concat(pparts), start_indent, indent, text_width) - parts[#parts + 1] = '\n' - elseif ntype == 'code_fence_content' then - local lines = vim.split(node.text:gsub('\n%s*$', ''), '\n') - - local cindent = indent + INDENTATION - if level > 3 then - -- The tree-sitter markdown parser doesn't parse the code blocks indents - -- correctly in lists. Fudge it! - lines[1] = ' ' .. lines[1] -- ¯\_(ツ)_/¯ - cindent = indent - level - local _, initial_indent = lines[1]:find('^%s*') - initial_indent = initial_indent + cindent - if initial_indent < indent then - cindent = indent - INDENTATION - end - end - - for _, l in ipairs(lines) do - if #l > 0 then - parts[#parts + 1] = string.rep(' ', cindent) - parts[#parts + 1] = l - end - parts[#parts + 1] = '\n' - end - elseif ntype == 'fenced_code_block' then - parts[#parts + 1] = '>' - for _, child in ipairs(node) do - if child.type == 'info_string' then - parts[#parts + 1] = child.text - break - end - end - parts[#parts + 1] = '\n' - for _, child in ipairs(node) do - if child.type ~= 'info_string' then - vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1)) - end - end - parts[#parts + 1] = '<\n' - elseif ntype == 'html_block' then - local text = node.text:gsub('^
help', '')
-    text = text:gsub('
%s*$', '') - parts[#parts + 1] = text - elseif ntype == 'list_marker_dot' then - parts[#parts + 1] = node.text - elseif contains(ntype, { 'list_marker_minus', 'list_marker_star' }) then - parts[#parts + 1] = '• ' - elseif ntype == 'list_item' then - parts[#parts + 1] = string.rep(' ', indent) - local offset = node[1].type == 'list_marker_dot' and 3 or 2 - for i, child in ipairs(node) do - local sindent = i <= 2 and 0 or (indent + offset) - vim.list_extend( - parts, - render_md(child, sindent, indent + offset, text_width, level + 1, true) - ) - end - else - if node.text then - error(fmt('cannot render:\n%s', vim.inspect(node))) - end - for i, child in ipairs(node) do - local start_indent0 = i == 1 and start_indent or indent - vim.list_extend( - parts, - render_md(child, start_indent0, indent, text_width, level + 1, is_list) - ) - if ntype ~= 'list' and i ~= #node then - if (node[i + 1] or {}).type ~= 'list' then - parts[#parts + 1] = '\n' - end - end - end - end - - if add_tag then - parts[#parts + 1] = '' - end - - return parts -end - ---- @param text_width integer -local function align_tags(text_width) - --- @param line string - --- @return string - return function(line) - local tag_pat = '%s*(%*.+%*)%s*$' - local tags = {} - for m in line:gmatch(tag_pat) do - table.insert(tags, m) - end - - if #tags > 0 then - line = line:gsub(tag_pat, '') - local tags_str = ' ' .. table.concat(tags, ' ') - --- @type integer - local conceal_offset = select(2, tags_str:gsub('%*', '')) - 2 - local pad = string.rep(' ', text_width - #line - #tags_str + conceal_offset) - return line .. pad .. tags_str - end - - return line - end -end - ---- @param text string ---- @param start_indent integer ---- @param indent integer ---- @param is_list? boolean ---- @return string -function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list) - -- Add an extra newline so the parser can properly capture ending ``` - local parsed = parse_md(text .. '\n') - local ret = render_md(parsed, start_indent, indent, text_width, 0, is_list) - - local lines = vim.split(table.concat(ret):gsub(NBSP, ' '), '\n') - - lines = vim.tbl_map(align_tags(text_width), lines) - - local s = table.concat(lines, '\n') - - -- Reduce whitespace in code-blocks - s = s:gsub('\n+%s*>([a-z]+)\n', ' >%1\n') - s = s:gsub('\n+%s*>\n?\n', ' >\n') - - return s -end - -return M diff --git a/scripts/util.lua b/scripts/util.lua new file mode 100644 index 0000000000..94598c32a6 --- /dev/null +++ b/scripts/util.lua @@ -0,0 +1,384 @@ +-- TODO(justinmk): move most of this to `vim.text`. + +local fmt = string.format + +--- @class nvim.util.MDNode +--- @field [integer] nvim.util.MDNode +--- @field type string +--- @field text? string + +local INDENTATION = 4 + +local NBSP = string.char(160) + +local M = {} + +local function contains(t, xs) + return vim.tbl_contains(xs, t) +end + +-- Map of api_level:version, by inspection of: +-- :lua= vim.mpack.decode(vim.fn.readfile('test/functional/fixtures/api_level_9.mpack','B')).version +M.version_level = { + [12] = '0.10.0', + [11] = '0.9.0', + [10] = '0.8.0', + [9] = '0.7.0', + [8] = '0.6.0', + [7] = '0.5.0', + [6] = '0.4.0', + [5] = '0.3.2', + [4] = '0.3.0', + [3] = '0.2.1', + [2] = '0.2.0', + [1] = '0.1.0', +} + +--- @param txt string +--- @param srow integer +--- @param scol integer +--- @param erow? integer +--- @param ecol? integer +--- @return string +local function slice_text(txt, srow, scol, erow, ecol) + local lines = vim.split(txt, '\n') + + if srow == erow then + return lines[srow + 1]:sub(scol + 1, ecol) + end + + if erow then + -- Trim the end + for _ = erow + 2, #lines do + table.remove(lines, #lines) + end + end + + -- Trim the start + for _ = 1, srow do + table.remove(lines, 1) + end + + lines[1] = lines[1]:sub(scol + 1) + lines[#lines] = lines[#lines]:sub(1, ecol) + + return table.concat(lines, '\n') +end + +--- @param text string +--- @return nvim.util.MDNode +local function parse_md_inline(text) + local parser = vim.treesitter.languagetree.new(text, 'markdown_inline') + local root = parser:parse(true)[1]:root() + + --- @param node TSNode + --- @return nvim.util.MDNode? + local function extract(node) + local ntype = node:type() + + if ntype:match('^%p$') then + return + end + + --- @type table + local ret = { type = ntype } + ret.text = vim.treesitter.get_node_text(node, text) + + local row, col = 0, 0 + + for child, child_field in node:iter_children() do + local e = extract(child) + if e and ntype == 'inline' then + local srow, scol = child:start() + if (srow == row and scol > col) or srow > row then + local t = slice_text(ret.text, row, col, srow, scol) + if t and t ~= '' then + table.insert(ret, { type = 'text', j = true, text = t }) + end + end + row, col = child:end_() + end + + if child_field then + ret[child_field] = e + else + table.insert(ret, e) + end + end + + if ntype == 'inline' and (row > 0 or col > 0) then + local t = slice_text(ret.text, row, col) + if t and t ~= '' then + table.insert(ret, { type = 'text', text = t }) + end + end + + return ret + end + + return extract(root) or {} +end + +--- @param text string +--- @return nvim.util.MDNode +local function parse_md(text) + local parser = vim.treesitter.languagetree.new(text, 'markdown', { + injections = { markdown = '' }, + }) + + local root = parser:parse(true)[1]:root() + + local EXCLUDE_TEXT_TYPE = { + list = true, + list_item = true, + section = true, + document = true, + fenced_code_block = true, + fenced_code_block_delimiter = true, + } + + --- @param node TSNode + --- @return nvim.util.MDNode? + local function extract(node) + local ntype = node:type() + + if ntype:match('^%p$') or contains(ntype, { 'block_continuation' }) then + return + end + + --- @type table + local ret = { type = ntype } + + if not EXCLUDE_TEXT_TYPE[ntype] then + ret.text = vim.treesitter.get_node_text(node, text) + end + + if ntype == 'inline' then + ret = parse_md_inline(ret.text) + end + + for child, child_field in node:iter_children() do + local e = extract(child) + if child_field then + ret[child_field] = e + else + table.insert(ret, e) + end + end + + return ret + end + + return extract(root) or {} +end + +--- @param x string +--- @param start_indent integer +--- @param indent integer +--- @param text_width integer +--- @return string +function M.wrap(x, start_indent, indent, text_width) + local words = vim.split(vim.trim(x), '%s+') + local parts = { string.rep(' ', start_indent) } --- @type string[] + local count = indent + + for i, w in ipairs(words) do + if count > indent and count + #w > text_width - 1 then + parts[#parts + 1] = '\n' + parts[#parts + 1] = string.rep(' ', indent) + count = indent + elseif i ~= 1 then + parts[#parts + 1] = ' ' + count = count + 1 + end + count = count + #w + parts[#parts + 1] = w + end + + return (table.concat(parts):gsub('%s+\n', '\n'):gsub('\n+$', '')) +end + +--- @param node nvim.util.MDNode +--- @param start_indent integer +--- @param indent integer +--- @param text_width integer +--- @param level integer +--- @return string[] +local function render_md(node, start_indent, indent, text_width, level, is_list) + local parts = {} --- @type string[] + + -- For debugging + local add_tag = false + -- local add_tag = true + + local ntype = node.type + + if add_tag then + parts[#parts + 1] = '<' .. ntype .. '>' + end + + if ntype == 'text' then + parts[#parts + 1] = node.text + elseif ntype == 'html_tag' then + error('html_tag: ' .. node.text) + elseif ntype == 'inline_link' then + vim.list_extend(parts, { '*', node[1].text, '*' }) + elseif ntype == 'shortcut_link' then + if node[1].text:find('^<.*>$') then + parts[#parts + 1] = node[1].text + elseif node[1].text:find('^%d+$') then + vim.list_extend(parts, { '[', node[1].text, ']' }) + else + vim.list_extend(parts, { '|', node[1].text, '|' }) + end + elseif ntype == 'backslash_escape' then + parts[#parts + 1] = node.text + elseif ntype == 'emphasis' then + parts[#parts + 1] = node.text:sub(2, -2) + elseif ntype == 'code_span' then + vim.list_extend(parts, { '`', node.text:sub(2, -2):gsub(' ', NBSP), '`' }) + elseif ntype == 'inline' then + if #node == 0 then + local text = assert(node.text) + parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width) + else + for _, child in ipairs(node) do + vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1)) + end + end + elseif ntype == 'paragraph' then + local pparts = {} + for _, child in ipairs(node) do + vim.list_extend(pparts, render_md(child, start_indent, indent, text_width, level + 1)) + end + parts[#parts + 1] = M.wrap(table.concat(pparts), start_indent, indent, text_width) + parts[#parts + 1] = '\n' + elseif ntype == 'code_fence_content' then + local lines = vim.split(node.text:gsub('\n%s*$', ''), '\n') + + local cindent = indent + INDENTATION + if level > 3 then + -- The tree-sitter markdown parser doesn't parse the code blocks indents + -- correctly in lists. Fudge it! + lines[1] = ' ' .. lines[1] -- ¯\_(ツ)_/¯ + cindent = indent - level + local _, initial_indent = lines[1]:find('^%s*') + initial_indent = initial_indent + cindent + if initial_indent < indent then + cindent = indent - INDENTATION + end + end + + for _, l in ipairs(lines) do + if #l > 0 then + parts[#parts + 1] = string.rep(' ', cindent) + parts[#parts + 1] = l + end + parts[#parts + 1] = '\n' + end + elseif ntype == 'fenced_code_block' then + parts[#parts + 1] = '>' + for _, child in ipairs(node) do + if child.type == 'info_string' then + parts[#parts + 1] = child.text + break + end + end + parts[#parts + 1] = '\n' + for _, child in ipairs(node) do + if child.type ~= 'info_string' then + vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1)) + end + end + parts[#parts + 1] = '<\n' + elseif ntype == 'html_block' then + local text = node.text:gsub('^
help', '')
+    text = text:gsub('
%s*$', '') + parts[#parts + 1] = text + elseif ntype == 'list_marker_dot' then + parts[#parts + 1] = node.text + elseif contains(ntype, { 'list_marker_minus', 'list_marker_star' }) then + parts[#parts + 1] = '• ' + elseif ntype == 'list_item' then + parts[#parts + 1] = string.rep(' ', indent) + local offset = node[1].type == 'list_marker_dot' and 3 or 2 + for i, child in ipairs(node) do + local sindent = i <= 2 and 0 or (indent + offset) + vim.list_extend( + parts, + render_md(child, sindent, indent + offset, text_width, level + 1, true) + ) + end + else + if node.text then + error(fmt('cannot render:\n%s', vim.inspect(node))) + end + for i, child in ipairs(node) do + local start_indent0 = i == 1 and start_indent or indent + vim.list_extend( + parts, + render_md(child, start_indent0, indent, text_width, level + 1, is_list) + ) + if ntype ~= 'list' and i ~= #node then + if (node[i + 1] or {}).type ~= 'list' then + parts[#parts + 1] = '\n' + end + end + end + end + + if add_tag then + parts[#parts + 1] = '' + end + + return parts +end + +--- @param text_width integer +local function align_tags(text_width) + --- @param line string + --- @return string + return function(line) + local tag_pat = '%s*(%*.+%*)%s*$' + local tags = {} + for m in line:gmatch(tag_pat) do + table.insert(tags, m) + end + + if #tags > 0 then + line = line:gsub(tag_pat, '') + local tags_str = ' ' .. table.concat(tags, ' ') + --- @type integer + local conceal_offset = select(2, tags_str:gsub('%*', '')) - 2 + local pad = string.rep(' ', text_width - #line - #tags_str + conceal_offset) + return line .. pad .. tags_str + end + + return line + end +end + +--- @param text string +--- @param start_indent integer +--- @param indent integer +--- @param is_list? boolean +--- @return string +function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list) + -- Add an extra newline so the parser can properly capture ending ``` + local parsed = parse_md(text .. '\n') + local ret = render_md(parsed, start_indent, indent, text_width, 0, is_list) + + local lines = vim.split(table.concat(ret):gsub(NBSP, ' '), '\n') + + lines = vim.tbl_map(align_tags(text_width), lines) + + local s = table.concat(lines, '\n') + + -- Reduce whitespace in code-blocks + s = s:gsub('\n+%s*>([a-z]+)\n', ' >%1\n') + s = s:gsub('\n+%s*>\n?\n', ' >\n') + + return s +end + +return M -- cgit From 9a5bbaf813df598fafb7456c44d528e02648c1d8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 4 Oct 2024 08:12:17 -0700 Subject: docs: more `@since` annotations #30660 --- scripts/util.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'scripts') diff --git a/scripts/util.lua b/scripts/util.lua index 94598c32a6..6343ad4b21 100644 --- a/scripts/util.lua +++ b/scripts/util.lua @@ -20,6 +20,7 @@ end -- Map of api_level:version, by inspection of: -- :lua= vim.mpack.decode(vim.fn.readfile('test/functional/fixtures/api_level_9.mpack','B')).version M.version_level = { + [13] = '0.11.0', [12] = '0.10.0', [11] = '0.9.0', [10] = '0.8.0', -- cgit From 8801b77ed09876605e42f4a3fddf8c020c14b711 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 5 Oct 2024 08:52:57 -0700 Subject: fix(docs): missing `@returns` desc in _meta/api.lua #30673 --- scripts/gen_eval_files.lua | 7 +++---- scripts/util.lua | 13 +++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 234028b972..095daaeb21 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -276,7 +276,7 @@ local function get_api_meta() if not deprecated then r.desc = fun.desc - r.return_desc = fun.returns[1].desc + r.returns_desc = fun.returns[1].desc end ret[fun.name] = r @@ -370,10 +370,9 @@ local function render_api_meta(_f, fun, write) end if fun.returns ~= '' then - local ret_desc = fun.returns_desc and ' : ' .. fun.returns_desc or '' - ret_desc = util.md_to_vimdoc(ret_desc, 0, 0, 74) + local ret_desc = fun.returns_desc and ' # ' .. fun.returns_desc or '' local ret = LUA_API_RETURN_OVERRIDES[fun.name] or fun.returns - write('--- @return ' .. ret .. ret_desc) + write(util.prefix('--- ', '@return ' .. ret .. ret_desc)) end local param_str = table.concat(param_names, ', ') diff --git a/scripts/util.lua b/scripts/util.lua index 6343ad4b21..dda18fb807 100644 --- a/scripts/util.lua +++ b/scripts/util.lua @@ -173,6 +173,19 @@ local function parse_md(text) return extract(root) or {} end +--- Prefixes each line in `text`. +--- +--- Does not wrap, that's not important for "meta" files? (You probably want md_to_vimdoc instead.) +--- +--- @param text string +--- @param prefix_ string +function M.prefix(prefix_, text) + if (text):find('\n$') then + return text:gsub('([^\n]*)[\t ]*\n', prefix_ .. '%1\n') + end + return prefix_ .. text:gsub('([^\n]*)[\t ]*\n', '%1\n' .. prefix_) +end + --- @param x string --- @param start_indent integer --- @param indent integer -- cgit From 056009f74146b349c66790fbcba3130e85a6c6da Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Oct 2024 03:24:21 -0700 Subject: fix(docs): markdown instead of vimdoc in meta docstrings #30680 LuaLS/meta docstrings expect markdown, not vimdoc. This matters for lists, codeblocks, etc. Also, line length doesn't matter for docstrings. --- scripts/gen_eval_files.lua | 22 ++++------------------ scripts/util.lua | 11 ++++++----- 2 files changed, 10 insertions(+), 23 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 095daaeb21..6aa8ee0112 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -321,30 +321,18 @@ local function render_api_meta(_f, fun, write) local desc = fun.desc if desc then - desc = util.md_to_vimdoc(desc, 0, 0, 74) - for _, l in ipairs(split(norm_text(desc))) do - write('--- ' .. l) - end + write(util.prefix_lines('--- ', norm_text(desc))) end -- LuaLS doesn't support @note. Render @note items as a markdown list. if fun.notes and #fun.notes > 0 then write('--- Note:') - for _, note in ipairs(fun.notes) do - -- XXX: abuse md_to_vimdoc() to force-fit the markdown list. Need norm_md()? - note = util.md_to_vimdoc(' - ' .. note, 0, 0, 74) - for _, l in ipairs(split(vim.trim(norm_text(note)))) do - write('--- ' .. l:gsub('\n*$', '')) - end - end + write(util.prefix_lines('--- ', table.concat(fun.notes, '\n'))) write('---') end for _, see in ipairs(fun.see or {}) do - see = util.md_to_vimdoc('@see ' .. see, 0, 0, 74) - for _, l in ipairs(split(vim.trim(norm_text(see)))) do - write('--- ' .. l:gsub([[\s*$]], '')) - end + write(util.prefix_lines('--- @see ', norm_text(see))) end local param_names = {} --- @type string[] @@ -354,8 +342,6 @@ local function render_api_meta(_f, fun, write) local pdesc = p[3] if pdesc then local s = '--- @param ' .. p[1] .. ' ' .. p[2] .. ' ' - local indent = #('@param ' .. p[1] .. ' ') - pdesc = util.md_to_vimdoc(pdesc, #s, indent, 74, true) local pdesc_a = split(vim.trim(norm_text(pdesc))) write(s .. pdesc_a[1]) for i = 2, #pdesc_a do @@ -372,7 +358,7 @@ local function render_api_meta(_f, fun, write) if fun.returns ~= '' then local ret_desc = fun.returns_desc and ' # ' .. fun.returns_desc or '' local ret = LUA_API_RETURN_OVERRIDES[fun.name] or fun.returns - write(util.prefix('--- ', '@return ' .. ret .. ret_desc)) + write(util.prefix_lines('--- ', '@return ' .. ret .. ret_desc)) end local param_str = table.concat(param_names, ', ') diff --git a/scripts/util.lua b/scripts/util.lua index dda18fb807..5940221abe 100644 --- a/scripts/util.lua +++ b/scripts/util.lua @@ -175,15 +175,16 @@ end --- Prefixes each line in `text`. --- ---- Does not wrap, that's not important for "meta" files? (You probably want md_to_vimdoc instead.) +--- Does not wrap, not important for "meta" files? (You probably want md_to_vimdoc instead.) --- --- @param text string --- @param prefix_ string -function M.prefix(prefix_, text) - if (text):find('\n$') then - return text:gsub('([^\n]*)[\t ]*\n', prefix_ .. '%1\n') +function M.prefix_lines(prefix_, text) + local r = '' + for _, l in ipairs(vim.split(text, '\n', { plain = true })) do + r = r .. vim.trim(prefix_ .. l) .. '\n' end - return prefix_ .. text:gsub('([^\n]*)[\t ]*\n', '%1\n' .. prefix_) + return r end --- @param x string -- cgit From 6628741ada73bcf60dd1cb249178aa18e60dbebc Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Oct 2024 09:12:35 -0700 Subject: feat(docs): improve `@see` meta docstrings #30693 --- scripts/gen_eval_files.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 6aa8ee0112..25f3e30b74 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -199,14 +199,6 @@ local function process_params(params) return params end ---- @class vim.gen_vim_doc_fun ---- @field signature string ---- @field doc string[] ---- @field parameters_doc table ---- @field return string[] ---- @field seealso string[] ---- @field annotations string[] - --- @return table local function get_api_meta() local ret = {} --- @type table @@ -290,8 +282,19 @@ end --- Ensure code blocks have one empty line before the start fence and after the closing fence. --- --- @param x string +--- @param special string? +--- | 'see-api-meta' Normalize `@see` for API meta docstrings. --- @return string -local function norm_text(x) +local function norm_text(x, special) + if special == 'see-api-meta' then + -- Try to guess a symbol that actually works in @see. + -- "nvim_xx()" => "vim.api.nvim_xx" + x = x:gsub([=[%|?(nvim_[^.()| ]+)%(?%)?%|?]=], 'vim.api.%1') + -- TODO: Remove backticks when LuaLS resolves: https://github.com/LuaLS/lua-language-server/issues/2889 + -- "|foo|" => "`:help foo`" + x = x:gsub([=[|([^ ]+)|]=], '`:help %1`') + end + return ( x:gsub('|([^ ]+)|', '`%1`') :gsub('\n*>lua', '\n\n```lua') @@ -332,7 +335,7 @@ local function render_api_meta(_f, fun, write) end for _, see in ipairs(fun.see or {}) do - write(util.prefix_lines('--- @see ', norm_text(see))) + write(util.prefix_lines('--- @see ', norm_text(see, 'see-api-meta'))) end local param_names = {} --- @type string[] -- cgit From 7335988ce6a5f41a8405462c6c4c90a54d3e588c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 7 Oct 2024 05:32:49 -0700 Subject: docs: generate params/returns in builtin.txt #30654 --- scripts/gen_eval_files.lua | 64 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) (limited to 'scripts') diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 25f3e30b74..b9ea4e73f0 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -2,7 +2,11 @@ -- Generator for various vimdoc and Lua type files +local util = require('scripts.util') +local fmt = string.format + local DEP_API_METADATA = 'build/funcs_metadata.mpack' +local TEXT_WIDTH = 78 --- @class vim.api.metadata --- @field name string @@ -170,9 +174,9 @@ local function render_fun_sig(f, params) end if LUA_KEYWORDS[f] then - return string.format("vim.fn['%s'] = function(%s) end", f, param_str) + return fmt("vim.fn['%s'] = function(%s) end", f, param_str) else - return string.format('function vim.fn.%s(%s) end', f, param_str) + return fmt('function vim.fn.%s(%s) end', f, param_str) end end @@ -306,14 +310,13 @@ local function norm_text(x, special) ) end +--- Generates LuaLS docstring for an API function. --- @param _f string --- @param fun vim.EvalFn --- @param write fun(line: string) local function render_api_meta(_f, fun, write) write('') - local util = require('scripts.util') - if vim.startswith(fun.name, 'nvim__') then write('--- @private') end @@ -365,7 +368,7 @@ local function render_api_meta(_f, fun, write) end local param_str = table.concat(param_names, ', ') - write(string.format('function vim.api.%s(%s) end', fun.name, param_str)) + write(fmt('function vim.api.%s(%s) end', fun.name, param_str)) end --- @return table @@ -393,6 +396,7 @@ local function get_api_keysets_meta() return ret end +--- Generates LuaLS docstring for an API keyset. --- @param _f string --- @param fun vim.EvalFn --- @param write fun(line: string) @@ -412,6 +416,7 @@ local function get_eval_meta() return require('src/nvim/eval').funcs end +--- Generates LuaLS docstring for a Vimscript "eval" function. --- @param f string --- @param fun vim.EvalFn --- @param write fun(line: string) @@ -421,7 +426,6 @@ local function render_eval_meta(f, fun, write) end local funname = fun.name or f - local params = process_params(fun.params) write('') @@ -445,16 +449,18 @@ local function render_eval_meta(f, fun, write) for i, param in ipairs(params) do local pname, ptype = param[1], param[2] local optional = (pname ~= '...' and i > req_args) and '?' or '' - write(string.format('--- @param %s%s %s', pname, optional, ptype)) + write(fmt('--- @param %s%s %s', pname, optional, ptype)) end if fun.returns ~= false then - write('--- @return ' .. (fun.returns or 'any')) + local ret_desc = fun.returns_desc and ' # ' .. fun.returns_desc or '' + write('--- @return ' .. (fun.returns or 'any') .. ret_desc) end write(render_fun_sig(funname, params)) end +--- Generates vimdoc heading for a Vimscript "eval" function signature. --- @param name string --- @param name_tag boolean --- @param fun vim.EvalFn @@ -486,19 +492,16 @@ local function render_sig_and_tag(name, name_tag, fun, write) write(string.rep(' ', tag_pad_len) .. tag) write(fun.signature) else - write(string.format('%s%s%s', fun.signature, string.rep(' ', tag_pad_len - siglen), tag)) + write(fmt('%s%s%s', fun.signature, string.rep(' ', tag_pad_len - siglen), tag)) end end +--- Generates vimdoc for a Vimscript "eval" function. --- @param f string --- @param fun vim.EvalFn --- @param write fun(line: string) local function render_eval_doc(f, fun, write) - if fun.deprecated then - return - end - - if not fun.signature then + if fun.deprecated or not fun.signature then return end @@ -508,6 +511,9 @@ local function render_eval_doc(f, fun, write) return end + local params = process_params(fun.params) + local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0 + local desc_l = split(vim.trim(fun.desc)) for _, l in ipairs(desc_l) do l = l:gsub('^ ', '') @@ -523,6 +529,26 @@ local function render_eval_doc(f, fun, write) if #desc_l > 0 and not desc_l[#desc_l]:match('^ 0 then + write(util.md_to_vimdoc('Parameters: ~', 16, 16, TEXT_WIDTH)) + for i, param in ipairs(params) do + local pname, ptype = param[1], param[2] + local optional = (pname ~= '...' and i > req_args) and '?' or '' + local s = fmt('- %-14s (`%s%s`)', fmt('{%s}', pname), ptype, optional) + write(util.md_to_vimdoc(s, 16, 18, TEXT_WIDTH)) + end + write('') + end + + if fun.returns ~= false then + write(util.md_to_vimdoc('Return: ~', 16, 16, TEXT_WIDTH)) + local ret = ('(`%s`)'):format((fun.returns or 'any')) + ret = ret .. (fun.returns_desc and ' ' .. fun.returns_desc or '') + ret = util.md_to_vimdoc(ret, 18, 18, TEXT_WIDTH) + write(ret) + write('') + end end --- @param d vim.option_defaults @@ -760,9 +786,9 @@ local function render_option_doc(_f, opt, write) local name_str --- @type string if opt.abbreviation then - name_str = string.format("'%s' '%s'", opt.full_name, opt.abbreviation) + name_str = fmt("'%s' '%s'", opt.full_name, opt.abbreviation) else - name_str = string.format("'%s'", opt.full_name) + name_str = fmt("'%s'", opt.full_name) end local otype = opt.type == 'boolean' and 'boolean' or opt.type @@ -770,13 +796,13 @@ local function render_option_doc(_f, opt, write) local v = render_option_default(opt.defaults, true) local pad = string.rep('\t', math.max(1, math.ceil((24 - #name_str) / 8))) if opt.defaults.doc then - local deflen = #string.format('%s%s%s (', name_str, pad, otype) + local deflen = #fmt('%s%s%s (', name_str, pad, otype) --- @type string v = v:gsub('\n', '\n' .. string.rep(' ', deflen - 2)) end - write(string.format('%s%s%s\t(default %s)', name_str, pad, otype, v)) + write(fmt('%s%s%s\t(default %s)', name_str, pad, otype, v)) else - write(string.format('%s\t%s', name_str, otype)) + write(fmt('%s\t%s', name_str, otype)) end write('\t\t\t' .. scope_to_doc(opt.scope) .. scope_more_doc(opt)) -- cgit