summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2026-04-20 04:23:54 -0400
committerGitHub <noreply@github.com>2026-04-20 04:23:54 -0400
commitc7d4892ce6615e83113035823979cf424f0c12f5 (patch)
treec9604b577d69ffa7ba544a2ed183d22f67d88a12
parent2e8f285f6c1be6a55a9aae2fab64421f24bd7df0 (diff)
parentea45e6f6ba5ead412053285ca3737bca6ce273f4 (diff)
Merge #39194 from justinmk/luavimfn
-rw-r--r--runtime/lua/vim/_core/ex_cmd.lua23
-rw-r--r--runtime/lua/vim/health.lua25
-rw-r--r--runtime/lua/vim/health/health.lua38
-rw-r--r--scripts/linterrcodes.lua2
-rw-r--r--src/nvim/eval/funcs.c13
-rw-r--r--src/nvim/ex_docmd.c83
-rw-r--r--src/nvim/ex_eval.c4
-rw-r--r--src/nvim/help.c53
-rw-r--r--src/nvim/lua/executor.c111
-rw-r--r--test/functional/ex_cmds/excmd_spec.lua15
-rw-r--r--test/functional/ex_cmds/lsp_spec.lua2
11 files changed, 183 insertions, 186 deletions
diff --git a/runtime/lua/vim/_core/ex_cmd.lua b/runtime/lua/vim/_core/ex_cmd.lua
index 1fe78d1e14..cd33a11662 100644
--- a/runtime/lua/vim/_core/ex_cmd.lua
+++ b/runtime/lua/vim/_core/ex_cmd.lua
@@ -233,4 +233,27 @@ function M.log_complete()
return names
end
+--- `:terminal [cmd]`
+--- @param eap vim._core.ExCmdArgs
+--- @param shell_argv? string[] Tokenized 'shell' from C (shell_build_argv), for the no-cmd case.
+M.ex_terminal = function(eap, shell_argv)
+ local smods = eap.smods
+ local has_mods = (smods.tab or 0) > 0
+ or (smods.split or '') ~= ''
+ or smods.horizontal
+ or smods.vertical
+
+ if has_mods then
+ vim.cmd.new { mods = smods }
+ else
+ vim.cmd.enew { bang = eap.bang }
+ end
+
+ if shell_argv then -- No `cmd`, run 'shell'.
+ vim.fn.jobstart(shell_argv, { term = true })
+ else -- Run [cmd] in 'shell'.
+ vim.fn.jobstart(eap.args, { term = true })
+ end
+end
+
return M
diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index 55dccc847f..10b066b4d3 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -387,14 +387,15 @@ local function progress_report(len)
end
end
---- Runs the specified healthchecks.
---- Runs all discovered healthchecks if plugin_names is empty.
+--- Runs the specified healthchecks, or all discovered healthchecks if eap.args is empty.
---
---- @param mods string command modifiers that affect splitting a window.
---- @param plugin_names string glob of plugin names, split on whitespace. For example, using
---- `:checkhealth vim.* nvim` will healthcheck `vim.lsp`, `vim.treesitter`
---- and `nvim` modules.
-function M._check(mods, plugin_names)
+--- Specified healthchecks are given as plugin names, split on whitespace. For example using
+--- `:checkhealth vim.* nvim` will check `vim.lsp`, `vim.treesitter` and `nvim` modules.
+---
+--- @param eap vim._core.ExCmdArgs
+function M._check(eap)
+ local plugin_names = eap.args
+ local smods = eap.smods
local healthchecks = plugin_names == '' and get_healthcheck('*') or get_healthcheck(plugin_names)
local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
@@ -421,8 +422,14 @@ function M._check(mods, plugin_names)
-- When no command modifiers are used:
-- - If the current buffer is empty, open healthcheck directly.
-- - If not specified otherwise open healthcheck in a tab.
- local buf_cmd = #mods > 0 and (mods .. ' sbuffer') or emptybuf and 'buffer' or 'tab sbuffer'
- vim.cmd(buf_cmd .. ' ' .. bufnr)
+ local has_mods = smods.tab > 0 or smods.split ~= '' or smods.horizontal or smods.vertical
+ if has_mods then
+ vim.cmd.sbuffer { bufnr, mods = smods }
+ elseif emptybuf then
+ vim.cmd.buffer(bufnr)
+ else
+ vim.cmd.sbuffer { bufnr, mods = { tab = 1 } }
+ end
end
if vim.fn.bufexists('health://') == 1 then
diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua
index a744f1b750..74cad281ac 100644
--- a/runtime/lua/vim/health/health.lua
+++ b/runtime/lua/vim/health/health.lua
@@ -3,10 +3,14 @@ local health = require('vim.health')
---Run a system command and return ok and its stdout and stderr combined.
---@param cmd string[]
+---@param timeout? integer Timeout in ms (default: no timeout).
---@return boolean
---@return string
-local function system(cmd)
- local result = vim.system(cmd, { text = true }):wait()
+local function system(cmd, timeout)
+ local result = vim.system(cmd, { text = true, timeout = timeout }):wait()
+ if not result then -- Workaround https://github.com/neovim/neovim/issues/37922
+ return false, 'command failed'
+ end
return result.code == 0, vim.trim(('%s\n%s'):format(result.stdout, result.stderr))
end
@@ -551,21 +555,15 @@ end
---@param nvim_version string
local function check_stable_version(nvim_version)
- local result = vim
- .system(
- { 'git', 'ls-remote', '--tags', 'https://github.com/neovim/neovim' },
- { text = true, timeout = 5000 }
- )
- :wait()
- if result.code ~= 0 or not result.stdout or result.stdout == '' then
+ local ok, output =
+ system({ 'git', 'ls-remote', '--tags', 'https://github.com/neovim/neovim' }, 5000)
+ if not ok or output == '' then
return
end
local stable_sha = assert(
- result.stdout:match('(%x+)%s+refs/tags/stable%^{}')
- or result.stdout:match('(%x+)%s+refs/tags/stable\n')
+ output:match('(%x+)%s+refs/tags/stable%^{}') or output:match('(%x+)%s+refs/tags/stable\n')
)
- local latest_version =
- assert(result.stdout:match(stable_sha .. '%s+refs/tags/v?(%d+%.%d+%.%d+)%^{}'))
+ local latest_version = assert(output:match(stable_sha .. '%s+refs/tags/v?(%d+%.%d+%.%d+)%^{}'))
local current_version = assert(nvim_version:match('v?(%d+%.%d+%.%d+)'))
local current = vim.version.parse(current_version)
local latest = vim.version.parse(latest_version)
@@ -578,18 +576,16 @@ end
---@param commit string
local function check_head_hash(commit)
- local result = vim
- .system(
- { 'git', 'ls-remote', 'https://github.com/neovim/neovim', 'HEAD', 'refs/tags/nightly' },
- { text = true, timeout = 5000 }
- )
- :wait()
- if result.code ~= 0 or not result.stdout or result.stdout == '' then
+ local ok, output = system(
+ { 'git', 'ls-remote', 'https://github.com/neovim/neovim', 'HEAD', 'refs/tags/nightly' },
+ 5000
+ )
+ if not ok or output == '' then
return
end
local refs = {} ---@type table<string, string>
- for line in result.stdout:gmatch('[^\n]+') do
+ for line in output:gmatch('[^\n]+') do
local sha, ref = line:match('^(%x+)%s+(%S+)$')
if sha and ref then
refs[ref] = sha
diff --git a/scripts/linterrcodes.lua b/scripts/linterrcodes.lua
index e6767fbeba..ed2ca92b6c 100644
--- a/scripts/linterrcodes.lua
+++ b/scripts/linterrcodes.lua
@@ -48,7 +48,7 @@ local dup_allowed = {
E509 = 2,
E5101 = 2,
E5102 = 2,
- E5108 = 6,
+ E5108 = 5,
E5111 = 2,
E513 = 2,
E521 = 2,
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index ea017b5954..a04671030a 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3382,13 +3382,14 @@ dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, cons
dict_T *env = tv_dict_alloc();
if (!clear_env) {
- typval_T temp_env = TV_INITIAL_VALUE;
- typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
- nlua_call_vimfn("vim._core.vimfn", "f_environ", no_args, &temp_env);
- if (temp_env.v_type == VAR_DICT) {
- tv_dict_extend(env, temp_env.vval.v_dict, "force");
+ uv_env_item_t *envitems;
+ int envcount;
+ if (uv_os_environ(&envitems, &envcount) == 0) {
+ for (int i = 0; i < envcount; i++) {
+ tv_dict_add_str(env, envitems[i].name, strlen(envitems[i].name), envitems[i].value);
+ }
+ uv_os_free_environ(envitems, envcount);
}
- tv_clear(&temp_env);
if (pty) {
// These env vars shouldn't propagate to the child process. #6764
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index e527b38b6a..bd011aa407 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -8197,23 +8197,11 @@ void set_pressedreturn(bool val)
/// ":checkhealth [plugins]"
static void ex_checkhealth(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, STRING_OBJ(((String){ .data = mods, .size = mods_len })));
- ADD_C(args, CSTR_AS_OBJ(eap->arg));
-
- NLUA_EXEC_STATIC("vim.health._check(...)", args, kRetNilBool, NULL, &err);
- if (!ERROR_SET(&err)) {
+ // Suppress the Lua error (E5108) so the VIMRUNTIME diagnostic is the primary error.
+ emsg_off++;
+ bool ok = nlua_call_excmd("vim.health", "_check", eap, &cmdmod, NULL);
+ emsg_off--;
+ if (ok) {
return;
}
@@ -8228,79 +8216,46 @@ static void ex_checkhealth(exarg_T *eap)
emsg(_("E5009: Invalid 'runtimepath'"));
}
}
- semsg_multiline("emsg", err.msg);
- api_clear_error(&err);
}
static void ex_terminal(exarg_T *eap)
{
- char ex_cmd[1024];
- size_t len = 0;
const int scroll_save = msg_scroll;
-
- msg_scroll = false; // don't scroll here
+ msg_scroll = false;
autowrite_all();
msg_scroll = scroll_save;
- if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) {
- bool multi_mods = false;
- // ex_cmd must be a null-terminated string before passing to add_win_cmd_modifiers
- ex_cmd[0] = NUL;
- len = add_win_cmd_modifiers(ex_cmd, &cmdmod, &multi_mods);
- assert(len < sizeof(ex_cmd));
- int result = snprintf(ex_cmd + len, sizeof(ex_cmd) - len, " new");
- assert(result > 0);
- len += (size_t)result;
+ if (*eap->arg != NUL) {
+ nlua_call_excmd("vim._core.ex_cmd", "ex_terminal", eap, &cmdmod, NULL);
} else {
- int result = snprintf(ex_cmd, sizeof(ex_cmd), "enew%s", eap->forceit ? "!" : "");
- assert(result > 0);
- len += (size_t)result;
- }
-
- assert(len < sizeof(ex_cmd));
-
- if (*eap->arg != NUL) { // Run {cmd} in 'shell'.
- char *name = vim_strsave_escaped(eap->arg, "\"\\");
- snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
- " | call jobstart(\"%s\",{'term':v:true})", name);
- xfree(name);
- } else { // No {cmd}: run the job with tokenized 'shell'.
+ // No cmd given, run 'shell'.
if (*p_sh == NUL) {
emsg(_(e_shellempty));
return;
}
-
+ // Tokenize 'shell' via shell_build_argv (handles quoting) and pass as arg2.
char **argv = shell_build_argv(NULL, NULL);
- char **p = argv;
- char tempstring[512];
- char shell_argv[512] = { 0 };
-
- while (*p != NULL) {
- char *escaped = vim_strsave_escaped(*p, "\"\\");
- snprintf(tempstring, sizeof(tempstring), ",\"%s\"", escaped);
- xfree(escaped);
- xstrlcat(shell_argv, tempstring, sizeof(shell_argv));
- p++;
+ typval_T shell_tv;
+ tv_list_alloc_ret(&shell_tv, 0);
+ for (char **p = argv; *p; p++) {
+ tv_list_append_allocated_string(shell_tv.vval.v_list, *p);
}
- shell_free_argv(argv);
-
- snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
- " | call jobstart([%s], {'term':v:true})", shell_argv + 1);
+ xfree(argv);
+ nlua_call_excmd("vim._core.ex_cmd", "ex_terminal", eap, &cmdmod, &shell_tv);
+ tv_clear(&shell_tv);
}
-
- do_cmdline_cmd(ex_cmd);
}
/// ":log {name}"
static void ex_log(exarg_T *eap)
{
- nlua_call_excmd("vim._core.ex_cmd", "ex_log", eap, &cmdmod);
+ nlua_call_excmd("vim._core.ex_cmd", "ex_log", eap, &cmdmod, NULL);
}
/// ":lsp {subcmd} {clients}"
static void ex_lsp(exarg_T *eap)
{
- nlua_call_excmd("vim._core.ex_cmd", "ex_lsp", eap, &cmdmod);
+ nlua_call_excmd("vim._core.ex_cmd", "ex_lsp", eap, &cmdmod, NULL);
}
/// ":fclose"
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index cedb58b4d0..ebbb940422 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -378,7 +378,9 @@ bool do_intthrow(cstack_T *cstack)
return true;
}
-/// Get an exception message that is to be stored in current_exception->value.
+/// Gets an exception message that is to be stored in current_exception->value.
+///
+/// For error exceptions (ET_ERROR), formats the message as "Vim(cmdname):Exx: …".
char *get_exception_string(void *value, except_type_T type, char *cmdname, bool *should_free)
{
char *ret;
diff --git a/src/nvim/help.c b/src/nvim/help.c
index 2928647e6b..78e2690772 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -14,6 +14,7 @@
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/errors.h"
+#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
@@ -105,16 +106,14 @@ void ex_help(exarg_T *eap)
// ":help!" (bang, no args): DWIM help, resolve best tag at cursor via Lua.
char *allocated_arg = NULL;
if (helpbang) {
- Error err = ERROR_INIT;
- Object res = NLUA_EXEC_STATIC("return require'vim._core.help'.resolve_tag()",
- (Array)ARRAY_DICT_INIT, kRetObject, NULL, &err);
- if (!ERROR_SET(&err) && res.type == kObjectTypeString && res.data.string.size > 0) {
- allocated_arg = xstrdup(res.data.string.data);
+ typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
+ typval_T rettv;
+ nlua_call_vimfn("vim._core.help", "resolve_tag", no_args, &rettv);
+ if (rettv.v_type == VAR_STRING && rettv.vval.v_string != NULL && *rettv.vval.v_string != NUL) {
+ allocated_arg = rettv.vval.v_string; // takes ownership
arg = allocated_arg;
- }
- api_free_object(res);
- api_clear_error(&err);
- if (allocated_arg == NULL) {
+ } else {
+ tv_clear(&rettv);
emsg(_(e_noident));
return;
}
@@ -335,24 +334,18 @@ static int help_compare(const void *s1, const void *s2)
/// When "keep_lang" is true try keeping the language of the current buffer.
int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang)
{
- Error err = ERROR_INIT;
- MAXSIZE_TEMP_ARRAY(args, 1);
-
- ADD_C(args, CSTR_AS_OBJ(arg));
-
- Object res = NLUA_EXEC_STATIC("return require'vim._core.help'.escape_subject(...)",
- args, kRetObject, NULL, &err);
-
- if (ERROR_SET(&err)) {
- emsg_multiline(err.msg, "lua_error", HLF_E, true);
- api_clear_error(&err);
+ typval_T tv_args[] = {
+ { .v_type = VAR_STRING, .vval.v_string = (char *)arg },
+ { .v_type = VAR_UNKNOWN },
+ };
+ typval_T rettv;
+ nlua_call_vimfn("vim._core.help", "escape_subject", tv_args, &rettv);
+ if (rettv.v_type != VAR_STRING || rettv.vval.v_string == NULL) {
+ tv_clear(&rettv);
return FAIL;
}
- api_clear_error(&err);
-
- assert(res.type == kObjectTypeString);
- xstrlcpy(IObuff, res.data.string.data, sizeof(IObuff));
- api_free_object(res);
+ xstrlcpy(IObuff, rettv.vval.v_string, sizeof(IObuff));
+ tv_clear(&rettv);
*matches = NULL;
*num_matches = 0;
@@ -467,14 +460,8 @@ void prepare_help_buffer(void)
/// Populate *local-additions* in help.txt
void get_local_additions(void)
{
- Error err = ERROR_INIT;
- Object res = NLUA_EXEC_STATIC("return require'vim._core.help'.local_additions()",
- (Array)ARRAY_DICT_INIT, kRetNilBool, NULL, &err);
- if (ERROR_SET(&err)) {
- emsg_multiline(err.msg, "lua_error", HLF_E, true);
- }
- api_free_object(res);
- api_clear_error(&err);
+ typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
+ nlua_call_vimfn("vim._core.help", "local_additions", no_args, NULL);
}
/// ":exusage"
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index e0e3f5ef9f..1771a01565 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -194,38 +194,18 @@ static void nlua_push_eap(lua_State *lstate, exarg_T *eap, const cmdmod_T *cmod)
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;
+ // Push pre-split args as "fargs" list, if available (set by the command-line parser).
+ if (eap->args != NULL && eap->argc > 0) {
+ lua_createtable(lstate, (int)eap->argc, 0);
+ for (size_t i = 0; i < eap->argc; i++) {
+ lua_pushlstring(lstate, eap->args[i], eap->arglens[i]);
+ lua_rawseti(lstate, -2, (int)i + 1);
+ }
+ lua_setfield(lstate, -2, "fargs");
}
- 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);
- }
+ nlua_push_cmdmod(lstate, cmod);
+ lua_setfield(lstate, -2, "smods");
}
#if __has_feature(address_sanitizer)
@@ -1788,6 +1768,47 @@ void nlua_call_vimfn(const char *module, const char *func, typval_T *argvars, ty
nlua_typval_exec(buf, strlen(buf), module, argvars, argcount, false, rettv);
}
+/// 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 info, passed as Lua arg1.
+/// @param cmod Excmd mods, included in Lua arg1.
+/// @param extra (Optional) Passed as Lua arg2.
+/// @return true on success, false on error (message already emitted).
+bool nlua_call_excmd(const char *module, const char *func, exarg_T *eap, const cmdmod_T *cmod,
+ typval_T *extra)
+{
+ lua_State *const lstate = global_lstate;
+
+ lua_getglobal(lstate, "require");
+ lua_pushstring(lstate, module);
+ if (lua_pcall(lstate, 1, 1, 0) != 0) {
+ nlua_error(lstate, "E5108: %s");
+ return false;
+ }
+ lua_getfield(lstate, -1, func);
+ lua_remove(lstate, -2);
+
+ lua_newtable(lstate);
+ nlua_push_eap(lstate, eap, cmod);
+
+ int nargs = 1;
+ if (extra) {
+ nlua_push_typval(lstate, extra, 0);
+ nargs = 2;
+ }
+
+ if (nlua_pcall(lstate, nargs, 0)) {
+ // Not "E5108" because this is a logical/application error, not a "Lua error".
+ emsg(lua_tostring(lstate, -1));
+ lua_pop(lstate, 1);
+ return false;
+ }
+ return true;
+}
+
/// Checks if a LuaRef refers to a function.
bool nlua_ref_is_function(LuaRef ref)
{
@@ -2417,21 +2438,19 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
lua_setfield(lstate, -2, "count");
}
- lua_newtable(lstate); // f-args table
- lua_pushstring(lstate, eap->arg); // for f-args splitting below
-
- // Split args by unescaped whitespace |<f-args>| (nargs dependent)
+ // Override fargs for user command-specific splitting (nlua_push_eap already set it
+ // for the eap->args!=NULL case, but user commands need special handling for nargs).
if (cmd->uc_argt & EX_NOSPC) {
- if ((cmd->uc_argt & EX_NEEDARG) || strlen(eap->arg)) {
- // For commands where nargs is 1 or "?" and argument is passed, fargs = { args }
+ // nargs=1 or "?": fargs is the whole arg as a single element, or empty.
+ lua_createtable(lstate, 1, 0);
+ if ((cmd->uc_argt & EX_NEEDARG) || *eap->arg != NUL) {
+ lua_pushstring(lstate, eap->arg);
lua_rawseti(lstate, -2, 1);
- } else {
- // if nargs = "?" and no argument is passed, fargs = {}
- lua_pop(lstate, 1); // Pop the reference of opts.args
}
+ lua_setfield(lstate, -2, "fargs");
} else if (eap->args == NULL) {
- // For commands with more than one possible argument, split if argument list isn't available.
- lua_pop(lstate, 1); // Pop the reference of opts.args
+ // Pre-split args not available: tokenize eap->arg by unescaped whitespace.
+ lua_newtable(lstate);
size_t length = strlen(eap->arg);
size_t end = 0;
size_t len = 0;
@@ -2447,15 +2466,9 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
}
}
xfree(buf);
- } else {
- // If argument list is available, just use it.
- lua_pop(lstate, 1);
- for (size_t i = 0; i < eap->argc; i++) {
- lua_pushlstring(lstate, eap->args[i], eap->arglens[i]);
- lua_rawseti(lstate, -2, (int)i + 1);
- }
+ lua_setfield(lstate, -2, "fargs");
}
- lua_setfield(lstate, -2, "fargs");
+ // else: eap->args was available, nlua_push_eap already set fargs.
char nargs[2];
if (cmd->uc_argt & EX_EXTRA) {
diff --git a/test/functional/ex_cmds/excmd_spec.lua b/test/functional/ex_cmds/excmd_spec.lua
index 7c67222cb3..4e86420101 100644
--- a/test/functional/ex_cmds/excmd_spec.lua
+++ b/test/functional/ex_cmds/excmd_spec.lua
@@ -8,7 +8,20 @@ local fn = n.fn
local pcall_err = t.pcall_err
local assert_alive = n.assert_alive
-describe('Ex cmds', function()
+describe('nlua_call_excmd excmds', function()
+ -- Exercise nlua_call_excmd by testing commands implemented with it (:log, :lsp).
+
+ before_each(function()
+ clear()
+ end)
+
+ it('error propagation, formatting', function()
+ t.eq('Vim(lsp):E5800: Invalid :lsp subcommand: bogus', pcall_err(command, 'lsp bogus'))
+ t.matches('Vim%(log%):E5200: No such log.*', pcall_err(command, 'log bogus'))
+ end)
+end)
+
+describe('excmds', function()
before_each(function()
clear()
end)
diff --git a/test/functional/ex_cmds/lsp_spec.lua b/test/functional/ex_cmds/lsp_spec.lua
index 2cef077b30..3922c55b14 100644
--- a/test/functional/ex_cmds/lsp_spec.lua
+++ b/test/functional/ex_cmds/lsp_spec.lua
@@ -29,7 +29,7 @@ describe(':lsp', function()
env = { VIMRUNTIME = 'non-existent' },
}
t.matches(
- [[Vim%(lsp%):E%d+: .*module 'vim%.lsp' not found:]],
+ [[.*module 'vim%.lsp' not found:]],
vim.split(t.pcall_err(n.command, 'lsp enable dummy'), '\n')[1]
)
end)