mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
29ded88957
Problem: The way option scopes currently work is inflexible and does not allow for nested option scopes or easily finding the value of an option at any arbitrary scope without having to do long handwritten switch-case statements like in `get_varp()`. `.indir` is also confusing and redundant since option indices for each scope can be autogenerated. Solution: Expand option scopes in such a way that an option can support any amount of scopes using a set of scope flags, similarly to how it's already done for option types. Also make options contain information about its index at each scope it supports. This allows for massively simplifying `get_varp()` and `get_varp_scope()` in the future by just using a struct for options at each scope. This would be done by creating a table that stores the offset of an option's variable at a scope by using the option's index at that scope as a key. This PR also autogenerates enums for option indices at each scope to remove the need for `.indir` entirely, and also to allow easily iterating over options all options that support any scope. Ref: #29314
1022 lines
26 KiB
Lua
Executable File
1022 lines
26 KiB
Lua
Executable File
#!/usr/bin/env -S nvim -l
|
|
|
|
-- 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 [string,string][]
|
|
--- @field return_type string
|
|
--- @field deprecated_since integer
|
|
--- @field eval boolean
|
|
--- @field fast boolean
|
|
--- @field handler_id integer
|
|
--- @field impl_name string
|
|
--- @field lua boolean
|
|
--- @field method boolean
|
|
--- @field remote boolean
|
|
--- @field since integer
|
|
|
|
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_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[]',
|
|
nvim_get_color_map = 'table<string,integer>',
|
|
nvim_get_command = 'table<string,vim.api.keyset.command_info>',
|
|
nvim_get_keymap = 'vim.api.keyset.keymap[]',
|
|
nvim_get_mark = 'vim.api.keyset.get_mark',
|
|
|
|
-- 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.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>',
|
|
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.win_config',
|
|
}
|
|
|
|
local LUA_META_HEADER = {
|
|
'--- @meta _',
|
|
'-- THIS FILE IS GENERATED',
|
|
'-- DO NOT EDIT',
|
|
"error('Cannot require a meta file')",
|
|
}
|
|
|
|
local LUA_API_META_HEADER = {
|
|
'--- @meta _',
|
|
'-- THIS FILE IS GENERATED',
|
|
'-- DO NOT EDIT',
|
|
"error('Cannot require a meta file')",
|
|
'',
|
|
'vim.api = {}',
|
|
}
|
|
|
|
local LUA_OPTION_META_HEADER = {
|
|
'--- @meta _',
|
|
'-- THIS FILE IS GENERATED',
|
|
'-- DO NOT EDIT',
|
|
"error('Cannot require a meta file')",
|
|
'',
|
|
'---@class vim.bo',
|
|
'---@field [integer] vim.bo',
|
|
'vim.bo = vim.bo',
|
|
'',
|
|
'---@class vim.wo',
|
|
'---@field [integer] vim.wo',
|
|
'vim.wo = vim.wo',
|
|
}
|
|
|
|
local LUA_VVAR_META_HEADER = {
|
|
'--- @meta _',
|
|
'-- THIS FILE IS GENERATED',
|
|
'-- DO NOT EDIT',
|
|
"error('Cannot require a meta file')",
|
|
'',
|
|
'--- @class vim.v',
|
|
'vim.v = ...',
|
|
}
|
|
|
|
local LUA_KEYWORDS = {
|
|
['and'] = true,
|
|
['end'] = true,
|
|
['function'] = true,
|
|
['or'] = true,
|
|
['if'] = true,
|
|
['while'] = true,
|
|
['repeat'] = true,
|
|
['true'] = true,
|
|
['false'] = true,
|
|
}
|
|
|
|
local OPTION_TYPES = {
|
|
boolean = 'boolean',
|
|
number = 'integer',
|
|
string = 'string',
|
|
}
|
|
|
|
local API_TYPES = {
|
|
Window = 'integer',
|
|
Tabpage = 'integer',
|
|
Buffer = 'integer',
|
|
Boolean = 'boolean',
|
|
Object = 'any',
|
|
Integer = 'integer',
|
|
String = 'string',
|
|
Array = 'any[]',
|
|
LuaRef = 'function',
|
|
Dict = 'table<string,any>',
|
|
Float = 'number',
|
|
HLGroupID = 'number|string',
|
|
void = '',
|
|
}
|
|
|
|
--- @param x string
|
|
--- @param sep? string
|
|
--- @return string[]
|
|
local function split(x, sep)
|
|
return vim.split(x, sep or '\n', { plain = true })
|
|
end
|
|
|
|
--- Convert an API type to Lua
|
|
--- @param t string
|
|
--- @return string
|
|
local function api_type(t)
|
|
local as0 = t:match('^ArrayOf%((.*)%)')
|
|
if as0 then
|
|
local as = split(as0, ', ')
|
|
return api_type(as[1]) .. '[]'
|
|
end
|
|
|
|
local d = t:match('^Dict%((.*)%)')
|
|
if d then
|
|
return 'vim.api.keyset.' .. d
|
|
end
|
|
|
|
local d0 = t:match('^DictOf%((.*)%)')
|
|
if d0 then
|
|
return 'table<string,' .. api_type(d0) .. '>'
|
|
end
|
|
|
|
return API_TYPES[t] or t
|
|
end
|
|
|
|
--- @param f string
|
|
--- @param params [string,string][]|true
|
|
--- @return string
|
|
local function render_fun_sig(f, params)
|
|
local param_str --- @type string
|
|
if params == true then
|
|
param_str = '...'
|
|
else
|
|
param_str = table.concat(
|
|
vim.tbl_map(
|
|
--- @param v [string,string]
|
|
--- @return string
|
|
function(v)
|
|
return v[1]
|
|
end,
|
|
params
|
|
),
|
|
', '
|
|
)
|
|
end
|
|
|
|
if LUA_KEYWORDS[f] then
|
|
return fmt("vim.fn['%s'] = function(%s) end", f, param_str)
|
|
else
|
|
return fmt('function vim.fn.%s(%s) end', f, param_str)
|
|
end
|
|
end
|
|
|
|
--- Uniquify names
|
|
--- Fix any names that are lua keywords
|
|
--- @param params [string,string,string][]
|
|
--- @return [string,string,string][]
|
|
local function process_params(params)
|
|
local seen = {} --- @type table<string,true>
|
|
local sfx = 1
|
|
|
|
for _, p in ipairs(params) do
|
|
if LUA_KEYWORDS[p[1]] then
|
|
p[1] = p[1] .. '_'
|
|
end
|
|
if seen[p[1]] then
|
|
p[1] = p[1] .. sfx
|
|
sfx = sfx + 1
|
|
else
|
|
seen[p[1]] = true
|
|
end
|
|
end
|
|
|
|
return params
|
|
end
|
|
|
|
--- @return table<string, vim.EvalFn>
|
|
local function get_api_meta()
|
|
local ret = {} --- @type table<string, vim.EvalFn>
|
|
|
|
local cdoc_parser = require('scripts.cdoc_parser')
|
|
|
|
local f = 'src/nvim/api'
|
|
|
|
local function include(fun)
|
|
if not vim.startswith(fun.name, 'nvim_') then
|
|
return false
|
|
end
|
|
if vim.tbl_contains(fun.attrs or {}, 'lua_only') then
|
|
return true
|
|
end
|
|
if vim.tbl_contains(fun.attrs or {}, 'remote_only') then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- @type table<string,nvim.cdoc.parser.fun>
|
|
local functions = {}
|
|
for path, ty in vim.fs.dir(f) do
|
|
if ty == 'file' then
|
|
local filename = vim.fs.joinpath(f, path)
|
|
local _, funs = cdoc_parser.parse(filename)
|
|
for _, fn in ipairs(funs) do
|
|
if include(fn) then
|
|
functions[fn.name] = fn
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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] = {
|
|
p.name,
|
|
api_type(p.type),
|
|
not deprecated and p.desc or nil,
|
|
}
|
|
end
|
|
|
|
local r = {
|
|
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.returns_desc = fun.returns[1].desc
|
|
end
|
|
|
|
ret[fun.name] = r
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- Convert vimdoc references to markdown literals
|
|
--- Convert vimdoc codeblocks to markdown codeblocks
|
|
---
|
|
--- 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, 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')
|
|
:gsub('\n*>vim', '\n\n```vim')
|
|
:gsub('\n+<$', '\n```')
|
|
:gsub('\n+<\n+', '\n```\n\n')
|
|
:gsub('%s+>\n+', '\n```\n')
|
|
:gsub('\n+<%s+\n?', '\n```\n')
|
|
)
|
|
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('')
|
|
|
|
if vim.startswith(fun.name, 'nvim__') then
|
|
write('--- @private')
|
|
end
|
|
|
|
if fun.deprecated then
|
|
write('--- @deprecated')
|
|
end
|
|
|
|
local desc = fun.desc
|
|
if desc then
|
|
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[]
|
|
local params = process_params(fun.params)
|
|
for _, p in ipairs(params) do
|
|
param_names[#param_names + 1] = p[1]
|
|
local pdesc = p[3]
|
|
if pdesc then
|
|
local s = '--- @param ' .. p[1] .. ' ' .. p[2] .. ' '
|
|
local pdesc_a = split(vim.trim(norm_text(pdesc)))
|
|
write(s .. pdesc_a[1])
|
|
for i = 2, #pdesc_a do
|
|
if not pdesc_a[i] then
|
|
break
|
|
end
|
|
write('--- ' .. pdesc_a[i])
|
|
end
|
|
else
|
|
write('--- @param ' .. p[1] .. ' ' .. p[2])
|
|
end
|
|
end
|
|
|
|
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_lines('--- ', '@return ' .. ret .. ret_desc))
|
|
end
|
|
local param_str = table.concat(param_names, ', ')
|
|
|
|
write(fmt('function vim.api.%s(%s) end', fun.name, param_str))
|
|
end
|
|
|
|
--- @return table<string, vim.EvalFn>
|
|
local function get_api_keysets_meta()
|
|
local mpack_f = assert(io.open(DEP_API_METADATA, 'rb'))
|
|
local metadata = assert(vim.mpack.decode(mpack_f:read('*all')))
|
|
|
|
local ret = {} --- @type table<string, vim.EvalFn>
|
|
|
|
--- @type {name: string, keys: string[], types: table<string,string>}[]
|
|
local keysets = metadata.keysets
|
|
|
|
for _, k in ipairs(keysets) do
|
|
local params = {}
|
|
for _, key in ipairs(k.keys) do
|
|
table.insert(params, { key .. '?', api_type(k.types[key] or 'any') })
|
|
end
|
|
ret[k.name] = {
|
|
signature = 'NA',
|
|
name = k.name,
|
|
params = params,
|
|
}
|
|
end
|
|
|
|
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
|
|
write('--- @field ' .. p[1] .. ' ' .. p[2])
|
|
end
|
|
end
|
|
|
|
--- @return table<string, vim.EvalFn>
|
|
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)
|
|
local function render_eval_meta(f, fun, write)
|
|
if fun.lua == false then
|
|
return
|
|
end
|
|
|
|
local funname = fun.name or f
|
|
local params = process_params(fun.params)
|
|
|
|
write('')
|
|
if fun.deprecated then
|
|
write('--- @deprecated')
|
|
end
|
|
|
|
local desc = fun.desc
|
|
|
|
if desc then
|
|
--- @type string
|
|
desc = desc:gsub('\n%s*\n%s*$', '\n')
|
|
for _, l in ipairs(split(desc)) do
|
|
l = l:gsub('^ ', ''):gsub('\t', ' '):gsub('@', '\\@')
|
|
write('--- ' .. l)
|
|
end
|
|
end
|
|
|
|
local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
|
|
|
|
for i, param in ipairs(params) do
|
|
local pname, ptype = param[1], param[2]
|
|
local optional = (pname ~= '...' and i > req_args) and '?' or ''
|
|
write(fmt('--- @param %s%s %s', pname, optional, ptype))
|
|
end
|
|
|
|
if fun.returns ~= false then
|
|
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, name_tag, fun, write)
|
|
if not fun.signature then
|
|
return
|
|
end
|
|
|
|
local tags = name_tag and { '*' .. name .. '()*' } or {}
|
|
|
|
if fun.tags then
|
|
for _, t in ipairs(fun.tags) do
|
|
tags[#tags + 1] = '*' .. t .. '*'
|
|
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)
|
|
local tag_pad_len = math.max(1, 80 - #tag + conceal_offset)
|
|
|
|
if siglen + #tag > 80 then
|
|
write(string.rep(' ', tag_pad_len) .. tag)
|
|
write(fun.signature)
|
|
else
|
|
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 or not fun.signature then
|
|
return
|
|
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('^ ', '')
|
|
if vim.startswith(l, '<') and not l:match('^<[^ \t]+>') then
|
|
write('<\t\t' .. l:sub(2))
|
|
elseif l:match('^>[a-z0-9]*$') then
|
|
write(l)
|
|
else
|
|
write('\t\t' .. l)
|
|
end
|
|
end
|
|
|
|
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
|
|
--- @param vimdoc? boolean
|
|
--- @return string
|
|
local function render_option_default(d, vimdoc)
|
|
local dt --- @type integer|boolean|string|fun(): string
|
|
if d.if_false ~= nil then
|
|
dt = d.if_false
|
|
else
|
|
dt = d.if_true
|
|
end
|
|
|
|
if vimdoc then
|
|
if d.doc then
|
|
return d.doc
|
|
end
|
|
if type(dt) == 'boolean' then
|
|
return dt and 'on' or 'off'
|
|
end
|
|
end
|
|
|
|
if dt == '' or dt == nil or type(dt) == 'function' then
|
|
dt = d.meta
|
|
end
|
|
|
|
local v --- @type string
|
|
if not vimdoc then
|
|
v = vim.inspect(dt) --[[@as string]]
|
|
else
|
|
v = type(dt) == 'string' and '"' .. dt .. '"' or tostring(dt)
|
|
end
|
|
|
|
--- @type table<string, string|false>
|
|
local envvars = {
|
|
TMPDIR = false,
|
|
VIMRUNTIME = false,
|
|
XDG_CONFIG_HOME = vim.env.HOME .. '/.local/config',
|
|
XDG_DATA_HOME = vim.env.HOME .. '/.local/share',
|
|
XDG_STATE_HOME = vim.env.HOME .. '/.local/state',
|
|
}
|
|
|
|
for name, default in pairs(envvars) do
|
|
local value = vim.env[name] or default
|
|
if value then
|
|
v = v:gsub(vim.pesc(value), '$' .. name)
|
|
end
|
|
end
|
|
|
|
return v
|
|
end
|
|
|
|
--- @param _f string
|
|
--- @param opt vim.option_meta
|
|
--- @param write fun(line: string)
|
|
local function render_option_meta(_f, opt, write)
|
|
write('')
|
|
for _, l in ipairs(split(norm_text(opt.desc))) do
|
|
write('--- ' .. l)
|
|
end
|
|
|
|
write('--- @type ' .. OPTION_TYPES[opt.type])
|
|
write('vim.o.' .. opt.full_name .. ' = ' .. render_option_default(opt.defaults))
|
|
if opt.abbreviation then
|
|
write('vim.o.' .. opt.abbreviation .. ' = vim.o.' .. opt.full_name)
|
|
end
|
|
|
|
for _, s in pairs {
|
|
{ 'wo', 'win' },
|
|
{ 'bo', 'buf' },
|
|
{ 'go', 'global' },
|
|
} do
|
|
local id, scope = s[1], s[2]
|
|
if vim.list_contains(opt.scope, scope) or (id == 'go' and #opt.scope > 1) then
|
|
local pfx = 'vim.' .. id .. '.'
|
|
write(pfx .. opt.full_name .. ' = vim.o.' .. opt.full_name)
|
|
if opt.abbreviation then
|
|
write(pfx .. opt.abbreviation .. ' = ' .. pfx .. opt.full_name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- @param _f string
|
|
--- @param opt vim.option_meta
|
|
--- @param write fun(line: string)
|
|
local function render_vvar_meta(_f, opt, write)
|
|
write('')
|
|
|
|
local desc = split(norm_text(opt.desc))
|
|
while desc[#desc]:match('^%s*$') do
|
|
desc[#desc] = nil
|
|
end
|
|
|
|
for _, l in ipairs(desc) do
|
|
write('--- ' .. l)
|
|
end
|
|
|
|
write('--- @type ' .. (opt.type or 'any'))
|
|
|
|
if LUA_KEYWORDS[opt.full_name] then
|
|
write("vim.v['" .. opt.full_name .. "'] = ...")
|
|
else
|
|
write('vim.v.' .. opt.full_name .. ' = ...')
|
|
end
|
|
end
|
|
|
|
--- @param s string[]
|
|
--- @return string
|
|
local function scope_to_doc(s)
|
|
local m = {
|
|
global = 'global',
|
|
buf = 'local to buffer',
|
|
win = 'local to window',
|
|
tab = 'local to tab page',
|
|
}
|
|
|
|
if #s == 1 then
|
|
return m[s[1]]
|
|
end
|
|
assert(s[1] == 'global')
|
|
return 'global or ' .. m[s[2]] .. (s[2] ~= 'tab' and ' |global-local|' or '')
|
|
end
|
|
|
|
-- @param o vim.option_meta
|
|
-- @return string
|
|
local function scope_more_doc(o)
|
|
if
|
|
vim.list_contains({
|
|
'bufhidden',
|
|
'buftype',
|
|
'filetype',
|
|
'modified',
|
|
'previewwindow',
|
|
'readonly',
|
|
'scroll',
|
|
'syntax',
|
|
'winfixheight',
|
|
'winfixwidth',
|
|
}, o.full_name)
|
|
then
|
|
return ' |local-noglobal|'
|
|
end
|
|
|
|
return ''
|
|
end
|
|
|
|
--- @param x string
|
|
--- @return string
|
|
local function dedent(x)
|
|
local xs = split(x)
|
|
local leading_ws = xs[1]:match('^%s*') --[[@as string]]
|
|
local leading_ws_pat = '^' .. leading_ws
|
|
|
|
for i in ipairs(xs) do
|
|
local strip_pat = xs[i]:match(leading_ws_pat) and leading_ws_pat or '^%s*'
|
|
xs[i] = xs[i]:gsub(strip_pat, '')
|
|
end
|
|
|
|
return table.concat(xs, '\n')
|
|
end
|
|
|
|
--- @return table<string,vim.option_meta>
|
|
local function get_option_meta()
|
|
local opts = require('src/nvim/options').options
|
|
local optinfo = vim.api.nvim_get_all_options_info()
|
|
local ret = {} --- @type table<string,vim.option_meta>
|
|
for _, o in ipairs(opts) do
|
|
local is_window_option = #o.scope == 1 and o.scope[1] == 'win'
|
|
local is_option_hidden = o.immutable and not o.varname and not is_window_option
|
|
if not is_option_hidden and o.desc then
|
|
if o.full_name == 'cmdheight' then
|
|
table.insert(o.scope, 'tab')
|
|
end
|
|
local r = vim.deepcopy(o) --[[@as vim.option_meta]]
|
|
r.desc = o.desc:gsub('^ ', ''):gsub('\n ', '\n')
|
|
r.defaults = r.defaults or {}
|
|
if r.defaults.meta == nil then
|
|
r.defaults.meta = optinfo[o.full_name].default
|
|
end
|
|
ret[o.full_name] = r
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- @return table<string,vim.option_meta>
|
|
local function get_vvar_meta()
|
|
local info = require('src/nvim/vvars').vars
|
|
local ret = {} --- @type table<string,vim.option_meta>
|
|
for name, o in pairs(info) do
|
|
o.desc = dedent(o.desc)
|
|
o.full_name = name
|
|
ret[name] = o
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- @param opt vim.option_meta
|
|
--- @return string[]
|
|
local function build_option_tags(opt)
|
|
--- @type string[]
|
|
local tags = { opt.full_name }
|
|
|
|
tags[#tags + 1] = opt.abbreviation
|
|
if opt.type == 'boolean' then
|
|
for i = 1, #tags do
|
|
tags[#tags + 1] = 'no' .. tags[i]
|
|
end
|
|
end
|
|
|
|
for i, t in ipairs(tags) do
|
|
tags[i] = "'" .. t .. "'"
|
|
end
|
|
|
|
for _, t in ipairs(opt.tags or {}) do
|
|
tags[#tags + 1] = t
|
|
end
|
|
|
|
for i, t in ipairs(tags) do
|
|
tags[i] = '*' .. t .. '*'
|
|
end
|
|
|
|
return tags
|
|
end
|
|
|
|
--- @param _f string
|
|
--- @param opt vim.option_meta
|
|
--- @param write fun(line: string)
|
|
local function render_option_doc(_f, opt, write)
|
|
local tags = build_option_tags(opt)
|
|
local tag_str = table.concat(tags, ' ')
|
|
local conceal_offset = 2 * (#tags - 1)
|
|
local tag_pad = string.rep('\t', math.ceil((64 - #tag_str + conceal_offset) / 8))
|
|
-- local pad = string.rep(' ', 80 - #tag_str + conceal_offset)
|
|
write(tag_pad .. tag_str)
|
|
|
|
local name_str --- @type string
|
|
if opt.abbreviation then
|
|
name_str = fmt("'%s' '%s'", opt.full_name, opt.abbreviation)
|
|
else
|
|
name_str = fmt("'%s'", opt.full_name)
|
|
end
|
|
|
|
local otype = opt.type == 'boolean' and 'boolean' or opt.type
|
|
if opt.defaults.doc or opt.defaults.if_true ~= nil or opt.defaults.meta ~= nil then
|
|
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 = #fmt('%s%s%s (', name_str, pad, otype)
|
|
--- @type string
|
|
v = v:gsub('\n', '\n' .. string.rep(' ', deflen - 2))
|
|
end
|
|
write(fmt('%s%s%s\t(default %s)', name_str, pad, otype, v))
|
|
else
|
|
write(fmt('%s\t%s', name_str, otype))
|
|
end
|
|
|
|
write('\t\t\t' .. scope_to_doc(opt.scope) .. scope_more_doc(opt))
|
|
for _, l in ipairs(split(opt.desc)) do
|
|
if l == '<' or l:match('^<%s') then
|
|
write(l)
|
|
else
|
|
write('\t' .. l:gsub('\\<', '<'))
|
|
end
|
|
end
|
|
end
|
|
|
|
--- @param _f string
|
|
--- @param vvar vim.option_meta
|
|
--- @param write fun(line: string)
|
|
local function render_vvar_doc(_f, vvar, write)
|
|
local name = vvar.full_name
|
|
|
|
local tags = { 'v:' .. name, name .. '-variable' }
|
|
if vvar.tags then
|
|
vim.list_extend(tags, vvar.tags)
|
|
end
|
|
|
|
for i, t in ipairs(tags) do
|
|
tags[i] = '*' .. t .. '*'
|
|
end
|
|
|
|
local tag_str = table.concat(tags, ' ')
|
|
local conceal_offset = 2 * (#tags - 1)
|
|
|
|
local tag_pad = string.rep('\t', math.ceil((64 - #tag_str + conceal_offset) / 8))
|
|
write(tag_pad .. tag_str)
|
|
|
|
local desc = split(vvar.desc)
|
|
|
|
if (#desc == 1 or #desc == 2 and desc[2]:match('^%s*$')) and #name < 10 then
|
|
-- single line
|
|
write('v:' .. name .. '\t' .. desc[1]:gsub('^%s*', ''))
|
|
write('')
|
|
else
|
|
write('v:' .. name)
|
|
for _, l in ipairs(desc) do
|
|
if l == '<' or l:match('^<%s') then
|
|
write(l)
|
|
else
|
|
write('\t\t' .. l:gsub('\\<', '<'))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- @class nvim.gen_eval_files.elem
|
|
--- @field path string
|
|
--- @field from? string Skip lines in path until this pattern is reached.
|
|
--- @field funcs fun(): table<string, table>
|
|
--- @field render fun(f:string,obj:table,write:fun(line:string))
|
|
--- @field header? string[]
|
|
--- @field footer? string[]
|
|
|
|
--- @type nvim.gen_eval_files.elem[]
|
|
local CONFIG = {
|
|
{
|
|
path = 'runtime/lua/vim/_meta/vimfn.lua',
|
|
header = LUA_META_HEADER,
|
|
funcs = get_eval_meta,
|
|
render = render_eval_meta,
|
|
},
|
|
{
|
|
path = 'runtime/lua/vim/_meta/api.lua',
|
|
header = LUA_API_META_HEADER,
|
|
funcs = get_api_meta,
|
|
render = render_api_meta,
|
|
},
|
|
{
|
|
path = 'runtime/lua/vim/_meta/api_keysets.lua',
|
|
header = LUA_META_HEADER,
|
|
funcs = get_api_keysets_meta,
|
|
render = render_api_keyset_meta,
|
|
},
|
|
{
|
|
path = 'runtime/doc/builtin.txt',
|
|
funcs = get_eval_meta,
|
|
render = render_eval_doc,
|
|
header = {
|
|
'*builtin.txt* Nvim',
|
|
'',
|
|
'',
|
|
'\t\t NVIM REFERENCE MANUAL',
|
|
'',
|
|
'',
|
|
'Builtin functions\t\t*vimscript-functions* *builtin-functions*',
|
|
'',
|
|
'For functions grouped by what they are used for see |function-list|.',
|
|
'',
|
|
'\t\t\t\t Type |gO| to see the table of contents.',
|
|
'==============================================================================',
|
|
'1. Details *builtin-function-details*',
|
|
'',
|
|
},
|
|
footer = {
|
|
'==============================================================================',
|
|
'2. Matching a pattern in a String *string-match*',
|
|
'',
|
|
'This is common between several functions. A regexp pattern as explained at',
|
|
'|pattern| is normally used to find a match in the buffer lines. When a',
|
|
'pattern is used to find a match in a String, almost everything works in the',
|
|
'same way. The difference is that a String is handled like it is one line.',
|
|
'When it contains a "\\n" character, this is not seen as a line break for the',
|
|
'pattern. It can be matched with a "\\n" in the pattern, or with ".". Example:',
|
|
'>vim',
|
|
'\tlet a = "aaaa\\nxxxx"',
|
|
'\techo matchstr(a, "..\\n..")',
|
|
'\t" aa',
|
|
'\t" xx',
|
|
'\techo matchstr(a, "a.x")',
|
|
'\t" a',
|
|
'\t" x',
|
|
'',
|
|
'Don\'t forget that "^" will only match at the first character of the String and',
|
|
'"$" at the last character of the string. They don\'t match after or before a',
|
|
'"\\n".',
|
|
'',
|
|
' vim:tw=78:ts=8:noet:ft=help:norl:',
|
|
},
|
|
},
|
|
{
|
|
path = 'runtime/lua/vim/_meta/options.lua',
|
|
header = LUA_OPTION_META_HEADER,
|
|
funcs = get_option_meta,
|
|
render = render_option_meta,
|
|
},
|
|
{
|
|
path = 'runtime/doc/options.txt',
|
|
header = { '' },
|
|
from = 'A jump table for the options with a short description can be found at |Q_op|.',
|
|
footer = {
|
|
' vim:tw=78:ts=8:noet:ft=help:norl:',
|
|
},
|
|
funcs = get_option_meta,
|
|
render = render_option_doc,
|
|
},
|
|
{
|
|
path = 'runtime/lua/vim/_meta/vvars.lua',
|
|
header = LUA_VVAR_META_HEADER,
|
|
funcs = get_vvar_meta,
|
|
render = render_vvar_meta,
|
|
},
|
|
{
|
|
path = 'runtime/doc/vvars.txt',
|
|
header = { '' },
|
|
from = 'Type |gO| to see the table of contents.',
|
|
footer = {
|
|
' vim:tw=78:ts=8:noet:ft=help:norl:',
|
|
},
|
|
funcs = get_vvar_meta,
|
|
render = render_vvar_doc,
|
|
},
|
|
}
|
|
|
|
--- @param elem nvim.gen_eval_files.elem
|
|
local function render(elem)
|
|
print('Rendering ' .. elem.path)
|
|
local from_lines = {} --- @type string[]
|
|
local from = elem.from
|
|
if from then
|
|
for line in io.lines(elem.path) do
|
|
from_lines[#from_lines + 1] = line
|
|
if line:match(from) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local o = assert(io.open(elem.path, 'w'))
|
|
|
|
--- @param l string
|
|
local function write(l)
|
|
local l1 = l:gsub('%s+$', '')
|
|
o:write(l1)
|
|
o:write('\n')
|
|
end
|
|
|
|
for _, l in ipairs(from_lines) do
|
|
write(l)
|
|
end
|
|
|
|
for _, l in ipairs(elem.header or {}) do
|
|
write(l)
|
|
end
|
|
|
|
local funcs = elem.funcs()
|
|
|
|
--- @type string[]
|
|
local fnames = vim.tbl_keys(funcs)
|
|
table.sort(fnames)
|
|
|
|
for _, f in ipairs(fnames) do
|
|
elem.render(f, funcs[f], write)
|
|
end
|
|
|
|
for _, l in ipairs(elem.footer or {}) do
|
|
write(l)
|
|
end
|
|
|
|
o:close()
|
|
end
|
|
|
|
local function main()
|
|
for _, c in ipairs(CONFIG) do
|
|
render(c)
|
|
end
|
|
end
|
|
|
|
main()
|