mirror of
https://github.com/vim/vim.git
synced 2026-06-10 15:37:26 +02:00
patch 9.2.0609: completion info popup cannot be scrolled with the keyboard
Problem: The info popup shown beside the insert-mode and command-line
completion menu can only be scrolled with the mouse wheel, so
the part below the visible area is unreachable when working
from the keyboard.
Solution: While the completion menu is shown, scroll the info popup with
CTRL-SHIFT-Up/Down (one line), CTRL-SHIFT-PageUp/PageDown (one
page) and CTRL-SHIFT-N/CTRL-SHIFT-P (one line). The menu stays
open and the selected item does not change.
related: #20418
fixes: #20441
closes: #20444
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
60ebdf7e34
commit
e2cf855bbe
+17
-1
@@ -1,4 +1,4 @@
|
||||
*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
|
||||
@@ -1449,6 +1449,22 @@ CTRL-E End completion, go back to what was there before selecting a
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*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
|
||||
@@ -10526,7 +10526,8 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
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:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*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
|
||||
@@ -52598,6 +52598,8 @@ Popups ~
|
||||
"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 ~
|
||||
---------
|
||||
|
||||
+35
@@ -1233,7 +1233,21 @@ doESCkey:
|
||||
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;
|
||||
|
||||
@@ -1250,7 +1264,19 @@ doESCkey:
|
||||
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;
|
||||
|
||||
@@ -1365,6 +1391,15 @@ doESCkey:
|
||||
|
||||
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
|
||||
|
||||
+36
-3
@@ -2064,15 +2064,18 @@ 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, 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)
|
||||
@@ -2518,6 +2521,15 @@ getcmdline_int(
|
||||
|
||||
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;
|
||||
@@ -2534,6 +2546,27 @@ getcmdline_int(
|
||||
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))
|
||||
|
||||
+4
-1
@@ -2728,7 +2728,10 @@ at_ins_compl_key(void)
|
||||
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));
|
||||
|
||||
@@ -6722,6 +6722,40 @@ popup_find_info_window(void)
|
||||
}
|
||||
#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)
|
||||
{
|
||||
|
||||
@@ -64,6 +64,7 @@ int set_ref_in_popups(int copyID);
|
||||
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);
|
||||
|
||||
@@ -4814,6 +4814,68 @@ func Test_wildmenu_pum_info_mouse_scroll()
|
||||
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
|
||||
|
||||
|
||||
@@ -3998,6 +3998,78 @@ func Test_popupmenu_info_border_mouse()
|
||||
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
|
||||
|
||||
@@ -729,6 +729,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
609,
|
||||
/**/
|
||||
608,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user