fix(ui2): only set dialog on_key callback once #37905

Problem:  vim.on_key() called for each message while cmdline is open.
          Cursor is on a seemingly random column when pager is entered.
          Entering the pager while the cmdline is expanded can be more
          convenient than pressing "g<".
          Pager window is unnecessarily clamped to half the shell height.
          Setting 'laststatus' while pager is open does not adjust its
          dimensions.
Solution: Only call vim.on_key() once when dialog window is opened.
          Ensure cursor is at the start of the first message when
          entering the pager.
          Enter the pager window when "<CR>" is pressed while the
          cmdline is expanded.
          Don't clamp the pager window height.
          Set message windows dimensions when 'laststatus' changes.
This commit is contained in:
luukvbaal
2026-02-16 23:11:32 +01:00
committed by GitHub
parent 13cf80deef
commit 16495e6863
4 changed files with 28 additions and 20 deletions

View File

@@ -141,7 +141,7 @@ function M.check_targets()
end
local function ui_callback(redraw_msg, event, ...)
local handler = M.msg[event] or M.cmd[event]
local handler = M.msg[event] or M.cmd[event] --[[@as function]]
M.check_targets()
handler(...)
-- Cmdline mode, non-fast message and non-empty showcmd require an immediate redraw.
@@ -226,9 +226,11 @@ function M.enable(opts)
api.nvim_create_autocmd('OptionSet', {
group = M.augroup,
pattern = { 'cmdheight' },
callback = function()
check_cmdheight(vim.v.option_new)
pattern = { 'cmdheight', 'laststatus' },
callback = function(ev)
if ev.match == 'cmdheight' then
check_cmdheight(vim.v.option_new)
end
M.msg.set_pos()
end,
desc = 'Set cmdline and message window dimensions for changed option values.',

View File

@@ -143,7 +143,7 @@ function M.cmdline_hide(level, abort)
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
M.dialog, ui.msg.dialog_on_key = false, nil
end
end)

View File

@@ -35,7 +35,7 @@ local M = {
ids = {}, ---@type { ['last'|'msg'|'top'|'bot']: integer? } Table of mark IDs.
delayed = false, -- Whether placement of 'last' virt_text is delayed.
},
dialog_on_key = 0, -- vim.on_key namespace for paging in the dialog window.
dialog_on_key = nil, ---@type integer? vim.on_key namespace for paging in the dialog window.
}
local cmd_on_key ---@type integer? Set to vim.on_key namespace while cmdline is expanded.
@@ -507,7 +507,8 @@ function M.set_pos(type)
local cfg = { hide = false, relative = 'laststatus', col = 10000 }
local texth = type and api.nvim_win_text_height(win, {}) or {}
local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' }
cfg.height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
cfg.height = type == 'pager' and texth.all
or type and math.min(texth.all, math.ceil(o.lines * 0.5))
cfg.border = win ~= ui.wins.msg and { '', top, '', '', '', '', '', '' } or nil
cfg.focusable = type == 'cmd' or nil
cfg.row = (win == ui.wins.msg and 0 or 1) - ui.cmd.wmnumode
@@ -522,11 +523,13 @@ function M.set_pos(type)
set_virttext('msg', 'cmd')
M.virt.msg[M.virt.idx.spill][1] = save_spill
cmd_on_key = vim.on_key(function(_, typed)
if not typed or fn.keytrans(typed) == '<MouseMove>' then
typed = typed and fn.keytrans(typed)
if not typed or typed == '<MouseMove>' then
return
end
vim.schedule(function()
local entered = api.nvim_get_current_win() == ui.wins.cmd
local entered = typed == '<CR>' or api.nvim_get_current_win() == ui.wins.cmd
cmd_on_key = nil
if api.nvim_win_is_valid(ui.wins.cmd) then
api.nvim_win_close(ui.wins.cmd, true)
@@ -537,7 +540,8 @@ function M.set_pos(type)
M.virt.msg[M.virt.idx.spill][1] = nil
api.nvim_buf_set_lines(ui.bufs.cmd, 0, -1, false, {})
if entered then
api.nvim_command('norm! g<') -- User entered the cmdline window: open the pager.
-- User entered the cmdline window or pressed enter: open the pager.
api.nvim_command('norm! g<')
end
elseif ui.cfg.msg.target == 'cmd' and ui.cmd.level == 0 then
ui.check_targets()
@@ -581,7 +585,7 @@ function M.set_pos(type)
set_top_bot_spill()
return fn.getwininfo(ui.wins.dialog)[1].topline ~= info.topline and '' or nil
end
end)
end, M.dialog_on_key)
elseif type == 'msg' then
-- Ensure last line is visible and first line is at top of window.
local row = (texth.all > cfg.height and texth.end_row or 0) + 1
@@ -597,6 +601,8 @@ function M.set_pos(type)
-- Cmdwin is actually closed one event iteration later so schedule in case it was open.
vim.schedule(function()
api.nvim_set_current_win(ui.wins.pager)
-- Ensure cursor is at beginning of first message.
api.nvim_win_set_cursor(ui.wins.pager, { 1, 0 })
-- Make pager relative to cmdwin when it is opened, restore when it is closed.
api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, {
callback = function(ev)

View File

@@ -45,9 +45,9 @@ describe('messages2', function()
|
{1:~ }|*9
{3: }|
fo^o |
^foo |
bar |
1,3 All|
1,1 All|
]])
-- Multiple messages in same event loop iteration are appended and shown in full.
feed([[q:echo "foo" | echo "bar\nbaz\n"->repeat(&lines)<CR>]])
@@ -100,10 +100,10 @@ describe('messages2', function()
|
{1:~ }|*8
{3: }|
fo^o |
^foo |
bar |
1 %a "[No Name]" line 1 |
1,3 All|
1,1 All|
]])
-- edit_unputchar() does not clear already updated screen #34515.
feed('qix<Esc>dwi<C-r>')
@@ -143,7 +143,7 @@ describe('messages2', function()
|
{1:~ }|*10
{3: }|
fo^o |
^foo |
foo |
]])
command('bdelete | messages')
@@ -417,7 +417,7 @@ describe('messages2', function()
|
{1:~ }|*10
{3: }|
foofoofoofoofoofoofoofoofo^o |
^foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofo|
|
]])
t.eq({ filetype = 5 }, n.eval('g:set')) -- still fires for 'filetype'
@@ -448,7 +448,7 @@ describe('messages2', function()
|
{1:~ }|*11
{3: }|
{101:fo^o}{100: }|
{101:^foo}{100: }|
]])
end)
@@ -564,7 +564,7 @@ describe('messages2', function()
|
{1:~ }|*8
{3: }|
x^! |
^x! |
x! |
i hate locks so much!!!! |*2
]])
@@ -647,7 +647,7 @@ describe('messages2', function()
foo |*2
{14:f}oo |
]])
feed('<CR>')
feed('<Esc>')
screen:expect([[
^ |
{1:~ }|*5