From 16c145d24042ebdb0e2a0479697c4c5808d3ebcf Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:44:37 -0500 Subject: [PATCH] fix(ui2): preserve Visual mode messages #40828 Problem: The existing `showmode` overlay can immediately cover messages emitted while Visual mode is active, including the `g CTRL-G` word count. Solution: Protect Visual mode messages with the existing message delay and temporarily hide the previous last-line overlay until it is restored. --- runtime/lua/vim/_core/ui2/messages.lua | 13 ++++++++++--- test/functional/ui/messages2_spec.lua | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/runtime/lua/vim/_core/ui2/messages.lua b/runtime/lua/vim/_core/ui2/messages.lua index 03d45b4c8f..3ee7493e6b 100644 --- a/runtime/lua/vim/_core/ui2/messages.lua +++ b/runtime/lua/vim/_core/ui2/messages.lua @@ -25,7 +25,7 @@ local M = { ids = {}, ---@type table List of visible messages. msg_row = -1, -- Last row of message to distinguish for placing virt_text. last_col = o.columns, -- Crop text to start column of 'last' virt_text. - last_emsg = 0, -- Time an error was printed that should not be overwritten. + last_msg = 0, -- Time an important message was printed that should not be overwritten. prev_msg = '', -- Concatenated content of the previous message. prev_id = 0, ---@type string|integer Message id of the previous message. dupe = 0, -- Number of times message is repeated. @@ -168,7 +168,12 @@ local function set_virttext(type, tgt) else if scol > M.cmd.last_col then -- Give the user some time to read an important message. - if os.time() - M.cmd.last_emsg < 2 then + if os.time() - M.cmd.last_msg < 2 then + -- The existing overlay may already cover the message, so hide it during the delay. + if M.virt.ids.last then + api.nvim_buf_del_extmark(ui.bufs.cmd, ui.ns, M.virt.ids.last) + M.virt.ids.last = nil + end M.virt.delayed = true vim.defer_fn(function() M.virt.delayed = false @@ -467,7 +472,9 @@ function M.msg_show(kind, content, replace_last, _, append, id, trigger) if tgt == 'cmd' then -- Store the time when an important message was emitted in order to not overwrite -- it with 'last' virt_text in the cmdline so that the user has a chance to read it. - M.cmd.last_emsg = (kind == 'emsg' or kind == 'wmsg') and os.time() or M.cmd.last_emsg + local mode = api.nvim_get_mode().mode + local visual = mode:match('^[vV\22]') ~= nil + M.cmd.last_msg = (kind == 'emsg' or kind == 'wmsg' or visual) and os.time() or M.cmd.last_msg -- Should clear the search count now, mark itself is cleared by invalidate. M.virt.last[M.virt.idx.search][1] = nil M.virt.last[M.virt.idx.cmd] = {} diff --git a/test/functional/ui/messages2_spec.lua b/test/functional/ui/messages2_spec.lua index 45e9bcb964..cee80fc8fb 100644 --- a/test/functional/ui/messages2_spec.lua +++ b/test/functional/ui/messages2_spec.lua @@ -448,6 +448,27 @@ describe('messages2', function() ]]) end) + it('showmode does not overwrite Visual word count #40824', function() + if t.is_os('win') then + pending('FIXME #40843') + end + api.nvim_buf_set_lines(0, 0, -1, true, { 'one two', 'three four' }) + feed('Vj') + screen:expect([[ + {17:one two} | + ^t{17:hree four} | + {1:~ }|*11 + {5:-- VISUAL LINE --} | + ]]) + feed('g') + screen:expect([[ + {17:one two} | + ^t{17:hree four} | + {1:~ }|*11 + Selected 2 of 2 Lines; 4 of 4 Words; 19 of 19 Bytes | + ]]) + end) + it('hit-enter prompt does not error for invalid window #35095', function() command('echo "foo\nbar"') screen:expect([[