commit 3038f0191e9d7d681a1b6a936ed452b85815a247
parent dbb3986f331f416213e8396cfb5a4335d69fc42b
Author: luukvbaal <luukvbaal@gmail.com>
Date: Tue, 3 Feb 2026 14:17:33 +0100
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.
Diffstat:
4 files changed, 57 insertions(+), 19 deletions(-)
diff --git a/runtime/lua/vim/_extui/cmdline.lua b/runtime/lua/vim/_extui/cmdline.lua
@@ -8,6 +8,7 @@ local M = {
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.
+ wmnumode = 0, -- Return value of wildmenumode(), dialog position adjusted when toggled.
}
--- 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
end)
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
diff --git a/runtime/lua/vim/_extui/messages.lua b/runtime/lua/vim/_extui/messages.lua
@@ -469,25 +469,20 @@ end
---@param type? 'cmd'|'dialog'|'msg'|'pager' Type of to be positioned window (nil for all).
function M.set_pos(type)
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 height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
local top = { vim.opt.fcs:get().msgsep or ' ', 'MsgSeparator' }
- local border = win ~= ext.wins.msg and { '', top, '', '', '', '', '', '' } or nil
- local config = {
- hide = false,
- relative = (win == ext.wins.pager or win == ext.wins.dialog) and 'editor' or 'laststatus',
- border = border,
- height = height,
- 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)
+ cfg.height = type and math.min(texth.all, math.ceil(o.lines * 0.5))
+ cfg.border = win ~= ext.wins.msg and { '', top, '', '', '', '', '', '' } or nil
+ cfg.focusable = type == 'cmd' or nil
+ cfg.row = (win == ext.wins.msg and 0 or 1) - ext.cmd.wmnumode
+ cfg.row = cfg.row - ((win == ext.wins.pager and o.laststatus == 3) and 1 or 0)
+ api.nvim_win_set_config(win, cfg)
if type == 'cmd' and not cmd_on_key then
-- Temporarily showing a full message in the cmdline, until next key press.
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
set_virttext('msg', 'cmd')
M.virt.msg[M.virt.idx.spill][1] = save_spill
@@ -554,7 +549,7 @@ function M.set_pos(type)
end)
elseif type == 'msg' then
-- 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 })
elseif type == 'pager' then
if fn.getcmdwintype() ~= '' then
@@ -571,10 +566,10 @@ function M.set_pos(type)
api.nvim_create_autocmd({ 'WinEnter', 'CmdwinEnter', 'CmdwinLeave' }, {
callback = function(ev)
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 { 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
return ev.event == 'WinEnter'
end,
diff --git a/runtime/lua/vim/_extui/shared.lua b/runtime/lua/vim/_extui/shared.lua
@@ -33,7 +33,7 @@ local tab = 0
---Ensure target buffers and windows are still valid.
function M.check_targets()
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])
if setopt then
M.bufs[type] = api.nvim_create_buf(false, false)
@@ -50,8 +50,8 @@ function M.check_targets()
anchor = type ~= 'cmd' and 'SE' or nil,
hide = type ~= 'cmd' or M.cmdheight == 0 or nil,
border = type ~= 'msg' and 'none' or nil,
- -- kZIndexMessages < zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
- zindex = 200 + (type == 'cmd' and 1 or type == 'pager' and -1 or 0),
+ -- kZIndexMessages < cmd zindex < kZIndexCmdlinePopupMenu (grid_defs.h), pager below others.
+ zindex = 201 - i,
_cmdline_offset = type == 'cmd' and 0 or nil,
})
if tab ~= curtab and api.nvim_win_is_valid(M.wins[type]) then
diff --git a/test/functional/ui/cmdline2_spec.lua b/test/functional/ui/cmdline2_spec.lua
@@ -13,6 +13,7 @@ describe('cmdline2', function()
screen = Screen.new()
screen:add_extra_attr_ids({
[100] = { foreground = Screen.colors.Magenta1, bold = true },
+ [101] = { background = Screen.colors.Yellow, foreground = Screen.colors.Grey0 },
})
exec_lua(function()
require('vim._extui').enable({})
@@ -180,6 +181,44 @@ describe('cmdline2', function()
{16::}{15:s}{16:/f}^ |
]])
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)
describe('cmdline2', function()