aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/lua/executor.c
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2026-04-17 18:14:14 +0200
committerJustin M. Keyes <justinkz@gmail.com>2026-04-18 16:57:37 +0200
commit3ebfa2a3cbbed848667fe26c16973ce7e43a04e4 (patch)
tree8026deb4b85a46be16eb32bbe85f3b9c24c5e4c7 /src/nvim/lua/executor.c
parent48d11681c2c108e34be088cf2f6ce6cc753fa57f (diff)
downloadrneovim-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.c259
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());