mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 19:09:39 +00:00
Merge pull #40619 fix(lua): statusline expr leaks into msg area
This commit is contained in:
@@ -216,7 +216,7 @@ order of preference):
|
||||
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
|
||||
2. Partial Lua: the C implementation calls `nlua_call_typval` or
|
||||
`nlua_call_excmd`. (Or in rare cases: `nlua_exec` / `NLUA_EXEC_STATIC`).
|
||||
Examples:
|
||||
- `ex_log`
|
||||
|
||||
@@ -4059,7 +4059,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
return;
|
||||
}
|
||||
|
||||
nlua_typval_eval(cstr_as_string(str), &argvars[1], rettv);
|
||||
nlua_call_luaeval(cstr_as_string(str), &argvars[1], rettv);
|
||||
}
|
||||
|
||||
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
|
||||
@@ -6373,7 +6373,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
.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);
|
||||
nlua_call_typval("vim._core.server", "serverlist", lua_args, rettv);
|
||||
tv_clear(&addrs_tv);
|
||||
}
|
||||
|
||||
|
||||
@@ -1407,7 +1407,7 @@ static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, ty
|
||||
FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
|
||||
{
|
||||
if (fp->uf_flags & FC_LUAREF) {
|
||||
return typval_exec_lua_callable(fp->uf_luaref, argcount, argvars, rettv);
|
||||
return nlua_exec_typval_callable(fp->uf_luaref, argcount, argvars, rettv);
|
||||
}
|
||||
|
||||
if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) {
|
||||
@@ -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_call_typval(funcname, (size_t)len, argvars, argcount, rettv);
|
||||
nlua_call_vlua(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_call_typval(funcname, len, argvars, 0, rettv);
|
||||
nlua_call_vlua(funcname, len, argvars, 0, rettv);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -4587,7 +4587,7 @@ static void cmdwin_invoke(const char *action, int firstc, char *content, int pos
|
||||
{ .v_type = VAR_NUMBER, .vval.v_number = pos + 1 },
|
||||
{ .v_type = VAR_UNKNOWN },
|
||||
};
|
||||
nlua_call_vimfn("vim._core.cmdwin", action, firstc ? tv_args : tv_args + 3, NULL);
|
||||
nlua_call_typval("vim._core.cmdwin", action, firstc ? tv_args : tv_args + 3, NULL);
|
||||
xfree(content);
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ void ex_help(exarg_T *eap)
|
||||
if (helpbang) {
|
||||
typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
|
||||
typval_T rettv;
|
||||
nlua_call_vimfn("vim._core.help", "resolve_tag", no_args, &rettv);
|
||||
nlua_call_typval("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;
|
||||
@@ -339,7 +339,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
|
||||
{ .v_type = VAR_UNKNOWN },
|
||||
};
|
||||
typval_T rettv;
|
||||
nlua_call_vimfn("vim._core.help", "escape_subject", tv_args, &rettv);
|
||||
nlua_call_typval("vim._core.help", "escape_subject", tv_args, &rettv);
|
||||
if (rettv.v_type != VAR_STRING || rettv.vval.v_string == NULL) {
|
||||
tv_clear(&rettv);
|
||||
return FAIL;
|
||||
@@ -461,7 +461,7 @@ void prepare_help_buffer(void)
|
||||
void get_local_additions(void)
|
||||
{
|
||||
typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.help", "local_additions", no_args, NULL);
|
||||
nlua_call_typval("vim._core.help", "local_additions", no_args, NULL);
|
||||
}
|
||||
|
||||
/// ":exusage"
|
||||
|
||||
@@ -1497,72 +1497,44 @@ LuaRef api_new_luaref(LuaRef original_ref)
|
||||
return new_ref;
|
||||
}
|
||||
|
||||
/// Evaluate lua string
|
||||
///
|
||||
/// Used for luaeval().
|
||||
///
|
||||
/// @param[in] str String to execute.
|
||||
/// @param[in] arg Second argument to `luaeval()`.
|
||||
/// @param[out] ret_tv Location where result will be saved.
|
||||
///
|
||||
/// @return Result of the execution.
|
||||
void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
/// Formats `fmt` (must contain a single "%.*s" for `bodylen`-byte `body`) into a Lua chunk and runs
|
||||
/// it via nlua_exec_typval().
|
||||
static void nlua_exec_typval_fmt(const char *fmt, const char *body, size_t bodylen,
|
||||
const char *name, typval_T *args, int argcount, bool special,
|
||||
typval_T *ret_tv)
|
||||
FUNC_ATTR_PRINTF(1, 0)
|
||||
{
|
||||
#define EVALHEADER "local _A=select(1,...) return ("
|
||||
const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1;
|
||||
char *lcmd;
|
||||
if (lcmd_len < IOSIZE) {
|
||||
lcmd = IObuff;
|
||||
} else {
|
||||
lcmd = xmalloc(lcmd_len);
|
||||
// Don't use shared IObuff; may reenter Lua (e.g. 'statusline' eval during a redraw). #40616
|
||||
// This local buf is safe across (re)entrant calls since luaL_loadbuffer() copies before exec.
|
||||
static char lcmd_buf[IOSIZE];
|
||||
char *lcmd = lcmd_buf;
|
||||
int lcmd_len = vim_snprintf(lcmd_buf, sizeof(lcmd_buf), fmt, (int)bodylen, body);
|
||||
if (lcmd_len >= IOSIZE) {
|
||||
lcmd = xmalloc((size_t)lcmd_len + 1);
|
||||
vim_snprintf(lcmd, (size_t)lcmd_len + 1, fmt, (int)bodylen, body);
|
||||
}
|
||||
memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1);
|
||||
memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size);
|
||||
lcmd[lcmd_len - 1] = ')';
|
||||
#undef EVALHEADER
|
||||
nlua_typval_exec(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv);
|
||||
|
||||
if (lcmd != IObuff) {
|
||||
nlua_exec_typval(lcmd, (size_t)lcmd_len, name, args, argcount, special, ret_tv);
|
||||
|
||||
if (lcmd != lcmd_buf) {
|
||||
xfree(lcmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// Implements vimfn: luaeval().
|
||||
void nlua_call_luaeval(const String str, typval_T *const arg, typval_T *const ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
#define CALLHEADER "return "
|
||||
#define CALLSUFFIX "(...)"
|
||||
const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1;
|
||||
char *lcmd;
|
||||
if (lcmd_len < IOSIZE) {
|
||||
lcmd = IObuff;
|
||||
} else {
|
||||
lcmd = xmalloc(lcmd_len);
|
||||
}
|
||||
memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1);
|
||||
memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len);
|
||||
memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX,
|
||||
sizeof(CALLSUFFIX) - 1);
|
||||
#undef CALLHEADER
|
||||
#undef CALLSUFFIX
|
||||
nlua_exec_typval_fmt("local _A=select(1,...) return (%.*s)", str.data, str.size, "luaeval()", arg,
|
||||
1, true, ret_tv);
|
||||
}
|
||||
|
||||
nlua_typval_exec(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv);
|
||||
|
||||
if (lcmd != IObuff) {
|
||||
xfree(lcmd);
|
||||
}
|
||||
/// Calls v:lua.func() with typval args.
|
||||
void nlua_call_vlua(const char *str, size_t len, typval_T *const args, int argcount,
|
||||
typval_T *ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
nlua_exec_typval_fmt("return %.*s(...)", str, len, "v:lua", args, argcount, false, ret_tv);
|
||||
}
|
||||
|
||||
/// Calls a Lua completion function (stored as LuaRef) for cmdline expansion.
|
||||
@@ -1585,7 +1557,7 @@ void nlua_call_user_expand_func(expand_T *xp, typval_T *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,
|
||||
static void nlua_exec_typval(const char *lcmd, size_t lcmd_len, const char *name,
|
||||
typval_T *const args, int argcount, bool special, typval_T *ret_tv)
|
||||
{
|
||||
if (check_secure()) {
|
||||
@@ -1626,20 +1598,18 @@ void nlua_exec_ga(garray_T *ga, char *name)
|
||||
{
|
||||
char *code = ga_concat_strings(ga, "\n");
|
||||
size_t len = strlen(code);
|
||||
nlua_typval_exec(code, len, name, NULL, 0, false, NULL);
|
||||
nlua_exec_typval(code, len, name, NULL, 0, false, NULL);
|
||||
xfree(code);
|
||||
}
|
||||
|
||||
/// Call a LuaCallable given some typvals
|
||||
///
|
||||
/// Used to call any Lua callable passed from Lua into Vimscript.
|
||||
/// Calls a Lua callable from Vimscript.
|
||||
///
|
||||
/// @param[in] lstate Lua State
|
||||
/// @param[in] lua_cb Lua Callable
|
||||
/// @param[in] argcount Count of typval arguments
|
||||
/// @param[in] argvars Typval Arguments
|
||||
/// @param[out] rettv The return value from the called function.
|
||||
int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typval_T *rettv)
|
||||
int nlua_exec_typval_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typval_T *rettv)
|
||||
{
|
||||
lua_State *lstate = global_lstate;
|
||||
|
||||
@@ -1697,7 +1667,7 @@ 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.
|
||||
/// Calls a Lua function with typval args.
|
||||
///
|
||||
/// Converts argvars directly to Lua values (no Object intermediate), calls the Lua function, and
|
||||
/// converts the result back to typval_T.
|
||||
@@ -1706,7 +1676,7 @@ Object nlua_exec(const String str, const char *chunkname, const Array args, LuaR
|
||||
/// @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)
|
||||
void nlua_call_typval(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++) {
|
||||
@@ -1716,7 +1686,7 @@ void nlua_call_vimfn(const char *module, const char *func, typval_T *argvars, ty
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "return require('%s').%s(...)", module, func);
|
||||
|
||||
nlua_typval_exec(buf, strlen(buf), module, argvars, argcount, false, rettv);
|
||||
nlua_exec_typval(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
|
||||
@@ -1885,8 +1855,6 @@ bool nlua_is_deferred_safe(void)
|
||||
return in_fast_callback == 0;
|
||||
}
|
||||
|
||||
/// Executes Lua code.
|
||||
///
|
||||
/// Implements `:lua` and `:lua ={expr}`.
|
||||
///
|
||||
/// @param eap Vimscript `:lua {code}`, `:{range}lua`, or `:lua ={expr}` command.
|
||||
@@ -1916,21 +1884,19 @@ void ex_lua(exarg_T *const eap)
|
||||
if (eap->cmdidx == CMD_equal || code[0] == '=') {
|
||||
size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1;
|
||||
len += sizeof("vim._print(true, )") - 1 - off;
|
||||
// `nlua_typval_exec` doesn't expect NUL-terminated string so `len` must end before NUL byte.
|
||||
// `nlua_exec_typval` doesn't expect NUL-terminated string so `len` must end before NUL byte.
|
||||
char *code_buf = xmallocz(len);
|
||||
vim_snprintf(code_buf, len + 1, "vim._print(true, %s)", code + off);
|
||||
xfree(code);
|
||||
code = code_buf;
|
||||
}
|
||||
|
||||
nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL);
|
||||
nlua_exec_typval(code, len, ":lua", NULL, 0, false, NULL);
|
||||
|
||||
xfree(code);
|
||||
}
|
||||
|
||||
/// Executes Lua code for-each line in a buffer range.
|
||||
///
|
||||
/// Implements `:luado`.
|
||||
/// Implements `:luado`: executes Lua code for-each line in a buffer range.
|
||||
///
|
||||
/// @param eap Vimscript `:luado {code}` command.
|
||||
void ex_luado(exarg_T *const eap)
|
||||
@@ -1950,9 +1916,12 @@ void ex_luado(exarg_T *const eap)
|
||||
const size_t lcmd_len = (cmd_len
|
||||
+ (sizeof(DOSTART) - 1)
|
||||
+ (sizeof(DOEND) - 1));
|
||||
// Don't use shared IObuff; may reenter Lua (e.g. 'statusline' eval during a redraw). #40616
|
||||
// This local buf is safe across (re)entrant calls since luaL_loadbuffer() copies before exec.
|
||||
static char lcmd_buf[IOSIZE];
|
||||
char *lcmd;
|
||||
if (lcmd_len < IOSIZE) {
|
||||
lcmd = IObuff;
|
||||
lcmd = lcmd_buf;
|
||||
} else {
|
||||
lcmd = xmalloc(lcmd_len + 1);
|
||||
}
|
||||
@@ -1964,12 +1933,12 @@ void ex_luado(exarg_T *const eap)
|
||||
|
||||
if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) {
|
||||
nlua_error(lstate, _("E5109: Lua: %.*s"));
|
||||
if (lcmd_len >= IOSIZE) {
|
||||
if (lcmd != lcmd_buf) {
|
||||
xfree(lcmd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (lcmd_len >= IOSIZE) {
|
||||
if (lcmd != lcmd_buf) {
|
||||
xfree(lcmd);
|
||||
}
|
||||
if (nlua_pcall(lstate, 0, 1)) {
|
||||
@@ -2022,9 +1991,7 @@ void ex_luado(exarg_T *const eap)
|
||||
redraw_curbuf_later(UPD_NOT_VALID);
|
||||
}
|
||||
|
||||
/// Executes Lua code from a file location.
|
||||
///
|
||||
/// Implements `:luafile`.
|
||||
/// Implements `:luafile`: executes Lua code from a file.
|
||||
///
|
||||
/// @param eap Vimscript `:luafile {file}` command.
|
||||
void ex_luafile(exarg_T *const eap)
|
||||
|
||||
@@ -503,7 +503,7 @@ int main(int argc, char **argv)
|
||||
tv_list_alloc_ret(&items_tv, 0);
|
||||
recover_names(NULL, false, items_tv.vval.v_list);
|
||||
typval_T lua_args[] = { items_tv, { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.swapfile", "list_swaps", lua_args, NULL);
|
||||
nlua_call_typval("vim._core.swapfile", "list_swaps", lua_args, NULL);
|
||||
tv_clear(&items_tv);
|
||||
os_exit(0);
|
||||
}
|
||||
|
||||
@@ -803,7 +803,7 @@ void ml_recover(bool checkext)
|
||||
if (n_swaps > 1) {
|
||||
// Several swapfiles found: prompt (async) via vim.ui.select().
|
||||
typval_T lua_args[] = { items_tv, { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.swapfile", "select_swap", lua_args, NULL);
|
||||
nlua_call_typval("vim._core.swapfile", "select_swap", lua_args, NULL);
|
||||
tv_clear(&items_tv);
|
||||
goto theend;
|
||||
}
|
||||
|
||||
@@ -6419,7 +6419,7 @@ static void nv_record(cmdarg_T *cap)
|
||||
{ .v_type = VAR_STRING, .vval.v_string = fc },
|
||||
{ .v_type = VAR_UNKNOWN },
|
||||
};
|
||||
nlua_call_vimfn("vim._core.cmdwin", "open", tv_args, NULL);
|
||||
nlua_call_typval("vim._core.cmdwin", "open", tv_args, NULL);
|
||||
} else {
|
||||
// (stop) recording into a named register, unless executing a
|
||||
// register.
|
||||
|
||||
@@ -479,7 +479,7 @@ static void select_spell_suggestion(suginfo_T *sug)
|
||||
typval_T bad_tv = { .v_type = VAR_STRING,
|
||||
.vval.v_string = xstrnsave(sug->su_badptr, (size_t)sug->su_badlen) };
|
||||
typval_T lua_args[] = { items_tv, bad_tv, { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.spell", "select_suggest", lua_args, NULL);
|
||||
nlua_call_typval("vim._core.spell", "select_suggest", lua_args, NULL);
|
||||
|
||||
tv_clear(&items_tv);
|
||||
tv_clear(&bad_tv);
|
||||
|
||||
@@ -5,7 +5,6 @@ local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed = n.clear, n.feed
|
||||
local eval = n.eval
|
||||
local eq = t.eq
|
||||
local pcall_err = t.pcall_err
|
||||
local neq = t.neq
|
||||
local command = n.command
|
||||
local set_method_error = n.set_method_error
|
||||
@@ -1751,6 +1750,30 @@ describe('ui/builtin messages', function()
|
||||
eq(busy_start, busy_stop) -- balanced: cursor restored afterwards
|
||||
end)
|
||||
|
||||
it(':write message not clobbered by v:lua in statusline redraw #40616', function()
|
||||
local fname = 'Xtest_write_progress'
|
||||
finally(function()
|
||||
os.remove(fname)
|
||||
end)
|
||||
exec_lua(function()
|
||||
-- The Progress event fired by the ":write" message (since ff68fd6b8a84)
|
||||
-- evaluates statusline mid-message.
|
||||
_G.Statusline = function()
|
||||
return 'STL'
|
||||
end
|
||||
vim.o.laststatus = 2
|
||||
vim.o.statusline = '%{v:lua.Statusline()}'
|
||||
vim.api.nvim_create_autocmd('Progress', {
|
||||
callback = function()
|
||||
vim.cmd('redrawstatus!')
|
||||
end,
|
||||
})
|
||||
end)
|
||||
command('write ' .. fname)
|
||||
-- Should not be overwritten by "return Statusline(...)").
|
||||
screen:expect({ any = ('"%s".*written'):format(fname) })
|
||||
end)
|
||||
|
||||
it(':hi Group output', function()
|
||||
screen:try_resize(70, 7)
|
||||
feed(':hi ErrorMsg<cr>')
|
||||
|
||||
Reference in New Issue
Block a user