summaryrefslogtreecommitdiffstatshomepage
path: root/src
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 /src
parent2e8f285f6c1be6a55a9aae2fab64421f24bd7df0 (diff)
parentea45e6f6ba5ead412053285ca3737bca6ce273f4 (diff)
Merge #39194 from justinmk/luavimfn
Diffstat (limited to 'src')
-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
5 files changed, 111 insertions, 153 deletions
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) {