mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
fix(messages)!: vim.ui_attach message callbacks are unsafe
Problem: Lua callbacks for "msg_show" events with vim.ui_attach() are executed when it is not safe. Solution: Disallow non-fast API calls for "msg_show" event callbacks. Automatically detach callback after excessive errors. Make sure fast APIs do not modify Nvim state.
This commit is contained in:
parent
7d771c3eee
commit
de48fbbd5f
@ -1083,6 +1083,10 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
|
||||
|ui-popupmenu| and the sections below for event format for respective
|
||||
events.
|
||||
|
||||
Callbacks for `msg_show` events are executed in |api-fast| context.
|
||||
|
||||
Excessive errors inside the callback will result in forced detachment.
|
||||
|
||||
WARNING: This api is considered experimental. Usability will vary for
|
||||
different screen elements. In particular `ext_messages` behavior is
|
||||
subject to further changes and usability improvements. This is expected to
|
||||
|
@ -72,7 +72,8 @@ EDITOR
|
||||
|
||||
EVENTS
|
||||
|
||||
• TODO
|
||||
• |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
|
||||
|api-fast| context.
|
||||
|
||||
LSP
|
||||
|
||||
|
@ -233,6 +233,10 @@ function vim.wait(time, callback, interval, fast_only) end
|
||||
--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
|
||||
--- and the sections below for event format for respective events.
|
||||
---
|
||||
--- Callbacks for `msg_show` events are executed in |api-fast| context.
|
||||
---
|
||||
--- Excessive errors inside the callback will result in forced detachment.
|
||||
---
|
||||
--- WARNING: This api is considered experimental. Usability will vary for
|
||||
--- different screen elements. In particular `ext_messages` behavior is subject
|
||||
--- to further changes and usability improvements. This is expected to be
|
||||
|
@ -159,7 +159,7 @@ void wildmenu_hide(void)
|
||||
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
|
||||
|
||||
void msg_show(String kind, Array content, Boolean replace_last)
|
||||
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
|
||||
FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY;
|
||||
void msg_clear(void)
|
||||
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
|
||||
void msg_showcmd(Array content)
|
||||
|
@ -594,10 +594,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er
|
||||
kvi_init(cookie.rv);
|
||||
|
||||
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
|
||||
TryState tstate;
|
||||
|
||||
try_enter(&tstate);
|
||||
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie);
|
||||
vim_ignored = try_leave(&tstate, err);
|
||||
|
||||
TRY_WRAP(err, {
|
||||
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie);
|
||||
});
|
||||
return arena_take_arraybuilder(arena, &cookie.rv);
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to ab
|
||||
|
||||
EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
|
||||
|
||||
EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
|
||||
EXTERN const char e_fast_api_disabled[] INIT(= N_("E5560: %s must not be called in a fast event context"));
|
||||
|
||||
EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
|
||||
EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
|
||||
|
@ -8616,7 +8616,7 @@ bool eval_has_provider(const char *feat, bool throw_if_fast)
|
||||
}
|
||||
|
||||
if (throw_if_fast && !nlua_is_deferred_safe()) {
|
||||
semsg(e_luv_api_disabled, "Vimscript function");
|
||||
semsg(e_fast_api_disabled, "Vimscript function");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -750,7 +750,7 @@ local function process_function(fn)
|
||||
write_shifted_output(
|
||||
[[
|
||||
if (!nlua_is_deferred_safe()) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "%s");
|
||||
return luaL_error(lstate, e_fast_api_disabled, "%s");
|
||||
}
|
||||
]],
|
||||
fn.name
|
||||
|
@ -136,8 +136,8 @@ for i = 1, #events do
|
||||
call_output:write(' }\n')
|
||||
call_output:write(' entered = true;\n')
|
||||
write_arglist(call_output, ev)
|
||||
call_output:write(' ui_call_event("' .. ev.name .. '", ' .. args .. ');\n')
|
||||
call_output:write(' entered = false;\n')
|
||||
call_output:write((' ui_call_event("%s", %s, %s)'):format(ev.name, tostring(ev.fast), args))
|
||||
call_output:write(';\n entered = false;\n')
|
||||
elseif ev.compositor_impl then
|
||||
call_output:write(' ui_comp_' .. ev.name)
|
||||
write_signature(call_output, ev, '', true)
|
||||
|
@ -187,7 +187,7 @@ static void nlua_luv_error_event(void **argv)
|
||||
msg_ext_set_kind("lua_error");
|
||||
switch (type) {
|
||||
case kCallback:
|
||||
semsg_multiline("Error executing luv callback:\n%s", error);
|
||||
semsg_multiline("Error executing callback:\n%s", error);
|
||||
break;
|
||||
case kThread:
|
||||
semsg_multiline("Error in luv thread:\n%s", error);
|
||||
@ -201,13 +201,13 @@ static void nlua_luv_error_event(void **argv)
|
||||
xfree(error);
|
||||
}
|
||||
|
||||
static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
|
||||
/// Execute callback in "fast" context. Used for luv and some vim.ui_event
|
||||
/// callbacks where using the API directly is not safe.
|
||||
static int nlua_fast_cfpcall(lua_State *lstate, int nargs, int nresult, int flags)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
int retval;
|
||||
|
||||
// luv callbacks might be executed at any os_breakcheck/line_breakcheck
|
||||
// call, so using the API directly here is not safe.
|
||||
in_fast_callback++;
|
||||
|
||||
int top = lua_gettop(lstate);
|
||||
@ -366,11 +366,13 @@ static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg
|
||||
static void nlua_schedule_event(void **argv)
|
||||
{
|
||||
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
|
||||
uint32_t ns_id = (uint32_t)(ptrdiff_t)argv[1];
|
||||
lua_State *const lstate = global_lstate;
|
||||
nlua_pushref(lstate, cb);
|
||||
nlua_unref_global(lstate, cb);
|
||||
if (nlua_pcall(lstate, 0, 0)) {
|
||||
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
|
||||
ui_remove_cb(ns_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,8 +394,9 @@ static int nlua_schedule(lua_State *const lstate)
|
||||
}
|
||||
|
||||
LuaRef cb = nlua_ref_global(lstate, 1);
|
||||
|
||||
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb);
|
||||
// Pass along UI event handler to disable on error.
|
||||
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb,
|
||||
(void *)(ptrdiff_t)ui_event_ns_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -425,7 +428,7 @@ static int nlua_wait(lua_State *lstate)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (in_fast_callback) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "vim.wait");
|
||||
return luaL_error(lstate, e_fast_api_disabled, "vim.wait");
|
||||
}
|
||||
|
||||
intptr_t timeout = luaL_checkinteger(lstate, 1);
|
||||
@ -598,7 +601,7 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan
|
||||
luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
|
||||
} else {
|
||||
luv_set_loop(lstate, &main_loop.uv);
|
||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
||||
luv_set_callback(lstate, nlua_fast_cfpcall);
|
||||
}
|
||||
luaopen_luv(lstate);
|
||||
lua_pushvalue(lstate, -1);
|
||||
@ -724,7 +727,7 @@ static int nlua_ui_detach(lua_State *lstate)
|
||||
return luaL_error(lstate, "invalid ns_id");
|
||||
}
|
||||
|
||||
ui_remove_cb(ns_id);
|
||||
ui_remove_cb(ns_id, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1174,7 +1177,7 @@ int nlua_call(lua_State *lstate)
|
||||
size_t name_len;
|
||||
const char *name = luaL_checklstring(lstate, 1, &name_len);
|
||||
if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "Vimscript function");
|
||||
return luaL_error(lstate, e_fast_api_disabled, "Vimscript function");
|
||||
}
|
||||
|
||||
int nargs = lua_gettop(lstate) - 1;
|
||||
@ -1231,7 +1234,7 @@ free_vim_args:
|
||||
static int nlua_rpcrequest(lua_State *lstate)
|
||||
{
|
||||
if (!nlua_is_deferred_safe()) {
|
||||
return luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
||||
return luaL_error(lstate, e_fast_api_disabled, "rpcrequest");
|
||||
}
|
||||
return nlua_rpc(lstate, true);
|
||||
}
|
||||
@ -1593,6 +1596,12 @@ bool nlua_ref_is_function(LuaRef ref)
|
||||
/// @return Return value of function, as per mode
|
||||
Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena,
|
||||
Error *err)
|
||||
{
|
||||
return nlua_call_ref_ctx(false, ref, name, args, mode, arena, err);
|
||||
}
|
||||
|
||||
Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode,
|
||||
Arena *arena, Error *err)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
nlua_pushref(lstate, ref);
|
||||
@ -1605,7 +1614,13 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode,
|
||||
nlua_push_Object(lstate, &args.items[i], 0);
|
||||
}
|
||||
|
||||
if (nlua_pcall(lstate, nargs, 1)) {
|
||||
if (fast) {
|
||||
if (nlua_fast_cfpcall(lstate, nargs, 1, -1) < 0) {
|
||||
// error is already scheduled, set anyways to convey failure.
|
||||
api_set_error(err, kErrorTypeException, "fast context failure");
|
||||
}
|
||||
return NIL;
|
||||
} else if (nlua_pcall(lstate, nargs, 1)) {
|
||||
// if err is passed, the caller will deal with the error.
|
||||
if (err) {
|
||||
size_t len;
|
||||
|
@ -44,6 +44,7 @@
|
||||
|
||||
typedef struct {
|
||||
LuaRef cb;
|
||||
uint8_t errors;
|
||||
bool ext_widgets[kUIGlobalCount];
|
||||
} UIEventCallback;
|
||||
|
||||
@ -212,21 +213,20 @@ void ui_refresh(void)
|
||||
cursor_row = cursor_col = 0;
|
||||
pending_cursor_update = true;
|
||||
|
||||
bool had_message = ui_ext[kUIMessages];
|
||||
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
|
||||
ui_ext[i] = ext_widgets[i] | ui_cb_ext[i];
|
||||
if (i < kUIGlobalCount) {
|
||||
ext_widgets[i] |= ui_cb_ext[i];
|
||||
ui_call_option_set(cstr_as_string(ui_ext_names[i]), BOOLEAN_OBJ(ui_ext[i]));
|
||||
}
|
||||
// Set 'cmdheight' to zero for all tabpages when ext_messages becomes active.
|
||||
if (i == kUIMessages && !ui_ext[i] && ext_widgets[i]) {
|
||||
set_option_value(kOptCmdheight, NUMBER_OPTVAL(0), 0);
|
||||
command_height();
|
||||
FOR_ALL_TABS(tp) {
|
||||
tp->tp_ch_used = 0;
|
||||
}
|
||||
}
|
||||
ui_ext[i] = ext_widgets[i];
|
||||
if (i < kUIGlobalCount) {
|
||||
ui_call_option_set(cstr_as_string(ui_ext_names[i]), BOOLEAN_OBJ(ext_widgets[i]));
|
||||
}
|
||||
|
||||
// Reset 'cmdheight' for all tabpages when ext_messages toggles.
|
||||
if (had_message != ui_ext[kUIMessages]) {
|
||||
set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0);
|
||||
command_height();
|
||||
FOR_ALL_TABS(tp) {
|
||||
tp->tp_ch_used = had_message;
|
||||
}
|
||||
}
|
||||
|
||||
@ -713,13 +713,15 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
|
||||
}
|
||||
}
|
||||
|
||||
void ui_call_event(char *name, Array args)
|
||||
void ui_call_event(char *name, bool fast, Array args)
|
||||
{
|
||||
UIEventCallback *event_cb;
|
||||
bool handled = false;
|
||||
map_foreach_value(&ui_event_cbs, event_cb, {
|
||||
UIEventCallback *event_cb;
|
||||
map_foreach(&ui_event_cbs, ui_event_ns_id, event_cb, {
|
||||
Error err = ERROR_INIT;
|
||||
Object res = nlua_call_ref(event_cb->cb, name, args, kRetNilBool, NULL, &err);
|
||||
uint32_t ns_id = ui_event_ns_id;
|
||||
Object res = nlua_call_ref_ctx(fast, event_cb->cb, name, args, kRetNilBool, NULL, &err);
|
||||
ui_event_ns_id = 0;
|
||||
// TODO(bfredl/luukvbaal): should this be documented or reconsidered?
|
||||
// Why does truthy return from Lua callback mean remote UI should not receive
|
||||
// the event.
|
||||
@ -728,6 +730,7 @@ void ui_call_event(char *name, Array args)
|
||||
}
|
||||
if (ERROR_SET(&err)) {
|
||||
ELOG("Error executing UI event callback: %s", err.msg);
|
||||
ui_remove_cb(ns_id, true);
|
||||
}
|
||||
api_clear_error(&err);
|
||||
})
|
||||
@ -780,12 +783,16 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
|
||||
ui_refresh();
|
||||
}
|
||||
|
||||
void ui_remove_cb(uint32_t ns_id)
|
||||
void ui_remove_cb(uint32_t ns_id, bool checkerr)
|
||||
{
|
||||
if (map_has(uint32_t, &ui_event_cbs, ns_id)) {
|
||||
UIEventCallback *item = pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
|
||||
UIEventCallback *item = pmap_get(uint32_t)(&ui_event_cbs, ns_id);
|
||||
if (item && (!checkerr || ++item->errors > 10)) {
|
||||
pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
|
||||
free_ui_event_callback(item);
|
||||
ui_cb_update_ext();
|
||||
ui_refresh();
|
||||
if (checkerr) {
|
||||
msg_schedule_semsg("Excessive errors in vim.ui_attach() callback from ns: %d.", ns_id);
|
||||
}
|
||||
}
|
||||
ui_cb_update_ext();
|
||||
ui_refresh();
|
||||
}
|
||||
|
@ -18,4 +18,6 @@ EXTERN Array noargs INIT(= ARRAY_DICT_INIT);
|
||||
#endif
|
||||
// uncrustify:on
|
||||
|
||||
EXTERN MultiQueue *resize_events;
|
||||
// vim.ui_attach() namespace of currently executed callback.
|
||||
EXTERN uint32_t ui_event_ns_id INIT( = 0);
|
||||
EXTERN MultiQueue *resize_events INIT( = NULL);
|
||||
|
@ -757,7 +757,7 @@ describe('API/extmarks', function()
|
||||
{
|
||||
Ïf (!nlua_is_deferred_safe(lstate)) {
|
||||
// strictly not allowed
|
||||
Яetörn luaL_error(lstate, e_luv_api_disabled, "rpcrequest");
|
||||
Яetörn luaL_error(lstate, e_fast_api_disabled, "rpcrequest");
|
||||
}
|
||||
return nlua_rpc(lstate, true);
|
||||
}]])
|
||||
|
@ -87,9 +87,9 @@ describe('vim.uv', function()
|
||||
screen:expect([[
|
||||
|
|
||||
{2: }|
|
||||
{3:Error executing luv callback:} |
|
||||
{3:Error executing callback:} |
|
||||
{3:[string "<nvim>"]:5: E5560: nvim_set_var must not }|
|
||||
{3:be called in a lua loop callback} |
|
||||
{3:be called in a fast event context} |
|
||||
{3:stack traceback:} |
|
||||
{3: [C]: in function 'nvim_set_var'} |
|
||||
{3: [string "<nvim>"]:5: in function <[string }|
|
||||
|
@ -32,15 +32,6 @@ describe('vim.ui_attach', function()
|
||||
]]
|
||||
|
||||
screen = Screen.new(40, 5)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = { bold = true, foreground = Screen.colors.Blue1 },
|
||||
[2] = { bold = true },
|
||||
[3] = { background = Screen.colors.Grey },
|
||||
[4] = { background = Screen.colors.LightMagenta },
|
||||
[5] = { reverse = true },
|
||||
[6] = { reverse = true, bold = true },
|
||||
[7] = { background = Screen.colors.Yellow1 },
|
||||
})
|
||||
end)
|
||||
|
||||
local function expect_events(expected)
|
||||
@ -55,7 +46,7 @@ describe('vim.ui_attach', function()
|
||||
grid = [[
|
||||
fo^ |
|
||||
{1:~ }|*3
|
||||
{2:-- INSERT --} |
|
||||
{5:-- INSERT --} |
|
||||
]],
|
||||
}
|
||||
|
||||
@ -64,7 +55,7 @@ describe('vim.ui_attach', function()
|
||||
grid = [[
|
||||
food^ |
|
||||
{1:~ }|*3
|
||||
{2:-- INSERT --} |
|
||||
{5:-- INSERT --} |
|
||||
]],
|
||||
}
|
||||
expect_events {
|
||||
@ -83,7 +74,7 @@ describe('vim.ui_attach', function()
|
||||
grid = [[
|
||||
foobar^ |
|
||||
{1:~ }|*3
|
||||
{2:-- INSERT --} |
|
||||
{5:-- INSERT --} |
|
||||
]],
|
||||
}
|
||||
expect_events {
|
||||
@ -105,10 +96,10 @@ describe('vim.ui_attach', function()
|
||||
screen:expect {
|
||||
grid = [[
|
||||
food^ |
|
||||
{3:food }{1: }|
|
||||
{12:food }{1: }|
|
||||
{4:foobar }{1: }|
|
||||
{4:foo }{1: }|
|
||||
{2:-- INSERT --} |
|
||||
{5:-- INSERT --} |
|
||||
]],
|
||||
}
|
||||
expect_events {}
|
||||
@ -180,12 +171,17 @@ describe('vim.ui_attach', function()
|
||||
exec_lua([[
|
||||
_G.cmdline = 0
|
||||
vim.ui_attach(ns, { ext_messages = true }, function(ev)
|
||||
vim.cmd.redraw()
|
||||
if ev == 'msg_show' then
|
||||
vim.schedule(function() vim.cmd.redraw() end)
|
||||
else
|
||||
vim.cmd.redraw()
|
||||
end
|
||||
_G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0)
|
||||
end
|
||||
)]])
|
||||
feed(':')
|
||||
eq(1, exec_lua('return _G.cmdline'))
|
||||
n.assert_alive()
|
||||
eq(2, exec_lua('return _G.cmdline'))
|
||||
n.assert_alive()
|
||||
feed('version<CR><CR>v<Esc>')
|
||||
n.assert_alive()
|
||||
@ -211,9 +207,9 @@ describe('vim.ui_attach', function()
|
||||
screen:expect({
|
||||
grid = [[
|
||||
cmdline |
|
||||
{5:cmdline [+] }|
|
||||
{2:cmdline [+] }|
|
||||
fooba^r |
|
||||
{6:[No Name] [+] }|
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
]],
|
||||
})
|
||||
@ -222,9 +218,9 @@ describe('vim.ui_attach', function()
|
||||
screen:expect({
|
||||
grid = [[
|
||||
foo |
|
||||
{5:cmdline [+] }|
|
||||
{5:foo}ba^r |
|
||||
{6:[No Name] [+] }|
|
||||
{2:cmdline [+] }|
|
||||
{2:foo}ba^r |
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
]],
|
||||
})
|
||||
@ -233,13 +229,124 @@ describe('vim.ui_attach', function()
|
||||
screen:expect({
|
||||
grid = [[
|
||||
%s/bar/baz |
|
||||
{5:cmdline [+] }|
|
||||
foo{7:ba^z} |
|
||||
{6:[No Name] [+] }|
|
||||
{2:cmdline [+] }|
|
||||
foo{10:ba^z} |
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
]],
|
||||
})
|
||||
end)
|
||||
|
||||
it('aborts :function on error with ext_messages', function()
|
||||
exec_lua([[
|
||||
vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
|
||||
if event == "msg_show" then
|
||||
-- "fast-api" does not prevent aborting :function
|
||||
vim.api.nvim_get_runtime_file("foo", false)
|
||||
-- non-"fast-api" is not allowed in msg_show callback and should be scheduled
|
||||
local _, err = pcall(vim.api.nvim_buf_set_lines, 0, -2, -1, false, { content[1][2] })
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_set_lines(0, -2, -1, false, { content[1][2], err })
|
||||
end)
|
||||
end
|
||||
end)
|
||||
]])
|
||||
feed(':func Foo()<cr>bar<cr>endf<cr>:func Foo()<cr>')
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^E122: Function Foo already exists, add !|
|
||||
to replace it |
|
||||
E5560: nvim_buf_set_lines must not be ca|
|
||||
lled in a fast event context |
|
||||
{1:~ }|
|
||||
]],
|
||||
messages = {
|
||||
{
|
||||
content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } },
|
||||
kind = 'emsg',
|
||||
},
|
||||
},
|
||||
})
|
||||
end)
|
||||
|
||||
it('detaches after excessive errors', function()
|
||||
screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.SeaGreen } })
|
||||
exec_lua([[
|
||||
vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function()
|
||||
vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] })
|
||||
end)
|
||||
]])
|
||||
screen:expect({
|
||||
grid = [[
|
||||
^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
})
|
||||
feed('ifoo')
|
||||
screen:expect({
|
||||
grid = [[
|
||||
foo^ |
|
||||
{1:~ }|*4
|
||||
]],
|
||||
showmode = { { '-- INSERT --', 5, 12 } },
|
||||
})
|
||||
feed('<esc>:1mes clear<cr>:mes<cr>')
|
||||
screen:expect({
|
||||
grid = [[
|
||||
foo |
|
||||
{3: }|
|
||||
{9:Excessive errors in vim.ui_attach() call}|
|
||||
{9:back from ns: 2.} |
|
||||
{100:Press ENTER or type command to continue}^ |
|
||||
]],
|
||||
})
|
||||
feed('<cr>')
|
||||
-- Also when scheduled
|
||||
exec_lua([[
|
||||
vim.ui_attach(vim.api.nvim_create_namespace(''), { ext_messages = true }, function()
|
||||
vim.schedule(function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end)
|
||||
end)
|
||||
]])
|
||||
screen:expect({
|
||||
any = 'fo^o',
|
||||
messages = {
|
||||
{
|
||||
content = {
|
||||
{
|
||||
'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>',
|
||||
9,
|
||||
7,
|
||||
},
|
||||
},
|
||||
kind = 'lua_error',
|
||||
},
|
||||
{
|
||||
content = {
|
||||
{
|
||||
'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>',
|
||||
9,
|
||||
7,
|
||||
},
|
||||
},
|
||||
kind = 'lua_error',
|
||||
},
|
||||
{
|
||||
content = { { 'Press ENTER or type command to continue', 100, 19 } },
|
||||
kind = 'return_prompt',
|
||||
},
|
||||
},
|
||||
})
|
||||
feed('<esc>:1mes clear<cr>:mes<cr>')
|
||||
screen:expect({
|
||||
grid = [[
|
||||
foo |
|
||||
{3: }|
|
||||
{9:Excessive errors in vim.ui_attach() call}|
|
||||
{9:back from ns: 3.} |
|
||||
{100:Press ENTER or type command to continue}^ |
|
||||
]],
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('vim.ui_attach', function()
|
||||
|
@ -1414,7 +1414,7 @@ describe('lua stdlib', function()
|
||||
screen:expect {
|
||||
grid = [[
|
||||
{9:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
|
||||
{9: called in a lua loop callback} |
|
||||
{9: called in a fast event context} |
|
||||
{9:stack traceback:} |
|
||||
{9: [C]: in function 'rpcrequest'} |
|
||||
{9: [string "<nvim>"]:6: in function <[string }|
|
||||
@ -3783,7 +3783,7 @@ stack traceback:
|
||||
end)
|
||||
]])
|
||||
screen:expect({
|
||||
any = pesc('E5560: vim.wait must not be called in a lua loop callback'),
|
||||
any = pesc('E5560: vim.wait must not be called in a fast event context'),
|
||||
})
|
||||
feed('<CR>')
|
||||
assert_alive()
|
||||
|
@ -25,8 +25,6 @@ local command = n.command
|
||||
local write_file = t.write_file
|
||||
local api = n.api
|
||||
local sleep = vim.uv.sleep
|
||||
local matches = t.matches
|
||||
local pcall_err = t.pcall_err
|
||||
local assert_alive = n.assert_alive
|
||||
local poke_eventloop = n.poke_eventloop
|
||||
local feed = n.feed
|
||||
@ -227,72 +225,6 @@ describe('listing functions using :function', function()
|
||||
exec_capture(('function <lambda>%s'):format(num))
|
||||
)
|
||||
end)
|
||||
|
||||
it('does not crash if another function is deleted while listing', function()
|
||||
local _ = Screen.new(80, 24)
|
||||
matches(
|
||||
'Vim%(function%):E454: Function list was modified$',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[=[
|
||||
vim.cmd([[
|
||||
func Func1()
|
||||
endfunc
|
||||
func Func2()
|
||||
endfunc
|
||||
func Func3()
|
||||
endfunc
|
||||
]])
|
||||
|
||||
local ns = vim.api.nvim_create_namespace('test')
|
||||
|
||||
vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
|
||||
if event == 'msg_show' and content[1][2] == 'function Func1()' then
|
||||
vim.cmd('delfunc Func3')
|
||||
end
|
||||
end)
|
||||
|
||||
vim.cmd('function')
|
||||
|
||||
vim.ui_detach(ns)
|
||||
]=]
|
||||
)
|
||||
)
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('does not crash if the same function is deleted while listing', function()
|
||||
local _ = Screen.new(80, 24)
|
||||
matches(
|
||||
'Vim%(function%):E454: Function list was modified$',
|
||||
pcall_err(
|
||||
exec_lua,
|
||||
[=[
|
||||
vim.cmd([[
|
||||
func Func1()
|
||||
endfunc
|
||||
func Func2()
|
||||
endfunc
|
||||
func Func3()
|
||||
endfunc
|
||||
]])
|
||||
|
||||
local ns = vim.api.nvim_create_namespace('test')
|
||||
|
||||
vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
|
||||
if event == 'msg_show' and content[1][2] == 'function Func1()' then
|
||||
vim.cmd('delfunc Func2')
|
||||
end
|
||||
end)
|
||||
|
||||
vim.cmd('function')
|
||||
|
||||
vim.ui_detach(ns)
|
||||
]=]
|
||||
)
|
||||
)
|
||||
assert_alive()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('no double-free in garbage collection #16287', function()
|
||||
|
Loading…
Reference in New Issue
Block a user