vim-patch:9.1.1621: flicker in popup menu during cmdline autocompletion

Problem:  When the popup menu (PUM) occupies more than half the screen
          height, it flickers whenever a character is typed or erased.
          This happens because the PUM is cleared and the screen is
          redrawn before a new PUM is rendered. The extra redraw between
          menu updates causes visible flicker.
Solution: A complete, non-hacky fix would require removing the
          CmdlineChanged event from the loop and letting autocompletion
          manage the process end-to-end. This is because screen redraws
          after any cmdline change are necessary for other features to
          work.
          This change modifies wildtrigger() so that the next typed
          character defers the screen update instead of redrawing
          immediately. This removes the intermediate redraw, eliminating
          flicker and making cmdline autocompletion feel smooth
          (Girish Palya).

Trade-offs:
This behavior change in wildtrigger() is tailored specifically for
:h cmdline-autocompletion. wildtrigger() now has no general-purpose use
outside this scenario.

closes: vim/vim#17932

da9c966893

Use pum_check_clear() instead of update_screen().
Cherry-pick Test_wildtrigger_update_screen() change from patch 9.1.1682.

Co-authored-by: Girish Palya <girishji@gmail.com>
This commit is contained in:
zeertzjq
2025-09-19 08:52:56 +08:00
parent c2c33249a7
commit dd306bd48a
4 changed files with 117 additions and 8 deletions

View File

@@ -422,16 +422,16 @@ bool cmdline_pum_active(void)
}
/// Remove the cmdline completion popup menu (if present), free the list of items.
void cmdline_pum_remove(void)
void cmdline_pum_remove(bool defer_redraw)
{
pum_undisplay(true);
pum_undisplay(!defer_redraw);
XFREE_CLEAR(compl_match_array);
compl_match_arraysize = 0;
}
void cmdline_pum_cleanup(CmdlineInfo *cclp)
{
cmdline_pum_remove();
cmdline_pum_remove(false);
wildmenu_cleanup(cclp);
}
@@ -936,7 +936,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
// The entries from xp_files may be used in the PUM, remove it.
if (compl_match_array != NULL) {
cmdline_pum_remove();
cmdline_pum_remove(false);
}
}
xp->xp_selected = (options & WILD_NOSELECT) ? -1 : 0;

View File

@@ -136,6 +136,7 @@ typedef struct {
int prev_cmdpos;
char *prev_cmdbuff;
char *save_p_icm;
bool skip_pum_redraw;
bool some_key_typed; // one of the keys was typed
// mouse drag and release events are ignored, unless they are
// preceded with a mouse down event
@@ -929,7 +930,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
// if certain special keys like <Esc> or <C-\> were used as wildchar. Make
// sure to still clean up to avoid memory corruption.
if (cmdline_pum_active()) {
cmdline_pum_remove();
cmdline_pum_remove(false);
}
wildmenu_cleanup(&ccline);
s->did_wild_list = false;
@@ -1035,12 +1036,17 @@ static int command_line_check(VimState *state)
// that occurs while typing a command should
// cause the command not to be executed.
// Trigger SafeState if nothing is pending.
may_trigger_safestate(s->xpc.xp_numfiles <= 0);
if (ccline.cmdbuff != NULL) {
s->prev_cmdbuff = xstrdup(ccline.cmdbuff);
}
// Trigger SafeState if nothing is pending.
may_trigger_safestate(s->xpc.xp_numfiles <= 0);
// Defer screen update to avoid pum flicker during wildtrigger()
if (s->c == K_WILD && s->firstc != '@') {
s->skip_pum_redraw = true;
}
cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
@@ -1124,6 +1130,7 @@ static int command_line_wildchar_complete(CommandLineState *s)
int res;
int options = WILD_NO_BEEP;
bool escape = s->firstc != '@';
bool redraw_if_menu_empty = s->c == K_WILD;
bool wim_noselect = p_wmnu && (wim_flags[0] & kOptWimFlagNoselect) != 0;
if (wim_flags[s->wim_index] & kOptWimFlagLastused) {
@@ -1171,6 +1178,11 @@ static int command_line_wildchar_complete(CommandLineState *s)
res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, escape);
}
// Remove popup menu if no completion items are available
if (redraw_if_menu_empty && s->xpc.xp_numfiles <= 0) {
pum_check_clear();
}
// if interrupted while completing, behave like it failed
if (got_int) {
vpeekc(); // remove <C-C> from input stream
@@ -1232,7 +1244,11 @@ static int command_line_wildchar_complete(CommandLineState *s)
static void command_line_end_wildmenu(CommandLineState *s)
{
if (cmdline_pum_active()) {
cmdline_pum_remove();
s->skip_pum_redraw = (s->skip_pum_redraw
&& (vim_isprintc(s->c)
|| s->c == K_BS || s->c == Ctrl_H || s->c == K_DEL
|| s->c == K_KDEL || s->c == Ctrl_W || s->c == Ctrl_U));
cmdline_pum_remove(s->skip_pum_redraw);
}
if (s->xpc.xp_numfiles != -1) {
ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);

View File

@@ -531,6 +531,62 @@ describe('cmdline', function()
]])
end)
-- oldtest: Test_wildtrigger_update_screen()
it('pum by wildtrigger() avoids flicker', function()
local screen = Screen.new(40, 10)
exec([[
command! -nargs=* -complete=customlist,TestFn TestCmd echo
func TestFn(cmdarg, b, c)
if a:cmdarg == 'ax'
return []
else
return map(range(1, 5), 'printf("abc%d", v:val)')
endif
endfunc
set wildmode=noselect,full
set wildoptions=pum
set wildmenu
cnoremap <F8> <C-R>=wildtrigger()[-1]<CR>
]])
feed(':TestCmd a<F8>')
screen:expect([[
|
{1:~ }|*3
{1:~ }{4: abc1 }{1: }|
{1:~ }{4: abc2 }{1: }|
{1:~ }{4: abc3 }{1: }|
{1:~ }{4: abc4 }{1: }|
{1:~ }{4: abc5 }{1: }|
:TestCmd a^ |
]])
-- Typing a character when pum is open does not close the pum window
-- This is needed to prevent pum window from flickering during
-- ':h cmdline-autocompletion'.
feed('x')
screen:expect([[
|
{1:~ }|*3
{1:~ }{4: abc1 }{1: }|
{1:~ }{4: abc2 }{1: }|
{1:~ }{4: abc3 }{1: }|
{1:~ }{4: abc4 }{1: }|
{1:~ }{4: abc5 }{1: }|
:TestCmd ax^ |
]])
-- pum window is closed when no completion candidates are available
feed('<F8>')
screen:expect([[
|
{1:~ }|*8
:TestCmd ax^ |
]])
feed('<esc>')
end)
-- oldtest: Test_long_line_noselect()
it("long line is shown properly with noselect in 'wildmode'", function()
local screen = Screen.new(60, 8)

View File

@@ -4939,6 +4939,43 @@ func Test_cmdline_changed()
call Ntest_override("char_avail", 0)
endfunc
func Test_wildtrigger_update_screen()
CheckScreendump
let lines =<< trim [SCRIPT]
command! -nargs=* -complete=customlist,TestFn TestCmd echo
func TestFn(cmdarg, b, c)
if a:cmdarg == 'ax'
return []
else
return map(range(1, 5), 'printf("abc%d", v:val)')
endif
endfunc
set wildmode=noselect,full
set wildoptions=pum
set wildmenu
cnoremap <F8> <C-R>=wildtrigger()[-1]<CR>
[SCRIPT]
call writefile(lines, 'XTest_wildtrigger', 'D')
let buf = RunVimInTerminal('-S XTest_wildtrigger', {'rows': 10})
call term_sendkeys(buf, ":TestCmd a\<F8>")
call VerifyScreenDump(buf, 'Test_wildtrigger_update_screen_1', {})
" Typing a character when pum is open does not close the pum window
" This is needed to prevent pum window from flickering during
" ':h cmdline-autocompletion'.
call term_sendkeys(buf, "x")
call VerifyScreenDump(buf, 'Test_wildtrigger_update_screen_2', {})
" pum window is closed when no completion candidates are available
call term_sendkeys(buf, "\<F8>")
call VerifyScreenDump(buf, 'Test_wildtrigger_update_screen_3', {})
call term_sendkeys(buf, "\<esc>")
call StopVimInTerminal(buf)
endfunc
" Issue #18035: long lines should not get listed twice in the menu when
" 'wildmode' contains 'noselect'
func Test_long_line_noselect()