neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/lua/vim/_extui/cmdline.lua | 4++++
Mruntime/lua/vim/_extui/messages.lua | 27+++++++++++----------------
Mruntime/lua/vim/_extui/shared.lua | 6+++---
Mtest/functional/ui/cmdline2_spec.lua | 39+++++++++++++++++++++++++++++++++++++++
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()