build: move all generator scripts to src/gen/

- Move all generator Lua scripts to the `src/gen/`
- Add a `.luarc.json` to `src/gen/`
- Add a `preload.lua` to `src/gen/`
  - Add `src` to `package.path` so it aligns with `.luarc.json'
- Fix all `require` statements in `src/gen/` so they are consistent:
    - `require('scripts.foo')` -> `require('gen.foo')`
    - `require('src.nvim.options')` -> `require('nvim.options')`
    - `require('api.dispatch_deprecated')` -> `require('nvim.api.dispatch_deprecated')`
This commit is contained in:
Lewis Russell
2025-02-26 11:38:07 +00:00
committed by Lewis Russell
parent 85caaa70d4
commit 0f24b0826a
38 changed files with 98 additions and 60 deletions

24
src/.luarc.json Normal file
View File

@@ -0,0 +1,24 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime": {
"version": "LuaJIT"
},
"workspace": {
"library": [
"../runtime/lua",
"${3rd}/luv/library"
],
"checkThirdParty": "Disable"
},
"diagnostics": {
"groupFileStatus": {
"strict": "Opened",
"strong": "Opened"
},
"groupSeverity": {
"strong": "Warning",
"strict": "Warning"
},
"unusedLocalExclude": [ "_*" ]
}
}

87
src/gen/cdoc_grammar.lua Normal file
View File

@@ -0,0 +1,87 @@
--[[!
LPEG grammar for C doc comments
]]
--- @class nvim.cdoc.Param
--- @field kind 'param'
--- @field name string
--- @field desc? string
--- @class nvim.cdoc.Return
--- @field kind 'return'
--- @field desc string
--- @class nvim.cdoc.Note
--- @field desc? string
--- @alias nvim.cdoc.grammar.result
--- | nvim.cdoc.Param
--- | nvim.cdoc.Return
--- | nvim.cdoc.Note
--- @class nvim.cdoc.grammar
--- @field match fun(self, input: string): nvim.cdoc.grammar.result?
local lpeg = vim.lpeg
local P, R, S = lpeg.P, lpeg.R, lpeg.S
local Ct, Cg = lpeg.Ct, lpeg.Cg
--- @param x vim.lpeg.Pattern
local function rep(x)
return x ^ 0
end
--- @param x vim.lpeg.Pattern
local function rep1(x)
return x ^ 1
end
--- @param x vim.lpeg.Pattern
local function opt(x)
return x ^ -1
end
local nl = P('\r\n') + P('\n')
local ws = rep1(S(' \t') + nl)
local any = P(1) -- (consume one character)
local letter = R('az', 'AZ') + S('_$')
local ident = letter * rep(letter + R('09'))
local io = P('[') * (P('in') + P('out') + P('inout')) * P(']')
--- @param x string
local function Pf(x)
return opt(ws) * P(x) * opt(ws)
end
--- @type table<string,vim.lpeg.Pattern>
local v = setmetatable({}, {
__index = function(_, k)
return lpeg.V(k)
end,
})
local grammar = P {
rep1(P('@') * v.ats),
ats = v.at_param + v.at_return + v.at_deprecated + v.at_see + v.at_brief + v.at_note + v.at_nodoc,
at_param = Ct(
Cg(P('param'), 'kind') * opt(io) * ws * Cg(ident, 'name') * opt(ws * Cg(rep(any), 'desc'))
),
at_return = Ct(Cg(P('return'), 'kind') * opt(S('s')) * opt(ws * Cg(rep(any), 'desc'))),
at_deprecated = Ct(Cg(P('deprecated'), 'kind')),
at_see = Ct(Cg(P('see'), 'kind') * ws * opt(Pf('#')) * Cg(rep(any), 'desc')),
at_brief = Ct(Cg(P('brief'), 'kind') * ws * Cg(rep(any), 'desc')),
at_note = Ct(Cg(P('note'), 'kind') * ws * Cg(rep(any), 'desc')),
at_nodoc = Ct(Cg(P('nodoc'), 'kind')),
}
return grammar --[[@as nvim.cdoc.grammar]]

223
src/gen/cdoc_parser.lua Normal file
View File

@@ -0,0 +1,223 @@
local cdoc_grammar = require('gen.cdoc_grammar')
local c_grammar = require('gen.c_grammar')
--- @class nvim.cdoc.parser.param
--- @field name string
--- @field type string
--- @field desc string
--- @class nvim.cdoc.parser.return
--- @field name string
--- @field type string
--- @field desc string
--- @class nvim.cdoc.parser.note
--- @field desc string
--- @class nvim.cdoc.parser.brief
--- @field kind 'brief'
--- @field desc string
--- @class nvim.cdoc.parser.fun
--- @field name string
--- @field params nvim.cdoc.parser.param[]
--- @field returns nvim.cdoc.parser.return[]
--- @field desc string
--- @field deprecated? true
--- @field since? string
--- @field attrs? string[]
--- @field nodoc? true
--- @field notes? nvim.cdoc.parser.note[]
--- @field see? nvim.cdoc.parser.note[]
--- @class nvim.cdoc.parser.State
--- @field doc_lines? string[]
--- @field cur_obj? nvim.cdoc.parser.obj
--- @field last_doc_item? nvim.cdoc.parser.param|nvim.cdoc.parser.return|nvim.cdoc.parser.note
--- @field last_doc_item_indent? integer
--- @alias nvim.cdoc.parser.obj
--- | nvim.cdoc.parser.fun
--- | nvim.cdoc.parser.brief
--- If we collected any `---` lines. Add them to the existing (or new) object
--- Used for function/class descriptions and multiline param descriptions.
--- @param state nvim.cdoc.parser.State
local function add_doc_lines_to_obj(state)
if state.doc_lines then
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
local txt = table.concat(state.doc_lines, '\n')
if cur_obj.desc then
cur_obj.desc = cur_obj.desc .. '\n' .. txt
else
cur_obj.desc = txt
end
state.doc_lines = nil
end
end
--- @param line string
--- @param state nvim.cdoc.parser.State
local function process_doc_line(line, state)
line = line:gsub('^%s+@', '@')
local parsed = cdoc_grammar:match(line)
if not parsed then
if line:match('^ ') then
line = line:sub(2)
end
if state.last_doc_item then
if not state.last_doc_item_indent then
state.last_doc_item_indent = #line:match('^%s*') + 1
end
state.last_doc_item.desc = (state.last_doc_item.desc or '')
.. '\n'
.. line:sub(state.last_doc_item_indent or 1)
else
state.doc_lines = state.doc_lines or {}
table.insert(state.doc_lines, line)
end
return
end
state.last_doc_item_indent = nil
state.last_doc_item = nil
local kind = parsed.kind
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
if kind == 'brief' then
state.cur_obj = {
kind = 'brief',
desc = parsed.desc,
}
elseif kind == 'param' then
state.last_doc_item_indent = nil
cur_obj.params = cur_obj.params or {}
state.last_doc_item = {
name = parsed.name,
desc = parsed.desc,
}
table.insert(cur_obj.params, state.last_doc_item)
elseif kind == 'return' then
cur_obj.returns = { {
desc = parsed.desc,
} }
state.last_doc_item_indent = nil
state.last_doc_item = cur_obj.returns[1]
elseif kind == 'deprecated' then
cur_obj.deprecated = true
elseif kind == 'nodoc' then
cur_obj.nodoc = true
elseif kind == 'since' then
cur_obj.since = parsed.desc
elseif kind == 'see' then
cur_obj.see = cur_obj.see or {}
table.insert(cur_obj.see, { desc = parsed.desc })
elseif kind == 'note' then
state.last_doc_item_indent = nil
state.last_doc_item = {
desc = parsed.desc,
}
cur_obj.notes = cur_obj.notes or {}
table.insert(cur_obj.notes, state.last_doc_item)
else
error('Unhandled' .. vim.inspect(parsed))
end
end
--- @param item table
--- @param state nvim.cdoc.parser.State
local function process_proto(item, state)
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
cur_obj.name = item.name
cur_obj.params = cur_obj.params or {}
for _, p in ipairs(item.parameters) do
local param = { name = p[2], type = p[1] }
local added = false
for _, cp in ipairs(cur_obj.params) do
if cp.name == param.name then
cp.type = param.type
added = true
break
end
end
if not added then
table.insert(cur_obj.params, param)
end
end
cur_obj.returns = cur_obj.returns or { {} }
cur_obj.returns[1].type = item.return_type
for _, a in ipairs({
'fast',
'remote_only',
'lua_only',
'textlock',
'textlock_allow_cmdwin',
}) do
if item[a] then
cur_obj.attrs = cur_obj.attrs or {}
table.insert(cur_obj.attrs, a)
end
end
cur_obj.deprecated_since = item.deprecated_since
-- Remove some arguments
for i = #cur_obj.params, 1, -1 do
local p = cur_obj.params[i]
if p.name == 'channel_id' or vim.tbl_contains({ 'lstate', 'arena', 'error' }, p.type) then
table.remove(cur_obj.params, i)
end
end
end
local M = {}
--- @param filename string
--- @return {} classes
--- @return nvim.cdoc.parser.fun[] funs
--- @return string[] briefs
function M.parse(filename)
local funs = {} --- @type nvim.cdoc.parser.fun[]
local briefs = {} --- @type string[]
local state = {} --- @type nvim.cdoc.parser.State
local txt = assert(io.open(filename, 'r')):read('*all')
local parsed = c_grammar.grammar:match(txt)
for _, item in ipairs(parsed) do
if item.comment then
process_doc_line(item.comment, state)
else
add_doc_lines_to_obj(state)
if item[1] == 'proto' then
process_proto(item, state)
table.insert(funs, state.cur_obj)
end
local cur_obj = state.cur_obj
if cur_obj and not item.static then
if cur_obj.kind == 'brief' then
table.insert(briefs, cur_obj.desc)
end
end
state = {}
end
end
return {}, funs, briefs
end
-- M.parse('src/nvim/api/vim.c')
return M

View File

@@ -7,7 +7,7 @@
local mpack = vim.mpack
local hashy = require 'generators.hashy'
local hashy = require 'gen.hashy'
local pre_args = 7
assert(#arg >= pre_args)
@@ -31,7 +31,7 @@ local headers = {}
-- set of function names, used to detect duplicates
local function_names = {}
local c_grammar = require('generators.c_grammar')
local c_grammar = require('gen.c_grammar')
local startswith = vim.startswith
@@ -150,7 +150,7 @@ end
-- Export functions under older deprecated names.
-- These will be removed eventually.
local deprecated_aliases = require('api.dispatch_deprecated')
local deprecated_aliases = require('nvim.api.dispatch_deprecated')
for _, f in ipairs(shallowcopy(functions)) do
local ismethod = false
if startswith(f.name, 'nvim_') then
@@ -300,7 +300,7 @@ for i, item in ipairs(types) do
end
local packed = table.concat(pieces)
local dump_bin_array = require('generators.dump_bin_array')
local dump_bin_array = require('gen.dump_bin_array')
dump_bin_array(api_metadata_output, 'packed_api_metadata', packed)
api_metadata_output:close()

View File

@@ -7,10 +7,10 @@ local remote_output = io.open(arg[3], 'wb')
local metadata_output = io.open(arg[4], 'wb')
local client_output = io.open(arg[5], 'wb')
local c_grammar = require('generators.c_grammar')
local c_grammar = require('gen.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
local hashy = require 'generators.hashy'
local hashy = require 'gen.hashy'
local function write_signature(output, ev, prefix, notype)
output:write('(' .. prefix)

View File

@@ -1,4 +1,4 @@
local grammar = require('generators.c_grammar').grammar
local grammar = require('gen.c_grammar').grammar
--- @param fname string
--- @return string?

View File

@@ -8,7 +8,7 @@ local funcsfname = autodir .. '/funcs.generated.h'
--Will generate funcs.generated.h with definition of functions static const array.
local hashy = require 'generators.hashy'
local hashy = require 'gen.hashy'
local hashpipe = assert(io.open(funcsfname, 'wb'))
@@ -47,7 +47,7 @@ hashpipe:write([[
]])
local funcs = require('eval').funcs
local funcs = require('nvim.eval').funcs
for _, func in pairs(funcs) do
if func.float_func then
func.func = 'float_op_wrapper'

1090
src/gen/gen_eval_files.lua Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
local fileio_enum_file = arg[1]
local names_file = arg[2]
local auevents = require('auevents')
local auevents = require('nvim.auevents')
local events = auevents.events
local enum_tgt = io.open(fileio_enum_file, 'w')

View File

@@ -11,7 +11,7 @@ local enumfile = io.open(enumfname, 'w')
local defsfile = io.open(defsfname, 'w')
local bit = require 'bit'
local ex_cmds = require('ex_cmds')
local ex_cmds = require('nvim.ex_cmds')
local defs = ex_cmds.cmds
local flags = ex_cmds.flags

209
src/gen/gen_filetype.lua Normal file
View File

@@ -0,0 +1,209 @@
local do_not_run = true
if do_not_run then
print([[
This script was used to bootstrap the filetype patterns in runtime/lua/vim/filetype.lua. It
should no longer be used except for testing purposes. New filetypes, or changes to existing
filetypes, should be ported manually as part of the vim-patch process.
]])
return
end
local filetype_vim = 'runtime/filetype.vim'
local filetype_lua = 'runtime/lua/vim/filetype.lua'
local keywords = {
['for'] = true,
['or'] = true,
['and'] = true,
['end'] = true,
['do'] = true,
['if'] = true,
['while'] = true,
['repeat'] = true,
}
local sections = {
extension = { str = {}, func = {} },
filename = { str = {}, func = {} },
pattern = { str = {}, func = {} },
}
local specialchars = '%*%?\\%$%[%]%{%}'
local function add_pattern(pat, ft)
local ok = true
-- Patterns that start or end with { or } confuse splitting on commas and make parsing harder, so just skip those
if not string.find(pat, '^%{') and not string.find(pat, '%}$') then
for part in string.gmatch(pat, '[^,]+') do
if not string.find(part, '[' .. specialchars .. ']') then
if type(ft) == 'string' then
sections.filename.str[part] = ft
else
sections.filename.func[part] = ft
end
elseif string.match(part, '^%*%.[^%./' .. specialchars .. ']+$') then
if type(ft) == 'string' then
sections.extension.str[part:sub(3)] = ft
else
sections.extension.func[part:sub(3)] = ft
end
else
if string.match(part, '^%*/[^' .. specialchars .. ']+$') then
-- For patterns matching */some/pattern we want to easily match files
-- with path /some/pattern, so include those in filename detection
if type(ft) == 'string' then
sections.filename.str[part:sub(2)] = ft
else
sections.filename.func[part:sub(2)] = ft
end
end
if string.find(part, '^[%w-_.*?%[%]/]+$') then
local p = part:gsub('%.', '%%.'):gsub('%*', '.*'):gsub('%?', '.')
-- Insert into array to maintain order rather than setting
-- key-value directly
if type(ft) == 'string' then
sections.pattern.str[p] = ft
else
sections.pattern.func[p] = ft
end
else
ok = false
end
end
end
end
return ok
end
local function parse_line(line)
local pat, ft
pat, ft = line:match('^%s*au%a* Buf[%a,]+%s+(%S+)%s+setf%s+(%S+)')
if pat then
return add_pattern(pat, ft)
else
local func
pat, func = line:match('^%s*au%a* Buf[%a,]+%s+(%S+)%s+call%s+(%S+)')
if pat then
return add_pattern(pat, function()
return func
end)
end
end
end
local unparsed = {}
local full_line
for line in io.lines(filetype_vim) do
local cont = string.match(line, '^%s*\\%s*(.*)$')
if cont then
full_line = full_line .. ' ' .. cont
else
if full_line then
if not parse_line(full_line) and string.find(full_line, '^%s*au%a* Buf') then
table.insert(unparsed, full_line)
end
end
full_line = line
end
end
if #unparsed > 0 then
print('Failed to parse the following patterns:')
for _, v in ipairs(unparsed) do
print(v)
end
end
local function add_item(indent, key, ft)
if type(ft) == 'string' then
if string.find(key, '%A') or keywords[key] then
key = string.format('["%s"]', key)
end
return string.format([[%s%s = "%s",]], indent, key, ft)
elseif type(ft) == 'function' then
local func = ft()
if string.find(key, '%A') or keywords[key] then
key = string.format('["%s"]', key)
end
-- Right now only a single argument is supported, which covers
-- everything in filetype.vim as of this writing
local arg = string.match(func, '%((.*)%)$')
func = string.gsub(func, '%(.*$', '')
if arg == '' then
-- Function with no arguments, call the function directly
return string.format([[%s%s = function() vim.fn["%s"]() end,]], indent, key, func)
elseif string.match(arg, [[^(["']).*%1$]]) then
-- String argument
if func == 's:StarSetf' then
return string.format([[%s%s = starsetf(%s),]], indent, key, arg)
else
return string.format([[%s%s = function() vim.fn["%s"](%s) end,]], indent, key, func, arg)
end
elseif string.find(arg, '%(') then
-- Function argument
return string.format(
[[%s%s = function() vim.fn["%s"](vim.fn.%s) end,]],
indent,
key,
func,
arg
)
else
assert(false, arg)
end
end
end
do
local lines = {}
local start = false
for line in io.lines(filetype_lua) do
if line:match('^%s+-- END [A-Z]+$') then
start = false
end
if not start then
table.insert(lines, line)
end
local indent, section = line:match('^(%s+)-- BEGIN ([A-Z]+)$')
if section then
start = true
local t = sections[string.lower(section)]
local sorted = {}
for k, v in pairs(t.str) do
table.insert(sorted, { [k] = v })
end
table.sort(sorted, function(a, b)
return a[next(a)] < b[next(b)]
end)
for _, v in ipairs(sorted) do
local k = next(v)
table.insert(lines, add_item(indent, k, v[k]))
end
sorted = {}
for k, v in pairs(t.func) do
table.insert(sorted, { [k] = v })
end
table.sort(sorted, function(a, b)
return next(a) < next(b)
end)
for _, v in ipairs(sorted) do
local k = next(v)
table.insert(lines, add_item(indent, k, v[k]))
end
end
end
local f = io.open(filetype_lua, 'w')
f:write(table.concat(lines, '\n') .. '\n')
f:close()
end

1491
src/gen/gen_help_html.lua Normal file

File diff suppressed because it is too large Load Diff

514
src/gen/gen_lsp.lua Normal file
View File

@@ -0,0 +1,514 @@
-- Generates lua-ls annotations for lsp.
local USAGE = [[
Generates lua-ls annotations for lsp.
USAGE:
nvim -l src/gen/gen_lsp.lua gen # by default, this will overwrite runtime/lua/vim/lsp/_meta/protocol.lua
nvim -l src/gen/gen_lsp.lua gen --version 3.18 --out runtime/lua/vim/lsp/_meta/protocol.lua
nvim -l src/gen/gen_lsp.lua gen --version 3.18 --methods --capabilities
]]
local DEFAULT_LSP_VERSION = '3.18'
local M = {}
local function tofile(fname, text)
local f = io.open(fname, 'w')
if not f then
error(('failed to write: %s'):format(f))
else
print(('Written to: %s'):format(fname))
f:write(text)
f:close()
end
end
--- The LSP protocol JSON data (it's partial, non-exhaustive).
--- https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/3.18/metaModel/metaModel.schema.json
--- @class vim._gen_lsp.Protocol
--- @field requests vim._gen_lsp.Request[]
--- @field notifications vim._gen_lsp.Notification[]
--- @field structures vim._gen_lsp.Structure[]
--- @field enumerations vim._gen_lsp.Enumeration[]
--- @field typeAliases vim._gen_lsp.TypeAlias[]
---@param opt vim._gen_lsp.opt
---@return vim._gen_lsp.Protocol
local function read_json(opt)
local uri = 'https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/'
.. opt.version
.. '/metaModel/metaModel.json'
print('Reading ' .. uri)
local res = vim.system({ 'curl', '--no-progress-meter', uri, '-o', '-' }):wait()
if res.code ~= 0 or (res.stdout or ''):len() < 999 then
print(('URL failed: %s'):format(uri))
vim.print(res)
error(res.stdout)
end
return vim.json.decode(res.stdout)
end
-- Gets the Lua symbol for a given fully-qualified LSP method name.
local function to_luaname(s)
-- "$/" prefix is special: https://microsoft.github.io/language-server-protocol/specification/#dollarRequests
return s:gsub('^%$', 'dollar'):gsub('/', '_')
end
---@param protocol vim._gen_lsp.Protocol
---@param gen_methods boolean
---@param gen_capabilities boolean
local function write_to_protocol(protocol, gen_methods, gen_capabilities)
if not gen_methods and not gen_capabilities then
return
end
local indent = (' '):rep(2)
--- @class vim._gen_lsp.Request
--- @field deprecated? string
--- @field documentation? string
--- @field messageDirection string
--- @field clientCapability? string
--- @field serverCapability? string
--- @field method string
--- @field params? any
--- @field proposed? boolean
--- @field registrationMethod? string
--- @field registrationOptions? any
--- @field since? string
--- @class vim._gen_lsp.Notification
--- @field deprecated? string
--- @field documentation? string
--- @field errorData? any
--- @field messageDirection string
--- @field clientCapability? string
--- @field serverCapability? string
--- @field method string
--- @field params? any[]
--- @field partialResult? any
--- @field proposed? boolean
--- @field registrationMethod? string
--- @field registrationOptions? any
--- @field result any
--- @field since? string
---@type (vim._gen_lsp.Request|vim._gen_lsp.Notification)[]
local all = vim.list_extend(protocol.requests, protocol.notifications)
table.sort(all, function(a, b)
return to_luaname(a.method) < to_luaname(b.method)
end)
local output = { '-- Generated by gen_lsp.lua, keep at end of file.' }
if gen_methods then
output[#output + 1] = '--- @alias vim.lsp.protocol.Method.ClientToServer'
for _, item in ipairs(all) do
if item.method and item.messageDirection == 'clientToServer' then
output[#output + 1] = ("--- | '%s',"):format(item.method)
end
end
vim.list_extend(output, {
'',
'--- @alias vim.lsp.protocol.Method.ServerToClient',
})
for _, item in ipairs(all) do
if item.method and item.messageDirection == 'serverToClient' then
output[#output + 1] = ("--- | '%s',"):format(item.method)
end
end
vim.list_extend(output, {
'',
'--- @alias vim.lsp.protocol.Method',
'--- | vim.lsp.protocol.Method.ClientToServer',
'--- | vim.lsp.protocol.Method.ServerToClient',
'',
'-- Generated by gen_lsp.lua, keep at end of file.',
'--- @enum vim.lsp.protocol.Methods',
'--- @see https://microsoft.github.io/language-server-protocol/specification/#metaModel',
'--- LSP method names.',
'protocol.Methods = {',
})
for _, item in ipairs(all) do
if item.method then
if item.documentation then
local document = vim.split(item.documentation, '\n?\n', { trimempty = true })
for _, docstring in ipairs(document) do
output[#output + 1] = indent .. '--- ' .. docstring
end
end
output[#output + 1] = ("%s%s = '%s',"):format(indent, to_luaname(item.method), item.method)
end
end
output[#output + 1] = '}'
end
if gen_capabilities then
vim.list_extend(output, {
'',
'-- stylua: ignore start',
'-- Generated by gen_lsp.lua, keep at end of file.',
'--- Maps method names to the required server capability',
'protocol._request_name_to_capability = {',
})
for _, item in ipairs(all) do
if item.serverCapability then
output[#output + 1] = ("%s['%s'] = { %s },"):format(
indent,
item.method,
table.concat(
vim
.iter(vim.split(item.serverCapability, '.', { plain = true }))
:map(function(segment)
return "'" .. segment .. "'"
end)
:totable(),
', '
)
)
end
end
output[#output + 1] = '}'
output[#output + 1] = '-- stylua: ignore end'
end
output[#output + 1] = ''
output[#output + 1] = 'return protocol'
local fname = './runtime/lua/vim/lsp/protocol.lua'
local bufnr = vim.fn.bufadd(fname)
vim.fn.bufload(bufnr)
vim.api.nvim_set_current_buf(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local index = vim.iter(ipairs(lines)):find(function(key, item)
return vim.startswith(item, '-- Generated by') and key or nil
end)
index = index and index - 1 or vim.api.nvim_buf_line_count(bufnr) - 1
vim.api.nvim_buf_set_lines(bufnr, index, -1, true, output)
vim.cmd.write()
end
---@class vim._gen_lsp.opt
---@field output_file string
---@field version string
---@field methods boolean
---@field capabilities boolean
---@param opt vim._gen_lsp.opt
function M.gen(opt)
--- @type vim._gen_lsp.Protocol
local protocol = read_json(opt)
write_to_protocol(protocol, opt.methods, opt.capabilities)
local output = {
'--' .. '[[',
'THIS FILE IS GENERATED by scr/gen/gen_lsp.lua',
'DO NOT EDIT MANUALLY',
'',
'Based on LSP protocol ' .. opt.version,
'',
'Regenerate:',
([=[nvim -l scr/gen/gen_lsp.lua gen --version %s]=]):format(DEFAULT_LSP_VERSION),
'--' .. ']]',
'',
'---@meta',
"error('Cannot require a meta file')",
'',
'---@alias lsp.null nil',
'---@alias uinteger integer',
'---@alias decimal number',
'---@alias lsp.DocumentUri string',
'---@alias lsp.URI string',
'',
}
local anonymous_num = 0
---@type string[]
local anonym_classes = {}
local simple_types = {
'string',
'boolean',
'integer',
'uinteger',
'decimal',
}
---@param documentation string
local _process_documentation = function(documentation)
documentation = documentation:gsub('\n', '\n---')
-- Remove <200b> (zero-width space) unicode characters: e.g., `**/<200b>*`
documentation = documentation:gsub('\226\128\139', '')
-- Escape annotations that are not recognized by lua-ls
documentation = documentation:gsub('%^---@sample', '---\\@sample')
return '---' .. documentation
end
--- @class vim._gen_lsp.Type
--- @field kind string a common field for all Types.
--- @field name? string for ReferenceType, BaseType
--- @field element? any for ArrayType
--- @field items? vim._gen_lsp.Type[] for OrType, AndType
--- @field key? vim._gen_lsp.Type for MapType
--- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
---@param type vim._gen_lsp.Type
---@param prefix? string Optional prefix associated with the this type, made of (nested) field name.
--- Used to generate class name for structure literal types.
---@return string
local function parse_type(type, prefix)
-- ReferenceType | BaseType
if type.kind == 'reference' or type.kind == 'base' then
if vim.tbl_contains(simple_types, type.name) then
return type.name
end
return 'lsp.' .. type.name
-- ArrayType
elseif type.kind == 'array' then
local parsed_items = parse_type(type.element, prefix)
if type.element.items and #type.element.items > 1 then
parsed_items = '(' .. parsed_items .. ')'
end
return parsed_items .. '[]'
-- OrType
elseif type.kind == 'or' then
local val = ''
for _, item in ipairs(type.items) do
val = val .. parse_type(item, prefix) .. '|' --[[ @as string ]]
end
val = val:sub(0, -2)
return val
-- StringLiteralType
elseif type.kind == 'stringLiteral' then
return '"' .. type.value .. '"'
-- MapType
elseif type.kind == 'map' then
local key = assert(type.key)
local value = type.value --[[ @as vim._gen_lsp.Type ]]
return 'table<' .. parse_type(key, prefix) .. ', ' .. parse_type(value, prefix) .. '>'
-- StructureLiteralType
elseif type.kind == 'literal' then
-- can I use ---@param disabled? {reason: string}
-- use | to continue the inline class to be able to add docs
-- https://github.com/LuaLS/lua-language-server/issues/2128
anonymous_num = anonymous_num + 1
local anonymous_classname = 'lsp._anonym' .. anonymous_num
if prefix then
anonymous_classname = anonymous_classname .. '.' .. prefix
end
local anonym = vim
.iter({
(anonymous_num > 1 and { '' } or {}),
{ '---@class ' .. anonymous_classname },
})
:flatten()
:totable()
--- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
--- @field deprecated? string
--- @field description? string
--- @field properties vim._gen_lsp.Property[]
--- @field proposed? boolean
--- @field since? string
---@type vim._gen_lsp.StructureLiteral
local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
for _, field in ipairs(structural_literal.properties) do
anonym[#anonym + 1] = '---'
if field.documentation then
anonym[#anonym + 1] = _process_documentation(field.documentation)
end
anonym[#anonym + 1] = '---@field '
.. field.name
.. (field.optional and '?' or '')
.. ' '
.. parse_type(field.type, prefix .. '.' .. field.name)
end
-- anonym[#anonym + 1] = ''
for _, line in ipairs(anonym) do
if line then
anonym_classes[#anonym_classes + 1] = line
end
end
return anonymous_classname
-- TupleType
elseif type.kind == 'tuple' then
local tuple = '['
for _, value in ipairs(type.items) do
tuple = tuple .. parse_type(value, prefix) .. ', '
end
-- remove , at the end
tuple = tuple:sub(0, -3)
return tuple .. ']'
end
vim.print('WARNING: Unknown type ', type)
return ''
end
--- @class vim._gen_lsp.Structure translated to @class
--- @field deprecated? string
--- @field documentation? string
--- @field extends? { kind: string, name: string }[]
--- @field mixins? { kind: string, name: string }[]
--- @field name string
--- @field properties? vim._gen_lsp.Property[] members, translated to @field
--- @field proposed? boolean
--- @field since? string
for _, structure in ipairs(protocol.structures) do
-- output[#output + 1] = ''
if structure.documentation then
output[#output + 1] = _process_documentation(structure.documentation)
end
local class_string = ('---@class lsp.%s'):format(structure.name)
if structure.extends or structure.mixins then
local inherits_from = table.concat(
vim.list_extend(
vim.tbl_map(parse_type, structure.extends or {}),
vim.tbl_map(parse_type, structure.mixins or {})
),
', '
)
class_string = class_string .. ': ' .. inherits_from
end
output[#output + 1] = class_string
--- @class vim._gen_lsp.Property translated to @field
--- @field deprecated? string
--- @field documentation? string
--- @field name string
--- @field optional? boolean
--- @field proposed? boolean
--- @field since? string
--- @field type { kind: string, name: string }
for _, field in ipairs(structure.properties or {}) do
output[#output + 1] = '---' -- Insert a single newline between @fields (and after @class)
if field.documentation then
output[#output + 1] = _process_documentation(field.documentation)
end
output[#output + 1] = '---@field '
.. field.name
.. (field.optional and '?' or '')
.. ' '
.. parse_type(field.type, field.name)
end
output[#output + 1] = ''
end
--- @class vim._gen_lsp.Enumeration translated to @enum
--- @field deprecated string?
--- @field documentation string?
--- @field name string?
--- @field proposed boolean?
--- @field since string?
--- @field suportsCustomValues boolean?
--- @field values { name: string, value: string, documentation?: string, since?: string }[]
for _, enum in ipairs(protocol.enumerations) do
if enum.documentation then
output[#output + 1] = _process_documentation(enum.documentation)
end
local enum_type = '---@alias lsp.' .. enum.name
for _, value in ipairs(enum.values) do
enum_type = enum_type
.. '\n---| '
.. (type(value.value) == 'string' and '"' .. value.value .. '"' or value.value)
.. ' # '
.. value.name
end
output[#output + 1] = enum_type
output[#output + 1] = ''
end
--- @class vim._gen_lsp.TypeAlias translated to @alias
--- @field deprecated? string?
--- @field documentation? string
--- @field name string
--- @field proposed? boolean
--- @field since? string
--- @field type vim._gen_lsp.Type
for _, alias in ipairs(protocol.typeAliases) do
if alias.documentation then
output[#output + 1] = _process_documentation(alias.documentation)
end
if alias.type.kind == 'or' then
local alias_type = '---@alias lsp.' .. alias.name .. ' '
for _, item in ipairs(alias.type.items) do
alias_type = alias_type .. parse_type(item, alias.name) .. '|'
end
alias_type = alias_type:sub(0, -2)
output[#output + 1] = alias_type
else
output[#output + 1] = '---@alias lsp.'
.. alias.name
.. ' '
.. parse_type(alias.type, alias.name)
end
output[#output + 1] = ''
end
-- anonymous classes
for _, line in ipairs(anonym_classes) do
output[#output + 1] = line
end
tofile(opt.output_file, table.concat(output, '\n') .. '\n')
end
---@type vim._gen_lsp.opt
local opt = {
output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
version = DEFAULT_LSP_VERSION,
methods = false,
capabilities = false,
}
local command = nil
local i = 1
while i <= #_G.arg do
if _G.arg[i] == '--out' then
opt.output_file = assert(_G.arg[i + 1], '--out <outfile> needed')
i = i + 1
elseif _G.arg[i] == '--version' then
opt.version = assert(_G.arg[i + 1], '--version <version> needed')
i = i + 1
elseif _G.arg[i] == '--methods' then
opt.methods = true
elseif _G.arg[i] == '--capabilities' then
opt.capabilities = true
elseif vim.startswith(_G.arg[i], '-') then
error('Unrecognized args: ' .. _G.arg[i])
else
if command then
error('More than one command was given: ' .. _G.arg[i])
else
command = _G.arg[i]
end
end
i = i + 1
end
if not command then
print(USAGE)
elseif M[command] then
M[command](opt) -- see M.gen()
else
error('Unknown command: ' .. command)
end
return M

View File

@@ -1,5 +1,5 @@
--- @module 'nvim.options'
local options = require('options')
local options = require('nvim.options')
local options_meta = options.options
local cstr = options.cstr
local valid_scopes = options.valid_scopes
@@ -418,7 +418,7 @@ end
--- @param option_index table<string,string>
local function gen_map(output_file, option_index)
-- Generate option index map.
local hashy = require('generators.hashy')
local hashy = require('gen.hashy')
local neworder, hashfun = hashy.hashy_hash(
'find_option',

1041
src/gen/gen_vimdoc.lua Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,9 @@ local function w(s)
end
end
local options = require('options')
local auevents = require('auevents')
local ex_cmds = require('ex_cmds')
local options = require('nvim.options')
local auevents = require('nvim.auevents')
local ex_cmds = require('nvim.ex_cmds')
local function cmd_kw(prev_cmd, cmd)
if not prev_cmd then

207
src/gen/luacats_grammar.lua Normal file
View File

@@ -0,0 +1,207 @@
--[[!
LPEG grammar for LuaCATS
]]
local lpeg = vim.lpeg
local P, R, S = lpeg.P, lpeg.R, lpeg.S
local C, Ct, Cg = lpeg.C, lpeg.Ct, lpeg.Cg
--- @param x vim.lpeg.Pattern
local function rep(x)
return x ^ 0
end
--- @param x vim.lpeg.Pattern
local function rep1(x)
return x ^ 1
end
--- @param x vim.lpeg.Pattern
local function opt(x)
return x ^ -1
end
local ws = rep1(S(' \t'))
local fill = opt(ws)
local any = P(1) -- (consume one character)
local letter = R('az', 'AZ')
local num = R('09')
--- @param x string | vim.lpeg.Pattern
local function Pf(x)
return fill * P(x) * fill
end
--- @param x string | vim.lpeg.Pattern
local function Plf(x)
return fill * P(x)
end
--- @param x string
local function Sf(x)
return fill * S(x) * fill
end
--- @param x vim.lpeg.Pattern
local function paren(x)
return Pf('(') * x * fill * P(')')
end
--- @param x vim.lpeg.Pattern
local function parenOpt(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>
local v = setmetatable({}, {
__index = function(_, k)
return lpeg.V(k)
end,
})
--- @class nvim.luacats.Param
--- @field kind 'param'
--- @field name string
--- @field type string
--- @field desc? string
--- @class nvim.luacats.Return
--- @field kind 'return'
--- @field [integer] { type: string, name?: string}
--- @field desc? string
--- @class nvim.luacats.Generic
--- @field kind 'generic'
--- @field name string
--- @field type? string
--- @class nvim.luacats.Class
--- @field kind 'class'
--- @field name string
--- @field parent? string
--- @field access? 'private'|'protected'|'package'
--- @class nvim.luacats.Field
--- @field kind 'field'
--- @field name string
--- @field type string
--- @field desc? string
--- @field access? 'private'|'protected'|'package'
--- @class nvim.luacats.Note
--- @field desc? string
--- @alias nvim.luacats.grammar.result
--- | nvim.luacats.Param
--- | nvim.luacats.Return
--- | nvim.luacats.Generic
--- | nvim.luacats.Class
--- | nvim.luacats.Field
--- | nvim.luacats.Note
--- @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 colon = Pf(':')
local ellipsis = P('...')
local ident_first = P('_') + letter
local ident = ident_first * rep(ident_first + num)
local opt_ident = ident * opt(P('?'))
local ty_ident_sep = S('-._')
local ty_ident = ident * rep(ty_ident_sep * ident)
local string_single = P "'" * rep(any - P "'") * P "'"
local string_double = P('"') * rep(any - P('"')) * P('"')
local generic = P('`') * ty_ident * P('`')
local literal = string_single + string_double + (opt(P('-')) * rep1(num)) + P('false') + P('true')
local ty_prims = ty_ident + literal + generic
local array_postfix = rep1(Plf('[]'))
local opt_postfix = rep1(Plf('?'))
local rep_array_opt_postfix = rep(array_postfix + opt_postfix)
local typedef = P({
'typedef',
typedef = C(v.type),
type = v.ty * rep_array_opt_postfix * rep(Pf('|') * v.ty * rep_array_opt_postfix),
ty = v.composite + paren(v.typedef),
composite = (v.types * array_postfix) + (v.types * opt_postfix) + v.types,
types = v.generics + v.kv_table + v.tuple + v.dict + v.table_literal + v.fun + ty_prims,
tuple = Pf('[') * comma1(v.type) * Plf(']'),
dict = Pf('{') * comma1(Pf('[') * v.type * Pf(']') * colon * v.type) * Plf('}'),
kv_table = Pf('table') * Pf('<') * v.type * Pf(',') * v.type * Plf('>'),
table_literal = Pf('{') * comma1(opt_ident * Pf(':') * v.type) * Plf('}'),
fun_param = (opt_ident + ellipsis) * opt(colon * v.type),
fun_ret = v.type + (ellipsis * opt(colon * v.type)),
fun = Pf('fun') * paren(comma(v.fun_param)) * opt(Pf(':') * comma1(v.fun_ret)),
generics = P(ty_ident) * Pf('<') * comma1(v.type) * Plf('>'),
}) / function(match)
return vim.trim(match):gsub('^%((.*)%)$', '%1'):gsub('%?+', '?')
end
local access = P('private') + P('protected') + P('package')
local caccess = Cg(access, 'access')
local cattr = Cg(comma(access + P('exact')), 'access')
local desc_delim = Sf '#:' + ws
local desc = Cg(rep(any), 'desc')
local opt_desc = opt(desc_delim * desc)
local ty_name = Cg(ty_ident, 'name')
local opt_parent = opt(colon * Cg(ty_ident, 'parent'))
local lname = (ident + ellipsis) * opt(P('?'))
local grammar = P {
rep1(P('@') * (v.ats + v.ext_ats)),
ats = annot('param', Cg(lname, 'name') * ws * v.ctype * opt_desc)
+ annot('return', comma1(Ct(v.ctype * opt(ws * (ty_name + Cg(ellipsis, 'name'))))) * opt_desc)
+ annot('type', comma1(Ct(v.ctype)) * opt_desc)
+ annot('cast', ty_name * ws * opt(Sf('+-')) * v.ctype)
+ annot('generic', ty_name * opt(colon * v.ctype))
+ annot('class', opt(paren(cattr)) * fill * ty_name * opt_parent)
+ annot('field', opt(caccess * ws) * v.field_name * ws * v.ctype * opt_desc)
+ annot('operator', ty_name * opt(paren(Cg(v.ctype, 'argtype'))) * colon * v.ctype)
+ annot(access)
+ annot('deprecated')
+ annot('alias', ty_name * opt(ws * v.ctype))
+ annot('enum', ty_name)
+ annot('overload', v.ctype)
+ annot('see', opt(desc_delim) * desc)
+ annot('diagnostic', opt(desc_delim) * desc)
+ annot('meta'),
--- Custom extensions
ext_ats = (
annot('note', desc)
+ annot('since', desc)
+ annot('nodoc')
+ annot('inlinedoc')
+ annot('brief', desc)
),
field_name = Cg(lname + (v.ty_index * opt(P('?'))), 'name'),
ty_index = C(Pf('[') * typedef * fill * P(']')),
ctype = Cg(typedef, 'type'),
}
return grammar --[[@as nvim.luacats.grammar]]

535
src/gen/luacats_parser.lua Normal file
View File

@@ -0,0 +1,535 @@
local luacats_grammar = require('gen.luacats_grammar')
--- @class nvim.luacats.parser.param : nvim.luacats.Param
--- @class nvim.luacats.parser.return
--- @field name string
--- @field type string
--- @field desc string
--- @class nvim.luacats.parser.note
--- @field desc string
--- @class nvim.luacats.parser.brief
--- @field kind 'brief'
--- @field desc string
--- @class nvim.luacats.parser.alias
--- @field kind 'alias'
--- @field type string[]
--- @field desc string
--- @class nvim.luacats.parser.fun
--- @field name string
--- @field params nvim.luacats.parser.param[]
--- @field returns nvim.luacats.parser.return[]
--- @field desc string
--- @field access? 'private'|'package'|'protected'
--- @field class? string
--- @field module? string
--- @field modvar? string
--- @field classvar? string
--- @field deprecated? true
--- @field since? string
--- @field attrs? string[]
--- @field nodoc? true
--- @field generics? table<string,string>
--- @field table? true
--- @field notes? nvim.luacats.parser.note[]
--- @field see? nvim.luacats.parser.note[]
--- @class nvim.luacats.parser.field : nvim.luacats.Field
--- @field classvar? string
--- @field nodoc? true
--- @class nvim.luacats.parser.class : nvim.luacats.Class
--- @field desc? string
--- @field nodoc? true
--- @field inlinedoc? true
--- @field fields nvim.luacats.parser.field[]
--- @field notes? string[]
--- @class nvim.luacats.parser.State
--- @field doc_lines? string[]
--- @field cur_obj? nvim.luacats.parser.obj
--- @field last_doc_item? nvim.luacats.parser.param|nvim.luacats.parser.return|nvim.luacats.parser.note
--- @field last_doc_item_indent? integer
--- @alias nvim.luacats.parser.obj
--- | 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:
--- param name some.complex.type (table) description
--- if so then transform the line to remove the complex type:
--- param name (table) description
--- @param line string
local function use_type_alt(line)
for _, type in ipairs({ 'table', 'function' }) do
line = line:gsub('@param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. ')%)', '@param %1 %2')
line = line:gsub('@param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. '|nil)%)', '@param %1 %2')
line = line:gsub('@param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. '%?)%)', '@param %1 %2')
line = line:gsub('@return%s+.*%((' .. type .. ')%)', '@return %1')
line = line:gsub('@return%s+.*%((' .. type .. '|nil)%)', '@return %1')
line = line:gsub('@return%s+.*%((' .. type .. '%?)%)', '@return %1')
end
return line
end
--- If we collected any `---` lines. Add them to the existing (or new) object
--- Used for function/class descriptions and multiline param descriptions.
--- @param state nvim.luacats.parser.State
local function add_doc_lines_to_obj(state)
if state.doc_lines then
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
local txt = table.concat(state.doc_lines, '\n')
if cur_obj.desc then
cur_obj.desc = cur_obj.desc .. '\n' .. txt
else
cur_obj.desc = txt
end
state.doc_lines = nil
end
end
--- @param line string
--- @param state nvim.luacats.parser.State
local function process_doc_line(line, state)
line = line:sub(4):gsub('^%s+@', '@')
line = use_type_alt(line)
local parsed = luacats_grammar:match(line)
if not parsed then
if line:match('^ ') then
line = line:sub(2)
end
if state.last_doc_item then
if not state.last_doc_item_indent then
state.last_doc_item_indent = #line:match('^%s*') + 1
end
state.last_doc_item.desc = (state.last_doc_item.desc or '')
.. '\n'
.. line:sub(state.last_doc_item_indent or 1)
else
state.doc_lines = state.doc_lines or {}
table.insert(state.doc_lines, line)
end
return
end
state.last_doc_item_indent = nil
state.last_doc_item = nil
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
local kind = parsed.kind
if kind == 'brief' then
state.cur_obj = {
kind = 'brief',
desc = parsed.desc,
}
elseif kind == 'class' then
--- @cast parsed nvim.luacats.Class
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
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
cur_obj.params = cur_obj.params or {}
if vim.endswith(parsed.name, '?') then
parsed.name = parsed.name:sub(1, -2)
parsed.type = parsed.type .. '?'
end
state.last_doc_item = {
name = parsed.name,
type = parsed.type,
desc = parsed.desc,
}
table.insert(cur_obj.params, state.last_doc_item)
elseif kind == 'return' then
cur_obj.returns = cur_obj.returns or {}
for _, t in ipairs(parsed) do
table.insert(cur_obj.returns, {
name = t.name,
type = t.type,
desc = parsed.desc,
})
end
state.last_doc_item_indent = nil
state.last_doc_item = cur_obj.returns[#cur_obj.returns]
elseif kind == 'private' then
cur_obj.access = 'private'
elseif kind == 'package' then
cur_obj.access = 'package'
elseif kind == 'protected' then
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
cur_obj.since = parsed.desc
elseif kind == 'see' then
cur_obj.see = cur_obj.see or {}
table.insert(cur_obj.see, { desc = parsed.desc })
elseif kind == 'note' then
state.last_doc_item_indent = nil
state.last_doc_item = {
desc = parsed.desc,
}
cur_obj.notes = cur_obj.notes or {}
table.insert(cur_obj.notes, state.last_doc_item)
elseif kind == 'type' then
cur_obj.desc = parsed.desc
parsed.desc = nil
parsed.kind = nil
cur_obj.type = parsed
elseif kind == 'alias' then
state.cur_obj = {
kind = 'alias',
desc = parsed.desc,
}
elseif kind == 'enum' then
-- TODO
state.doc_lines = nil
elseif
vim.tbl_contains({
'diagnostic',
'cast',
'overload',
'meta',
}, kind)
then
-- Ignore
return
elseif kind == 'generic' then
cur_obj.generics = cur_obj.generics or {}
cur_obj.generics[parsed.name] = parsed.type or 'any'
else
error('Unhandled' .. vim.inspect(parsed))
end
end
--- @param fun nvim.luacats.parser.fun
--- @return nvim.luacats.parser.field
local function fun2field(fun)
local parts = { 'fun(' }
local params = {} ---@type string[]
for _, p in ipairs(fun.params or {}) do
params[#params + 1] = string.format('%s: %s', p.name, p.type)
end
parts[#parts + 1] = table.concat(params, ', ')
parts[#parts + 1] = ')'
if fun.returns then
parts[#parts + 1] = ': '
local tys = {} --- @type string[]
for _, p in ipairs(fun.returns) do
tys[#tys + 1] = p.type
end
parts[#parts + 1] = table.concat(tys, ', ')
end
return {
name = fun.name,
type = table.concat(parts, ''),
access = fun.access,
desc = fun.desc,
nodoc = fun.nodoc,
}
end
--- Function to normalize known form for declaring functions and normalize into a more standard
--- form.
--- @param line string
--- @return string
local function filter_decl(line)
-- M.fun = vim._memoize(function(...)
-- ->
-- function M.fun(...)
line = line:gsub('^local (.+) = memoize%([^,]+, function%((.*)%)$', 'local function %1(%2)')
line = line:gsub('^(.+) = memoize%([^,]+, function%((.*)%)$', 'function %1(%2)')
return line
end
--- @param line string
--- @param state nvim.luacats.parser.State
--- @param classes table<string,nvim.luacats.parser.class>
--- @param classvars table<string,string>
--- @param has_indent boolean
local function process_lua_line(line, state, classes, classvars, has_indent)
line = filter_decl(line)
if state.cur_obj and state.cur_obj.kind == 'class' then
local nm = line:match('^local%s+([a-zA-Z0-9_]+)%s*=')
if nm then
classvars[nm] = state.cur_obj.name
end
return
end
do
local parent_tbl, sep, fun_or_meth_nm =
line:match('^function%s+([a-zA-Z0-9_]+)([.:])([a-zA-Z0-9_]+)%s*%(')
if parent_tbl then
-- Have a decl. Ensure cur_obj
state.cur_obj = state.cur_obj or {}
local cur_obj = assert(state.cur_obj)
-- Match `Class:foo` methods for defined classes
local class = classvars[parent_tbl]
if class then
--- @cast cur_obj nvim.luacats.parser.fun
cur_obj.name = fun_or_meth_nm
cur_obj.class = class
cur_obj.classvar = parent_tbl
-- Add self param to methods
if sep == ':' then
cur_obj.params = cur_obj.params or {}
table.insert(cur_obj.params, 1, {
name = 'self',
type = class,
})
end
-- Add method as the field to the class
local cls = classes[class]
local field = fun2field(cur_obj)
field.classvar = cur_obj.classvar
table.insert(cls.fields, field)
return
end
-- Match `M.foo`
if cur_obj and parent_tbl == cur_obj.modvar then
cur_obj.name = fun_or_meth_nm
return
end
end
end
do
-- Handle: `function A.B.C.foo(...)`
local fn_nm = line:match('^function%s+([.a-zA-Z0-9_]+)%s*%(')
if fn_nm then
state.cur_obj = state.cur_obj or {}
state.cur_obj.name = fn_nm
return
end
end
do
-- Handle: `M.foo = {...}` where `M` is the modvar
local parent_tbl, tbl_nm = line:match('([a-zA-Z_]+)%.([a-zA-Z0-9_]+)%s*=')
if state.cur_obj and parent_tbl and parent_tbl == state.cur_obj.modvar then
state.cur_obj.name = tbl_nm
state.cur_obj.table = true
return
end
end
do
-- Handle: `foo = {...}`
local tbl_nm = line:match('^([a-zA-Z0-9_]+)%s*=')
if tbl_nm and not has_indent then
state.cur_obj = state.cur_obj or {}
state.cur_obj.name = tbl_nm
state.cur_obj.table = true
return
end
end
do
-- Handle: `vim.foo = {...}`
local tbl_nm = line:match('^(vim%.[a-zA-Z0-9_]+)%s*=')
if state.cur_obj and tbl_nm and not has_indent then
state.cur_obj.name = tbl_nm
state.cur_obj.table = true
return
end
end
if state.cur_obj then
if line:find('^%s*%-%- luacheck:') then
state.cur_obj = nil
elseif line:find('^%s*local%s+') then
state.cur_obj = nil
elseif line:find('^%s*return%s+') then
state.cur_obj = nil
elseif line:find('^%s*[a-zA-Z_.]+%(%s+') then
state.cur_obj = nil
end
end
end
--- Determine the table name used to export functions of a module
--- Usually this is `M`.
--- @param str string
--- @return string?
local function determine_modvar(str)
local modvar --- @type string?
for line in vim.gsplit(str, '\n') do
do
--- @type string?
local m = line:match('^return%s+([a-zA-Z_]+)')
if m then
modvar = m
end
end
do
--- @type string?
local m = line:match('^return%s+setmetatable%(([a-zA-Z_]+),')
if m then
modvar = m
end
end
end
return modvar
end
--- @param obj nvim.luacats.parser.obj
--- @param funs nvim.luacats.parser.fun[]
--- @param classes table<string,nvim.luacats.parser.class>
--- @param briefs string[]
--- @param uncommitted nvim.luacats.parser.obj[]
local function commit_obj(obj, classes, funs, briefs, uncommitted)
local commit = false
if obj.kind == 'class' then
--- @cast obj nvim.luacats.parser.class
if not classes[obj.name] then
classes[obj.name] = obj
commit = true
end
elseif obj.kind == 'alias' then
-- Just pretend
commit = true
elseif obj.kind == 'brief' then
--- @cast obj nvim.luacats.parser.brief`
briefs[#briefs + 1] = obj.desc
commit = true
else
--- @cast obj nvim.luacats.parser.fun`
if obj.name then
funs[#funs + 1] = obj
commit = true
end
end
if not commit then
table.insert(uncommitted, obj)
end
return commit
end
--- @param filename string
--- @param uncommitted nvim.luacats.parser.obj[]
-- luacheck: no unused
local function dump_uncommitted(filename, uncommitted)
local out_path = 'luacats-uncommited/' .. filename:gsub('/', '%%') .. '.txt'
if #uncommitted > 0 then
print(string.format('Could not commit %d objects in %s', #uncommitted, filename))
vim.fn.mkdir(vim.fs.dirname(out_path), 'p')
local f = assert(io.open(out_path, 'w'))
for i, x in ipairs(uncommitted) do
f:write(i)
f:write(': ')
f:write(vim.inspect(x))
f:write('\n')
end
f:close()
else
vim.fn.delete(out_path)
end
end
local M = {}
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(str)
--- @type string
local module = filename:match('.*/lua/([a-z_][a-z0-9_/]+)%.lua') or filename
module = module:gsub('/', '.')
local classvars = {} --- @type table<string,string>
local state = {} --- @type nvim.luacats.parser.State
-- Keep track of any partial objects we don't commit
local uncommitted = {} --- @type nvim.luacats.parser.obj[]
for line in vim.gsplit(str, '\n') do
local has_indent = line:match('^%s+') ~= nil
line = vim.trim(line)
if vim.startswith(line, '---') then
process_doc_line(line, state)
else
add_doc_lines_to_obj(state)
if state.cur_obj then
state.cur_obj.modvar = mod_return
state.cur_obj.module = module
end
process_lua_line(line, state, classes, classvars, has_indent)
-- Commit the object
local cur_obj = state.cur_obj
if cur_obj then
if not commit_obj(cur_obj, classes, funs, briefs, uncommitted) then
--- @diagnostic disable-next-line:inject-field
cur_obj.line = line
end
end
state = {}
end
end
-- dump_uncommitted(filename, uncommitted)
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

6
src/gen/preload.lua Normal file
View File

@@ -0,0 +1,6 @@
local srcdir = table.remove(arg, 1)
package.path = (srcdir .. '/src/?.lua;') .. (srcdir .. '/runtime/lua/?.lua;') .. package.path
arg[0] = table.remove(arg, 1)
return loadfile(arg[0])()

View File

@@ -1,8 +1,12 @@
local srcdir = table.remove(arg, 1)
local nlualib = table.remove(arg, 1)
local gendir = table.remove(arg, 1)
package.path = srcdir .. '/src/nvim/?.lua;' .. srcdir .. '/runtime/lua/?.lua;' .. package.path
package.path = gendir .. '/?.lua;' .. package.path
package.path = (srcdir .. '/src/?.lua;')
.. (srcdir .. '/runtime/lua/?.lua;')
.. (gendir .. '/?.lua;')
.. package.path
_G.vim = require 'vim.shared'
_G.vim.inspect = require 'vim.inspect'
package.cpath = package.cpath .. ';' .. nlualib

399
src/gen/util.lua Normal file
View File

@@ -0,0 +1,399 @@
-- TODO(justinmk): move most of this to `vim.text`.
local fmt = string.format
--- @class nvim.util.MDNode
--- @field [integer] nvim.util.MDNode
--- @field type string
--- @field text? string
local INDENTATION = 4
local NBSP = string.char(160)
local M = {}
local function contains(t, xs)
return vim.tbl_contains(xs, t)
end
-- Map of api_level:version, by inspection of:
-- :lua= vim.mpack.decode(vim.fn.readfile('test/functional/fixtures/api_level_9.mpack','B')).version
M.version_level = {
[13] = '0.11.0',
[12] = '0.10.0',
[11] = '0.9.0',
[10] = '0.8.0',
[9] = '0.7.0',
[8] = '0.6.0',
[7] = '0.5.0',
[6] = '0.4.0',
[5] = '0.3.2',
[4] = '0.3.0',
[3] = '0.2.1',
[2] = '0.2.0',
[1] = '0.1.0',
}
--- @param txt string
--- @param srow integer
--- @param scol integer
--- @param erow? integer
--- @param ecol? integer
--- @return string
local function slice_text(txt, srow, scol, erow, ecol)
local lines = vim.split(txt, '\n')
if srow == erow then
return lines[srow + 1]:sub(scol + 1, ecol)
end
if erow then
-- Trim the end
for _ = erow + 2, #lines do
table.remove(lines, #lines)
end
end
-- Trim the start
for _ = 1, srow do
table.remove(lines, 1)
end
lines[1] = lines[1]:sub(scol + 1)
lines[#lines] = lines[#lines]:sub(1, ecol)
return table.concat(lines, '\n')
end
--- @param text string
--- @return nvim.util.MDNode
local function parse_md_inline(text)
local parser = vim.treesitter.languagetree.new(text, 'markdown_inline')
local root = parser:parse(true)[1]:root()
--- @param node TSNode
--- @return nvim.util.MDNode?
local function extract(node)
local ntype = node:type()
if ntype:match('^%p$') then
return
end
--- @type table<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.util.MDNode
local function parse_md(text)
local parser = vim.treesitter.languagetree.new(text, 'markdown', {
injections = { markdown = '' },
})
local root = parser:parse(true)[1]:root()
local EXCLUDE_TEXT_TYPE = {
list = true,
list_item = true,
section = true,
document = true,
fenced_code_block = true,
fenced_code_block_delimiter = true,
}
--- @param node TSNode
--- @return nvim.util.MDNode?
local function extract(node)
local ntype = node:type()
if ntype:match('^%p$') or contains(ntype, { 'block_continuation' }) then
return
end
--- @type table<any,any>
local ret = { type = ntype }
if not EXCLUDE_TEXT_TYPE[ntype] then
ret.text = vim.treesitter.get_node_text(node, text)
end
if ntype == 'inline' then
ret = parse_md_inline(ret.text)
end
for child, child_field in node:iter_children() do
local e = extract(child)
if child_field then
ret[child_field] = e
else
table.insert(ret, e)
end
end
return ret
end
return extract(root) or {}
end
--- Prefixes each line in `text`.
---
--- Does not wrap, not important for "meta" files? (You probably want md_to_vimdoc instead.)
---
--- @param text string
--- @param prefix_ string
function M.prefix_lines(prefix_, text)
local r = ''
for _, l in ipairs(vim.split(text, '\n', { plain = true })) do
r = r .. vim.trim(prefix_ .. l) .. '\n'
end
return r
end
--- @param x string
--- @param start_indent integer
--- @param indent integer
--- @param text_width integer
--- @return string
function M.wrap(x, start_indent, indent, text_width)
local words = vim.split(vim.trim(x), '%s+')
local parts = { string.rep(' ', start_indent) } --- @type string[]
local count = indent
for i, w in ipairs(words) do
if count > indent and count + #w > text_width - 1 then
parts[#parts + 1] = '\n'
parts[#parts + 1] = string.rep(' ', indent)
count = indent
elseif i ~= 1 then
parts[#parts + 1] = ' '
count = count + 1
end
count = count + #w
parts[#parts + 1] = w
end
return (table.concat(parts):gsub('%s+\n', '\n'):gsub('\n+$', ''))
end
--- @param node nvim.util.MDNode
--- @param start_indent integer
--- @param indent integer
--- @param text_width integer
--- @param level integer
--- @return string[]
local function render_md(node, start_indent, indent, text_width, level, is_list)
local parts = {} --- @type string[]
-- For debugging
local add_tag = false
-- local add_tag = true
local ntype = node.type
if add_tag then
parts[#parts + 1] = '<' .. ntype .. '>'
end
if ntype == 'text' then
parts[#parts + 1] = node.text
elseif ntype == 'html_tag' then
error('html_tag: ' .. node.text)
elseif ntype == 'inline_link' then
vim.list_extend(parts, { '*', node[1].text, '*' })
elseif ntype == 'shortcut_link' then
if node[1].text:find('^<.*>$') then
parts[#parts + 1] = node[1].text
elseif node[1].text:find('^%d+$') then
vim.list_extend(parts, { '[', node[1].text, ']' })
else
vim.list_extend(parts, { '|', node[1].text, '|' })
end
elseif ntype == 'backslash_escape' then
parts[#parts + 1] = node.text
elseif ntype == 'emphasis' then
parts[#parts + 1] = node.text:sub(2, -2)
elseif ntype == 'code_span' then
vim.list_extend(parts, { '`', node.text:sub(2, -2):gsub(' ', NBSP), '`' })
elseif ntype == 'inline' then
if #node == 0 then
local text = assert(node.text)
parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width)
else
for _, child in ipairs(node) do
vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1))
end
end
elseif ntype == 'paragraph' then
local pparts = {}
for _, child in ipairs(node) do
vim.list_extend(pparts, render_md(child, start_indent, indent, text_width, level + 1))
end
parts[#parts + 1] = M.wrap(table.concat(pparts), start_indent, indent, text_width)
parts[#parts + 1] = '\n'
elseif ntype == 'code_fence_content' then
local lines = vim.split(node.text:gsub('\n%s*$', ''), '\n')
local cindent = indent + INDENTATION
if level > 3 then
-- The tree-sitter markdown parser doesn't parse the code blocks indents
-- correctly in lists. Fudge it!
lines[1] = ' ' .. lines[1] -- ¯\_(ツ)_/¯
cindent = indent - level
local _, initial_indent = lines[1]:find('^%s*')
initial_indent = initial_indent + cindent
if initial_indent < indent then
cindent = indent - INDENTATION
end
end
for _, l in ipairs(lines) do
if #l > 0 then
parts[#parts + 1] = string.rep(' ', cindent)
parts[#parts + 1] = l
end
parts[#parts + 1] = '\n'
end
elseif ntype == 'fenced_code_block' then
parts[#parts + 1] = '>'
for _, child in ipairs(node) do
if child.type == 'info_string' then
parts[#parts + 1] = child.text
break
end
end
parts[#parts + 1] = '\n'
for _, child in ipairs(node) do
if child.type ~= 'info_string' then
vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1))
end
end
parts[#parts + 1] = '<\n'
elseif ntype == 'html_block' then
local text = node.text:gsub('^<pre>help', '')
text = text:gsub('</pre>%s*$', '')
parts[#parts + 1] = text
elseif ntype == 'list_marker_dot' then
parts[#parts + 1] = node.text
elseif contains(ntype, { 'list_marker_minus', 'list_marker_star' }) then
parts[#parts + 1] = ''
elseif ntype == 'list_item' then
parts[#parts + 1] = string.rep(' ', indent)
local offset = node[1].type == 'list_marker_dot' and 3 or 2
for i, child in ipairs(node) do
local sindent = i <= 2 and 0 or (indent + offset)
vim.list_extend(
parts,
render_md(child, sindent, indent + offset, text_width, level + 1, true)
)
end
else
if node.text then
error(fmt('cannot render:\n%s', vim.inspect(node)))
end
for i, child in ipairs(node) do
local start_indent0 = i == 1 and start_indent or indent
vim.list_extend(
parts,
render_md(child, start_indent0, indent, text_width, level + 1, is_list)
)
if ntype ~= 'list' and i ~= #node then
if (node[i + 1] or {}).type ~= 'list' then
parts[#parts + 1] = '\n'
end
end
end
end
if add_tag then
parts[#parts + 1] = '</' .. ntype .. '>'
end
return parts
end
--- @param text_width integer
local function align_tags(text_width)
--- @param line string
--- @return string
return function(line)
local tag_pat = '%s*(%*.+%*)%s*$'
local tags = {}
for m in line:gmatch(tag_pat) do
table.insert(tags, m)
end
if #tags > 0 then
line = line:gsub(tag_pat, '')
local tags_str = ' ' .. table.concat(tags, ' ')
--- @type integer
local conceal_offset = select(2, tags_str:gsub('%*', '')) - 2
local pad = string.rep(' ', text_width - #line - #tags_str + conceal_offset)
return line .. pad .. tags_str
end
return line
end
end
--- @param text string
--- @param start_indent integer
--- @param indent integer
--- @param is_list? boolean
--- @return string
function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list)
-- Add an extra newline so the parser can properly capture ending ```
local parsed = parse_md(text .. '\n')
local ret = render_md(parsed, start_indent, indent, text_width, 0, is_list)
local lines = vim.split(table.concat(ret):gsub(NBSP, ' '), '\n')
lines = vim.tbl_map(align_tags(text_width), lines)
local s = table.concat(lines, '\n')
-- Reduce whitespace in code-blocks
s = s:gsub('\n+%s*>([a-z]+)\n', ' >%1\n')
s = s:gsub('\n+%s*>\n?\n', ' >\n')
return s
end
return M

View File

@@ -292,7 +292,7 @@ set(UI_METADATA ${PROJECT_BINARY_DIR}/ui_metadata.mpack)
set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GENERATOR_DIR ${PROJECT_SOURCE_DIR}/src/gen)
set(GEN_EVAL_TOUCH ${TOUCHES_DIR}/gen_doc_eval)
set(LUAJIT_RUNTIME_DIR ${DEPS_PREFIX}/share/luajit-2.1/jit)
set(NVIM_RUNTIME_DIR ${PROJECT_SOURCE_DIR}/runtime)
@@ -306,7 +306,8 @@ set(EX_CMDS_GENERATOR ${GENERATOR_DIR}/gen_ex_cmds.lua)
set(FUNCS_GENERATOR ${GENERATOR_DIR}/gen_eval.lua)
set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua)
set(GENERATOR_HASHY ${GENERATOR_DIR}/hashy.lua)
set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload.lua)
set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload_nlua.lua)
set(NVIM_LUA_PRELOAD ${GENERATOR_DIR}/preload.lua)
set(HEADER_GENERATOR ${GENERATOR_DIR}/gen_declarations.lua)
set(OPTIONS_GENERATOR ${GENERATOR_DIR}/gen_options.lua)
@@ -514,6 +515,9 @@ add_custom_command(
set(LUA_GEN ${LUA_GEN_PRG} ${GENERATOR_PRELOAD} ${PROJECT_SOURCE_DIR} $<TARGET_FILE:nlua0> ${PROJECT_BINARY_DIR})
set(LUA_GEN_DEPS ${GENERATOR_PRELOAD} $<TARGET_FILE:nlua0>)
# Like LUA_GEN but includes also vim.fn, vim.api, vim.uv, etc
set(NVIM_LUA $<TARGET_FILE:nvim_bin> -u NONE -l ${NVIM_LUA_PRELOAD} ${PROJECT_SOURCE_DIR})
# NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers
# NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources
# These lists must be mutually exclusive.
@@ -937,12 +941,12 @@ file(GLOB LUA_SOURCES CONFIGURE_DEPENDS
)
add_target(doc-vim
COMMAND $<TARGET_FILE:nvim_bin> -u NONE -l scripts/gen_vimdoc.lua
COMMAND ${NVIM_LUA} src/gen/gen_vimdoc.lua
DEPENDS
nvim
${API_SOURCES}
${LUA_SOURCES}
${PROJECT_SOURCE_DIR}/scripts/gen_vimdoc.lua
${PROJECT_SOURCE_DIR}/src/gen/gen_vimdoc.lua
${NVIM_RUNTIME_DIR}/doc/api.txt
${NVIM_RUNTIME_DIR}/doc/diagnostic.txt
${NVIM_RUNTIME_DIR}/doc/lsp.txt
@@ -951,11 +955,11 @@ add_target(doc-vim
)
add_target(doc-eval
COMMAND $<TARGET_FILE:nvim_bin> -u NONE -l ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
COMMAND ${NVIM_LUA} ${PROJECT_SOURCE_DIR}/src/gen/gen_eval_files.lua
DEPENDS
nvim
${FUNCS_METADATA}
${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
${PROJECT_SOURCE_DIR}/src/gen/gen_eval_files.lua
${PROJECT_SOURCE_DIR}/src/nvim/eval.lua
${PROJECT_SOURCE_DIR}/src/nvim/options.lua
${PROJECT_SOURCE_DIR}/src/nvim/vvars.lua
@@ -966,7 +970,7 @@ add_custom_target(doc)
add_dependencies(doc doc-vim doc-eval)
add_target(lintdoc
COMMAND $<TARGET_FILE:nvim_bin> -u NONE -l scripts/lintdoc.lua
COMMAND ${NVIM_LUA} scripts/lintdoc.lua
DEPENDS ${DOCFILES}
CUSTOM_COMMAND_ARGS USES_TERMINAL)
add_dependencies(lintdoc nvim)

View File

@@ -1,6 +1,6 @@
// Undefined DEFINE_FUNC_ATTRIBUTES and undefined DEFINE_EMPTY_ATTRIBUTES
// leaves file with untouched FUNC_ATTR_* macros. This variant is used for
// scripts/gen_declarations.lua.
// src/gen/gen_declarations.lua.
//
// Empty macros are used for *.c files.
// (undefined DEFINE_FUNC_ATTRIBUTES and defined DEFINE_EMPTY_ATTRIBUTES)