From 3ebfa2a3cbbed848667fe26c16973ce7e43a04e4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 17 Apr 2026 18:14:14 +0200 Subject: [PATCH] feat(vimfn): use Lua for more excmds/vimfns Problem: Too much boilerplate needed to use Lua to impl an excmd or f_xx function. Solution: - Add `nlua_call_vimfn` which takes the args typval, executes Lua, and returns a typval. - refactor(excmd): lua impl for :log, :lsp --- runtime/doc/dev_arch.txt | 24 ++ runtime/lua/vim/_core/ex_cmd.lua | 20 +- runtime/lua/vim/_core/server.lua | 28 ++- runtime/lua/vim/_core/util.lua | 14 +- runtime/lua/vim/_meta/api_keysets_extra.lua | 2 +- scripts/linterrcodes.lua | 2 +- src/nvim/eval/funcs.c | 46 +--- src/nvim/eval/userfunc.c | 4 +- src/nvim/ex_docmd.c | 32 +-- src/nvim/lua/executor.c | 259 +++++++++++++------- src/nvim/types_defs.h | 3 +- 11 files changed, 253 insertions(+), 181 deletions(-) diff --git a/runtime/doc/dev_arch.txt b/runtime/doc/dev_arch.txt index 7604497d84..a7c4e613b0 100644 --- a/runtime/doc/dev_arch.txt +++ b/runtime/doc/dev_arch.txt @@ -192,6 +192,30 @@ be "fast". For example, commit 3940c435e405 fixed such a bug with Common examples of non-fast code: regexp matching, wildcard expansion, expression evaluation. +============================================================================== +Internal practices and patterns *dev-internals-howto* + +------------------------------------------------------------------------------ +How to add a new Ex cmd or "vimfn" function ("f_xx"/"builtin"/"eval") + +To implement an Ex cmd or f_xx function, choose from one of three ways (in +order of preference): + +1. Full Lua: the implementation lives in entirely in Lua. This has + a performance benefit: the Vimscript <=> Lua bridge is skipped when the + `vim.fn.xx()` function is called from Lua. Examples: + - `f_hostname` +2. Partial Lua: the C implementation calls `nlua_call_vimfn` or + `nlua_call_excmd`. (Or in rare cases: `nlua_exec` / `NLUA_EXEC_STATIC`). + Examples: + - `ex_log` + - `ex_lsp` + - `f_serverlist` +3. Legacy way: implement the function entirely in C. + Examples: + - `f_chansend` + - `f_flatten` + - `f_searchpos` ============================================================================== The event-loop *event-loop* diff --git a/runtime/lua/vim/_core/ex_cmd.lua b/runtime/lua/vim/_core/ex_cmd.lua index a9a7a588ed..3c58dc5ddd 100644 --- a/runtime/lua/vim/_core/ex_cmd.lua +++ b/runtime/lua/vim/_core/ex_cmd.lua @@ -2,6 +2,10 @@ local api = vim.api local fs = vim.fs local util = require('vim._core.util') +--- Parsed ex command arguments for builtin commands, passed from C via `nlua_call_excmd`. +--- Inherits fields from user command args: args, bang, line1, line2, range, count, reg, smods. +--- @class vim._core.ExCmdArgs : vim.api.keyset.create_user_command.command_args + local M = {} --- @param msg string @@ -151,9 +155,9 @@ local actions = { local available_subcmds = vim.tbl_keys(actions) --- Implements command: `:lsp {subcmd} {name}?`. ---- @param args string -M.ex_lsp = function(args) - local fargs = api.nvim_parse_cmd('lsp ' .. args, {}).args +--- @param eap vim._core.ExCmdArgs +M.ex_lsp = function(eap) + local fargs = api.nvim_parse_cmd('lsp ' .. eap.args, {}).args if not fargs then return end @@ -192,11 +196,11 @@ end local log_dir = vim.fn.stdpath('log') --- Implements command: `:log {file}`. ---- @param filename string ---- @param mods string -M.ex_log = function(filename, mods) +--- @param eap vim._core.ExCmdArgs +M.ex_log = function(eap) + local filename = eap.args if filename == '' then - util.wrapped_edit(log_dir, mods) + util.wrapped_edit(log_dir, eap.smods) else local path --- @type string -- Special case for NVIM_LOG_FILE @@ -210,7 +214,7 @@ M.ex_log = function(filename, mods) echo_err(("No such log file: '%s'"):format(path)) return end - util.wrapped_edit(path, mods) + util.wrapped_edit(path, eap.smods) vim.cmd.normal { 'G', bang = true } end end diff --git a/runtime/lua/vim/_core/server.lua b/runtime/lua/vim/_core/server.lua index 14f942459d..54d8d104f0 100644 --- a/runtime/lua/vim/_core/server.lua +++ b/runtime/lua/vim/_core/server.lua @@ -2,36 +2,40 @@ local M = {} ---- Called by builtin serverlist(). Returns all running servers in stdpath("run"). +--- Called by builtin serverlist(). Returns the combined server list (own + peers). --- ---- - TODO: track TCP servers, somehow. ---- - TODO: support Windows named pipes. ---- ---- @param listed string[] Already listed servers ---- @return string[] # List of servers found on the current machine in stdpath("run"). -function M.serverlist(listed) +--- @param opts? table Options: +--- - opts.peer is true, also discover peer servers. +--- @param addrs string[] Internal ("own") addresses, from `server_address_list`. +--- @return string[] # Combined list of servers (own + peers). +function M.serverlist(opts, addrs) + if type(opts) ~= 'table' or not opts.peer then + return addrs + end + + -- Discover peer servers in stdpath("run"). + -- TODO: track TCP servers, somehow. + -- TODO: support Windows named pipes. local root = vim.fs.normalize(vim.fn.stdpath('run') .. '/..') local socket_paths = vim.fs.find(function(name, _) return name:match('nvim.*') end, { path = root, type = 'socket', limit = math.huge }) - local found = {} ---@type string[] for _, socket in ipairs(socket_paths) do - -- Don't list servers twice - if not vim.list_contains(listed, socket) then + if not vim.list_contains(addrs, socket) then local ok, chan = pcall(vim.fn.sockconnect, 'pipe', socket, { rpc = true }) if ok and chan then -- Check that the server is responding -- TODO: do we need a timeout or error handling here? if vim.fn.rpcrequest(chan, 'nvim_get_chan_info', 0).id then - table.insert(found, socket) + table.insert(addrs, socket) end vim.fn.chanclose(chan) end end end - return found + return addrs end return M diff --git a/runtime/lua/vim/_core/util.lua b/runtime/lua/vim/_core/util.lua index 973cf1da2d..29ba8c5720 100644 --- a/runtime/lua/vim/_core/util.lua +++ b/runtime/lua/vim/_core/util.lua @@ -62,12 +62,14 @@ end --- :edit, but it respects commands like :hor, :vert, :tab, etc. --- @param file string ---- @param mods_str string -function M.wrapped_edit(file, mods_str) - local cmdline = table.concat({ mods_str, 'edit' }, ' ') - local mods = vim.api.nvim_parse_cmd(cmdline, {}).mods - --- @diagnostic disable-next-line: need-check-nil - if mods.tab > 0 or mods.split ~= '' or mods.horizontal or mods.vertical then +--- @param mods string|vim.api.keyset.cmd_mods Modifier string ("vertical") or structured mods table. +function M.wrapped_edit(file, mods) + assert(mods) + if type(mods) == 'string' then + mods = vim.api.nvim_parse_cmd(mods .. ' edit', {}).mods --[[@as vim.api.keyset.cmd_mods]] + end + --- @cast mods vim.api.keyset.cmd_mods + if (mods.tab or 0) > 0 or (mods.split or '') ~= '' or mods.horizontal or mods.vertical then local buf = M.get_buf_by_name(file) if buf == nil then buf = vim.api.nvim_create_buf(true, false) diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index 3b8461892b..7714a27e93 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -111,7 +111,7 @@ error('Cannot require a meta file') --- --- Command modifiers in a structured format. Has the same structure as the --- "mods" key of |nvim_parse_cmd()|. ---- @field smods table +--- @field smods vim.api.keyset.cmd_mods --- @class vim.api.keyset.command_info --- @field name string diff --git a/scripts/linterrcodes.lua b/scripts/linterrcodes.lua index 578ff16a01..e6767fbeba 100644 --- a/scripts/linterrcodes.lua +++ b/scripts/linterrcodes.lua @@ -48,7 +48,7 @@ local dup_allowed = { E509 = 2, E5101 = 2, E5102 = 2, - E5108 = 4, + E5108 = 6, E5111 = 2, E513 = 2, E521 = 2, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index a5101d1cb8..3fcc7ab43d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -403,7 +403,6 @@ static void lua_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) ADD_C(args, vim_to_object(tv, &arena, false)); } - // `func_lua` is the function name (e.g. "f_hostname") in `_core/vimfn.lua`. char buf[256]; snprintf(buf, sizeof(buf), "return require('vim._core.vimfn').%s(...)", fptr.func_lua); @@ -6376,42 +6375,21 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) size_t n; char **addrs = server_address_list(&n); - Arena arena = ARENA_EMPTY; - // Passed to vim._core.server.serverlist() to avoid duplicates - Array addrs_arr = arena_array(&arena, n); - - // Copy addrs into a linked list. - list_T *const l = tv_list_alloc_ret(rettv, (ptrdiff_t)n); + // Get the (internal) address list as a typval to pass to Lua. + typval_T addrs_tv; + tv_list_alloc_ret(&addrs_tv, (ptrdiff_t)n); for (size_t i = 0; i < n; i++) { - tv_list_append_allocated_string(l, addrs[i]); - ADD_C(addrs_arr, CSTR_AS_OBJ(addrs[i])); + tv_list_append_allocated_string(addrs_tv.vval.v_list, addrs[i]); } - - if (!(argvars[0].v_type == VAR_DICT && tv_dict_get_bool(argvars[0].vval.v_dict, "peer", false))) { - goto cleanup; - } - - MAXSIZE_TEMP_ARRAY(args, 1); - ADD_C(args, ARRAY_OBJ(addrs_arr)); - - Error err = ERROR_INIT; - Object rv = NLUA_EXEC_STATIC("return require('vim._core.server').serverlist(...)", - args, kRetObject, - &arena, &err); - - if (ERROR_SET(&err)) { - ELOG("vim._core.serverlist failed: %s", err.msg); - goto cleanup; - } - - for (size_t i = 0; i < rv.data.array.size; i++) { - char *curr_server = rv.data.array.items[i].data.string.data; - tv_list_append_string(l, curr_server, -1); - } - -cleanup: xfree(addrs); - arena_mem_free(arena_finish(&arena)); + + // Lua handles options (e.g. {peer=true}), peer discovery, and returns combined list. + typval_T opts = argvars[0].v_type != VAR_UNKNOWN ? argvars[0] : (typval_T){ .v_type = VAR_SPECIAL, + .vval.v_special = + kSpecialVarNull }; + typval_T lua_args[] = { opts, addrs_tv, { .v_type = VAR_UNKNOWN } }; + nlua_call_vimfn("vim._core.server", "serverlist", lua_args, rettv); + tv_clear(&addrs_tv); } /// "serverstart()" function diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 1ffd404c71..a16ff39e6f 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -1741,7 +1741,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t if (len > 0) { error = FCERR_NONE; argv_add_base(funcexe->fe_basetv, &argvars, &argcount, argv, &argv_base); - nlua_typval_call(funcname, (size_t)len, argvars, argcount, rettv); + nlua_call_typval(funcname, (size_t)len, argvars, argcount, rettv); } else { // v:lua was called directly; show its name in the emsg XFREE_CLEAR(name); @@ -1827,7 +1827,7 @@ int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv) typval_T argvars[1]; argvars[0].v_type = VAR_UNKNOWN; - nlua_typval_call(funcname, len, argvars, 0, rettv); + nlua_call_typval(funcname, len, argvars, 0, rettv); return OK; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 3dc9df8ef0..34aef2e476 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8294,41 +8294,13 @@ static void ex_terminal(exarg_T *eap) /// ":log {name}" static void ex_log(exarg_T *eap) { - Error err = ERROR_INIT; - MAXSIZE_TEMP_ARRAY(args, 2); - - char mods[1024]; - size_t mods_len = 0; - mods[0] = NUL; - - if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) { - bool multi_mods = false; - mods_len = add_win_cmd_modifiers(mods, &cmdmod, &multi_mods); - assert(mods_len < sizeof(mods)); - } - ADD_C(args, CSTR_AS_OBJ(eap->arg)); - ADD_C(args, STRING_OBJ(((String){ .data = mods, .size = mods_len }))); - - NLUA_EXEC_STATIC("require'vim._core.ex_cmd'.ex_log(...)", args, kRetNilBool, NULL, &err); - if (ERROR_SET(&err)) { - emsg_multiline(err.msg, "lua_error", HLF_E, true); - } - api_clear_error(&err); + nlua_call_excmd("vim._core.ex_cmd", "ex_log", eap, &cmdmod); } /// ":lsp {subcmd} {clients}" static void ex_lsp(exarg_T *eap) { - Error err = ERROR_INIT; - MAXSIZE_TEMP_ARRAY(args, 1); - - ADD_C(args, CSTR_AS_OBJ(eap->arg)); - - NLUA_EXEC_STATIC("require'vim._core.ex_cmd'.ex_lsp(...)", args, kRetNilBool, NULL, &err); - if (ERROR_SET(&err)) { - emsg_multiline(err.msg, "lua_error", HLF_E, true); - } - api_clear_error(&err); + nlua_call_excmd("vim._core.ex_cmd", "ex_lsp", eap, &cmdmod); } /// ":fclose" diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 4cd81e365d..e0e3f5ef9f 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -111,6 +111,123 @@ typedef struct { } \ } +/// Pushes cmdmod_T as a table (Lua type: `vim.api.keyset.cmd_mods`) onto the stack. +static void nlua_push_cmdmod(lua_State *lstate, const cmdmod_T *cmod) +{ + lua_newtable(lstate); + + lua_pushinteger(lstate, cmod->cmod_tab - 1); + lua_setfield(lstate, -2, "tab"); + + lua_pushinteger(lstate, cmod->cmod_verbose - 1); + lua_setfield(lstate, -2, "verbose"); + + if (cmod->cmod_split & WSP_ABOVE) { + lua_pushstring(lstate, "aboveleft"); + } else if (cmod->cmod_split & WSP_BELOW) { + lua_pushstring(lstate, "belowright"); + } else if (cmod->cmod_split & WSP_TOP) { + lua_pushstring(lstate, "topleft"); + } else if (cmod->cmod_split & WSP_BOT) { + lua_pushstring(lstate, "botright"); + } else { + lua_pushstring(lstate, ""); + } + lua_setfield(lstate, -2, "split"); + + lua_pushboolean(lstate, cmod->cmod_split & WSP_VERT); + lua_setfield(lstate, -2, "vertical"); + lua_pushboolean(lstate, cmod->cmod_split & WSP_HOR); + lua_setfield(lstate, -2, "horizontal"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_SILENT); + lua_setfield(lstate, -2, "silent"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_ERRSILENT); + lua_setfield(lstate, -2, "emsg_silent"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_UNSILENT); + lua_setfield(lstate, -2, "unsilent"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_SANDBOX); + lua_setfield(lstate, -2, "sandbox"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_NOAUTOCMD); + lua_setfield(lstate, -2, "noautocmd"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_BROWSE); + lua_setfield(lstate, -2, "browse"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_CONFIRM); + lua_setfield(lstate, -2, "confirm"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_HIDE); + lua_setfield(lstate, -2, "hide"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPALT); + lua_setfield(lstate, -2, "keepalt"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPJUMPS); + lua_setfield(lstate, -2, "keepjumps"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPMARKS); + lua_setfield(lstate, -2, "keepmarks"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPPATTERNS); + lua_setfield(lstate, -2, "keeppatterns"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_LOCKMARKS); + lua_setfield(lstate, -2, "lockmarks"); + lua_pushboolean(lstate, cmod->cmod_flags & CMOD_NOSWAPFILE); + lua_setfield(lstate, -2, "noswapfile"); +} + +/// Pushes common exarg_T fields (bang, line1, line2, …) onto a table at the top of the stack. +static void nlua_push_eap(lua_State *lstate, exarg_T *eap, const cmdmod_T *cmod) +{ + lua_pushstring(lstate, eap->arg); + lua_setfield(lstate, -2, "args"); + + lua_pushboolean(lstate, eap->forceit); + lua_setfield(lstate, -2, "bang"); + + lua_pushinteger(lstate, eap->line1); + lua_setfield(lstate, -2, "line1"); + + lua_pushinteger(lstate, eap->line2); + lua_setfield(lstate, -2, "line2"); + + lua_pushinteger(lstate, eap->addr_count); + lua_setfield(lstate, -2, "range"); + + lua_pushinteger(lstate, eap->line2); + lua_setfield(lstate, -2, "count"); + + char reg[2] = { (char)eap->regname, NUL }; + lua_pushstring(lstate, reg); + lua_setfield(lstate, -2, "reg"); + + nlua_push_cmdmod(lstate, cmod); + lua_setfield(lstate, -2, "smods"); +} + +/// Calls Lua to implement an excmd. Passes `eap` + `cmdmod` to Lua as a dict arg, which is arranged +/// to match the Lua type `vim.api.keyset.create_user_command.command_args`. +/// +/// @param module Lua module name, e.g. "vim._core.ex_cmd". +/// @param func Function name in the module, e.g. "ex_log". +/// @param eap Excmd args. +/// @param cmod Excmd mods. +void nlua_call_excmd(const char *module, const char *func, exarg_T *eap, const cmdmod_T *cmod) +{ + lua_State *const lstate = global_lstate; + + lua_getglobal(lstate, "require"); + lua_pushstring(lstate, module); + if (lua_pcall(lstate, 1, 1, 0) != 0) { + semsg("E5108: %s", lua_tostring(lstate, -1)); + lua_pop(lstate, 1); + return; + } + lua_getfield(lstate, -1, func); + lua_remove(lstate, -2); + + lua_newtable(lstate); + nlua_push_eap(lstate, eap, cmod); + + if (nlua_pcall(lstate, 1, 0)) { + semsg("E5108: %s", lua_tostring(lstate, -1)); + lua_pop(lstate, 1); + } +} + #if __has_feature(address_sanitizer) static bool nlua_track_refs = false; # define NLUA_TRACK_REFS @@ -1191,6 +1308,7 @@ static int nlua_debug(lua_State *lstate) return 0; } +/// "vim.in_fast_event()" function. int nlua_in_fast_event(lua_State *lstate) { lua_pushboolean(lstate, in_fast_callback > 0); @@ -1479,7 +1597,17 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret } } -void nlua_typval_call(const char *str, size_t len, typval_T *const args, int argcount, +/// Calls a Lua function by name with typval args. Used by v:lua.func(). +/// +/// Builds "return func(...)" and executes it via luaL_loadbuffer. +/// Converts args via nlua_push_typval, result via nlua_pop_typval. +/// +/// @param str Lua expression (function name), e.g. "my_func" or "mod.func". +/// @param len Length of str. +/// @param args typval_T arguments. +/// @param argcount Number of arguments. +/// @param ret_tv Return value (always set). +void nlua_call_typval(const char *str, size_t len, typval_T *const args, int argcount, typval_T *ret_tv) FUNC_ATTR_NONNULL_ALL { @@ -1506,6 +1634,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg } } +/// Calls a Lua completion function (stored as LuaRef) for cmdline expansion. void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv) FUNC_ATTR_NONNULL_ALL { @@ -1524,6 +1653,7 @@ void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv) nlua_pop_typval(lstate, ret_tv); } +/// Executes a Lua chunk with typval arguments and optional typval return. static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name, typval_T *const args, int argcount, bool special, typval_T *ret_tv) { @@ -1537,13 +1667,19 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name lua_State *const lstate = global_lstate; + // 1. Compile lcmd into a Lua chunk (function), push it on the stack. + // + // TODO(justinmk): Cache the chunk in LUA_REGISTRYINDEX? But one-off callers (:lua, luaeval) may + // bloat the cache. Could add a `cache:boolean` param. if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { nlua_error(lstate, _("E5107: Lua: %.*s")); return; } + // 2. Push typval args as Lua values. Stack: [chunk, arg1, ..., argN]. PUSH_ALL_TYPVALS(lstate, args, argcount, special); + // 3. Call: chunk(arg1, ..., argN). Essentially loadstring(). if (nlua_pcall(lstate, argcount, ret_tv ? 1 : 0)) { nlua_error(lstate, _("E5108: Lua: %.*s")); return; @@ -1554,6 +1690,7 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name } } +/// Execute Lua code from a growarray of lines. Used by ":lua << EOF". void nlua_exec_ga(garray_T *ga, char *name) { char *code = ga_concat_strings(ga, "\n"); @@ -1629,6 +1766,29 @@ Object nlua_exec(const String str, const char *chunkname, const Array args, LuaR return nlua_call_pop_retval(lstate, mode, arena, top, err); } +/// Calls Lua to implement a "vimfn" ("f_xx"/"eval"/"builtin") function. +/// +/// Converts argvars directly to Lua values (no Object intermediate), calls the Lua function, and +/// converts the result back to typval_T. +/// +/// @param module Lua module name, e.g. "vim._core.server". +/// @param func Function name in the module, e.g. "serverlist". +/// @param argvars typval args (VAR_UNKNOWN-terminated). +/// @param rettv Return value (caller must tv_clear), or NULL to discard. +void nlua_call_vimfn(const char *module, const char *func, typval_T *argvars, typval_T *rettv) +{ + int argcount = 0; + for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) { + argcount++; + } + + char buf[256]; + snprintf(buf, sizeof(buf), "return require('%s').%s(...)", module, func); + + nlua_typval_exec(buf, strlen(buf), module, argvars, argcount, false, rettv); +} + +/// Checks if a LuaRef refers to a function. bool nlua_ref_is_function(LuaRef ref) { lua_State *const lstate = global_lstate; @@ -1662,6 +1822,7 @@ static int mode_ret(LuaRetMode mode) return mode == kRetMulti ? LUA_MULTRET : 1; } +/// Like nlua_call_ref, but with an option to run in fast (api-fast) context. Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena, Error *err) { @@ -2054,6 +2215,7 @@ static int nlua_is_thread(lua_State *lstate) return 1; } +/// Check if a typval dict/list originated from Lua (has a LuaRef). bool nlua_is_table_from_lua(const typval_T *const arg) { if (arg->v_type == VAR_DICT) { @@ -2244,22 +2406,19 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref); lua_newtable(lstate); + nlua_push_eap(lstate, eap, &cmdmod); + lua_pushstring(lstate, cmd->uc_name); lua_setfield(lstate, -2, "name"); - lua_pushboolean(lstate, eap->forceit == 1); - lua_setfield(lstate, -2, "bang"); - - lua_pushinteger(lstate, eap->line1); - lua_setfield(lstate, -2, "line1"); - - lua_pushinteger(lstate, eap->line2); - lua_setfield(lstate, -2, "line2"); + // Override count with uc_def fallback for user commands. + if (eap->addr_count == 0) { + lua_pushinteger(lstate, cmd->uc_def); + lua_setfield(lstate, -2, "count"); + } lua_newtable(lstate); // f-args table - lua_pushstring(lstate, eap->arg); - lua_pushvalue(lstate, -1); // Reference for potential use on f-args - lua_setfield(lstate, -4, "args"); + lua_pushstring(lstate, eap->arg); // for f-args splitting below // Split args by unescaped whitespace || (nargs dependent) if (cmd->uc_argt & EX_NOSPC) { @@ -2298,20 +2457,6 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) } lua_setfield(lstate, -2, "fargs"); - char reg[2] = { (char)eap->regname, NUL }; - lua_pushstring(lstate, reg); - lua_setfield(lstate, -2, "reg"); - - lua_pushinteger(lstate, eap->addr_count); - lua_setfield(lstate, -2, "range"); - - if (eap->addr_count > 0) { - lua_pushinteger(lstate, eap->line2); - } else { - lua_pushinteger(lstate, cmd->uc_def); - } - lua_setfield(lstate, -2, "count"); - char nargs[2]; if (cmd->uc_argt & EX_EXTRA) { if (cmd->uc_argt & EX_NOSPC) { @@ -2332,6 +2477,8 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_pushstring(lstate, nargs); lua_setfield(lstate, -2, "nargs"); + // User commands also get a string "mods" field (in addition to "smods" from nlua_push_eap). + // // The size of this buffer is chosen empirically to be large enough to hold // every possible modifier (with room to spare). If the list of possible // modifiers grows this may need to be updated. @@ -2340,66 +2487,6 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_pushstring(lstate, buf); lua_setfield(lstate, -2, "mods"); - lua_newtable(lstate); // smods table - - lua_pushinteger(lstate, cmdmod.cmod_tab - 1); - lua_setfield(lstate, -2, "tab"); - - lua_pushinteger(lstate, cmdmod.cmod_verbose - 1); - lua_setfield(lstate, -2, "verbose"); - - if (cmdmod.cmod_split & WSP_ABOVE) { - lua_pushstring(lstate, "aboveleft"); - } else if (cmdmod.cmod_split & WSP_BELOW) { - lua_pushstring(lstate, "belowright"); - } else if (cmdmod.cmod_split & WSP_TOP) { - lua_pushstring(lstate, "topleft"); - } else if (cmdmod.cmod_split & WSP_BOT) { - lua_pushstring(lstate, "botright"); - } else { - lua_pushstring(lstate, ""); - } - lua_setfield(lstate, -2, "split"); - - lua_pushboolean(lstate, cmdmod.cmod_split & WSP_VERT); - lua_setfield(lstate, -2, "vertical"); - lua_pushboolean(lstate, cmdmod.cmod_split & WSP_HOR); - lua_setfield(lstate, -2, "horizontal"); - lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SILENT); - lua_setfield(lstate, -2, "silent"); - lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_ERRSILENT); - lua_setfield(lstate, -2, "emsg_silent"); - lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_UNSILENT); - lua_setfield(lstate, -2, "unsilent"); - lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SANDBOX); - lua_setfield(lstate, -2, "sandbox"); - lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_NOAUTOCMD); - lua_setfield(lstate, -2, "noautocmd"); - - typedef struct { - int flag; - char *name; - } mod_entry_T; - static mod_entry_T mod_entries[] = { - { CMOD_BROWSE, "browse" }, - { CMOD_CONFIRM, "confirm" }, - { CMOD_HIDE, "hide" }, - { CMOD_KEEPALT, "keepalt" }, - { CMOD_KEEPJUMPS, "keepjumps" }, - { CMOD_KEEPMARKS, "keepmarks" }, - { CMOD_KEEPPATTERNS, "keeppatterns" }, - { CMOD_LOCKMARKS, "lockmarks" }, - { CMOD_NOSWAPFILE, "noswapfile" } - }; - - // The modifiers that are simple flags - for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { - lua_pushboolean(lstate, cmdmod.cmod_flags & mod_entries[i].flag); - lua_setfield(lstate, -2, mod_entries[i].name); - } - - lua_setfield(lstate, -2, "smods"); - if (preview) { lua_pushinteger(lstate, cmdpreview_get_ns()); diff --git a/src/nvim/types_defs.h b/src/nvim/types_defs.h index 4c00d993ef..e2e58546ff 100644 --- a/src/nvim/types_defs.h +++ b/src/nvim/types_defs.h @@ -31,9 +31,10 @@ typedef double float_T; typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler; +/// vimfn metatdata defined in `src/nvim/eval.lua`. typedef union { float_T (*func_float)(float_T); - const MsgpackRpcRequestHandler *func_api; // Vimscript bridge to API fn. + const MsgpackRpcRequestHandler *func_api; // Vimscript bridge to API fn (eval=true in eval.lua). const char *func_lua; ///< Lua-implemented vimfn. void *null; } EvalFuncData;