From b2adfe775d5e22efca2f9943abeb48e0cc4498c5 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Tue, 24 Mar 2026 14:53:25 +0100 Subject: [PATCH] fix(ui2): show messages in dialog window when entering expanded cmdline #38465 Problem: - With expanded messages exceeding cfg.msg.cmd.height, entering the cmdline scrolls to the bottom and expands to the full "cmd" buffer text height. - Cursor in the pager is not always at the last message and at the bottom of the window when appending to the pager. - unreliable test: messages2_spec: "closed msg window timer removes empty lines". Solution: - Achieve separation of the cmdline and message text by moving messages to the dialog window when entering the cmdline below expanded messages. - Set cursor to start of the first message only when first entering the pager. Use `norm! zb` to position last message at the bottom of the window (which shouldn't crash anymore since 911337eb). - Increase cfg.msg.msg.timeout used in the test file. --- runtime/lua/vim/_core/ui2/cmdline.lua | 7 +++--- runtime/lua/vim/_core/ui2/messages.lua | 31 +++++++++++++------------- test/functional/ui/messages2_spec.lua | 6 ++--- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/runtime/lua/vim/_core/ui2/cmdline.lua b/runtime/lua/vim/_core/ui2/cmdline.lua index 6532074181..0b9638275f 100644 --- a/runtime/lua/vim/_core/ui2/cmdline.lua +++ b/runtime/lua/vim/_core/ui2/cmdline.lua @@ -92,10 +92,11 @@ end ---@param level integer ---@param hl_id integer function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id) - -- When entering the cmdline while it is expanded, place cmdline below messages. + -- When entering the cmdline while it is expanded, move messages to dialog window. if M.level == 0 and ui.msg.cmd_on_key then - M.srow = api.nvim_buf_line_count(ui.bufs.cmd) - M.expand, ui.msg.cmd_on_key = 1, nil + M.expand, M.dialog, ui.msg.cmd_on_key = 1, true, nil + api.nvim_win_set_config(ui.wins.cmd, { border = 'none' }) + ui.msg.expand_msg('cmd') elseif ui.msg.cmd.msg_row ~= -1 and M.expand == 0 then ui.msg.msg_clear() end diff --git a/runtime/lua/vim/_core/ui2/messages.lua b/runtime/lua/vim/_core/ui2/messages.lua index 9ddbca5e76..8f1bea929d 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -191,13 +191,13 @@ local function set_virttext(type, tgt) end local hlopts = { undo_restore = false, invalidate = true, priority = 1 } ---- Move messages to expanded cmdline or pager to show in full. -local function expand_msg(src) +--- Move messages to expanded cmdline, dialog or pager to show in full. +function M.expand_msg(src) -- Copy and clear message from src to enlarged cmdline that is dismissed by any -- key press. Append to pager instead if it isn't hidden or we want to enter it -- after cmdline was entered during expanded cmdline. local hidden = api.nvim_win_get_config(ui.wins.pager).hide - local tgt = (ui.cmd.expand > 0 or not hidden) and 'pager' or 'cmd' + local tgt = (src == 'dialog' or not hidden) and 'pager' or ui.cmd.expand > 0 and 'dialog' or 'cmd' if tgt ~= src then local srow = hidden and 0 or api.nvim_buf_line_count(ui.bufs.pager) local opts = { details = true, type = 'highlight' } @@ -322,17 +322,16 @@ function M.show_msg(tgt, kind, content, replace_last, append, id) api.nvim_win_set_width(ui.wins.msg, width) local texth = api.nvim_win_text_height(ui.wins.msg, { start_row = start_row, end_row = row }) if texth.all > math.ceil(o.lines * 0.5) then - expand_msg(tgt) + M.expand_msg(tgt) else M.msg.width = width M.msg:start_timer(buf, id) end elseif tgt == 'cmd' and dupe == 0 then fn.clearmatches(ui.wins.cmd) -- Clear matchparen highlights. - if ui.cmd.srow > 0 and ui.cmd.expand == 0 then + if ui.cmd.srow > 0 then -- In block mode the cmdheight is already dynamic, so just print the full message - -- regardless of height. Put cmdline below message. Don't do this if the block mode - -- was simulated for a cmdline entered while expanded, will open pager instead. + -- regardless of height. Put cmdline below message. ui.cmd.srow = row + 1 else api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 }) -- ensure first line is visible @@ -345,7 +344,7 @@ function M.show_msg(tgt, kind, content, replace_last, append, id) -- Expand the cmdline for a non-error message that doesn't fit. local error_kinds = { rpc_error = 1, emsg = 1, echoerr = 1, lua_error = 1 } if texth.all > ui.cmdheight and (ui.cmdheight == 0 or not error_kinds[kind]) then - expand_msg(tgt) + M.expand_msg(tgt) end end end @@ -429,17 +428,18 @@ function M.msg_show(kind, content, replace_last, _, append, id, trigger) -- When message was emitted below an already expanded cmdline, move and route to pager. tgt = ui.cmd.expand > 0 and 'pager' or tgt if ui.cmd.expand == 1 then - expand_msg('cmd') + M.expand_msg('dialog') end ui.cmd.expand = ui.cmd.expand + (ui.cmd.expand > 0 and 1 or 0) local enter_pager = tgt == 'pager' and not in_pager - M.show_msg(tgt, kind, content, replace_last or enter_pager or ui.cmd.expand > 0, append, id) - -- Don't remember search_cmd message as actual message. + M.show_msg(tgt, kind, content, replace_last or enter_pager, append, id) if kind == 'search_cmd' then + -- Don't remember search_cmd message as actual message. M.cmd.ids, M.prev_msg = {}, '' - elseif tgt == 'pager' and in_pager and not enter_pager then - api.nvim_win_set_cursor(ui.wins.pager, { api.nvim_buf_line_count(ui.bufs.pager), 0 }) + elseif tgt == 'pager' then + -- Position cursor at start of first or last message at bottom of window. + fn.win_execute(ui.wins.pager, 'norm! ' .. (enter_pager and 'gg0' or 'G0zb')) end end end @@ -500,6 +500,7 @@ function M.msg_history_show(entries, prev_cmd) for i, entry in ipairs(entries) do M.show_msg('pager', entry[1], entry[2], i == 1, entry[3], 0) end + api.nvim_win_set_cursor(ui.wins.pager, { 1, 0 }) M.set_pos('pager') end @@ -526,6 +527,7 @@ local cmd_on_key = function(_, typed) api.nvim_command('norm! g<') end set_virttext('msg') + return entered and '' end --- Add virtual [+x] text to indicate scrolling is possible. @@ -595,7 +597,6 @@ local function enter_pager() -- Cmdwin is closed one event iteration later so schedule in case it was open. vim.schedule(function() local height, id = api.nvim_win_get_height(ui.wins.pager), 0 - api.nvim_win_set_cursor(ui.wins.pager, { 1, 0 }) api.nvim_set_option_value('eiw', '', { scope = 'local', win = ui.wins.pager }) api.nvim_set_current_win(ui.wins.pager) id = api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'WinResized' }, { @@ -646,7 +647,7 @@ function M.set_pos(tgt) cfg.row, cfg.height = win_row_height(t, texth.all) cfg.border = t ~= 'msg' and { '', top, '', '', '', '', '', '' } or nil cfg.mouse = tgt == 'cmd' or nil - cfg.title = tgt == 'dialog' and cfg.height < texth.all and { hint } or nil + cfg.title = tgt == 'dialog' and { cfg.height < texth.all and hint or { '' } } or nil api.nvim_win_set_config(win, cfg) if tgt == 'cmd' and not M.cmd_on_key then diff --git a/test/functional/ui/messages2_spec.lua b/test/functional/ui/messages2_spec.lua index fc89b0c885..8ac0e07e48 100644 --- a/test/functional/ui/messages2_spec.lua +++ b/test/functional/ui/messages2_spec.lua @@ -6,7 +6,7 @@ local Screen = require('test.functional.ui.screen') local api, clear, command, exec_lua, feed = n.api, n.clear, n.command, n.exec_lua, n.feed -local msg_timeout = 200 +local msg_timeout = 400 local function set_msg_target_zero_ch() exec_lua(function() require('vim._core.ui2').enable({ msg = { target = 'msg', msg = { timeout = msg_timeout } } }) @@ -447,9 +447,9 @@ describe('messages2', function() foo | {1:~ }|*8 {3: }| - ^foo | + foo | bar | - baz | + ^baz | {16::}{15:echo} {26:"baz"} | ]]) -- Subsequent typed commands are appended to the pager.