mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
Merge pull request #27874 from luukvbaal/funcerr
fix(messages)!: vim.ui_attach message callbacks are unsafe
This commit is contained in:
commit
05d354e216
@ -1083,6 +1083,10 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
|
|||||||
|ui-popupmenu| and the sections below for event format for respective
|
|ui-popupmenu| and the sections below for event format for respective
|
||||||
events.
|
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
|
WARNING: This api is considered experimental. Usability will vary for
|
||||||
different screen elements. In particular `ext_messages` behavior is
|
different screen elements. In particular `ext_messages` behavior is
|
||||||
subject to further changes and usability improvements. This is expected to
|
subject to further changes and usability improvements. This is expected to
|
||||||
|
@ -72,7 +72,8 @@ EDITOR
|
|||||||
|
|
||||||
EVENTS
|
EVENTS
|
||||||
|
|
||||||
• TODO
|
• |vim.ui_attach()| callbacks for |ui-messages| `msg_show` events are executed in
|
||||||
|
|api-fast| context.
|
||||||
|
|
||||||
LSP
|
LSP
|
||||||
|
|
||||||
|
@ -233,6 +233,10 @@ function vim.wait(time, callback, interval, fast_only) end
|
|||||||
--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
|
--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
|
||||||
--- and the sections below for event format for respective events.
|
--- 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
|
--- WARNING: This api is considered experimental. Usability will vary for
|
||||||
--- different screen elements. In particular `ext_messages` behavior is subject
|
--- different screen elements. In particular `ext_messages` behavior is subject
|
||||||
--- to further changes and usability improvements. This is expected to be
|
--- 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;
|
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
|
||||||
|
|
||||||
void msg_show(String kind, Array content, Boolean replace_last)
|
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)
|
void msg_clear(void)
|
||||||
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
|
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
|
||||||
void msg_showcmd(Array content)
|
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);
|
kvi_init(cookie.rv);
|
||||||
|
|
||||||
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
|
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);
|
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_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_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"));
|
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()) {
|
if (throw_if_fast && !nlua_is_deferred_safe()) {
|
||||||
semsg(e_luv_api_disabled, "Vimscript function");
|
semsg(e_fast_api_disabled, "Vimscript function");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -750,7 +750,7 @@ local function process_function(fn)
|
|||||||
write_shifted_output(
|
write_shifted_output(
|
||||||
[[
|
[[
|
||||||
if (!nlua_is_deferred_safe()) {
|
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
|
fn.name
|
||||||
|
@ -136,8 +136,8 @@ for i = 1, #events do
|
|||||||
call_output:write(' }\n')
|
call_output:write(' }\n')
|
||||||
call_output:write(' entered = true;\n')
|
call_output:write(' entered = true;\n')
|
||||||
write_arglist(call_output, ev)
|
write_arglist(call_output, ev)
|
||||||
call_output:write(' ui_call_event("' .. ev.name .. '", ' .. args .. ');\n')
|
call_output:write((' ui_call_event("%s", %s, %s)'):format(ev.name, tostring(ev.fast), args))
|
||||||
call_output:write(' entered = false;\n')
|
call_output:write(';\n entered = false;\n')
|
||||||
elseif ev.compositor_impl then
|
elseif ev.compositor_impl then
|
||||||
call_output:write(' ui_comp_' .. ev.name)
|
call_output:write(' ui_comp_' .. ev.name)
|
||||||
write_signature(call_output, ev, '', true)
|
write_signature(call_output, ev, '', true)
|
||||||
|
@ -187,7 +187,7 @@ static void nlua_luv_error_event(void **argv)
|
|||||||
msg_ext_set_kind("lua_error");
|
msg_ext_set_kind("lua_error");
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case kCallback:
|
case kCallback:
|
||||||
semsg_multiline("Error executing luv callback:\n%s", error);
|
semsg_multiline("Error executing callback:\n%s", error);
|
||||||
break;
|
break;
|
||||||
case kThread:
|
case kThread:
|
||||||
semsg_multiline("Error in luv thread:\n%s", error);
|
semsg_multiline("Error in luv thread:\n%s", error);
|
||||||
@ -201,13 +201,13 @@ static void nlua_luv_error_event(void **argv)
|
|||||||
xfree(error);
|
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
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
int retval;
|
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++;
|
in_fast_callback++;
|
||||||
|
|
||||||
int top = lua_gettop(lstate);
|
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)
|
static void nlua_schedule_event(void **argv)
|
||||||
{
|
{
|
||||||
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
|
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
|
||||||
|
uint32_t ns_id = (uint32_t)(ptrdiff_t)argv[1];
|
||||||
lua_State *const lstate = global_lstate;
|
lua_State *const lstate = global_lstate;
|
||||||
nlua_pushref(lstate, cb);
|
nlua_pushref(lstate, cb);
|
||||||
nlua_unref_global(lstate, cb);
|
nlua_unref_global(lstate, cb);
|
||||||
if (nlua_pcall(lstate, 0, 0)) {
|
if (nlua_pcall(lstate, 0, 0)) {
|
||||||
nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s"));
|
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);
|
LuaRef cb = nlua_ref_global(lstate, 1);
|
||||||
|
// Pass along UI event handler to disable on error.
|
||||||
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb);
|
multiqueue_put(main_loop.events, nlua_schedule_event, (void *)(ptrdiff_t)cb,
|
||||||
|
(void *)(ptrdiff_t)ui_event_ns_id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,7 +428,7 @@ static int nlua_wait(lua_State *lstate)
|
|||||||
FUNC_ATTR_NONNULL_ALL
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
if (in_fast_callback) {
|
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);
|
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);
|
luv_set_cthread(lstate, nlua_luv_thread_cfcpcall);
|
||||||
} else {
|
} else {
|
||||||
luv_set_loop(lstate, &main_loop.uv);
|
luv_set_loop(lstate, &main_loop.uv);
|
||||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
luv_set_callback(lstate, nlua_fast_cfpcall);
|
||||||
}
|
}
|
||||||
luaopen_luv(lstate);
|
luaopen_luv(lstate);
|
||||||
lua_pushvalue(lstate, -1);
|
lua_pushvalue(lstate, -1);
|
||||||
@ -724,7 +727,7 @@ static int nlua_ui_detach(lua_State *lstate)
|
|||||||
return luaL_error(lstate, "invalid ns_id");
|
return luaL_error(lstate, "invalid ns_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_remove_cb(ns_id);
|
ui_remove_cb(ns_id, false);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1174,7 +1177,7 @@ int nlua_call(lua_State *lstate)
|
|||||||
size_t name_len;
|
size_t name_len;
|
||||||
const char *name = luaL_checklstring(lstate, 1, &name_len);
|
const char *name = luaL_checklstring(lstate, 1, &name_len);
|
||||||
if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
|
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;
|
int nargs = lua_gettop(lstate) - 1;
|
||||||
@ -1231,7 +1234,7 @@ free_vim_args:
|
|||||||
static int nlua_rpcrequest(lua_State *lstate)
|
static int nlua_rpcrequest(lua_State *lstate)
|
||||||
{
|
{
|
||||||
if (!nlua_is_deferred_safe()) {
|
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);
|
return nlua_rpc(lstate, true);
|
||||||
}
|
}
|
||||||
@ -1593,6 +1596,12 @@ bool nlua_ref_is_function(LuaRef ref)
|
|||||||
/// @return Return value of function, as per mode
|
/// @return Return value of function, as per mode
|
||||||
Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena,
|
Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode, Arena *arena,
|
||||||
Error *err)
|
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;
|
lua_State *const lstate = global_lstate;
|
||||||
nlua_pushref(lstate, ref);
|
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);
|
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 is passed, the caller will deal with the error.
|
||||||
if (err) {
|
if (err) {
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
LuaRef cb;
|
LuaRef cb;
|
||||||
|
uint8_t errors;
|
||||||
bool ext_widgets[kUIGlobalCount];
|
bool ext_widgets[kUIGlobalCount];
|
||||||
} UIEventCallback;
|
} UIEventCallback;
|
||||||
|
|
||||||
@ -212,21 +213,20 @@ void ui_refresh(void)
|
|||||||
cursor_row = cursor_col = 0;
|
cursor_row = cursor_col = 0;
|
||||||
pending_cursor_update = true;
|
pending_cursor_update = true;
|
||||||
|
|
||||||
|
bool had_message = ui_ext[kUIMessages];
|
||||||
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
|
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
|
||||||
|
ui_ext[i] = ext_widgets[i] | ui_cb_ext[i];
|
||||||
if (i < kUIGlobalCount) {
|
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);
|
// Reset 'cmdheight' for all tabpages when ext_messages toggles.
|
||||||
command_height();
|
if (had_message != ui_ext[kUIMessages]) {
|
||||||
FOR_ALL_TABS(tp) {
|
set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0);
|
||||||
tp->tp_ch_used = 0;
|
command_height();
|
||||||
}
|
FOR_ALL_TABS(tp) {
|
||||||
}
|
tp->tp_ch_used = had_message;
|
||||||
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]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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;
|
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;
|
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?
|
// TODO(bfredl/luukvbaal): should this be documented or reconsidered?
|
||||||
// Why does truthy return from Lua callback mean remote UI should not receive
|
// Why does truthy return from Lua callback mean remote UI should not receive
|
||||||
// the event.
|
// the event.
|
||||||
@ -728,6 +730,7 @@ void ui_call_event(char *name, Array args)
|
|||||||
}
|
}
|
||||||
if (ERROR_SET(&err)) {
|
if (ERROR_SET(&err)) {
|
||||||
ELOG("Error executing UI event callback: %s", err.msg);
|
ELOG("Error executing UI event callback: %s", err.msg);
|
||||||
|
ui_remove_cb(ns_id, true);
|
||||||
}
|
}
|
||||||
api_clear_error(&err);
|
api_clear_error(&err);
|
||||||
})
|
})
|
||||||
@ -780,12 +783,16 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
|
|||||||
ui_refresh();
|
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_get(uint32_t)(&ui_event_cbs, ns_id);
|
||||||
UIEventCallback *item = pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
|
if (item && (!checkerr || ++item->errors > 10)) {
|
||||||
|
pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
|
||||||
free_ui_event_callback(item);
|
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
|
#endif
|
||||||
// uncrustify:on
|
// 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)) {
|
Ïf (!nlua_is_deferred_safe(lstate)) {
|
||||||
// strictly not allowed
|
// 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);
|
return nlua_rpc(lstate, true);
|
||||||
}]])
|
}]])
|
||||||
|
@ -87,9 +87,9 @@ describe('vim.uv', function()
|
|||||||
screen:expect([[
|
screen:expect([[
|
||||||
|
|
|
|
||||||
{2: }|
|
{2: }|
|
||||||
{3:Error executing luv callback:} |
|
{3:Error executing callback:} |
|
||||||
{3:[string "<nvim>"]:5: E5560: nvim_set_var must not }|
|
{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:stack traceback:} |
|
||||||
{3: [C]: in function 'nvim_set_var'} |
|
{3: [C]: in function 'nvim_set_var'} |
|
||||||
{3: [string "<nvim>"]:5: in function <[string }|
|
{3: [string "<nvim>"]:5: in function <[string }|
|
||||||
|
@ -32,15 +32,6 @@ describe('vim.ui_attach', function()
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
screen = Screen.new(40, 5)
|
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)
|
end)
|
||||||
|
|
||||||
local function expect_events(expected)
|
local function expect_events(expected)
|
||||||
@ -55,7 +46,7 @@ describe('vim.ui_attach', function()
|
|||||||
grid = [[
|
grid = [[
|
||||||
fo^ |
|
fo^ |
|
||||||
{1:~ }|*3
|
{1:~ }|*3
|
||||||
{2:-- INSERT --} |
|
{5:-- INSERT --} |
|
||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +55,7 @@ describe('vim.ui_attach', function()
|
|||||||
grid = [[
|
grid = [[
|
||||||
food^ |
|
food^ |
|
||||||
{1:~ }|*3
|
{1:~ }|*3
|
||||||
{2:-- INSERT --} |
|
{5:-- INSERT --} |
|
||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
expect_events {
|
expect_events {
|
||||||
@ -83,7 +74,7 @@ describe('vim.ui_attach', function()
|
|||||||
grid = [[
|
grid = [[
|
||||||
foobar^ |
|
foobar^ |
|
||||||
{1:~ }|*3
|
{1:~ }|*3
|
||||||
{2:-- INSERT --} |
|
{5:-- INSERT --} |
|
||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
expect_events {
|
expect_events {
|
||||||
@ -105,10 +96,10 @@ describe('vim.ui_attach', function()
|
|||||||
screen:expect {
|
screen:expect {
|
||||||
grid = [[
|
grid = [[
|
||||||
food^ |
|
food^ |
|
||||||
{3:food }{1: }|
|
{12:food }{1: }|
|
||||||
{4:foobar }{1: }|
|
{4:foobar }{1: }|
|
||||||
{4:foo }{1: }|
|
{4:foo }{1: }|
|
||||||
{2:-- INSERT --} |
|
{5:-- INSERT --} |
|
||||||
]],
|
]],
|
||||||
}
|
}
|
||||||
expect_events {}
|
expect_events {}
|
||||||
@ -180,12 +171,17 @@ describe('vim.ui_attach', function()
|
|||||||
exec_lua([[
|
exec_lua([[
|
||||||
_G.cmdline = 0
|
_G.cmdline = 0
|
||||||
vim.ui_attach(ns, { ext_messages = true }, function(ev)
|
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)
|
_G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0)
|
||||||
end
|
end
|
||||||
)]])
|
)]])
|
||||||
feed(':')
|
feed(':')
|
||||||
eq(1, exec_lua('return _G.cmdline'))
|
n.assert_alive()
|
||||||
|
eq(2, exec_lua('return _G.cmdline'))
|
||||||
n.assert_alive()
|
n.assert_alive()
|
||||||
feed('version<CR><CR>v<Esc>')
|
feed('version<CR><CR>v<Esc>')
|
||||||
n.assert_alive()
|
n.assert_alive()
|
||||||
@ -211,9 +207,9 @@ describe('vim.ui_attach', function()
|
|||||||
screen:expect({
|
screen:expect({
|
||||||
grid = [[
|
grid = [[
|
||||||
cmdline |
|
cmdline |
|
||||||
{5:cmdline [+] }|
|
{2:cmdline [+] }|
|
||||||
fooba^r |
|
fooba^r |
|
||||||
{6:[No Name] [+] }|
|
{3:[No Name] [+] }|
|
||||||
|
|
|
|
||||||
]],
|
]],
|
||||||
})
|
})
|
||||||
@ -222,9 +218,9 @@ describe('vim.ui_attach', function()
|
|||||||
screen:expect({
|
screen:expect({
|
||||||
grid = [[
|
grid = [[
|
||||||
foo |
|
foo |
|
||||||
{5:cmdline [+] }|
|
{2:cmdline [+] }|
|
||||||
{5:foo}ba^r |
|
{2:foo}ba^r |
|
||||||
{6:[No Name] [+] }|
|
{3:[No Name] [+] }|
|
||||||
|
|
|
|
||||||
]],
|
]],
|
||||||
})
|
})
|
||||||
@ -233,13 +229,124 @@ describe('vim.ui_attach', function()
|
|||||||
screen:expect({
|
screen:expect({
|
||||||
grid = [[
|
grid = [[
|
||||||
%s/bar/baz |
|
%s/bar/baz |
|
||||||
{5:cmdline [+] }|
|
{2:cmdline [+] }|
|
||||||
foo{7:ba^z} |
|
foo{10:ba^z} |
|
||||||
{6:[No Name] [+] }|
|
{3:[No Name] [+] }|
|
||||||
|
|
|
|
||||||
]],
|
]],
|
||||||
})
|
})
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
describe('vim.ui_attach', function()
|
describe('vim.ui_attach', function()
|
||||||
|
@ -1414,7 +1414,7 @@ describe('lua stdlib', function()
|
|||||||
screen:expect {
|
screen:expect {
|
||||||
grid = [[
|
grid = [[
|
||||||
{9:[string "<nvim>"]:6: E5560: rpcrequest must not be}|
|
{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:stack traceback:} |
|
||||||
{9: [C]: in function 'rpcrequest'} |
|
{9: [C]: in function 'rpcrequest'} |
|
||||||
{9: [string "<nvim>"]:6: in function <[string }|
|
{9: [string "<nvim>"]:6: in function <[string }|
|
||||||
@ -3783,7 +3783,7 @@ stack traceback:
|
|||||||
end)
|
end)
|
||||||
]])
|
]])
|
||||||
screen:expect({
|
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>')
|
feed('<CR>')
|
||||||
assert_alive()
|
assert_alive()
|
||||||
|
@ -25,8 +25,6 @@ local command = n.command
|
|||||||
local write_file = t.write_file
|
local write_file = t.write_file
|
||||||
local api = n.api
|
local api = n.api
|
||||||
local sleep = vim.uv.sleep
|
local sleep = vim.uv.sleep
|
||||||
local matches = t.matches
|
|
||||||
local pcall_err = t.pcall_err
|
|
||||||
local assert_alive = n.assert_alive
|
local assert_alive = n.assert_alive
|
||||||
local poke_eventloop = n.poke_eventloop
|
local poke_eventloop = n.poke_eventloop
|
||||||
local feed = n.feed
|
local feed = n.feed
|
||||||
@ -227,72 +225,6 @@ describe('listing functions using :function', function()
|
|||||||
exec_capture(('function <lambda>%s'):format(num))
|
exec_capture(('function <lambda>%s'):format(num))
|
||||||
)
|
)
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
it('no double-free in garbage collection #16287', function()
|
it('no double-free in garbage collection #16287', function()
|
||||||
|
Loading…
Reference in New Issue
Block a user