fix(extui): adjust "more" window routing (#34251)

Problem:  Message lines from multiple message events that end up
          spilling 'cmdheight' end up spread out over the cmdline
          and "more" window.
          Messages emitted as feedback to a typed :command (rather than
          its sole purpose like :echo/:=) are routed to the more window.
          The more window isn't closed when entering the cmdwin, and
          doesn't allow `vim.hl.on_yank()`.
Solution: When first opening the "more" window for spilled messages,
          move the message buffer to the more window.
          Restrict routing of typed commands to echo kinds.
          Ignore all events but WinLeave and TextYankPost.
This commit is contained in:
luukvbaal
2025-06-01 20:54:38 +02:00
committed by GitHub
parent b95189b7e3
commit ff95d7ff9a
3 changed files with 31 additions and 23 deletions

View File

@ -110,10 +110,8 @@ function M.enable(opts)
end end
end end
if vim.v.vim_did_enter == 1 then ext.tab_check_wins()
ext.tab_check_wins() check_opt('cmdheight', vim.o.cmdheight)
check_opt('cmdheight', vim.o.cmdheight)
end
api.nvim_create_autocmd('OptionSet', { api.nvim_create_autocmd('OptionSet', {
group = ext.augroup, group = ext.augroup,

View File

@ -42,8 +42,8 @@ local M = {
---@param len integer Number of rows that should be removed. ---@param len integer Number of rows that should be removed.
function M.box:start_timer(buf, len) function M.box:start_timer(buf, len)
self.timer = vim.defer_fn(function() self.timer = vim.defer_fn(function()
if self.count == 0 or not api.nvim_buf_is_valid(buf) then if buf ~= ext.bufs.box or not api.nvim_buf_is_valid(buf) then
return -- Messages moved to split or buffer was closed. return -- Messages moved to more or buffer was closed.
end end
api.nvim_buf_set_lines(buf, 0, len, false, {}) api.nvim_buf_set_lines(buf, 0, len, false, {})
self.count = self.count - 1 self.count = self.count - 1
@ -168,6 +168,16 @@ local function set_virttext(type)
end end
end end
---Move message buffer to more window.
local function msg_to_more(tar)
api.nvim_win_set_buf(ext.wins[ext.tab].more, ext.bufs[tar])
api.nvim_buf_delete(ext.bufs.more, { force = true })
api.nvim_buf_set_name(ext.bufs[tar], 'vim._extui.more')
ext.bufs.more, ext.bufs[tar], M[tar].count = ext.bufs[tar], -1, 0
ext.tab_check_wins()
M.set_pos('more')
end
-- We need to keep track of the current message column to be able to -- We need to keep track of the current message column to be able to
-- append or overwrite messages for :echon or carriage returns. -- append or overwrite messages for :echon or carriage returns.
local col = 0 local col = 0
@ -231,6 +241,7 @@ function M.show_msg(tar, content, replace_last, append, more)
hl_group = chunk[3], hl_group = chunk[3],
undo_restore = false, undo_restore = false,
invalidate = true, invalidate = true,
priority = 1,
}) })
end end
@ -245,21 +256,20 @@ function M.show_msg(tar, content, replace_last, append, more)
if tar == 'box' then if tar == 'box' then
api.nvim_win_set_width(ext.wins[ext.tab].box, width) api.nvim_win_set_width(ext.wins[ext.tab].box, width)
local h = api.nvim_win_text_height(ext.wins[ext.tab].box, { start_row = start_row }) local h = api.nvim_win_text_height(ext.wins[ext.tab].box, { start_row = start_row })
if h.all > (more and 1 or math.ceil(o.lines * 0.5)) then if more and h.all > 1 then
api.nvim_buf_set_lines(ext.bufs.box, start_row, -1, false, {}) msg_to_more(tar)
api.nvim_win_set_width(ext.wins[ext.tab].box, M.box.width) api.nvim_win_set_width(ext.wins[ext.tab].box, M.box.width)
M.msg_history_show({ { 'spill', content } }) -- show message in 'more' window
return return
end end
M.set_pos('box') M.set_pos('box')
M.box.width = width
if restart then if restart then
M.box.timer:stop() M.box.timer:stop()
M.box.timer:set_repeat(4000) M.box.timer:set_repeat(4000)
M.box.timer:again() M.box.timer:again()
else else
M.box:start_timer(ext.bufs.box, row - start_row + 1) M.box:start_timer(ext.bufs.box, row - start_row + 1)
M.box.width = width
end end
elseif tar == 'cmd' and dupe == 0 then elseif tar == 'cmd' and dupe == 0 then
fn.clearmatches(ext.wins[ext.tab].cmd) -- Clear matchparen highlights. fn.clearmatches(ext.wins[ext.tab].cmd) -- Clear matchparen highlights.
@ -272,8 +282,8 @@ function M.show_msg(tar, content, replace_last, append, more)
else else
local h = api.nvim_win_text_height(ext.wins[ext.tab].cmd, {}) local h = api.nvim_win_text_height(ext.wins[ext.tab].cmd, {})
if more and h.all > ext.cmdheight then if more and h.all > ext.cmdheight then
api.nvim_buf_set_lines(ext.bufs.cmd, start_row, -1, false, {}) ext.cmd.highlighter:destroy()
M.msg_history_show({ { 'spill', content } }) -- show message in 'more' window msg_to_more(tar)
return return
end end
@ -302,7 +312,6 @@ function M.show_msg(tar, content, replace_last, append, more)
end end
end end
local append_more = 0
local replace_bufwrite = false local replace_bufwrite = false
--- Route the message to the appropriate sink. --- Route the message to the appropriate sink.
--- ---
@ -329,7 +338,7 @@ function M.msg_show(kind, content, _, _, append)
-- Verbose messages are sent too often to be meaningful in the cmdline: -- Verbose messages are sent too often to be meaningful in the cmdline:
-- always route to box regardless of cfg.msg.pos. -- always route to box regardless of cfg.msg.pos.
M.show_msg('box', content, false, append) M.show_msg('box', content, false, append)
elseif append_more > 0 and ext.cfg.msg.pos == 'cmd' then elseif ext.cfg.msg.pos == 'cmd' and api.nvim_get_current_win() == ext.wins[ext.tab].more then
-- Append message to already open 'more' window. -- Append message to already open 'more' window.
M.msg_history_show({ { 'spill', content } }) M.msg_history_show({ { 'spill', content } })
api.nvim_command('norm! G') api.nvim_command('norm! G')
@ -351,8 +360,9 @@ function M.msg_show(kind, content, _, _, append)
M.virt.last[M.virt.idx.search][1] = nil M.virt.last[M.virt.idx.search][1] = nil
end end
-- Messages sent as a result of a typed command should be routed to the more window. -- Typed "inspection" messages should be routed to the more window.
local more = ext.cmd.level >= 0 or kind == 'list_cmd' local typed_more = { 'echo', 'echomsg', 'lua_print' }
local more = kind == 'list_cmd' or (ext.cmd.level >= 0 and vim.tbl_contains(typed_more, kind))
M.show_msg(tar, content, replace_bufwrite, append, more) M.show_msg(tar, content, replace_bufwrite, append, more)
-- Replace message for every second bufwrite message. -- Replace message for every second bufwrite message.
replace_bufwrite = not replace_bufwrite and kind == 'bufwrite' replace_bufwrite = not replace_bufwrite and kind == 'bufwrite'
@ -402,13 +412,13 @@ function M.msg_history_show(entries)
end end
-- Appending messages while 'more' window is open. -- Appending messages while 'more' window is open.
if append_more == 0 then local append_more = api.nvim_get_current_win() == ext.wins[ext.tab].more
if not append_more then
api.nvim_buf_set_lines(ext.bufs.more, 0, -1, false, {}) api.nvim_buf_set_lines(ext.bufs.more, 0, -1, false, {})
end end
append_more = append_more + 1
for i, entry in ipairs(entries) do for i, entry in ipairs(entries) do
M.show_msg('more', entry[2], i == 1 and append_more < 2, false) M.show_msg('more', entry[2], i == 1 and not append_more, false)
end end
M.set_pos('more') M.set_pos('more')
@ -443,17 +453,16 @@ function M.set_pos(type)
end end
-- It's actually closed one event iteration later so schedule in case it was open. -- It's actually closed one event iteration later so schedule in case it was open.
vim.schedule(function() vim.schedule(function()
api.nvim_create_autocmd('WinEnter', { api.nvim_set_current_win(win)
api.nvim_create_autocmd('WinLeave', {
once = true, once = true,
callback = function() callback = function()
if api.nvim_win_is_valid(win) then if api.nvim_win_is_valid(win) then
api.nvim_win_set_config(win, { hide = true }) api.nvim_win_set_config(win, { hide = true })
end end
append_more = 0
end, end,
desc = 'Hide inactive "more" window.', desc = 'Hide inactive "more" window.',
}) })
api.nvim_set_current_win(win)
end) end)
end end
end end

View File

@ -88,7 +88,8 @@ function M.tab_check_wins()
api.nvim_set_option_value('smoothscroll', true, { scope = 'local' }) api.nvim_set_option_value('smoothscroll', true, { scope = 'local' })
local ft = type == 'cmd' and 'cmdline' or ('msg' .. type) local ft = type == 'cmd' and 'cmdline' or ('msg' .. type)
api.nvim_set_option_value('filetype', ft, { scope = 'local' }) api.nvim_set_option_value('filetype', ft, { scope = 'local' })
api.nvim_set_option_value('eventignorewin', 'all', { scope = 'local' }) local ignore = 'all' .. (type == 'more' and ',-WinLeave,-TextYankPost' or '')
api.nvim_set_option_value('eventignorewin', ignore, { scope = 'local' })
end) end)
end end
end end