Commit: patch 9.2.0795: popup menu shadow is not cleared when the menu shrinks

1 view
Skip to first unread message

Christian Brabandt

unread,
10:15 AM (8 hours ago) 10:15 AM
to vim...@googlegroups.com
patch 9.2.0795: popup menu shadow is not cleared when the menu shrinks

Commit: https://github.com/vim/vim/commit/6b9af206227eac03bfaf7b3b19774a14c145efa2
Author: Hirohito Higashi <h.eas...@gmail.com>
Date: Sat Jul 18 14:10:41 2026 +0000

patch 9.2.0795: popup menu shadow is not cleared when the menu shrinks

Problem: When the popup menu shrinks its shadow is not cleared: a stale
shadow cell is left on the empty (~) lines near the menu, and when
the menu also gets narrower the border and text of the larger menu
show through the shadow (Maxim Kim)
Solution: Exclude the shadow corner cells that are never drawn, and when the
menu is redrawn redraw the background under the shadow first, so
content left by a larger menu is cleared instead of showing through
(Hirohito Higashi).

fixes: #20740
closes: #20750

Co-Authored-By: Claude Opus 4.8 (1M context) <nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/popupmenu.c b/src/popupmenu.c
index 08aa44286..36000d9bf 100644
--- a/src/popupmenu.c
+++ b/src/popupmenu.c
@@ -425,12 +425,40 @@ pum_under_menu(int row, int col, int only_redrawing)
+ (pum_shadow ? 2 : 0);
int extra_above = pum_border;
int extra_below = pum_border + (pum_shadow ? 1 : 0);
+ int top = pum_row - extra_above;
+ int bot = pum_row + pum_height + extra_below;
+ int left = pum_col - 1 - extra_left;
+ int right = pum_col + pum_width + pum_scrollbar + extra_right;

- return (!only_redrawing || pum_will_redraw)
- && row >= pum_row - extra_above
- && row < pum_row + pum_height + extra_below
- && col >= pum_col - 1 - extra_left
- && col < pum_col + pum_width + pum_scrollbar + extra_right;
+ if (!((!only_redrawing || pum_will_redraw)
+ && row >= top && row < bot && col >= left && col < right))
+ return FALSE;
+
+ if (pum_shadow)
+ {
+ // The shadow recolors the cells underneath. When the menu will be
+ // redrawn, leave the shadow cells unprotected so the window refreshes
+ // them first, clearing stale content left by a larger menu.
+ if (only_redrawing)
+ {
+ if (col >= right - 2 || row == bot - 1)
+ return FALSE;
+ }
+ else
+ {
+ // Menu stays as-is: exclude only the corner cells the shadow never
+ // draws.
+ int right_margin, left_margin, left_padding;
+
+ compute_margins(&right_margin, &left_margin, &left_padding);
+ if (row == top && col >= right - 2)
+ return FALSE;
+ if (row == bot - 1 && col < pum_col + 2 - left_padding - pum_border
+ - left_margin)
+ return FALSE;
+ }
+ }
+ return TRUE;
}

/*
diff --git a/src/testdir/dumps/Test_wildmenu_pum_shadow_1.dump b/src/testdir/dumps/Test_wildmenu_pum_shadow_1.dump
new file mode 100644
index 000000000..fe19d510b
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_shadow_1.dump
@@ -0,0 +1,12 @@
+| +0#0000001#a8a8a8255@39
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @4| +0#0000001#ffd7ff255|a|x@8| @4| +0#4040ff13#a8a8a8255@17
+|~| @4| +0#0000001#ffd7ff255|a|b| @12| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@15
+|~| @4| +0#0000001#ffd7ff255|a|c| @12| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@15
+|~| @6| +0#ffffff16#e000002@15| +0#4040ff13#a8a8a8255@15
+|:+0#0000001&|M|y|C|m|d| |a> @31
diff --git a/src/testdir/dumps/Test_wildmenu_pum_shadow_2.dump b/src/testdir/dumps/Test_wildmenu_pum_shadow_2.dump
new file mode 100644
index 000000000..d7370a954
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_shadow_2.dump
@@ -0,0 +1,12 @@
+| +0#0000001#a8a8a8255@39
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @4| +0#0000001#ffd7ff255|a|b| @12| +0#4040ff13#a8a8a8255@17
+|~| @6| +0#ffffff16#e000002@15| +0#4040ff13#a8a8a8255@15
+|:+0#0000001&|M|y|C|m|d| |a|b> @30
diff --git a/src/testdir/dumps/Test_wildmenu_pum_shadow_border_1.dump b/src/testdir/dumps/Test_wildmenu_pum_shadow_border_1.dump
new file mode 100644
index 000000000..49316c726
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_shadow_border_1.dump
@@ -0,0 +1,12 @@
+| +0#0000001#a8a8a8255@39
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @3|┌+0#0000001#ffd7ff255|─@18|┐| +0#4040ff13#a8a8a8255@13
+|~| @3|│+0#0000001#ffd7ff255| |a|V|e|r|y|L|o|n|g|I|t|e|m|N|a|m|e| |│| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@11
+|~| @3|│+0#0000001#ffd7ff255| |a|b| @15|│| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@11
+|~| @3|│+0#0000001#ffd7ff255| |a|c| @15|│| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@11
+|~| @3|└+0#0000001#ffd7ff255|─@18|┘| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@11
+|~| @5| +0#ffffff16#e000002@20| +0#4040ff13#a8a8a8255@11
+|:+0#0000001&|M|y|C|m|d| |a> @31
diff --git a/src/testdir/dumps/Test_wildmenu_pum_shadow_border_2.dump b/src/testdir/dumps/Test_wildmenu_pum_shadow_border_2.dump
new file mode 100644
index 000000000..8b9d74531
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_shadow_border_2.dump
@@ -0,0 +1,12 @@
+| +0#0000001#a8a8a8255@39
+|~+0#4040ff13&| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @38
+|~| @3|┌+0#0000001#ffd7ff255|─@15|┐| +0#4040ff13#a8a8a8255@16
+|~| @3|│+0#0000001#ffd7ff255| |a|b| @12|│| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@14
+|~| @3|└+0#0000001#ffd7ff255|─@15|┘| +0#ffffff16#e000002@1| +0#4040ff13#a8a8a8255@14
+|~| @5| +0#ffffff16#e000002@17| +0#4040ff13#a8a8a8255@14
+|:+0#0000001&|M|y|C|m|d| |a|b> @30
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index ef1cd7adc..ef036d2b0 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -226,6 +226,85 @@ func Test_wildmenu_screendump()
call StopVimInTerminal(buf)
endfunc

+" When the wildmenu popup menu shrinks, its shadow must be cleared, also on the
+" empty (~) lines above the menu. See issue #20740.
+func Test_wildmenu_pum_shadow()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ func MyCompl(a, l, p)
+ return filter(['axxxxxxxxx', 'ab', 'ac'], 'stridx(v:val, a:a) == 0')
+ endfunc
+ command! -nargs=1 -complete=customlist,MyCompl MyCmd echo <q-args>
+ set wildmode=noselect:lastused,full
+ set wildoptions=pum,fuzzy
+ set pumopt=shadow
+ set shortmess+=I
+ " Give the shadow a distinct color so it is easy to tell apart from the
+ " background in the screen dump.
+ hi Normal ctermbg=grey ctermfg=black guibg=grey guifg=black
+ hi PmenuShadow ctermbg=darkred ctermfg=white guibg=darkred guifg=white
+ autocmd CmdlineChanged : call wildtrigger()
+ [SCRIPT]
+ call writefile(lines, 'XTest_wildshadow', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_wildshadow', {'rows': 12, 'cols': 40})
+
+ " Wide popup with a shadow drawn over the ~ lines.
+ call term_sendkeys(buf, ":MyCmd a")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_shadow_1', {})
+
+ " Narrow the matches: the popup shrinks and the wider shadow must be cleared.
+ call term_sendkeys(buf, "b")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_shadow_2', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" Same as above but with a border, and the menu also gets narrower. When the
+" menu shrinks the stale content of the wider menu must be cleared before the
+" shadow is drawn over it, otherwise the old border and text show through the
+" shadow. See issue #20740.
+func Test_wildmenu_pum_shadow_border()
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ func MyCompl(a, l, p)
+ return filter(['aVeryLongItemName', 'ab', 'ac'], 'stridx(v:val, a:a) == 0')
+ endfunc
+ command! -nargs=1 -complete=customlist,MyCompl MyCmd echo <q-args>
+ set wildmode=noselect:lastused,full
+ set wildoptions=pum,fuzzy
+ set pumopt=shadow,border:single
+ set shortmess+=I
+ " Give the shadow a distinct color so it is easy to tell apart from the
+ " background in the screen dump.
+ hi Normal ctermbg=grey ctermfg=black guibg=grey guifg=black
+ hi PmenuShadow ctermbg=darkred ctermfg=white guibg=darkred guifg=white
+ autocmd CmdlineChanged : call wildtrigger()
+ [SCRIPT]
+ call writefile(lines, 'XTest_wildshadowborder', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_wildshadowborder', {'rows': 12, 'cols': 40})
+
+ " Wide popup with a border and a shadow drawn over the ~ lines.
+ call term_sendkeys(buf, ":MyCmd a")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_shadow_border_1', {})
+
+ " Narrow the matches: the popup shrinks in both width and height, the wider
+ " menu's border and text must be cleared next to and under the shadow.
+ call term_sendkeys(buf, "b")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_shadow_border_2', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_wildmenu_with_input_func()
CheckScreendump

diff --git a/src/version.c b/src/version.c
index d2d3c45dc..e0861aa38 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 795,
/**/
794,
/**/
Reply all
Reply to author
Forward
0 new messages