fix(ui2): wildmenu hidden behind dialog window #37684

Problem:  The wildmenu is hidden behind the dialog window with "list" in 'wildmode'.
          Global ('laststatus' set to 3) statusline is hidden behind the
          pager window.
Solution: Check wildmenumode() to adjust the dialog position when necessary.
          Ensure pager is positioned above the global statusline with 'laststus' set to 3.
This commit is contained in:
luukvbaal
2026-02-03 14:17:33 +01:00
committed by GitHub
parent dbb3986f33
commit 3038f0191e
4 changed files with 57 additions and 19 deletions

View File

@@ -8,6 +8,7 @@ local M = {
srow = 0, -- Buffer row at which the current cmdline starts; > 0 in block mode. 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. 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 = -1, -- Current cmdline level; 0 when inactive, -1 one loop iteration after closing.
wmnumode = 0, -- Return value of wildmenumode(), dialog position adjusted when toggled.
} }
--- Set the 'cmdheight' and cmdline window height. Reposition message windows. --- Set the 'cmdheight' and cmdline window height. Reposition message windows.
@@ -28,6 +29,9 @@ local function win_config(win, hide, height)
vim.o.cmdheight = height vim.o.cmdheight = height
end) end)
ext.msg.set_pos() ext.msg.set_pos()
elseif M.wmnumode ~= (M.prompt and fn.wildmenumode() or 0) then
M.wmnumode = (M.wmnumode == 1 and 0 or 1)
ext.msg.set_pos()
end end
end end

View File

@@ -469,25 +469,20 @@ end
---@param type? 'cmd'|'dialog'|'msg'|'pager' Type of to be positioned window (nil for all). ---@param type? 'cmd'|'dialog'|'msg'|'pager' Type of to be positioned window (nil for all).
function M.set_pos(type) function M.set_pos(type)
local function win_set_pos(win) local function win_set_pos(win)
local cfg = { hide = false, relative = 'laststatus', col = 10000 }
local texth = type and api.nvim_win_text_height(win, {}) or {} local texth = type and api.nvim_win_text_height(win, {}) or {}
local height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' } local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' }
local border = win ~= ext.wins.msg and { '', top, '', '', '', '', '', '' } or nil cfg.height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
local config = { cfg.border = win ~= ext.wins.msg and { '', top, '', '', '', '', '', '' } or nil
hide = false, cfg.focusable = type == 'cmd' or nil
relative = (win == ext.wins.pager or win == ext.wins.dialog) and 'editor' or 'laststatus', cfg.row = (win == ext.wins.msg and 0 or 1) - ext.cmd.wmnumode
border = border, cfg.row = cfg.row - ((win == ext.wins.pager and o.laststatus == 3) and 1 or 0)
height = height, api.nvim_win_set_config(win, cfg)
row = (win == ext.wins.pager or win == ext.wins.dialog) and o.lines - o.cmdheight or 0,
col = 10000,
focusable = type == 'cmd' or nil, -- Allow entering the cmdline window.
}
api.nvim_win_set_config(win, config)
if type == 'cmd' and not cmd_on_key then if type == 'cmd' and not cmd_on_key then
-- Temporarily showing a full message in the cmdline, until next key press. -- Temporarily showing a full message in the cmdline, until next key press.
local save_spill = M.virt.msg[M.virt.idx.spill][1] local save_spill = M.virt.msg[M.virt.idx.spill][1]
local spill = texth.all > height and (' [+%d]'):format(texth.all - height) local spill = texth.all > cfg.height and (' [+%d]'):format(texth.all - cfg.height)
M.virt.msg[M.virt.idx.spill][1] = spill and { 0, spill } or nil M.virt.msg[M.virt.idx.spill][1] = spill and { 0, spill } or nil
set_virttext('msg', 'cmd') set_virttext('msg', 'cmd')
M.virt.msg[M.virt.idx.spill][1] = save_spill M.virt.msg[M.virt.idx.spill][1] = save_spill
@@ -554,7 +549,7 @@ function M.set_pos(type)
end) end)
elseif type == 'msg' then elseif type == 'msg' then
-- Ensure last line is visible and first line is at top of window. -- Ensure last line is visible and first line is at top of window.
local row = (texth.all > height and texth.end_row or 0) + 1 local row = (texth.all > cfg.height and texth.end_row or 0) + 1
api.nvim_win_set_cursor(ext.wins.msg, { row, 0 }) api.nvim_win_set_cursor(ext.wins.msg, { row, 0 })
elseif type == 'pager' then elseif type == 'pager' then
if fn.getcmdwintype() ~= '' then if fn.getcmdwintype() ~= '' then
@@ -571,10 +566,10 @@ function M.set_pos(type)
api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, { api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, {
callback = function(ev) callback = function(ev)
if api.nvim_win_is_valid(ext.wins.pager) then if api.nvim_win_is_valid(ext.wins.pager) then
local cfg = ev.event == 'CmdwinLeave' and config local config = ev.event == 'CmdwinLeave' and cfg
or ev.event == 'WinEnter' and { hide = true } or ev.event == 'WinEnter' and { hide = true }
or { relative = 'win', win = 0, row = 0, col = 0 } or { relative = 'win', win = 0, row = 0, col = 0 }
api.nvim_win_set_config(ext.wins.pager, cfg) api.nvim_win_set_config(ext.wins.pager, config)
end end
return ev.event == 'WinEnter' return ev.event == 'WinEnter'
end, end,

View File

@@ -33,7 +33,7 @@ local tab = 0
---Ensure target buffers and windows are still valid. ---Ensure target buffers and windows are still valid.
function M.check_targets() function M.check_targets()
local curtab = api.nvim_get_current_tabpage() local curtab = api.nvim_get_current_tabpage()
for _, type in ipairs({ 'cmd', 'dialog', 'msg', 'pager' }) do for i, type in ipairs({ 'cmd', 'dialog', 'msg', 'pager' }) do
local setopt = not api.nvim_buf_is_valid(M.bufs[type]) local setopt = not api.nvim_buf_is_valid(M.bufs[type])
if setopt then if setopt then
M.bufs[type] = api.nvim_create_buf(false, false) M.bufs[type] = api.nvim_create_buf(false, false)
@@ -50,8 +50,8 @@ function M.check_targets()
anchor = type ~= 'cmd' and 'SE' or nil, anchor = type ~= 'cmd' and 'SE' or nil,
hide = type ~= 'cmd' or M.cmdheight == 0 or nil, hide = type ~= 'cmd' or M.cmdheight == 0 or nil,
border = type ~= 'msg' and 'none' or nil, border = type ~= 'msg' and 'none' or nil,
-- kZIndexMessages < zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others. -- kZIndexMessages < cmd zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
zindex = 200 + (type == 'cmd' and 1 or type == 'pager' and -1 or 0), zindex = 201 - i,
_cmdline_offset = type == 'cmd' and 0 or nil, _cmdline_offset = type == 'cmd' and 0 or nil,
}) })
if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then

View File

@@ -13,6 +13,7 @@ describe('cmdline2', function()
screen = Screen.new() screen = Screen.new()
screen:add_extra_attr_ids({ screen:add_extra_attr_ids({
[100] = { foreground = Screen.colors.Magenta1, bold = true }, [100] = { foreground = Screen.colors.Magenta1, bold = true },
[101] = { background = Screen.colors.Yellow, foreground = Screen.colors.Grey0 },
}) })
exec_lua(function() exec_lua(function()
require('vim._extui').enable({}) require('vim._extui').enable({})
@@ -180,6 +181,44 @@ describe('cmdline2', function()
{16::}{15:s}{16:/f}^ | {16::}{15:s}{16:/f}^ |
]]) ]])
end) end)
it('dialog position is adjusted for toggled wildmenu', function()
exec([[
set wildmode=list:full,full wildoptions-=pum
func Foo()
endf
func Fooo()
endf
]])
feed(':call Fo<C-Z>')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} Fo^ |
]])
feed('<C-Z>')
screen:expect([[
|
{1:~ }|*8
{3: }|
Foo() Fooo() |
|
{101:Foo()}{3: Fooo() }|
{16::}{15:call} {25:Foo}{16:()}^ |
]])
feed('()')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} {25:Foo}{16:()()}^ |
]])
end)
end) end)
describe('cmdline2', function() describe('cmdline2', function()