feat(ui): avoid setting 'cmdheight' with vim.ui_attach()

Problem:  We allow setting 'cmdheight' to 0 with ext_messages enabled
          since b72931e7. Enabling ext_messages with vim.ui_attach()
          implicitly sets 'cmdheight' to 0 for BWC. When non-zero
          'cmdheight' is wanted, this behavior make it unnecessarily
          hard to keep track of the user configured value.
Solution: Add set_cmdheight to vim.ui_attach() opts table that can be
          set to false to avoid setting 'cmdheight' to 0.
This commit is contained in:
Luuk van Baal
2025-04-20 01:09:24 +02:00
committed by luukvbaal
parent de7306a250
commit 7ba0f623d7
6 changed files with 44 additions and 27 deletions

View File

@@ -1031,16 +1031,13 @@ vim.stricmp({a}, {b}) *vim.stricmp()*
(`0|1|-1`) if strings are equal, {a} is greater than {b} or {a} is
lesser than {b}, respectively.
vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
vim.ui_attach({ns}, {opts}, {callback}) *vim.ui_attach()*
WARNING: This feature is experimental/unstable.
Subscribe to |ui-events|, similar to |nvim_ui_attach()| but receive events
in a Lua callback. Used to implement screen elements like popupmenu or
message handling in Lua.
{options} is a dict with one or more `ext_…` |ui-option|s set to true to
enable events for the respective UI element.
{callback} receives event name plus additional parameters. See
|ui-popupmenu| and the sections below for event format for respective
events.
@@ -1073,16 +1070,21 @@ vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
<
Parameters: ~
• {ns} (`integer`)
• {options} (`table<string, any>`)
• {callback} (`fun()`)
• {ns} (`integer`) Namespace ID
• {opts} (`table<string, any>`) Optional parameters.
• {ext_…}? (`boolean`) Any of |ui-ext-options|, if true
enable events for the respective UI element.
• {set_cmdheight}? (`boolean`) If false, avoid setting
'cmdheight' to 0 when `ext_messages` is enabled.
• {callback} (`fun(event: string, ...)`) Function called for each UI
event
vim.ui_detach({ns}) *vim.ui_detach()*
Detach a callback previously attached with |vim.ui_attach()| for the given
namespace {ns}.
Parameters: ~
• {ns} (`integer`)
• {ns} (`integer`) Namespace ID
vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
Wait for {time} in milliseconds until {callback} returns `true`.

View File

@@ -226,9 +226,6 @@ function vim.wait(time, callback, interval, fast_only) end
--- Subscribe to |ui-events|, similar to |nvim_ui_attach()| but receive events in a Lua callback.
--- Used to implement screen elements like popupmenu or message handling in Lua.
---
--- {options} is a dict with one or more `ext_…` |ui-option|s set to true to enable events for
--- the respective UI element.
---
--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
--- and the sections below for event format for respective events.
---
@@ -263,12 +260,16 @@ function vim.wait(time, callback, interval, fast_only) end
---
--- @since 0
---
--- @param ns integer
--- @param options table<string, any>
--- @param callback fun()
function vim.ui_attach(ns, options, callback) end
--- @param ns integer Namespace ID
--- @param opts table<string, any> Optional parameters.
--- - {ext_…}? (`boolean`) Any of |ui-ext-options|, if true
--- enable events for the respective UI element.
--- - {set_cmdheight}? (`boolean`) If false, avoid setting
--- 'cmdheight' to 0 when `ext_messages` is enabled.
--- @param callback fun(event: string, ...) Function called for each UI event
function vim.ui_attach(ns, opts, callback) end
--- Detach a callback previously attached with |vim.ui_attach()| for the
--- given namespace {ns}.
--- @param ns integer
--- @param ns integer Namespace ID
function vim.ui_detach(ns) end

View File

@@ -682,7 +682,7 @@ static int nlua_ui_attach(lua_State *lstate)
return luaL_error(lstate, "invalid ns_id");
}
if (!lua_istable(lstate, 2)) {
return luaL_error(lstate, "ext_widgets must be a table");
return luaL_error(lstate, "opts must be a table");
}
if (!lua_isfunction(lstate, 3)) {
return luaL_error(lstate, "callback must be a Lua function");
@@ -699,13 +699,18 @@ static int nlua_ui_attach(lua_State *lstate)
const char *s = lua_tolstring(lstate, -2, &len);
bool val = lua_toboolean(lstate, -1);
for (size_t i = 0; i < kUIGlobalCount; i++) {
if (strequal(s, ui_ext_names[i])) {
if (val) {
tbl_has_true_val = true;
if (strequal(s, "set_cmdheight")) {
ui_refresh_cmdheight = val;
goto ok;
} else {
for (size_t i = 0; i < kUIGlobalCount; i++) {
if (strequal(s, ui_ext_names[i])) {
if (val) {
tbl_has_true_val = true;
}
ext_widgets[i] = val;
goto ok;
}
ext_widgets[i] = val;
goto ok;
}
}
@@ -715,11 +720,12 @@ ok:
}
if (!tbl_has_true_val) {
return luaL_error(lstate, "ext_widgets table must contain at least one 'true' value");
return luaL_error(lstate, "opts table must contain at least one 'true' ext_widget");
}
LuaRef ui_event_cb = nlua_ref_global(lstate, 3);
ui_add_cb(ns_id, ui_event_cb, ext_widgets);
ui_refresh_cmdheight = true;
return 0;
}

View File

@@ -223,9 +223,11 @@ void ui_refresh(void)
// Reset 'cmdheight' for all tabpages when ext_messages toggles.
if (had_message != ui_ext[kUIMessages]) {
set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0);
FOR_ALL_TABS(tp) {
tp->tp_ch_used = had_message;
if (ui_refresh_cmdheight) {
set_option_value(kOptCmdheight, NUMBER_OPTVAL(had_message), 0);
FOR_ALL_TABS(tp) {
tp->tp_ch_used = had_message;
}
}
msg_scroll_flush();
}

View File

@@ -21,3 +21,4 @@ EXTERN Array noargs INIT(= ARRAY_DICT_INIT);
// vim.ui_attach() namespace of currently executed callback.
EXTERN uint32_t ui_event_ns_id INIT( = 0);
EXTERN MultiQueue *resize_events INIT( = NULL);
EXTERN bool ui_refresh_cmdheight INIT( = true);

View File

@@ -162,6 +162,11 @@ describe('vim.ui_attach', function()
eq(0, n.api.nvim_get_option_value('cmdheight', {}))
end)
it("can attach ext_messages without changing 'cmdheight'", function()
exec_lua('vim.ui_attach(ns, { ext_messages = true, set_cmdheight = false }, on_event)')
eq(1, n.api.nvim_get_option_value('cmdheight', {}))
end)
it('avoids recursive flushing and invalid memory access with :redraw', function()
exec_lua([[
_G.cmdline = 0