patch 9.2.0596: cmdline completion popup cannot be scrolled with the mouse
Commit:
https://github.com/vim/vim/commit/96dbab257a881ee4a552e1acf62e7d4168dc444a
Author: Hirohito Higashi <
h.eas...@gmail.com>
Date: Thu Jun 4 19:52:50 2026 +0000
patch 9.2.0596: cmdline completion popup cannot be scrolled with the mouse
Problem: In command-line completion with a popup menu ('wildoptions'
contains "pum"), the info popup shown next to the menu could
not be scrolled, unlike the Insert mode completion info popup
which scrolls with the mouse wheel.
Solution: When the mouse pointer is on top of the info popup, scroll it
with the mouse wheel in command-line mode as well, without
closing the completion popup menu.
closes: #20146
closes: #20418
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/runtime/doc/options.txt b/runtime/doc/options.txt
index 0ef255210..0df50ff60 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.2. Last change: 2026 May 25
+*options.txt* For Vim version 9.2. Last change: 2026 Jun 04
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -10523,7 +10523,10 @@ A jump table for the options with a short description can be found at |Q_op|.
is not supported for file and directory names and
instead wildcard expansion is used.
pum Display the completion matches using the popup menu in
- the same style as the |ins-completion-menu|.
+ the same style as the |ins-completion-menu|. When an
+ info popup is shown next to the menu, it can be
+ scrolled by moving the mouse pointer on top of it and
+ using the scroll wheel.
tagfile When using CTRL-D to list matching tags, the kind of
tag and the file of the tag is listed. Only one match
is displayed per line. Often used tag kinds are:
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 7e26d6cdb..ea20fa96b 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -2064,12 +2064,15 @@ getcmdline_int(
// navigating the wild menu (i.e. the key is not 'wildchar' or
// 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L).
// If the popup menu is displayed, then PageDown and PageUp keys are
- // also used to navigate the menu.
+ // also used to navigate the menu, and the mouse scroll wheel keys
+ // scroll the info popup.
end_wildmenu = (!key_is_wc
&& c != Ctrl_N && c != Ctrl_P && c != Ctrl_A && c != Ctrl_L);
end_wildmenu = end_wildmenu && (!cmdline_pum_active() ||
(c != K_PAGEDOWN && c != K_PAGEUP
- && c != K_KPAGEDOWN && c != K_KPAGEUP));
+ && c != K_KPAGEDOWN && c != K_KPAGEUP
+ && c != K_MOUSEDOWN && c != K_MOUSEUP
+ && c != K_MOUSELEFT && c != K_MOUSERIGHT));
// free expanded names when finished walking through matches
if (end_wildmenu)
@@ -2413,11 +2416,21 @@ getcmdline_int(
cmdline_left_right_mouse(c, &ignore_drag_release);
goto cmdline_not_changed;
- // Mouse scroll wheel: ignored here
+ // Mouse scroll wheel: scroll the completion info popup when the mouse
+ // is on top of it, otherwise ignored here.
case K_MOUSEDOWN:
case K_MOUSEUP:
case K_MOUSELEFT:
case K_MOUSERIGHT:
+#ifdef FEAT_PROP_POPUP
+ if (cmdline_pum_active())
+ cmdline_mousescroll(c == K_MOUSEDOWN ? MSCR_DOWN
+ : c == K_MOUSEUP ? MSCR_UP
+ : c == K_MOUSELEFT ? MSCR_LEFT
+ : MSCR_RIGHT);
+#endif
+ goto cmdline_not_changed;
+
// Alternate buttons ignored here
case K_X1MOUSE:
case K_X1DRAG:
diff --git a/src/mouse.c b/src/mouse.c
index ddd94fffc..9080334e7 100644
--- a/src/mouse.c
+++ b/src/mouse.c
@@ -1396,6 +1396,62 @@ ins_mousescroll(int dir)
}
}
+#if defined(FEAT_PROP_POPUP) || defined(PROTO)
+/*
+ * Command-line mode implementation for scrolling in direction "dir", which is
+ * one of the MSCR_ values. Scrolls the completion info popup when the mouse
+ * pointer is on top of it.
+ * Returns TRUE when the info popup was scrolled.
+ */
+ int
+cmdline_mousescroll(int dir)
+{
+ cmdarg_T cap;
+ oparg_T oa;
+
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+ cap.arg = dir;
+
+ switch (dir)
+ {
+ case MSCR_UP: cap.cmdchar = K_MOUSEUP; break;
+ case MSCR_DOWN: cap.cmdchar = K_MOUSEDOWN; break;
+ case MSCR_LEFT: cap.cmdchar = K_MOUSELEFT; break;
+ case MSCR_RIGHT: cap.cmdchar = K_MOUSERIGHT; break;
+ }
+
+ if (mouse_row < 0 || mouse_col < 0)
+ return FALSE;
+
+ int row = mouse_row;
+ int col = mouse_col;
+ win_T *wp;
+
+ // Only scroll when the mouse is on top of the info popup.
+ wp = mouse_find_win(&row, &col, FIND_POPUP);
+ if (wp == NULL || !WIN_IS_POPUP(wp) || !(wp->w_popup_flags & POPF_INFO)
+ || !wp->w_has_scrollbar)
+ return FALSE;
+
+ win_T *old_curwin = curwin;
+
+ curwin = wp;
+ curbuf = wp->w_buffer;
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(&cap);
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+
+ // Cmdline mode doesn't normally call update_screen(), so redraw the
+ // completion popup menu, which also repaints the info popup.
+ if (cmdline_pum_active())
+ cmdline_pum_display();
+ return TRUE;
+}
+#endif
+
/*
* Return TRUE if "c" is a mouse key.
*/
diff --git a/src/proto/
mouse.pro b/src/proto/
mouse.pro
index ead818498..0c320769b 100644
--- a/src/proto/
mouse.pro
+++ b/src/proto/
mouse.pro
@@ -4,6 +4,7 @@ void mouse_set_hor_scroll_step(long step);
int do_mouse(oparg_T *oap, int c, int dir, long count, int fixindent);
void ins_mouse(int c);
void ins_mousescroll(int dir);
+int cmdline_mousescroll(int dir);
int is_mouse_key(int c);
int get_mouse_button(int code, int *is_click, int *is_drag);
int get_pseudo_mouse_code(int button, int is_click, int is_drag);
diff --git a/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_1.dump b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_1.dump
new file mode 100644
index 000000000..dc622d74a
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_1.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@23|╔+0#0000001#e0e0e08|═@14|X| +0#0000000#ffffff0@33
+|~+0#4040ff13&| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |2| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |3| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |4| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |5| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |6| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |7| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |8| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#e0e0e08|a|p@1|l|e| @1|f| |f|r|u|i|t| |║| |i|n|f|o| |l|i|n|e| |9| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#ffd7ff255|b|a|n|a|n|a| |f| |f|r|u|i|t| |╚+0&#e0e0e08|═@14|⇲| +0#4040ff13#ffffff0@33
+|:+0#0000000&|D|i|c|t|C|m|d| |a|p@1|l|e> @60
diff --git a/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_2.dump b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_2.dump
new file mode 100644
index 000000000..115b1b949
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_2.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@23|╔+0#0000001#e0e0e08|═@14|X| +0#0000000#ffffff0@33
+|~+0#4040ff13&| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|0| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1@1| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|2| | +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|3| | +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|4| | +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|5| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|6| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|7| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#e0e0e08|a|p@1|l|e| @1|f| |f|r|u|i|t| |║| |i|n|f|o| |l|i|n|e| |1|8| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#ffd7ff255|b|a|n|a|n|a| |f| |f|r|u|i|t| |╚+0&#e0e0e08|═@14|⇲| +0#4040ff13#ffffff0@33
+|:+0#0000000&|D|i|c|t|C|m|d| |a|p@1|l|e> @42|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_3.dump b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_3.dump
new file mode 100644
index 000000000..1a0c4fbcb
--- /dev/null
+++ b/src/testdir/dumps/Test_wildmenu_pum_info_mouse_scroll_3.dump
@@ -0,0 +1,12 @@
+| +0&#ffffff0@23|╔+0#0000001#e0e0e08|═@14|X| +0#0000000#ffffff0@33
+|~+0#4040ff13&| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |4| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |5| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |6| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |7| @1| +0#0000000#0000001|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |8| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |9| @1| +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1|0| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @22|║+0#0000001#e0e0e08| |i|n|f|o| |l|i|n|e| |1@1| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#e0e0e08|a|p@1|l|e| @1|f| |f|r|u|i|t| |║| |i|n|f|o| |l|i|n|e| |1|2| | +0#0000000#a8a8a8255|║+0#0000001#e0e0e08| +0#4040ff13#ffffff0@33
+|~| @6| +0#0000001#ffd7ff255|b|a|n|a|n|a| |f| |f|r|u|i|t| |╚+0&#e0e0e08|═@14|⇲| +0#4040ff13#ffffff0@33
+|:+0#0000000&|D|i|c|t|C|m|d| |a|p@1|l|e> @42|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 31fb1f8ff..8fbaa502b 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -4768,6 +4768,52 @@ func Test_customlist_dict_completion_info_popup()
call StopVimInTerminal(buf)
endfunc
+" Test that the mouse scroll wheel scrolls the info popup of the command line
+" completion popup menu when the mouse pointer is on top of it.
+func Test_wildmenu_pum_info_mouse_scroll()
+ CheckScreendump
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ func DictComp(A, L, P)
+ let info = join(map(range(1, 30), '"info line " .. v:val'), "
")
+ return [
+ \ {'word': 'apple', 'kind': 'f', 'menu': 'fruit', 'info': info},
+ \ {'word': 'banana', 'kind': 'f', 'menu': 'fruit', 'info': info},
+ \ ]
+ endfunc
+ command -nargs=1 -complete=customlist,DictComp DictCmd echo <q-args>
+ set wildmenu wildoptions=pum completeopt=menu,popup mouse=a
+
+ " Put the mouse on top of the info popup and turn the scroll wheel.
+ func ScrollInfo(keys)
+ let pos = popup_getpos(popup_findinfo())
+ call test_setmouse(pos.line + 1, pos.col + 1)
+ call feedkeys(a:keys, 'nt')
+ endfunc
+ cnoremap <F6> <Cmd>call ScrollInfo(repeat("\<ScrollWheelDown>", 3))<CR>
+ cnoremap <F7> <Cmd>call ScrollInfo(repeat("\<ScrollWheelUp>", 2))<CR>
+ END
+ call writefile(lines, 'XtestWildmenuMouseScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestWildmenuMouseScroll', #{rows: 12})
+
+ " The info popup is shown next to the completion popup menu.
+ call term_sendkeys(buf, ":DictCmd \<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_info_mouse_scroll_1', {})
+
+ " Scrolling down with the wheel scrolls the info popup without closing the
+ " completion popup menu.
+ call term_sendkeys(buf, "\<F6>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_info_mouse_scroll_2', {})
+
+ " Scrolling back up scrolls the info popup up again.
+ call term_sendkeys(buf, "\<F7>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_info_mouse_scroll_3', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_cmdline_complete_findfunc_dict()
CheckScreendump
diff --git a/src/version.c b/src/version.c
index 80200eb4c..b8863bad2 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 596,
/**/
595,
/**/