-*insert.txt* For Vim version 9.2. Last change: 2026 Jun 02
+*insert.txt* For Vim version 9.2. Last change: 2026 Jun 09
VIM REFERENCE MANUAL by Bram Moolenaar
insert it.
<Down> Select the next match, as if CTRL-N was used, but don't
insert it.
+CTRL-SHIFT-<Up>
+ Scroll the info popup up one line, when it is shown, see
+ |complete-popup|.
+ Note: these CTRL-SHIFT keys need the GUI or a terminal that
+ reports key modifiers; the Linux console does not.
+CTRL-SHIFT-<Down>
+ Scroll the info popup down one line.
+CTRL-SHIFT-<PageUp>
+ Scroll the info popup up one page.
+CTRL-SHIFT-<PageDown>
+ Scroll the info popup down one page.
+CTRL-SHIFT-P Like CTRL-SHIFT-<Up>, scroll the info popup up one line.
+CTRL-SHIFT-N Like CTRL-SHIFT-<Down>, scroll the info popup down one line.
+ Note: CTRL-SHIFT-N and CTRL-SHIFT-P additionally need the
+ terminal to report modifiers for letter keys, see
+ |modifyOtherKeys|.
<Space> or <Tab> Stop completion without changing the match and insert the
typed character.
-*options.txt* For Vim version 9.2. Last change: 2026 Jun 04
+*options.txt* For Vim version 9.2. Last change: 2026 Jun 09
VIM REFERENCE MANUAL by Bram Moolenaar
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.
+ using the scroll wheel, or with the keyboard like in
+ Insert mode completion, see |popupmenu-keys|.
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:
-*version9.txt* For Vim version 9.2. Last change: 2026 May 31
+*version9.txt* For Vim version 9.2. Last change: 2026 Jun 09
VIM REFERENCE MANUAL by Bram Moolenaar
"align").
- Support "opacity" setting for 'completepopup' option.
- Support for clipping textproperty popups |popup-clipwindow|.
+- Completion popup menu can be scrolled with the mouse or using keys
+ |popupmenu-keys|.
Diff mode ~
---------
case K_PAGEUP:
case K_KPAGEUP:
if (pum_visible())
+ {
+#ifdef FEAT_PROP_POPUP
+ // CTRL-SHIFT-<Up> scrolls the info popup up a line,
+ // CTRL-SHIFT-<PageUp> a page. Shift is folded into K_S_UP but
+ // stays in mod_mask for PageUp, hence the asymmetric check.
+ if (c == K_S_UP ? (mod_mask & MOD_MASK_CTRL)
+ : ((mod_mask & MOD_MASK_CTRL)
+ && (mod_mask & MOD_MASK_SHIFT)))
+ {
+ popup_scroll_info(-1, c != K_S_UP);
+ break;
+ }
+#endif
goto docomplete;
+ }
ins_pageup();
break;
case K_PAGEDOWN:
case K_KPAGEDOWN:
if (pum_visible())
+ {
+#ifdef FEAT_PROP_POPUP
+ // CTRL-SHIFT-<Down>/<PageDown> scroll the info popup down.
+ if (c == K_S_DOWN ? (mod_mask & MOD_MASK_CTRL)
+ : ((mod_mask & MOD_MASK_CTRL)
+ && (mod_mask & MOD_MASK_SHIFT)))
+ {
+ popup_scroll_info(1, c != K_S_DOWN);
+ break;
+ }
+#endif
goto docomplete;
+ }
ins_pagedown();
break;
case Ctrl_P: // Do previous/next pattern completion
case Ctrl_N:
+#ifdef FEAT_PROP_POPUP
+ // CTRL-SHIFT-P/N scroll the info popup one line.
+ if (pum_visible() && (mod_mask & MOD_MASK_SHIFT)
+ && (c == Ctrl_P || c == Ctrl_N))
+ {
+ popup_scroll_info(c == Ctrl_P ? -1 : 1, false);
+ break;
+ }
+#endif
// if 'complete' is empty then plain ^P is no longer special,
// but it is under other ^X modes
if (*curbuf->b_p_cpt == NUL
// 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, and the mouse scroll wheel keys
- // scroll the info popup.
+ // also used to navigate the menu, the mouse scroll wheel keys scroll
+ // the info popup, and CTRL-SHIFT-<Up>/<Down> scroll it with the
+ // keyboard.
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_MOUSEDOWN && c != K_MOUSEUP
- && c != K_MOUSELEFT && c != K_MOUSERIGHT));
+ && c != K_MOUSELEFT && c != K_MOUSERIGHT
+ && !((c == K_S_UP || c == K_S_DOWN)
+ && (mod_mask & MOD_MASK_CTRL))));
// free expanded names when finished walking through matches
if (end_wildmenu)
case Ctrl_N: // next match
case Ctrl_P: // previous match
+#ifdef FEAT_PROP_POPUP
+ // CTRL-SHIFT-P/N scroll the info popup one line.
+ if (cmdline_pum_active() && (mod_mask & MOD_MASK_SHIFT))
+ {
+ if (popup_scroll_info(c == Ctrl_P ? -1 : 1, false))
+ cmdline_pum_display();
+ goto cmdline_not_changed;
+ }
+#endif
if (xpc.xp_numfiles > 0)
{
wild_type = (c == Ctrl_P) ? WILD_PREV : WILD_NEXT;
case K_KPAGEUP:
case K_PAGEDOWN:
case K_KPAGEDOWN:
+#ifdef FEAT_PROP_POPUP
+ // CTRL-SHIFT-<Up>/<Down> scroll the info popup a line,
+ // CTRL-SHIFT-<PageUp>/<PageDown> a page. Shift is folded into
+ // K_S_UP/K_S_DOWN but stays in mod_mask for the Page keys.
+ if (cmdline_pum_active()
+ && ((c == K_S_UP || c == K_S_DOWN)
+ ? (mod_mask & MOD_MASK_CTRL)
+ : ((c == K_PAGEUP || c == K_KPAGEUP
+ || c == K_PAGEDOWN || c == K_KPAGEDOWN)
+ && (mod_mask & MOD_MASK_CTRL)
+ && (mod_mask & MOD_MASK_SHIFT))))
+ {
+ int up = c == K_S_UP || c == K_PAGEUP
+ || c == K_KPAGEUP;
+
+ if (popup_scroll_info(up ? -1 : 1,
+ c != K_S_UP && c != K_S_DOWN))
+ cmdline_pum_display();
+ goto cmdline_not_changed;
+ }
+#endif
if (cmdline_pum_active()
&& (c == K_PAGEUP || c == K_PAGEDOWN ||
c == K_KPAGEUP || c == K_KPAGEDOWN))
if (typebuf.tb_len > 3
&& (c == K_SPECIAL || c == CSI) // CSI is used by the GUI
&& p[1] == KS_MODIFIER
- && (p[2] & MOD_MASK_CTRL))
+ && (p[2] & MOD_MASK_CTRL)
+ // CTRL-SHIFT-N/P scroll the info popup, so they must not be folded
+ // to the CTRL-N/CTRL-P completion keys here.
+ && !(p[2] & MOD_MASK_SHIFT))
c = p[3] & 0x1f;
return (ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c))
|| (compl_status_local() && (c == Ctrl_N || c == Ctrl_P));
}
#endif
+/*
+ * Scroll the completion info popup one line (by_page false) or one page
+ * (by_page true); "dir" negative scrolls up, positive down.
+ * Returns true when an info popup was found.
+ */
+ bool
+popup_scroll_info(int dir, bool by_page)
+{
+#ifdef FEAT_QUICKFIX
+ win_T *wp = popup_find_info_window();
+ int by;
+ linenr_T new_topline;
+
+ if (wp == NULL)
+ return false;
+
+ by = by_page ? (wp->w_height > 2 ? wp->w_height - 1 : 1) : 1;
+ new_topline = wp->w_topline + (dir < 0 ? -by : by);
+ if (new_topline < 1)
+ new_topline = 1;
+ if (new_topline > wp->w_buffer->b_ml.ml_line_count)
+ new_topline = wp->w_buffer->b_ml.ml_line_count;
+ if (new_topline != wp->w_topline)
+ {
+ set_topline(wp, new_topline);
+ popup_set_firstline(wp);
+ redraw_win_later(wp, UPD_NOT_VALID);
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
void
f_popup_findecho(typval_T *argvars UNUSED, typval_T *rettv)
{
int popup_is_popup(win_T *wp);
win_T *popup_find_preview_window(void);
win_T *popup_find_info_window(void);
+bool popup_scroll_info(int dir, bool by_page);
void f_popup_findecho(typval_T *argvars, typval_T *rettv);
void f_popup_findinfo(typval_T *argvars, typval_T *rettv);
void f_popup_findpreview(typval_T *argvars, typval_T *rettv);
call StopVimInTerminal(buf)
endfunc
+func s:ReadCmdlineInfo()
+ let l = filereadable('Xclinfo') ? map(readfile('Xclinfo'), 'str2nr(v:val)') : []
+ return len(l) == 2 ? l : [-1, -1]
+endfunc
+
+func Test_wildmenu_pum_info_scroll_keys()
+ CheckRunVimInTerminal
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ func DictComp(A, L, P)
+ let info = join(map(range(1, 40), '"info line " .. v:val'), "\n")
+ return [{'word': 'apple', 'info': info}, {'word': 'banana', 'info': info}]
+ endfunc
+ command -nargs=1 -complete=customlist,DictComp DictCmd echo <q-args>
+ set wildmenu wildoptions=pum completeopt=menu,popup
+ func InfoState()
+ let id = popup_findinfo()
+ call writefile([id ? popup_getpos(id).firstline : -1, wildmenumode()],
+ \ 'Xclinfo')
+ endfunc
+ " A <Cmd> mapping runs without closing the wildmenu, so it can report the
+ " info popup state while completion is active.
+ cnoremap <F4> <Cmd>call InfoState()<CR>
+ END
+ call writefile(lines, 'XtestCmdlineScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestCmdlineScroll', #{rows: 12})
+ call TermWait(buf, 50)
+
+ " Show the completion popup menu with the info popup next to it.
+ call term_sendkeys(buf, ":DictCmd \<Tab>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1], s:ReadCmdlineInfo())})
+
+ " Ctrl-Shift-Down then Ctrl-Shift-Up scroll the info popup by a line without
+ " closing the wildmenu.
+ call term_sendkeys(buf, "\<Esc>[1;6B")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([2, 1], s:ReadCmdlineInfo())})
+ call term_sendkeys(buf, "\<Esc>[1;6A")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1], s:ReadCmdlineInfo())})
+
+ " Ctrl-Shift-N then Ctrl-Shift-P scroll like the arrows.
+ call term_sendkeys(buf, "\<Esc>[27;6;110~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([2, 1], s:ReadCmdlineInfo())})
+ call term_sendkeys(buf, "\<Esc>[27;6;112~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1], s:ReadCmdlineInfo())})
+
+ " Ctrl-Shift-PageDown scrolls down by a page (more than one line).
+ call term_sendkeys(buf, "\<Esc>[6;6~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_true(s:ReadCmdlineInfo()[0] > 2)})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xclinfo')
+endfunc
+
func Test_cmdline_complete_findfunc_dict()
CheckScreendump
call StopVimInTerminal(buf)
endfunc
+func s:ReadInfoState()
+ let l = filereadable('Xinfofl') ? map(readfile('Xinfofl'), 'str2nr(v:val)') : []
+ return len(l) == 3 ? l : [-1, -1, -1]
+endfunc
+
+func Test_popupmenu_info_scroll_keys()
+ CheckRunVimInTerminal
+ CheckFeature quickfix
+
+ let lines =<< trim END
+ func Omni_test(findstart, base)
+ if a:findstart
+ return col(".")
+ endif
+ return [#{word: "scrollme",
+ \ info: join(map(range(1, 40), '"info line " .. v:val'), "\n")},
+ \ #{word: "another", info: "short"}]
+ endfunc
+ set completeopt=menu,menuone,popup
+ set omnifunc=Omni_test
+ func InfoState()
+ let id = popup_findinfo()
+ call writefile([id ? popup_getpos(id).firstline : -1, pumvisible(),
+ \ get(complete_info(['selected']), 'selected', -1)], 'Xinfofl')
+ endfunc
+ " A <Cmd> mapping runs without closing the completion menu, so it can
+ " report the info popup state while completion is active.
+ inoremap <F4> <Cmd>call InfoState()<CR>
+ END
+ call writefile(lines, 'XtestInfoScroll', 'D')
+ let buf = RunVimInTerminal('-S XtestInfoScroll', #{rows: 14})
+ call TermWait(buf, 50)
+
+ " Open insert-mode completion; the info popup is shown, first item selected.
+ call term_sendkeys(buf, "i\<C-X>\<C-O>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1, 0], s:ReadInfoState())})
+
+ " Ctrl-Shift-Down then Ctrl-Shift-Up scroll the info popup by a line; the
+ " menu stays open and the selected item does not change.
+ call term_sendkeys(buf, "\<Esc>[1;6B")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([2, 1, 0], s:ReadInfoState())})
+ call term_sendkeys(buf, "\<Esc>[1;6A")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1, 0], s:ReadInfoState())})
+
+ " Ctrl-Shift-N then Ctrl-Shift-P scroll like the arrows, again without
+ " moving the selection.
+ call term_sendkeys(buf, "\<Esc>[27;6;110~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([2, 1, 0], s:ReadInfoState())})
+ call term_sendkeys(buf, "\<Esc>[27;6;112~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal([1, 1, 0], s:ReadInfoState())})
+
+ " Ctrl-Shift-PageDown scrolls down by a page (more than one line).
+ call term_sendkeys(buf, "\<Esc>[6;6~")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_true(s:ReadInfoState()[0] > 2)})
+
+ " Plain Ctrl-N still moves the selection to the next item.
+ call term_sendkeys(buf, "\<C-N>")
+ call term_sendkeys(buf, "\<F4>")
+ call WaitForAssert({-> assert_equal(1, s:ReadInfoState()[2])})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xinfofl')
+endfunc
+
func Test_popupmenu_info_align_menu()
CheckScreendump
CheckFeature quickfix
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 609,
/**/
608,
/**/