fix(ui2): always route to dialog when cmdline is open #37730

Problem:  Messages emitted while cmdline is open are not shown with
          cmdline message target.
Solution: Route to dialog window when cmdline is open.
This commit is contained in:
luukvbaal
2026-02-05 20:54:22 +01:00
committed by GitHub
parent 7a490d65c5
commit e198037148
4 changed files with 72 additions and 38 deletions

View File

@@ -4,10 +4,11 @@ local api, fn = vim.api, vim.fn
local M = {
highlighter = nil, ---@type vim.treesitter.highlighter?
indent = 0, -- Current indent for block event.
prompt = false, -- Whether a prompt is active; messages are placed in the 'dialog' window.
prompt = false, -- Whether a prompt is active; route to dialog regardless of ui.cfg.msg.target.
dialog = false, -- Whether a dialog window was opened.
srow = 0, -- Buffer row at which the current cmdline starts; > 0 in block mode.
erow = 0, -- Buffer row at which the current cmdline ends; messages appended here in block mode.
level = -1, -- Current cmdline level; 0 when inactive, -1 one loop iteration after closing.
level = 0, -- Current cmdline level; 0 when inactive.
wmnumode = 0, -- wildmenumode() when not using the pum, dialog position adjusted when toggled.
}
@@ -29,7 +30,7 @@ local function win_config(win, hide, height)
vim.o.cmdheight = height
end)
ui.msg.set_pos()
elseif M.wmnumode ~= (M.prompt and fn.pumvisible() == 0 and fn.wildmenumode() or 0) then
elseif M.wmnumode ~= (M.dialog and fn.pumvisible() == 0 and fn.wildmenumode() or 0) then
M.wmnumode = (M.wmnumode == 1 and 0 or 1)
ui.msg.set_pos()
end
@@ -66,7 +67,7 @@ end
---@param level integer
---@param hl_id integer
function M.cmdline_show(content, pos, firstc, prompt, indent, level, hl_id)
M.level, M.indent, M.prompt = level, indent, M.prompt or #prompt > 0
M.level, M.indent, M.prompt = level, indent, #prompt > 0
if M.highlighter == nil or M.highlighter.bufnr ~= ui.bufs.cmd then
local parser = assert(vim.treesitter.get_parser(ui.bufs.cmd, 'vim', {}))
M.highlighter = vim.treesitter.highlighter.new(parser)
@@ -130,28 +131,21 @@ function M.cmdline_hide(level, abort)
fn.clearmatches(ui.wins.cmd) -- Clear matchparen highlights.
api.nvim_win_set_cursor(ui.wins.cmd, { 1, 0 })
if abort then
-- Clear cmd buffer for aborted command (non-abort is left visible).
if M.prompt or abort then
-- Clear cmd buffer prompt or aborted command (non-abort is left visible).
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
end
local clear = vim.schedule_wrap(function(was_prompt)
vim.schedule(function()
-- Avoid clearing prompt window when it is re-entered before the next event
-- loop iteration. E.g. when a non-choice confirm button is pressed.
if was_prompt and not M.prompt then
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
if M.dialog and M.level == 0 then
api.nvim_buf_set_lines(ui.bufs.dialog, 0, -1, false, {})
api.nvim_win_set_config(ui.wins.dialog, { hide = true })
vim.on_key(nil, ui.msg.dialog_on_key)
M.dialog = false
end
-- Messages emitted as a result of a typed command are treated specially:
-- remember if the cmdline was used this event loop iteration.
-- NOTE: Message event callbacks are themselves scheduled, so delay two iterations.
vim.schedule(function()
M.level = -1
end)
end)
clear(M.prompt)
M.prompt, M.level, curpos[1], curpos[2] = false, 0, 0, 0
win_config(ui.wins.cmd, true, ui.cmdheight)

View File

@@ -286,7 +286,7 @@ function M.show_msg(tar, content, replace_last, append, id)
local start_col = col
-- Accumulate to be inserted and highlighted message chunks.
for _, chunk in ipairs(content) do
for i, chunk in ipairs(content) do
-- Split at newline and write to start of line after carriage return.
for str in (chunk[2] .. '\0'):gmatch('.-[\n\r%z]') do
local repl, pat = str:sub(1, -2), str:sub(-1)
@@ -302,7 +302,6 @@ function M.show_msg(tar, content, replace_last, append, id)
api.nvim_buf_set_text(buf, row, col, erow, ecol, { repl })
end
curline = api.nvim_buf_get_lines(buf, row, row + 1, false)[1]
width = tar == 'msg' and math.max(width, api.nvim_strwidth(curline)) or 0
mark[3] = nil
if chunk[3] > 0 then
@@ -315,6 +314,11 @@ function M.show_msg(tar, content, replace_last, append, id)
else
col = pat == '\r' and 0 or end_col
end
if tar == 'msg' and (pat == '\n' or (i == #content and pat == '\0')) then
width = api.nvim_win_call(ui.wins.msg, function()
return math.max(width, fn.strdisplaywidth(curline))
end)
end
end
end
@@ -398,21 +402,23 @@ function M.msg_show(kind, content, replace_last, _, append, id)
M.virt.last[M.virt.idx.search] = content
M.virt.last[M.virt.idx.cmd] = { { 0, (' '):rep(11) } }
set_virttext('last')
elseif (ui.cmd.prompt or kind == 'wildlist') and ui.cmd.srow == 0 then
-- Route to dialog that stays open so long as the cmdline prompt is active.
elseif
(ui.cmd.prompt or (ui.cmd.level > 0 and ui.cfg.msg.target == 'cmd')) and ui.cmd.srow == 0
then
-- Route to dialog when a prompt is active, or message would overwrite active cmdline.
replace_last = api.nvim_win_get_config(ui.wins.dialog).hide or kind == 'wildlist'
if kind == 'wildlist' then
api.nvim_buf_set_lines(ui.bufs.dialog, 0, -1, false, {})
ui.cmd.prompt = true -- Ensure dialog is closed when cmdline is hidden.
end
ui.cmd.dialog = true -- Ensure dialog is closed when cmdline is hidden.
M.show_msg('dialog', content, replace_last, append, id)
M.set_pos('dialog')
else
-- Set the entered search command in the cmdline (if available).
local tar = kind == 'search_cmd' and 'cmd' or ui.cfg.msg.target
if tar == 'cmd' then
if ui.cmdheight == 0 or (ui.cmd.level > 0 and ui.cmd.srow == 0) then
return -- Do not overwrite an active cmdline unless in block mode.
if ui.cmdheight == 0 and ui.cmd.srow == 0 then
return
end
-- 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.
@@ -531,7 +537,7 @@ function M.set_pos(type)
if entered then
api.nvim_command('norm! g<') -- User entered the cmdline window: open the pager.
end
elseif ui.cfg.msg.target == 'cmd' and ui.cmd.level <= 0 then
elseif ui.cfg.msg.target == 'cmd' and ui.cmd.level == 0 then
ui.check_targets()
set_virttext('msg')
end

View File

@@ -147,7 +147,7 @@ describe('cmdline2', function()
end)
it('highlights after deleting buffer', function()
feed(':%bw!<CR>:call foo()')
feed(':sil %bw!<CR>:call foo()')
screen:expect([[
|
{1:~ }|*12
@@ -209,17 +209,17 @@ describe('cmdline2', function()
{101:Foo()}{3: Fooo() }|
{16::}{15:call} {25:Foo}{16:()}^ |
]])
feed('()')
feed('<BS><BS>')
exec('set wildoptions+=pum laststatus=2')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} {25:Foo}{16:()()}^ |
{16::}{15:call} Foo^ |
]])
exec('set wildoptions+=pum laststatus=2')
feed('<C-U>call Fo<C-Z><C-Z>')
feed('<C-Z><C-Z>')
screen:expect([[
|
{1:~ }|*9
@@ -228,15 +228,6 @@ describe('cmdline2', function()
{4: Fooo() } |
{16::}{15:call} {25:Foo}{16:()}^ |
]])
feed('()')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} {25:Foo}{16:()()}^ |
]])
end)
end)

View File

@@ -612,4 +612,47 @@ describe('messages2', function()
{1:~ }|*5
]])
end)
it('while cmdline is open', function()
command('cnoremap <C-A> <Cmd>lua error("foo")<CR>')
feed(':echo "bar"<C-A>')
screen:expect([[
|
{1:~ }|*7
{3: }|
{9:E5108: Lua: [string ":lua"]:1: foo} |
{9:stack traceback:} |
{9: [C]: in function 'error'} |
{9: [string ":lua"]:1: in main chunk} |
{16::}{15:echo} {26:"bar"}^ |
]])
feed('<CR>')
screen:expect([[
^ |
{1:~ }|*12
bar |
]])
command('set cmdheight=0')
feed([[:call confirm("foo\nbar")<C-A>]])
screen:expect([[
|
{1:~ }|*8
{1:~ }{9:E5108: Lua: [string ":lua"]:1: foo}{4: }|
{1:~ }{9:stack traceback:}{4: }|
{1:~ }{9: [C]: in function 'error'}{4: }|
{1:~ }{9: [string ":lua"]:1: in main chunk}|
{16::}{15:call} {25:confirm}{16:(}{26:"foo\nbar"}{16:)}^ |
]])
feed('<CR>')
screen:expect([[
|
{1:~ }|*7
{3: }|
|
{6:foo} |
{6:bar} |
|
{6:[O]k: }^ |
]])
end)
end)