mirror of
https://github.com/neovim/neovim.git
synced 2025-09-24 12:08:33 +00:00
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:
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user