12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- #!/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()
|