fix(ui2): dialog paging is inconsistent #39128

Problem:  - Paging keys in the dialog window consume input when the user
            may not expect it. The dismissable title hint intended to
            mitigate that results in having to press Escape twice to
            abandon the prompt.
          - Mimicked "msgsep" float border is taking up unnecessary
            space when window takes up the entire screen.

Solution: - Use (conventional, albeit less convenient) keys intended
            for scrolling to page the dialog window:
            <(Mousewheel/Page)Up/Down>, <Home/End>.
          - Only set the float top border when separation is actually
            necessary, i.e. window does not reach the first row.
(cherry picked from commit f0a8e6f337)
This commit is contained in:
luukvbaal
2026-04-16 22:32:08 +02:00
committed by github-actions[bot]
parent 9d66c7828e
commit b08c289a45
2 changed files with 40 additions and 102 deletions

View File

@@ -527,6 +527,7 @@ local cmd_on_key = function(key, typed)
or (typed:find('LeftMouse') and fn.getmousepos().winid == ui.wins.cmd)
if entered then
M.expand_msg('cmd', 'pager')
api.nvim_win_set_cursor(ui.wins.pager, { 1, 0 })
end
pcall(api.nvim_win_close, ui.wins.cmd, true)
ui.check_targets()
@@ -554,28 +555,19 @@ local dialog_on_key = function(_, typed)
typed = typed and fn.keytrans(typed)
if not typed then
return
elseif typed == '<Esc>' then
-- Stop paging, redraw empty title to reflect paging is no longer active.
api.nvim_win_set_config(ui.wins.dialog, { title = '' })
api.nvim__redraw({ flush = true })
vim.on_key(nil, M.dialog_on_key)
M.dialog_on_key = nil
return ''
end
local page_keys = {
g = 'gg',
G = 'G',
j = 'Lj',
k = 'Hk',
d = [[\<C-D>]],
u = [[\<C-U>]],
f = [[\<C-F>]],
b = [[\<C-B>]],
}
local info = page_keys[typed] and fn.getwininfo(ui.wins.dialog)[1]
if info and (typed ~= 'f' or info.botline < api.nvim_buf_line_count(ui.bufs.dialog)) then
fn.win_execute(ui.wins.dialog, ('exe "norm! %s"'):format(page_keys[typed]))
-- TODO: no hint anymore, so should at least be documented at some point.
local map, eob = {}, typed:match('PageDown')
map['<Home>'], map['<End>'] = 'gg', 'G'
map['<Up>'], map['<C-Y>'] = 'Hk', 'Hk'
map['<Down>'], map['<C-E>'] = 'Lj', 'Lj'
map['<PageUp>'], map['<S-PageUp>'] = [[\<C-B>]], [[\<C-B>]]
map['<PageDown>'], map['<S-PageDown>'] = [[\<C-F>]], [[\<C-F>]]
local info = map[typed] and fn.getwininfo(ui.wins.dialog)[1]
if info and (not eob or info.botline < api.nvim_buf_line_count(ui.bufs.dialog)) then
fn.win_execute(ui.wins.dialog, ('exe "norm! %s"'):format(map[typed]))
set_top_bot_spill()
return fn.getwininfo(ui.wins.dialog)[1].topline ~= info.topline and '' or nil
end
@@ -583,16 +575,17 @@ end
local was_cmdwin = ''
---@param min integer Minimum window height.
local function win_row_height(tgt, min)
local function win_row_height_border(tgt, min)
local cfgmin = ui.cfg.msg[tgt].height --[[@as number]]
cfgmin = cfgmin > 1 and cfgmin or math.ceil(o.lines * cfgmin)
min = math.min(min, cfgmin > 1 and cfgmin or math.ceil(o.lines * cfgmin))
if tgt ~= 'pager' then
return (tgt == 'msg' and 0 or 1) - ui.cmd.wmnumode, math.min(min, cfgmin)
return (tgt == 'msg' and 0 or 1) - ui.cmd.wmnumode, min, min < o.lines - ui.cmdheight
end
local cmdwin = fn.getcmdwintype() ~= was_cmdwin and api.nvim_win_get_height(0) or 0
local global_stl = (cmdwin > 0 or o.laststatus == 3) and 1 or 0
local row = 1 - cmdwin - global_stl
return row, math.min(math.min(cfgmin, min), o.lines - 1 - ui.cmdheight - global_stl - cmdwin)
local top = min < o.lines - ui.cmdheight - global_stl - cmdwin
return row, math.min(min, o.lines - (top and 1 or 0) - ui.cmdheight - global_stl - cmdwin), top
end
local function enter_pager()
@@ -624,7 +617,7 @@ local function enter_pager()
in_pager = in_pager and api.nvim_win_is_valid(ui.wins.pager)
local cfg = in_pager and { relative = 'laststatus', col = 0 } or { hide = true }
if in_pager then
cfg.row, cfg.height = win_row_height('pager', height)
cfg.row, cfg.height, cfg.border = win_row_height_border('pager', height)
else
pcall(api.nvim_set_option_value, 'eiw', 'all', { scope = 'local', win = ui.wins.pager })
api.nvim_del_autocmd(id)
@@ -651,14 +644,10 @@ function M.set_pos(tgt)
if cfg and (tgt or not cfg.hide) then
local texth = api.nvim_win_text_height(win, {})
local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' }
local hint = 'f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging'
cfg = { hide = false, relative = 'laststatus', col = 10000 } ---@type table
cfg.row, cfg.height = win_row_height(t, texth.all)
cfg.border = t ~= 'msg' and { '', top, '', '', '', '', '', '' } or nil
cfg.row, cfg.height, cfg.border = win_row_height_border(t, texth.all)
cfg.border = cfg.border and t ~= 'msg' and { '', top, '', '', '', '', '', '' } or nil
cfg.mouse = tgt == 'cmd' or nil
cfg.title = tgt == 'dialog'
and { { ui.cmd.expand == 0 and cfg.height < texth.all and hint or '', 'MsgMore' } }
or nil
api.nvim_win_set_config(win, cfg)
if tgt == 'cmd' then
@@ -668,7 +657,7 @@ function M.set_pos(tgt)
set_virttext('msg', 'cmd')
M.virt.msg[M.virt.idx.spill][1] = { 0, (' [+%d]'):format(texth.all - ui.cmdheight) }
M.cmd_on_key = vim.on_key(cmd_on_key, ui.ns)
elseif tgt == 'dialog' and set_top_bot_spill() and #cfg.title[1][1] > 0 then
elseif tgt == 'dialog' and set_top_bot_spill() then
M.dialog_on_key = vim.on_key(dialog_on_key, M.dialog_on_key)
elseif tgt == 'msg' then
-- Ensure last line is visible and first line is at top of window.

View File

@@ -182,25 +182,22 @@ describe('messages2', function()
-- Do enter the pager in normal mode.
feed('<CR>')
screen:expect([[
{3: }|
^foo |
foo |*11
foo |*12
1,1 Top|
]])
-- Changing 'laststatus' reveals the global statusline with a pager height
-- exceeding the available lines: #38008.
command('set laststatus=3')
screen:expect([[
{3: }|
^foo |
foo |*10
foo |*11
{3:[Pager] 1,1 Top}|
|
]])
feed(':<C-F>')
screen:expect([[
{3: }|
foo |*4
foo |*5
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*5
@@ -209,8 +206,7 @@ describe('messages2', function()
]])
command('wincmd +')
screen:expect([[
{3: }|
foo |*3
foo |*4
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*6
@@ -219,8 +215,7 @@ describe('messages2', function()
]])
command('echo "foo"')
screen:expect([[
{3: }|
foo |*3
foo |*4
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*6
@@ -229,8 +224,7 @@ describe('messages2', function()
]])
feed('<C-C>')
screen:expect([[
{3: }|
foo |*11
foo |*12
{3:[Pager] 1,1 Top}|
{16::}^ |
]])
@@ -248,9 +242,8 @@ describe('messages2', function()
]])
feed(':messages<CR>')
screen:expect([[
{3: }|
^foo |
foo |*10
foo |*11
{3:[Pager] 1,1 Top}|
|
]])
@@ -527,7 +520,7 @@ describe('messages2', function()
local top = [[
|
{1:~ }|*4
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
{3: }|
0 |
1 |
2 |
@@ -539,11 +532,11 @@ describe('messages2', function()
]]
feed(':call inputlist(range(100))<CR>')
screen:expect(top)
feed('j')
feed('<Down>')
screen:expect([[
|
{1:~ }|*4
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
{3: }|
1 [+1] |
2 |
3 |
@@ -553,29 +546,13 @@ describe('messages2', function()
7 [+92] |
Type number and <Enter> or click with the mouse (q or empty cancels): ^ |
]])
feed('k')
feed('<Up>')
screen:expect(top)
feed('d')
feed('<PageDown>')
screen:expect([[
|
{1:~ }|*4
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
3 [+3] |
4 |
5 |
6 |
7 |
8 |
9 [+90] |
Type number and <Enter> or click with the mouse (q or empty cancels): ^ |
]])
feed('u')
screen:expect(top)
feed('f')
screen:expect([[
|
{1:~ }|*4
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
{3: }|
5 [+5] |
6 |
7 |
@@ -585,13 +562,13 @@ describe('messages2', function()
11 [+88] |
Type number and <Enter> or click with the mouse (q or empty cancels): ^ |
]])
feed('b')
feed('<PageUp>')
screen:expect(top)
feed('G')
feed('<End>')
screen:expect([[
|
{1:~ }|*4
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
{3: }|
93 [+93] |
94 |
95 |
@@ -602,38 +579,10 @@ describe('messages2', function()
Type number and <Enter> or click with the mouse (q or empty cancels): ^ |
]])
-- No scrolling beyond end of buffer #36114
feed('f')
screen:expect([[
|
{1:~ }|*3
{3: }f/d/j: screen/page/line down, b/u/k: up, <Esc>: stop paging{3: }|
93 [+93] |
94 |
95 |
96 |
97 |
98 |
99 |
Type number and <Enter> or click with the mouse (q or empty cancels): f|
^ |
]])
feed('<Backspace>g')
feed('<PageDown>')
screen:expect_unchanged()
feed('<Home>')
screen:expect(top)
feed('<Esc>f')
screen:expect([[
|
{1:~ }|*3
{3: }|
0 |
1 |
2 |
3 |
4 |
5 |
6 [+93] |
Type number and <Enter> or click with the mouse (q or empty cancels): f|
^ |
]])
end)
it('FileType is fired after default options are set', function()