diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
| commit | 9be89f131f87608f224f0ee06d199fcd09d32176 (patch) | |
| tree | 11022dcfa9e08cb4ac5581b16734196128688d48 /scripts | |
| parent | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff) | |
| parent | 88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff) | |
| download | rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2 rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip | |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/bump_deps.lua | 26 | ||||
| -rwxr-xr-x | scripts/gen_eval_files.lua | 166 | ||||
| -rw-r--r-- | scripts/gen_help_html.lua | 178 | ||||
| -rw-r--r-- | scripts/gen_lsp.lua | 33 | ||||
| -rwxr-xr-x | scripts/gen_vimdoc.lua | 69 | ||||
| -rw-r--r-- | scripts/lintcommit.lua | 19 | ||||
| -rw-r--r-- | scripts/luacats_grammar.lua | 108 | ||||
| -rw-r--r-- | scripts/luacats_parser.lua | 9 | ||||
| -rw-r--r-- | scripts/util.lua (renamed from scripts/text_utils.lua) | 50 | ||||
| -rwxr-xr-x | scripts/vim-patch.sh | 12 |
10 files changed, 443 insertions, 227 deletions
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 diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index f1bba5c0a2..b9ea4e73f0 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -2,11 +2,15 @@ -- 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 ---- @field parameters {[1]:string,[2]:string}[] +--- @field parameters [string,string][] --- @field return_type string --- @field deprecated_since integer --- @field eval boolean @@ -20,7 +24,7 @@ local DEP_API_METADATA = 'build/funcs_metadata.mpack' local LUA_API_RETURN_OVERRIDES = { nvim_buf_get_command = 'table<string,vim.api.keyset.command_info>', - 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[]', @@ -29,11 +33,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<string,vim.api.keyset.hl_info>, however we need to + -- Can also return table<string,vim.api.keyset.get_hl_info>, however we need to -- pick one to get some benefit. -- REVISIT lewrus01 (26/01/24): we can maybe add - -- @overload fun(ns: integer, {}): table<string,vim.api.keyset.hl_info> - nvim_get_hl = 'vim.api.keyset.hl_info', + -- @overload fun(ns: integer, {}): table<string,vim.api.keyset.get_hl_info> + nvim_get_hl = 'vim.api.keyset.get_hl_info', nvim_get_mode = 'vim.api.keyset.get_mode', nvim_get_namespaces = 'table<string,integer>', @@ -112,7 +116,7 @@ local API_TYPES = { String = 'string', Array = 'any[]', LuaRef = 'function', - Dictionary = 'table<string,any>', + Dict = 'table<string,any>', Float = 'number', HLGroupID = 'number|string', void = '', @@ -140,7 +144,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<string,' .. api_type(d0) .. '>' end @@ -149,7 +153,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 +162,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] @@ -170,16 +174,16 @@ 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 --- 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<string,true> local sfx = 1 @@ -199,14 +203,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<string,string> ---- @field return string[] ---- @field seealso string[] ---- @field annotations string[] - --- @return table<string, vim.EvalFn> local function get_api_meta() local ret = {} --- @type table<string, vim.EvalFn> @@ -245,7 +241,17 @@ 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 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] = { p.name, @@ -258,13 +264,15 @@ 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, } 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 @@ -278,8 +286,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') @@ -291,14 +310,13 @@ local function norm_text(x) ) 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 text_utils = require('scripts.text_utils') - if vim.startswith(fun.name, 'nvim__') then write('--- @private') end @@ -309,10 +327,18 @@ 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) - 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:') + write(util.prefix_lines('--- ', table.concat(fun.notes, '\n'))) + write('---') + end + + for _, see in ipairs(fun.see or {}) do + write(util.prefix_lines('--- @see ', norm_text(see, 'see-api-meta'))) end local param_names = {} --- @type string[] @@ -322,8 +348,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 = text_utils.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 @@ -336,15 +360,15 @@ 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) + 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_lines('--- ', '@return ' .. ret .. ret_desc)) 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<string, vim.EvalFn> @@ -372,10 +396,14 @@ 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) 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 @@ -388,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) @@ -397,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('') @@ -421,25 +449,28 @@ 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 --- @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 +478,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) @@ -456,32 +492,28 @@ local function render_sig_and_tag(name, 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 - 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 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('^ ', '') @@ -497,6 +529,26 @@ local function render_eval_doc(f, fun, write) if #desc_l > 0 and not desc_l[#desc_l]:match('^<?$') then write('') end + + if #params > 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 @@ -734,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 @@ -744,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)) diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index cdfb85bde6..f6e799508b 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. @@ -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, @@ -76,10 +77,17 @@ local new_layout = { ['news-0.10.txt'] = true, ['nvim.txt'] = true, ['provider.txt'] = true, + ['tui.txt'] = true, ['ui.txt'] = true, ['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 +220,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 +236,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 +265,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. @@ -490,7 +500,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 +517,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 +547,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 +570,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</%s>\n'):format(el, tagname, text, el) + return ('<%s id="%s" class="help-heading">%s</%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 @@ -648,13 +665,13 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) code = ('<pre>%s</pre>'):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 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 -- Force the first 2 tags in the doc to be anchored at the main heading. @@ -694,8 +711,8 @@ local function visit_node(root, level, lang_tree, headings, opt, stats) -- End the <span> container for tags in a heading. return string.format('%s</span>', s) end - return s - elseif node_name == 'modeline' then + return s .. (h4 and '<br>' or '') -- HACK: <br> avoids h4 pseudo-heading mushing with text. + elseif node_name == 'delimiter' or node_name == 'modeline' then return '' elseif node_name == 'ERROR' then if ignore_parse_error(opt.fname, trimmed) then @@ -760,14 +777,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 @@ -779,7 +803,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, nil, { error = false })) return lang_tree, buf end @@ -794,7 +818,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 @@ -805,20 +829,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)) @@ -1126,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 { @@ -1139,10 +1165,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 { @@ -1177,8 +1204,7 @@ local function gen_css(fname) pre:last-child { margin-bottom: 0; } - pre:hover, - .help-heading:hover { + pre:hover { overflow: visible; } .generator-stats { @@ -1216,7 +1242,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( @@ -1278,7 +1304,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', }, @@ -1288,40 +1314,88 @@ 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', }, } local err_count = 0 + local redirects_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)) + 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 { @@ -1351,7 +1425,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', }, @@ -1359,7 +1433,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', }, @@ -1367,12 +1441,12 @@ function M.validate(help_dir, include, parser_path) local err_count = 0 ---@type integer local files_to_errors = {} ---@type table<string, string[]> 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 = 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 @@ -1404,7 +1478,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) @@ -1430,16 +1504,14 @@ 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())) - help_dir = vim.fn.expand(help_dir or '$VIMRUNTIME/doc') + local tmpdir = vim.fs.dirname(vim.fn.tempname()) + help_dir = vim.fs.normalize(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 diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua index 04d19f22e6..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) @@ -109,26 +110,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) @@ -297,13 +280,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) diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index 9c6225efc3..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 @@ -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 @@ -273,6 +273,7 @@ local config = { 'buf.lua', 'diagnostic.lua', 'codelens.lua', + 'completion.lua', 'inlay_hint.lua', 'tagfunc.lua', 'semantic_tokens.lua', @@ -318,6 +319,8 @@ local config = { treesitter = { filename = 'treesitter.txt', section_order = { + 'tstree.lua', + 'tsnode.lua', 'treesitter.lua', 'language.lua', 'query.lua', @@ -326,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, @@ -372,8 +384,8 @@ local config = { section_fmt = function(_name) return 'Checkhealth' end, - helptag_fmt = function(name) - return name:lower() + helptag_fmt = function() + return { 'vim.health', 'health' } end, }, } @@ -420,8 +432,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 @@ -523,7 +538,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 @@ -715,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 @@ -813,7 +834,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[] @@ -865,7 +886,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 @@ -874,7 +899,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, } @@ -934,7 +959,7 @@ local function gen_target(cfg) expand_files(cfg.files) - --- @type table<string,{[1]:table<string,nvim.luacats.parser.class>, [2]: nvim.luacats.parser.fun[], [3]: string[]}> + --- @type table<string,[table<string,nvim.luacats.parser.class>, nvim.luacats.parser.fun[], string[]]> local file_results = {} --- @type table<string,nvim.luacats.parser.class> @@ -965,7 +990,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/lintcommit.lua b/scripts/lintcommit.lua index 96f6304247..7cb57de901 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. @@ -74,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 @@ -229,9 +224,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, @@ -253,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, diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua index 29f3bda5aa..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,21 +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, - 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), - ty_fun = Pf('fun') * paren(comma(lname * opt(colon * v.ltype))) * opt(colon * comma1(v.ltype)), - ty_generic = P('`') * letter * P('`'), + ty_index = C(Pf('[') * typedef * fill * P(']')), + ctype = Cg(typedef, 'type'), } return grammar --[[@as nvim.luacats.grammar]] diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua index cb301b32e4..9a763e4d7b 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' @@ -252,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] = ': ' @@ -270,6 +274,7 @@ local function fun2field(fun) type = table.concat(parts, ''), access = fun.access, desc = fun.desc, + nodoc = fun.nodoc, } end @@ -458,7 +463,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) diff --git a/scripts/text_utils.lua b/scripts/util.lua index 75b3bfedd5..5940221abe 100644 --- a/scripts/text_utils.lua +++ b/scripts/util.lua @@ -1,7 +1,9 @@ +-- TODO(justinmk): move most of this to `vim.text`. + local fmt = string.format ---- @class nvim.text_utils.MDNode ---- @field [integer] nvim.text_utils.MDNode +--- @class nvim.util.MDNode +--- @field [integer] nvim.util.MDNode --- @field type string --- @field text? string @@ -15,6 +17,24 @@ 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 = { + [13] = '0.11.0', + [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 @@ -47,13 +67,13 @@ local function slice_text(txt, srow, scol, erow, ecol) end --- @param text string ---- @return nvim.text_utils.MDNode +--- @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.text_utils.MDNode? + --- @return nvim.util.MDNode? local function extract(node) local ntype = node:type() @@ -101,7 +121,7 @@ local function parse_md_inline(text) end --- @param text string ---- @return nvim.text_utils.MDNode +--- @return nvim.util.MDNode local function parse_md(text) local parser = vim.treesitter.languagetree.new(text, 'markdown', { injections = { markdown = '' }, @@ -119,7 +139,7 @@ local function parse_md(text) } --- @param node TSNode - --- @return nvim.text_utils.MDNode? + --- @return nvim.util.MDNode? local function extract(node) local ntype = node:type() @@ -153,6 +173,20 @@ local function parse_md(text) return extract(root) or {} end +--- Prefixes each line in `text`. +--- +--- Does not wrap, not important for "meta" files? (You probably want md_to_vimdoc instead.) +--- +--- @param text string +--- @param prefix_ string +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 r +end + --- @param x string --- @param start_indent integer --- @param indent integer @@ -179,7 +213,7 @@ function M.wrap(x, start_indent, indent, text_width) return (table.concat(parts):gsub('%s+\n', '\n'):gsub('\n+$', '')) end ---- @param node nvim.text_utils.MDNode +--- @param node nvim.util.MDNode --- @param start_indent integer --- @param indent integer --- @param text_width integer @@ -207,6 +241,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 diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index e8758c064f..bfa9f6d99c 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 @@ -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,8 +293,12 @@ 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 terminal.txt to nvim_terminal_emulator.txt - LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/terminal\.txt/\1\/nvim_terminal_emulator.txt/g' \ + # 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 test_urls.vim to check_urls.vim |