aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/gen_eval_files.lua2
-rw-r--r--scripts/gen_help_html.lua2
-rwxr-xr-xscripts/gen_vimdoc.lua315
-rw-r--r--scripts/luacats_grammar.lua182
-rw-r--r--scripts/luacats_parser.lua66
-rw-r--r--scripts/text_utils.lua168
6 files changed, 511 insertions, 224 deletions
diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua
index 83e55b3bc4..f1bba5c0a2 100755
--- a/scripts/gen_eval_files.lua
+++ b/scripts/gen_eval_files.lua
@@ -40,7 +40,7 @@ local LUA_API_RETURN_OVERRIDES = {
nvim_get_option_info = 'vim.api.keyset.get_option_info',
nvim_get_option_info2 = 'vim.api.keyset.get_option_info',
nvim_parse_cmd = 'vim.api.keyset.parse_cmd',
- nvim_win_get_config = 'vim.api.keyset.float_config',
+ nvim_win_get_config = 'vim.api.keyset.win_config',
}
local LUA_META_HEADER = {
diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua
index 5cda16bbe6..43040151eb 100644
--- a/scripts/gen_help_html.lua
+++ b/scripts/gen_help_html.lua
@@ -752,7 +752,7 @@ end
---
--- @param fname string help file to parse
--- @param parser_path string? path to non-default vimdoc.so
---- @return LanguageTree, integer (lang_tree, bufnr)
+--- @return vim.treesitter.LanguageTree, integer (lang_tree, bufnr)
local function parse_buf(fname, parser_path)
local buf ---@type integer
if type(fname) == 'string' then
diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua
index 8acae66f49..b3b211d5a6 100755
--- a/scripts/gen_vimdoc.lua
+++ b/scripts/gen_vimdoc.lua
@@ -94,12 +94,12 @@ end
local function fn_helptag_fmt_common(fun)
local fn_sfx = fun.table and '' or '()'
if fun.classvar then
- return fmt('*%s:%s%s*', fun.classvar, fun.name, fn_sfx)
+ return fmt('%s:%s%s', fun.classvar, fun.name, fn_sfx)
end
if fun.module then
- return fmt('*%s.%s%s*', fun.module, fun.name, fn_sfx)
+ return fmt('%s.%s%s', fun.module, fun.name, fn_sfx)
end
- return fmt('*%s%s*', fun.name, fn_sfx)
+ return fun.name .. fn_sfx
end
--- @type table<string,nvim.gen_vimdoc.Config>
@@ -129,7 +129,7 @@ local config = {
return name .. ' Functions'
end,
helptag_fmt = function(name)
- return fmt('*api-%s*', name:lower())
+ return fmt('api-%s', name:lower())
end,
},
lua = {
@@ -241,22 +241,22 @@ local config = {
end,
helptag_fmt = function(name)
if name == '_editor' then
- return '*lua-vim*'
+ return 'lua-vim'
elseif name == '_options' then
- return '*lua-vimscript*'
+ return 'lua-vimscript'
elseif name == 'tohtml' then
- return '*tohtml*'
+ return 'tohtml'
end
- return '*vim.' .. name:lower() .. '*'
+ return 'vim.' .. name:lower()
end,
fn_helptag_fmt = function(fun)
local name = fun.name
if vim.startswith(name, 'vim.') then
local fn_sfx = fun.table and '' or '()'
- return fmt('*%s%s*', name, fn_sfx)
+ return name .. fn_sfx
elseif fun.classvar == 'Option' then
- return fmt('*vim.opt:%s()*', name)
+ return fmt('vim.opt:%s()', name)
end
return fn_helptag_fmt_common(fun)
@@ -269,6 +269,7 @@ local config = {
filename = 'lsp.txt',
section_order = {
'lsp.lua',
+ 'client.lua',
'buf.lua',
'diagnostic.lua',
'codelens.lua',
@@ -296,9 +297,9 @@ local config = {
end,
helptag_fmt = function(name)
if name:lower() == 'lsp' then
- return '*lsp-core*'
+ return 'lsp-core'
end
- return fmt('*lsp-%s*', name:lower())
+ return fmt('lsp-%s', name:lower())
end,
},
diagnostic = {
@@ -311,7 +312,7 @@ local config = {
return 'Lua module: vim.diagnostic'
end,
helptag_fmt = function()
- return '*diagnostic-api*'
+ return 'diagnostic-api'
end,
},
treesitter = {
@@ -336,9 +337,28 @@ local config = {
end,
helptag_fmt = function(name)
if name:lower() == 'treesitter' then
- return '*lua-treesitter-core*'
+ return 'lua-treesitter-core'
end
- return '*lua-treesitter-' .. name:lower() .. '*'
+ return 'lua-treesitter-' .. name:lower()
+ end,
+ },
+ editorconfig = {
+ filename = 'editorconfig.txt',
+ files = {
+ 'runtime/lua/editorconfig.lua',
+ },
+ section_order = {
+ 'editorconfig.lua',
+ },
+ section_fmt = function(_name)
+ return 'EditorConfig integration'
+ end,
+ helptag_fmt = function(name)
+ return name:lower()
+ end,
+ fn_xform = function(fun)
+ fun.table = true
+ fun.name = vim.split(fun.name, '.', { plain = true })[2]
end,
},
}
@@ -362,15 +382,25 @@ local function replace_generics(ty, generics)
return generics[ty] or ty
end
+--- @param name string
+local function fmt_field_name(name)
+ local name0, opt = name:match('^([^?]*)(%??)$')
+ return fmt('{%s}%s', name0, opt)
+end
+
--- @param ty string
--- @param generics? table<string,string>
-local function render_type(ty, generics)
+--- @param default? string
+local function render_type(ty, generics, default)
if generics then
ty = replace_generics(ty, generics)
end
ty = ty:gsub('%s*|%s*nil', '?')
ty = ty:gsub('nil%s*|%s*(.*)', '%1?')
ty = ty:gsub('%s*|%s*', '|')
+ if default then
+ return fmt('(`%s`, default: %s)', ty, default)
+ end
return fmt('(`%s`)', ty)
end
@@ -379,10 +409,101 @@ local function should_render_param(p)
return not p.access and not contains(p.name, { '_', 'self' })
end
+--- @param desc? string
+--- @return string?, string?
+local function get_default(desc)
+ if not desc then
+ return
+ end
+
+ local default = desc:match('\n%s*%([dD]efault: ([^)]+)%)')
+ if default then
+ desc = desc:gsub('\n%s*%([dD]efault: [^)]+%)', '')
+ end
+
+ return desc, default
+end
+
+--- @param ty string
+--- @param classes? table<string,nvim.luacats.parser.class>
+--- @return nvim.luacats.parser.class?
+local function get_class(ty, classes)
+ if not classes then
+ return
+ end
+
+ local cty = ty:gsub('%s*|%s*nil', '?'):gsub('?$', ''):gsub('%[%]$', '')
+
+ return classes[cty]
+end
+
+--- @param obj nvim.luacats.parser.param|nvim.luacats.parser.return|nvim.luacats.parser.field
+--- @param classes? table<string,nvim.luacats.parser.class>
+local function inline_type(obj, classes)
+ local ty = obj.type
+ if not ty then
+ return
+ end
+
+ local cls = get_class(ty, classes)
+
+ if not cls or cls.nodoc then
+ return
+ end
+
+ if not cls.inlinedoc then
+ -- Not inlining so just add a: "See |tag|."
+ local tag = fmt('|%s|', cls.name)
+ if obj.desc and obj.desc:find(tag) then
+ -- Tag already there
+ return
+ end
+
+ -- TODO(lewis6991): Aim to remove this. Need this to prevent dead
+ -- references to types defined in runtime/lua/vim/lsp/_meta/protocol.lua
+ if not vim.startswith(cls.name, 'vim.') then
+ return
+ end
+
+ obj.desc = obj.desc or ''
+ local period = (obj.desc == '' or vim.endswith(obj.desc, '.')) and '' or '.'
+ obj.desc = obj.desc .. fmt('%s See %s.', period, tag)
+ return
+ end
+
+ local ty_isopt = (ty:match('%?$') or ty:match('%s*|%s*nil')) ~= nil
+ local ty_islist = (ty:match('%[%]$')) ~= nil
+ ty = ty_isopt and 'table?' or ty_islist and 'table[]' or 'table'
+
+ local desc = obj.desc or ''
+ if cls.desc then
+ desc = desc .. cls.desc
+ elseif desc == '' then
+ if ty_islist then
+ desc = desc .. 'A list of objects with the following fields:'
+ else
+ desc = desc .. 'A table with the following fields:'
+ end
+ end
+
+ local desc_append = {}
+ for _, f in ipairs(cls.fields) do
+ local fdesc, default = get_default(f.desc)
+ local fty = render_type(f.type, nil, default)
+ local fnm = fmt_field_name(f.name)
+ table.insert(desc_append, table.concat({ '-', fnm, fty, fdesc }, ' '))
+ end
+
+ desc = desc .. '\n' .. table.concat(desc_append, '\n')
+ obj.type = ty
+ obj.desc = desc
+end
+
--- @param xs (nvim.luacats.parser.param|nvim.luacats.parser.field)[]
--- @param generics? table<string,string>
+--- @param classes? table<string,nvim.luacats.parser.class>
--- @param exclude_types? true
-local function render_fields_or_params(xs, generics, exclude_types)
+local function render_fields_or_params(xs, generics, classes, exclude_types)
local ret = {} --- @type string[]
xs = vim.tbl_filter(should_render_param, xs)
@@ -398,15 +519,27 @@ local function render_fields_or_params(xs, generics, exclude_types)
end
for _, p in ipairs(xs) do
- local nm, ty = p.name, p.type
- local desc = p.desc
- local pnm = fmt(' • %-' .. indent .. 's', '{' .. nm .. '}')
+ local pdesc, default = get_default(p.desc)
+ p.desc = pdesc
+
+ inline_type(p, classes)
+ local nm, ty, desc = p.name, p.type, p.desc
+
+ local fnm = p.kind == 'operator' and fmt('op(%s)', nm) or fmt_field_name(nm)
+ local pnm = fmt(' • %-' .. indent .. 's', fnm)
+
if ty then
- local pty = render_type(ty, generics)
+ local pty = render_type(ty, generics, default)
+
if desc then
- desc = fmt('%s %s', pty, desc)
table.insert(ret, pnm)
- table.insert(ret, md_to_vimdoc(desc, 1, 9 + indent, TEXT_WIDTH, true))
+ if #pty > TEXT_WIDTH - indent then
+ vim.list_extend(ret, { ' ', pty, '\n' })
+ table.insert(ret, md_to_vimdoc(desc, 9 + indent, 9 + indent, TEXT_WIDTH, true))
+ else
+ desc = fmt('%s %s', pty, desc)
+ table.insert(ret, md_to_vimdoc(desc, 1, 9 + indent, TEXT_WIDTH, true))
+ end
else
table.insert(ret, fmt('%s %s\n', pnm, pty))
end
@@ -421,24 +554,47 @@ local function render_fields_or_params(xs, generics, exclude_types)
return table.concat(ret)
end
--- --- @param class lua2vimdoc.class
--- local function render_class(class)
--- writeln(fmt('*%s*', class.name))
--- writeln()
--- if #class.fields > 0 then
--- writeln(' Fields: ~')
--- render_fields_or_params(class.fields)
--- end
--- writeln()
--- end
-
--- --- @param cls table<string,lua2vimdoc.class>
--- local function render_classes(cls)
--- --- @diagnostic disable-next-line:no-unknown
--- for _, class in vim.spairs(cls) do
--- render_class(class)
--- end
--- end
+--- @param class nvim.luacats.parser.class
+--- @param classes table<string,nvim.luacats.parser.class>
+local function render_class(class, classes)
+ if class.access or class.nodoc or class.inlinedoc then
+ return
+ end
+
+ local ret = {} --- @type string[]
+
+ table.insert(ret, fmt('*%s*\n', class.name))
+
+ if class.parent then
+ local txt = fmt('Extends: |%s|', class.parent)
+ table.insert(ret, md_to_vimdoc(txt, INDENTATION, INDENTATION, TEXT_WIDTH))
+ table.insert(ret, '\n')
+ end
+
+ if class.desc then
+ table.insert(ret, md_to_vimdoc(class.desc, INDENTATION, INDENTATION, TEXT_WIDTH))
+ end
+
+ local fields_txt = render_fields_or_params(class.fields, nil, classes)
+ if not fields_txt:match('^%s*$') then
+ table.insert(ret, '\n Fields: ~\n')
+ table.insert(ret, fields_txt)
+ end
+ table.insert(ret, '\n')
+
+ return table.concat(ret)
+end
+
+--- @param classes table<string,nvim.luacats.parser.class>
+local function render_classes(classes)
+ local ret = {} --- @type string[]
+
+ for _, class in vim.spairs(classes) do
+ ret[#ret + 1] = render_class(class, classes)
+ end
+
+ return table.concat(ret)
+end
--- @param fun nvim.luacats.parser.fun
--- @param cfg nvim.gen_vimdoc.Config
@@ -448,7 +604,7 @@ local function render_fun_header(fun, cfg)
local args = {} --- @type string[]
for _, p in ipairs(fun.params or {}) do
if p.name ~= 'self' then
- args[#args + 1] = fmt('{%s}', p.name:gsub('%?$', ''))
+ args[#args + 1] = fmt_field_name(p.name)
end
end
@@ -463,7 +619,7 @@ local function render_fun_header(fun, cfg)
cfg.fn_helptag_fmt = fn_helptag_fmt_common
end
- local tag = cfg.fn_helptag_fmt(fun)
+ local tag = '*' .. cfg.fn_helptag_fmt(fun) .. '*'
if #proto + #tag > TEXT_WIDTH - 8 then
table.insert(ret, fmt('%78s\n', tag))
@@ -480,8 +636,9 @@ end
--- @param returns nvim.luacats.parser.return[]
--- @param generics? table<string,string>
+--- @param classes? table<string,nvim.luacats.parser.class>
--- @param exclude_types boolean
-local function render_returns(returns, generics, exclude_types)
+local function render_returns(returns, generics, classes, exclude_types)
local ret = {} --- @type string[]
returns = vim.deepcopy(returns)
@@ -498,26 +655,26 @@ local function render_returns(returns, generics, exclude_types)
end
for _, p in ipairs(returns) do
+ inline_type(p, classes)
local rnm, ty, desc = p.name, p.type, p.desc
- local blk = ''
+
+ local blk = {} --- @type string[]
if ty then
- blk = render_type(ty, generics)
+ blk[#blk + 1] = render_type(ty, generics)
end
- if rnm then
- blk = blk .. ' ' .. rnm
- end
- if desc then
- blk = blk .. ' ' .. desc
- end
- table.insert(ret, md_to_vimdoc(blk, 8, 8, TEXT_WIDTH, true))
+ blk[#blk + 1] = rnm
+ blk[#blk + 1] = desc
+
+ table.insert(ret, md_to_vimdoc(table.concat(blk, ' '), 8, 8, TEXT_WIDTH, true))
end
return table.concat(ret)
end
--- @param fun nvim.luacats.parser.fun
+--- @param classes table<string,nvim.luacats.parser.class>
--- @param cfg nvim.gen_vimdoc.Config
-local function render_fun(fun, cfg)
+local function render_fun(fun, classes, cfg)
if fun.access or fun.deprecated or fun.nodoc then
return
end
@@ -570,7 +727,7 @@ local function render_fun(fun, cfg)
end
if fun.params and #fun.params > 0 then
- local param_txt = render_fields_or_params(fun.params, fun.generics, cfg.exclude_types)
+ local param_txt = render_fields_or_params(fun.params, fun.generics, classes, cfg.exclude_types)
if not param_txt:match('^%s*$') then
table.insert(ret, '\n Parameters: ~\n')
ret[#ret + 1] = param_txt
@@ -578,7 +735,7 @@ local function render_fun(fun, cfg)
end
if fun.returns then
- local txt = render_returns(fun.returns, fun.generics, cfg.exclude_types)
+ local txt = render_returns(fun.returns, fun.generics, classes, cfg.exclude_types)
if not txt:match('^%s*$') then
table.insert(ret, '\n')
ret[#ret + 1] = txt
@@ -597,15 +754,16 @@ local function render_fun(fun, cfg)
end
--- @param funs nvim.luacats.parser.fun[]
+--- @param classes table<string,nvim.luacats.parser.class>
--- @param cfg nvim.gen_vimdoc.Config
-local function render_funs(funs, cfg)
+local function render_funs(funs, classes, cfg)
local ret = {} --- @type string[]
for _, f in ipairs(funs) do
if cfg.fn_xform then
cfg.fn_xform(f)
end
- ret[#ret + 1] = render_fun(f, cfg)
+ ret[#ret + 1] = render_fun(f, classes, cfg)
end
-- Sort via prototype
@@ -677,7 +835,7 @@ 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_tag = '*' .. cfg.helptag_fmt(sectname) .. '*'
if funs_txt == '' and #section_docs == 0 then
return
@@ -706,9 +864,9 @@ local function render_section(section, add_header)
})
end
- if section.doc and #section.doc > 0 then
- table.insert(doc, '\n\n')
- vim.list_extend(doc, section.doc)
+ local sdoc = '\n\n' .. table.concat(section.doc or {}, '\n')
+ if sdoc:find('[^%s]') then
+ doc[#doc + 1] = sdoc
end
if section.funs_txt then
@@ -741,19 +899,41 @@ end
--- @param cfg nvim.gen_vimdoc.Config
local function gen_target(cfg)
+ print('Target:', cfg.filename)
local sections = {} --- @type table<string,nvim.gen_vimdoc.Section>
expand_files(cfg.files)
- for _, f in pairs(cfg.files) do
+ --- @type table<string,{[1]:table<string,nvim.luacats.parser.class>, [2]: nvim.luacats.parser.fun[], [3]: string[]}>
+ local file_results = {}
+
+ --- @type table<string,nvim.luacats.parser.class>
+ local all_classes = {}
+
+ --- First pass so we can collect all classes
+ for _, f in vim.spairs(cfg.files) do
local ext = assert(f:match('%.([^.]+)$')) --[[@as 'h'|'c'|'lua']]
local parser = assert(parsers[ext])
- local _, funs, briefs = parser(f)
+ local classes, funs, briefs = parser(f)
+ file_results[f] = { classes, funs, briefs }
+ all_classes = vim.tbl_extend('error', all_classes, classes)
+ end
+
+ for f, r in vim.spairs(file_results) do
+ local classes, funs, briefs = r[1], r[2], r[3]
+
local briefs_txt = {} --- @type string[]
for _, b in ipairs(briefs) do
briefs_txt[#briefs_txt + 1] = md_to_vimdoc(b, 0, 0, TEXT_WIDTH)
end
- local funs_txt = render_funs(funs, cfg)
+ print(' Processing file:', f)
+ local funs_txt = render_funs(funs, all_classes, cfg)
+ if next(classes) then
+ local classes_txt = render_classes(classes)
+ if vim.trim(classes_txt) ~= '' then
+ funs_txt = classes_txt .. '\n' .. funs_txt
+ end
+ end
-- FIXME: Using f_base will confuse `_meta/protocol.lua` with `protocol.lua`
local f_base = assert(vim.fs.basename(f))
sections[f_base] = make_section(f_base, cfg, briefs_txt, funs_txt)
@@ -764,8 +944,9 @@ local function gen_target(cfg)
for _, f in ipairs(cfg.section_order) do
local section = sections[f]
if section then
+ print(string.format(" Rendering section: '%s'", section.title))
local add_sep_and_header = not vim.tbl_contains(cfg.append_only or {}, f)
- table.insert(docs, render_section(section, add_sep_and_header))
+ docs[#docs + 1] = render_section(section, add_sep_and_header)
end
end
@@ -786,7 +967,7 @@ local function gen_target(cfg)
end
local function run()
- for _, cfg in pairs(config) do
+ for _, cfg in vim.spairs(config) do
gen_target(cfg)
end
end
diff --git a/scripts/luacats_grammar.lua b/scripts/luacats_grammar.lua
index ee0f9d8e87..ca26c70156 100644
--- a/scripts/luacats_grammar.lua
+++ b/scripts/luacats_grammar.lua
@@ -21,8 +21,7 @@ local function opt(x)
return x ^ -1
end
-local nl = P('\r\n') + P('\n')
-local ws = rep1(S(' \t') + nl)
+local ws = rep1(S(' \t'))
local fill = opt(ws)
local any = P(1) -- (consume one character)
@@ -30,11 +29,11 @@ local letter = R('az', 'AZ') + S('_$')
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 string_double = P('"') * rep(any - P('"')) * P('"')
-local literal = (string_single + string_double + (opt(P '-') * num) + P 'false' + P 'true')
+local literal = (string_single + string_double + (opt(P('-')) * num) + P('false') + P('true'))
-local lname = (ident + P '...') * opt(P '?')
+local lname = (ident + P('...')) * opt(P('?'))
--- @param x string
local function Pf(x)
@@ -47,13 +46,23 @@ local function Sf(x)
end
--- @param x vim.lpeg.Pattern
-local function comma(x)
- return x * rep(Pf ',' * x)
+local function paren(x)
+ return Pf('(') * x * fill * P(')')
end
--- @param x vim.lpeg.Pattern
local function parenOpt(x)
- return (Pf('(') * x * fill * P(')')) + x
+ return paren(x) + x
+end
+
+--- @param x vim.lpeg.Pattern
+local function comma1(x)
+ return parenOpt(x * rep(Pf(',') * x))
+end
+
+--- @param x vim.lpeg.Pattern
+local function comma(x)
+ return opt(comma1(x))
end
--- @type table<string,vim.lpeg.Pattern>
@@ -63,7 +72,15 @@ 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'
@@ -85,6 +102,7 @@ local desc_delim = Sf '#:' + ws
--- @field kind 'class'
--- @field name string
--- @field parent? string
+--- @field access? 'private'|'protected'|'package'
--- @class nvim.luacats.Field
--- @field kind 'field'
@@ -107,112 +125,60 @@ local desc_delim = Sf '#:' + ws
--- @class nvim.luacats.grammar
--- @field match fun(self, input: string): nvim.luacats.grammar.result?
+local function annot(nm, pat)
+ if type(nm) == 'string' then
+ nm = P(nm)
+ end
+ if pat then
+ return Ct(Cg(P(nm), 'kind') * fill * pat)
+ end
+ return Ct(Cg(P(nm), 'kind'))
+end
+
local grammar = P {
rep1(P('@') * (v.ats + v.ext_ats)),
- ats = v.at_param
- + v.at_return
- + v.at_type
- + v.at_cast
- + v.at_generic
- + v.at_class
- + v.at_field
- + v.at_access
- + v.at_deprecated
- + v.at_alias
- + v.at_enum
- + v.at_see
- + v.at_diagnostic
- + v.at_overload
- + v.at_meta,
-
- ext_ats = v.ext_at_note + v.ext_at_since + v.ext_at_nodoc + v.ext_at_brief,
-
- at_param = Ct(
- Cg(P('param'), 'kind')
- * ws
- * Cg(lname, 'name')
- * ws
- * parenOpt(Cg(v.ltype, 'type'))
- * opt(desc_delim * Cg(rep(any), 'desc'))
- ),
-
- at_return = Ct(
- Cg(P('return'), 'kind')
- * ws
- * parenOpt(comma(Ct(Cg(v.ltype, 'type') * opt(ws * Cg(ident, 'name')))))
- * opt(desc_delim * Cg(rep(any), 'desc'))
- ),
-
- at_type = Ct(
- Cg(P('type'), 'kind')
- * ws
- * parenOpt(comma(Ct(Cg(v.ltype, 'type'))))
- * opt(desc_delim * Cg(rep(any), 'desc'))
- ),
-
- at_cast = Ct(
- Cg(P('cast'), 'kind') * ws * Cg(lname, 'name') * ws * opt(Sf('+-')) * Cg(v.ltype, 'type')
- ),
-
- at_generic = Ct(
- Cg(P('generic'), 'kind') * ws * Cg(ident, 'name') * opt(Pf ':' * Cg(v.ltype, 'type'))
- ),
-
- at_class = Ct(
- Cg(P('class'), 'kind')
- * ws
- * opt(P('(exact)') * ws)
- * Cg(lname, 'name')
- * opt(Pf(':') * Cg(lname, 'parent'))
- ),
-
- at_field = Ct(
- Cg(P('field'), 'kind')
- * ws
- * opt(Cg(Pf('private') + Pf('package') + Pf('protected'), 'access'))
- * Cg(lname, 'name')
- * ws
- * Cg(v.ltype, 'type')
- * opt(desc_delim * Cg(rep(any), 'desc'))
- ),
-
- at_access = Ct(Cg(P('private') + P('protected') + P('package'), 'kind')),
-
- at_deprecated = Ct(Cg(P('deprecated'), 'kind')),
-
- -- Types may be provided on subsequent lines
- at_alias = Ct(Cg(P('alias'), 'kind') * ws * Cg(lname, 'name') * opt(ws * Cg(v.ltype, 'type'))),
-
- at_enum = Ct(Cg(P('enum'), 'kind') * ws * Cg(lname, 'name')),
-
- at_see = Ct(Cg(P('see'), 'kind') * ws * opt(Pf('#')) * Cg(rep(any), 'desc')),
- at_diagnostic = Ct(Cg(P('diagnostic'), 'kind') * ws * opt(Pf('#')) * Cg(rep(any), 'desc')),
- at_overload = Ct(Cg(P('overload'), 'kind') * ws * Cg(v.ltype, 'type')),
- at_meta = Ct(Cg(P('meta'), 'kind')),
+ ats = annot('param', Cg(lname, 'name') * ws * v.ctype * opt_desc)
+ + annot('return', comma1(Ct(v.ctype * opt(ws * cname))) * 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('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(access)
+ + annot('deprecated')
+ + annot('alias', cname * opt(ws * v.ctype))
+ + annot('enum', cname)
+ + annot('overload', v.ctype)
+ + annot('see', opt(desc_delim) * desc)
+ + annot('diagnostic', opt(desc_delim) * desc)
+ + annot('meta'),
--- Custom extensions
- ext_at_note = Ct(Cg(P('note'), 'kind') * ws * Cg(rep(any), 'desc')),
-
- -- TODO only consume 1 line
- ext_at_since = Ct(Cg(P('since'), 'kind') * ws * Cg(rep(any), 'desc')),
-
- ext_at_nodoc = Ct(Cg(P('nodoc'), 'kind')),
- ext_at_brief = Ct(Cg(P('brief'), 'kind') * opt(ws * Cg(rep(any), 'desc'))),
-
- ltype = v.ty_union + Pf '(' * v.ty_union * fill * P ')',
-
- ty_union = v.ty_opt * rep(Pf '|' * v.ty_opt),
- ty = v.ty_fun + ident + v.ty_table + literal,
- ty_param = Pf '<' * comma(v.ltype) * fill * P '>',
- ty_opt = v.ty * opt(v.ty_param) * opt(P '[]') * opt(P '?'),
-
- table_key = (Pf '[' * literal * Pf ']') + lname,
- table_elem = v.table_key * Pf ':' * v.ltype,
- ty_table = Pf '{' * comma(v.table_elem) * Pf '}',
+ ext_ats = (
+ annot('note', desc)
+ + annot('since', desc)
+ + annot('nodoc')
+ + annot('inlinedoc')
+ + annot('brief', desc)
+ ),
- fun_param = lname * opt(Pf ':' * v.ltype),
- ty_fun = Pf 'fun(' * rep(comma(v.fun_param)) * fill * P ')' * opt(Pf ':' * comma(v.ltype)),
+ 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),
+ 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)),
}
return grammar --[[@as nvim.luacats.grammar]]
diff --git a/scripts/luacats_parser.lua b/scripts/luacats_parser.lua
index 520272d1dc..cd671fb9dc 100644
--- a/scripts/luacats_parser.lua
+++ b/scripts/luacats_parser.lua
@@ -19,7 +19,7 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- @class nvim.luacats.parser.alias
--- @field kind 'alias'
---- @field type string
+--- @field type string[]
--- @field desc string
--- @class nvim.luacats.parser.fun
@@ -49,8 +49,12 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- @class nvim.luacats.parser.class
--- @field kind 'class'
+--- @field parent? string
--- @field name string
--- @field desc string
+--- @field nodoc? true
+--- @field inlinedoc? true
+--- @field access? 'private'|'package'|'protected'
--- @field fields nvim.luacats.parser.field[]
--- @field notes? string[]
@@ -64,6 +68,7 @@ local luacats_grammar = require('scripts.luacats_grammar')
--- | nvim.luacats.parser.class
--- | nvim.luacats.parser.fun
--- | nvim.luacats.parser.brief
+--- | nvim.luacats.parser.alias
-- Remove this when we document classes properly
--- Some doc lines have the form:
@@ -142,22 +147,27 @@ local function process_doc_line(line, state)
}
elseif kind == 'class' then
--- @cast parsed nvim.luacats.Class
- state.cur_obj = {
- kind = 'class',
- name = parsed.name,
- parent = parsed.parent,
- desc = '',
- fields = {},
- }
+ cur_obj.kind = 'class'
+ cur_obj.name = parsed.name
+ cur_obj.parent = parsed.parent
+ cur_obj.access = parsed.access
+ cur_obj.desc = state.doc_lines and table.concat(state.doc_lines, '\n') or nil
+ state.doc_lines = nil
+ cur_obj.fields = {}
elseif kind == 'field' then
--- @cast parsed nvim.luacats.Field
- if not parsed.access then
- parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
- if parsed.desc then
- parsed.desc = vim.trim(parsed.desc)
- end
- table.insert(cur_obj.fields, parsed)
+ parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
+ if parsed.desc then
+ parsed.desc = vim.trim(parsed.desc)
+ end
+ table.insert(cur_obj.fields, parsed)
+ state.doc_lines = nil
+ elseif kind == 'operator' then
+ parsed.desc = parsed.desc or state.doc_lines and table.concat(state.doc_lines, '\n') or nil
+ if parsed.desc then
+ parsed.desc = vim.trim(parsed.desc)
end
+ table.insert(cur_obj.fields, parsed)
state.doc_lines = nil
elseif kind == 'param' then
state.last_doc_item_indent = nil
@@ -191,6 +201,8 @@ local function process_doc_line(line, state)
cur_obj.access = 'protected'
elseif kind == 'deprecated' then
cur_obj.deprecated = true
+ elseif kind == 'inlinedoc' then
+ cur_obj.inlinedoc = true
elseif kind == 'nodoc' then
cur_obj.nodoc = true
elseif kind == 'since' then
@@ -383,11 +395,11 @@ end
--- Determine the table name used to export functions of a module
--- Usually this is `M`.
---- @param filename string
+--- @param str string
--- @return string?
-local function determine_modvar(filename)
+local function determine_modvar(str)
local modvar --- @type string?
- for line in io.lines(filename) do
+ for line in vim.gsplit(str, '\n') do
do
--- @type string?
local m = line:match('^return%s+([a-zA-Z_]+)')
@@ -462,17 +474,12 @@ end
local M = {}
---- @param filename string
---- @return table<string,nvim.luacats.parser.class> classes
---- @return nvim.luacats.parser.fun[] funs
---- @return string[] briefs
---- @return nvim.luacats.parser.obj[]
-function M.parse(filename)
+function M.parse_str(str, filename)
local funs = {} --- @type nvim.luacats.parser.fun[]
local classes = {} --- @type table<string,nvim.luacats.parser.class>
local briefs = {} --- @type string[]
- local mod_return = determine_modvar(filename)
+ local mod_return = determine_modvar(str)
--- @type string
local module = filename:match('.*/lua/([a-z_][a-z0-9_/]+)%.lua') or filename
@@ -485,7 +492,7 @@ function M.parse(filename)
-- Keep track of any partial objects we don't commit
local uncommitted = {} --- @type nvim.luacats.parser.obj[]
- for line in io.lines(filename) do
+ for line in vim.gsplit(str, '\n') do
local has_indent = line:match('^%s+') ~= nil
line = vim.trim(line)
if vim.startswith(line, '---') then
@@ -518,4 +525,13 @@ function M.parse(filename)
return classes, funs, briefs, uncommitted
end
+--- @param filename string
+function M.parse(filename)
+ local f = assert(io.open(filename, 'r'))
+ local txt = f:read('*all')
+ f:close()
+
+ return M.parse_str(txt, filename)
+end
+
return M
diff --git a/scripts/text_utils.lua b/scripts/text_utils.lua
index 5167ec42f2..75b3bfedd5 100644
--- a/scripts/text_utils.lua
+++ b/scripts/text_utils.lua
@@ -7,12 +7,99 @@ local fmt = string.format
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<any,any>
+ 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)
@@ -47,6 +134,10 @@ local function parse_md(text)
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
@@ -101,20 +192,47 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
local add_tag = false
-- local add_tag = true
+ local ntype = node.type
+
if add_tag then
- parts[#parts + 1] = '<' .. node.type .. '>'
+ parts[#parts + 1] = '<' .. ntype .. '>'
end
- if node.type == 'paragraph' then
- local text = assert(node.text)
- text = text:gsub('(%s)%*(%w+)%*(%s)', '%1%2%3')
- text = text:gsub('(%s)_(%w+)_(%s)', '%1%2%3')
- text = text:gsub('\\|', '|')
- text = text:gsub('\\%*', '*')
- text = text:gsub('\\_', '_')
- parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width)
+ 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
+ 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 node.type == 'code_fence_content' then
+ elseif ntype == 'code_fence_content' then
local lines = vim.split(node.text:gsub('\n%s*$', ''), '\n')
local cindent = indent + INDENTATION
@@ -137,7 +255,7 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
parts[#parts + 1] = '\n'
end
- elseif node.type == 'fenced_code_block' then
+ elseif ntype == 'fenced_code_block' then
parts[#parts + 1] = '>'
for _, child in ipairs(node) do
if child.type == 'info_string' then
@@ -152,15 +270,15 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
end
parts[#parts + 1] = '<\n'
- elseif node.type == 'html_block' then
+ elseif ntype == 'html_block' then
local text = node.text:gsub('^<pre>help', '')
text = text:gsub('</pre>%s*$', '')
parts[#parts + 1] = text
- elseif node.type == 'list_marker_dot' then
+ elseif ntype == 'list_marker_dot' then
parts[#parts + 1] = node.text
- elseif contains(node.type, { 'list_marker_minus', 'list_marker_star' }) then
+ elseif contains(ntype, { 'list_marker_minus', 'list_marker_star' }) then
parts[#parts + 1] = '• '
- elseif node.type == 'list_item' then
+ 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
@@ -175,8 +293,12 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
error(fmt('cannot render:\n%s', vim.inspect(node)))
end
for i, child in ipairs(node) do
- vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1, is_list))
- if node.type ~= 'list' and i ~= #node then
+ 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
@@ -185,7 +307,7 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
if add_tag then
- parts[#parts + 1] = '</' .. node.type .. '>'
+ parts[#parts + 1] = '</' .. ntype .. '>'
end
return parts
@@ -196,7 +318,7 @@ local function align_tags(text_width)
--- @param line string
--- @return string
return function(line)
- local tag_pat = '%s+(%*[^ ]+%*)%s*$'
+ local tag_pat = '%s*(%*.+%*)%s*$'
local tags = {}
for m in line:gmatch(tag_pat) do
table.insert(tags, m)
@@ -205,7 +327,9 @@ local function align_tags(text_width)
if #tags > 0 then
line = line:gsub(tag_pat, '')
local tags_str = ' ' .. table.concat(tags, ' ')
- local pad = string.rep(' ', text_width - #line - #tags_str)
+ --- @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
@@ -223,14 +347,14 @@ function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list)
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), '\n')
+ 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?\n', ' >%1\n')
+ s = s:gsub('\n+%s*>([a-z]+)\n', ' >%1\n')
s = s:gsub('\n+%s*>\n?\n', ' >\n')
return s