diff options
author | Lewis Russell <lewis6991@gmail.com> | 2023-07-26 09:50:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-26 09:50:54 +0100 |
commit | fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2 (patch) | |
tree | fca392972189b68ddeb65722463b464f4440024b /scripts | |
parent | b8b77820371978a5f937ccc0db356574ae33371b (diff) | |
download | rneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.tar.gz rneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.tar.bz2 rneovim-fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2.zip |
feat(lua): typing for vim.fn.* (#24473)
Problem:
No LSP information for `vim.fn.*`
Solution:
Add meta file for `vim.fn.*`.
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/gen_vimfn_types.lua | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/scripts/gen_vimfn_types.lua b/scripts/gen_vimfn_types.lua new file mode 100755 index 0000000000..f0dfd0665c --- /dev/null +++ b/scripts/gen_vimfn_types.lua @@ -0,0 +1,244 @@ +#!/usr/bin/env -S nvim -l + +--- @class vim.EvalFn2 : vim.EvalFn +--- @field signature string +--- @field desc string[] +--- @field params {[1]: string, [2]: string}[] + +--- @param filename string +--- @return string +local function safe_read(filename) + local file, err = io.open(filename, 'r') + if not file then + error(err) + end + local content = file:read('*a') + io.close(file) + return content +end + +local nvim_eval = require'src/nvim/eval' + +local funcs = nvim_eval.funcs --[[@as table<string,vim.EvalFn2>]] + +local LUA_KEYWORDS = { + ['and'] = true, + ['end'] = true, + ['function'] = true, + ['or'] = true, + ['if'] = true, + ['while'] = true, + ['repeat'] = true +} + +local SOURCES = { + { + path = 'runtime/doc/builtin.txt', + from = '^2. Details', + to = '==========', + }, + { + path = 'runtime/doc/sign.txt', + from = '^3. Functions', + to = 'vim:' + }, + { + path = 'runtime/doc/testing.txt', + from = '^3. Assert functions', + to = 'vim:' + } +} + +local ARG_NAME_TYPES = { + col = 'integer', + nosuf = 'boolean', + dir = 'string', + mode = 'string', + width = 'integer', + height = 'integer', + timeout = 'integer', + libname = 'string', + funcname = 'string', + end_ = 'integer', + file = 'string', + flags = 'string', + fname = 'integer', + idx = 'integer', + lnum = 'integer', + mods = 'string', + name = 'string', + nr = 'integer', + options = 'table', + opts = 'table', + path = 'string', + regname = 'string', + silent = 'boolean', + string = 'string', + tabnr = 'integer', + varname = 'string', + winid = 'integer', + winnr = 'integer', +} + +local function process_source(source) + local src_txt = safe_read(source.path) + + --- @type string[] + local src_lines = vim.split(src_txt, '\n', { plain = true }) + + local s = 0 + for i, l in ipairs(src_lines) do + if l:match(source.from) then + s = i+1 + end + end + + local lines = {} --- @type string[] + local last_f --- @type string? + + for i = s, #src_lines do + local l = src_lines[i] + if not l or l:match(source.to) then + break + end + local f = l:match('^([a-z][a-zA-Z0-9_]*)%(') + if f then + if last_f then + funcs[last_f].desc = lines + end + last_f = f + local sig = l:match('[^)]+%)') + local params = {} --- @type string[] + if sig then + for param in string.gmatch(sig, '{([a-z][a-zA-Z0-9_]*)}') do + local t = ARG_NAME_TYPES[param] or 'any' + params[#params+1] = {param, t} + end + else + print('error parsing', l) + end + + funcs[last_f].signature = sig + funcs[last_f].params = params + + lines = {} + else + lines[#lines+1] = l:gsub('^(<?)\t\t', '%1'):gsub('\t', ' ') + end + end + + if last_f then + funcs[last_f].desc = lines + end +end + +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(function(v) + return v[1] + end, params), ', ') + end + + if LUA_KEYWORDS[f] then + return string.format('vim.fn[\'%s\'] = function(%s) end', f, param_str) + else + return string.format('function vim.fn.%s(%s) end', f, param_str) + end +end + +--- Uniquify names +--- Fix any names that are lua keywords +--- @param fun vim.EvalFn2 +local function process_params(fun) + if not fun.params then + return + end + + local seen = {} --- @type table<string,true> + local sfx = 1 + + for _, p in ipairs(fun.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 +end + +--- @param funname string +--- @param fun vim.EvalFn2 +--- @param write fun(line: string) +local function render_fun(funname, fun, write) + if fun.deprecated then + write('') + write('--- @deprecated') + for _, l in ipairs(fun.deprecated) do + write('--- '.. l) + end + write(render_fun_sig(funname, true)) + return + end + + if fun.desc and fun.signature then + write('') + for _, l in ipairs(fun.desc) do + write('--- '.. l:gsub('@', '\\@')) + end + + local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0 + + for i, param in ipairs(fun.params) do + if i <= req_args then + write('--- @param '..param[1]..' '..param[2]) + else + write('--- @param '..param[1]..'? '..param[2]) + end + end + if fun.returns ~= false then + write('--- @return '..(fun.returns or 'any')) + end + write(render_fun_sig(funname, fun.params)) + return + end + + print('no doc for', funname) +end + +local function main(outfile) + local o = assert(io.open(outfile, 'w')) + + local function write(l) + local l1 = l:gsub('%s+$', '') + o:write(l1) + o:write('\n') + end + + for _, source in ipairs(SOURCES) do + process_source(source) + end + + --- @type string[] + local fnames = vim.tbl_keys(funcs) + table.sort(fnames) + + write('--- @meta') + write('-- THIS FILE IS GENERATED') + write('-- DO NOT EDIT') + + for _, f in ipairs(fnames) do + local fun = funcs[f] + process_params(fun) + render_fun(f, fun, write) + end +end + +main('runtime/lua/vim/_meta/vimfn.lua') + |