From 131a3cacb365934bb2d98fb93e00972dbbf94cad Mon Sep 17 00:00:00 2001 From: "neovim-backports[bot]" <175700243+neovim-backports[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 20:41:20 -0400 Subject: [PATCH] fix(ui2): prevent flicker when entering pager from expanded cmdline (#38662) fix(ui2): flicker when entering pager from expanded cmdline #38639 Problem: 'showcmd' causes flickering when pressing "g<" to enter the pager when the cmdline is expanded for messages. Initial keypress for an incomplete mapping is not giving 'showcmd' feedback while cmdline is expanded for messages (which is only dismissed upon the vim.on_key callback after 'timeoutlen'). Solution: Delay dismissing expanded cmdline when vim.on_key() callback receives "g". Place 'showcmd' "last" virtual text during expanded cmdline. (cherry picked from commit 75e5e37942a64dd7904799661580bb6c7ba3e97e) Co-authored-by: luukvbaal --- runtime/lua/vim/_core/ui2/cmdline.lua | 2 +- runtime/lua/vim/_core/ui2/messages.lua | 49 ++++++++++++++++---------- test/functional/ui/messages2_spec.lua | 17 ++++++--- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/runtime/lua/vim/_core/ui2/cmdline.lua b/runtime/lua/vim/_core/ui2/cmdline.lua index 2260d85819..aa9d48538f 100644 --- a/runtime/lua/vim/_core/ui2/cmdline.lua +++ b/runtime/lua/vim/_core/ui2/cmdline.lua @@ -96,7 +96,7 @@ function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id) if M.level == 0 and ui.msg.cmd_on_key then 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') + ui.msg.expand_msg('cmd', 'dialog') 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 1cc64afc22..9fc01aaa42 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -86,7 +86,12 @@ end ---@param type 'last'|'msg'|'top'|'bot' ---@param tgt? 'cmd'|'msg'|'dialog' local function set_virttext(type, tgt) - if (type == 'last' and (ui.cmdheight == 0 or M.virt.delayed)) or M.cmd_on_key then + if + -- Cannot show with 0 'cmdheight' and delay for error. + (type == 'last' and (ui.cmdheight == 0 or M.virt.delayed)) + -- Do not overwrite temporary spill indicator during expanded cmdline. + or (M.cmd_on_key and type == 'msg') + then return -- Don't show virtual text while cmdline is expanded or delaying for error. end @@ -192,12 +197,12 @@ end local hlopts = { undo_restore = false, invalidate = true, priority = 1 } --- Move messages to expanded cmdline, dialog or pager to show in full. -function M.expand_msg(src) +function M.expand_msg(src, tgt) -- 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 = (src == 'dialog' or not hidden) and 'pager' or ui.cmd.expand > 0 and 'dialog' or 'cmd' + tgt = tgt or not hidden and 'pager' 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' } @@ -428,7 +433,7 @@ 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 - M.expand_msg('dialog') + M.expand_msg('dialog', 'pager') end ui.cmd.expand = ui.cmd.expand + (ui.cmd.expand > 0 and 1 or 0) @@ -476,7 +481,7 @@ end --- ---@param content MsgContent function M.msg_ruler(content) - M.virt.last[M.virt.idx.ruler] = ui.cmd.level > 0 and {} or content + M.virt.last[M.virt.idx.ruler] = (ui.cmd.level > 0 or M.cmd_on_key) and {} or content set_virttext('last') end @@ -493,7 +498,6 @@ function M.msg_history_show(entries, prev_cmd) -- Showing output of previous command, clear in case still visible. if M.cmd_on_key or prev_cmd then M.msg_clear() - api.nvim_feedkeys(vim.keycode(''), 'n', false) end api.nvim_buf_set_lines(ui.bufs.pager, 0, -1, false, {}) @@ -505,28 +509,33 @@ function M.msg_history_show(entries, prev_cmd) M.set_pos('pager') end -local cmd_on_key = function(_, typed) +local typed_g = false +local cmd_on_key = function(key, typed) typed = typed and fn.keytrans(typed) - if not typed or typed == '' or typed == ':' then - if typed == ':' then - vim.on_key(nil, ui.ns) - end + -- Don't dismiss for non-typed keys and mouse movement. When 'g' is passed (typed + -- or mapped), wait until the next key to avoid flickering when the pager is opened. + if not typed_g and (not typed or typed == '' or typed == 'g' or key == 'g') then + typed_g = typed == 'g' or key == 'g' return end vim.on_key(nil, ui.ns) - M.cmd_on_key, M.cmd.ids = nil, {} + if typed == ':' then + return -- Keep expanded messages open until cmdline closes. + end -- Check if window was entered and reopen with original config. - local entered = typed == '' and not api.nvim_get_mode().mode:match('[it]') - or typed:find('LeftMouse') and fn.getmousepos().winid == ui.wins.cmd + local mode = not api.nvim_get_mode().mode:match('[it]') + local entered = mode and (typed == '' or typed_g and (typed == '' or key == '<')) + or (typed:find('LeftMouse') and fn.getmousepos().winid == ui.wins.cmd) + if entered then + M.expand_msg('cmd', 'pager') + end pcall(api.nvim_win_close, ui.wins.cmd, true) ui.check_targets() - - -- Show or clear the message depending on if the pager was opened. - if entered then - api.nvim_command('norm! g<') - end set_virttext('msg') + api.nvim__redraw({ flush = true }) + + typed_g, M.cmd_on_key, M.cmd.ids = false, nil, {} return entered and '' end @@ -593,6 +602,8 @@ local function enter_pager() in_pager, was_cmdwin = true, fn.getcmdwintype() if was_cmdwin ~= '' then api.nvim_command('quit') + elseif M.cmd_on_key then + api.nvim_feedkeys(vim.keycode(''), 'n', false) end -- Cmdwin is closed one event iteration later so schedule in case it was open. vim.schedule(function() diff --git a/test/functional/ui/messages2_spec.lua b/test/functional/ui/messages2_spec.lua index 4bc5ae186f..179cd4b008 100644 --- a/test/functional/ui/messages2_spec.lua +++ b/test/functional/ui/messages2_spec.lua @@ -39,7 +39,7 @@ describe('messages2', function() end) it('multiline messages and pager', function() - command('echo "foo\nbar"') + command('set ruler showcmd noshowmode | echo "foo\nbar"') screen:expect([[ ^ | {1:~ }|*10 @@ -47,8 +47,15 @@ describe('messages2', function() foo | bar | ]]) - command('set ruler showcmd noshowmode') - feed('g') + feed('g') + screen:expect([[ + ^ | + {1:~ }|*10 + {3: }| + foo | + bar g | + ]]) + feed('') screen:expect([[ | {1:~ }|*9 @@ -76,7 +83,7 @@ describe('messages2', function() screen:expect([[ ^ | {1:~ }|*12 - foo [+29] 0,0-1 All| + foo [+29] | ]]) command('echo "foo"') -- New message clears spill indicator. @@ -835,7 +842,7 @@ describe('messages2', function() - cCommentL | ]]) feed('Q') - screen:expect_unchanged() + screen:expect_unchanged(true) feed('') -- close expanded cmdline set_msg_target_zero_ch() api.nvim_echo({ { 'foo' } }, true, { id = 1 })