diff options
| author | Justin M. Keyes <justinkz@gmail.com> | 2026-04-17 18:14:14 +0200 |
|---|---|---|
| committer | Justin M. Keyes <justinkz@gmail.com> | 2026-04-18 16:57:37 +0200 |
| commit | 3ebfa2a3cbbed848667fe26c16973ce7e43a04e4 (patch) | |
| tree | 8026deb4b85a46be16eb32bbe85f3b9c24c5e4c7 /src/nvim/lua/executor.c | |
| parent | 48d11681c2c108e34be088cf2f6ce6cc753fa57f (diff) | |
| download | rneovim-3ebfa2a3cbbed848667fe26c16973ce7e43a04e4.tar.gz rneovim-3ebfa2a3cbbed848667fe26c16973ce7e43a04e4.tar.bz2 rneovim-3ebfa2a3cbbed848667fe26c16973ce7e43a04e4.zip | |
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
Diffstat (limited to 'src/nvim/lua/executor.c')
| -rw-r--r-- | src/nvim/lua/executor.c | 259 |
1 files changed, 173 insertions, 86 deletions
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 |<f-args>| (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()); |