neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit b443a3cb33f5c49f3cf703815cd262db13437846
parent e3f15d5424caac762dcd384083d94920e86e676d
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Fri, 19 Sep 2025 10:46:36 +0800

Merge pull request #35833 from zeertzjq/vim-9.1.1621

vim-patch:9.1.{1621,1771}
Diffstat:
Msrc/nvim/cmdexpand.c | 8++++----
Msrc/nvim/ex_getln.c | 31++++++++++++++++++++++++-------
Msrc/nvim/insexpand.c | 14++++++++------
Mtest/functional/legacy/cmdline_spec.lua | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/old/testdir/test_cmdline.vim | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/old/testdir/test_ins_complete.vim | 9++++++++-
6 files changed, 185 insertions(+), 18 deletions(-)

diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c @@ -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; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c @@ -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 @@ -1229,10 +1241,15 @@ static int command_line_wildchar_complete(CommandLineState *s) return (res == OK) ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED; } -static void command_line_end_wildmenu(CommandLineState *s) +static void command_line_end_wildmenu(CommandLineState *s, bool key_is_wc) { if (cmdline_pum_active()) { - cmdline_pum_remove(); + s->skip_pum_redraw = (s->skip_pum_redraw && !key_is_wc + && !ascii_iswhite(s->c) + && (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); @@ -1285,7 +1302,7 @@ static int command_line_execute(VimState *state, int key) nextwild(&s->xpc, WILD_PUM_WANT, 0, s->firstc != '@'); if (pum_want.finish) { nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, s->firstc != '@'); - command_line_end_wildmenu(s); + command_line_end_wildmenu(s, false); } } pum_want.active = false; @@ -1392,7 +1409,7 @@ static int command_line_execute(VimState *state, int key) // free expanded names when finished walking through matches if (end_wildmenu) { - command_line_end_wildmenu(s); + command_line_end_wildmenu(s, key_is_wc); } if (p_wmnu) { diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c @@ -5189,12 +5189,14 @@ static void ins_compl_show_filename(void) MB_PTR_ADV(s); } } - msg_hist_off = true; - vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead, - s > compl_shown_match->cp_fname ? "<" : "", s); - msg(IObuff, 0); - msg_hist_off = false; - redraw_cmdline = false; // don't overwrite! + if (!compl_autocomplete) { + msg_hist_off = true; + vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead, + s > compl_shown_match->cp_fname ? "<" : "", s); + msg(IObuff, 0); + msg_hist_off = false; + redraw_cmdline = false; // don't overwrite! + } } /// Find the appropriate completion item when 'complete' ('cpt') includes diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua @@ -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) @@ -571,6 +627,35 @@ describe('cmdline', function() feed('<Esc>') end) + + -- oldtest: Test_update_screen_after_wildtrigger() + it('pum is dismissed after wildtrigger() and whitespace', function() + local screen = Screen.new(40, 10) + exec([[ + set wildmode=noselect:lastused,full wildmenu wildoptions=pum + autocmd CmdlineChanged : if getcmdcompltype() != 'shellcmd' | call wildtrigger() | endif + ]]) + + feed(':term') + screen:expect([[ + | + {1:~ }|*7 + {4: terminal }{1: }| + :term^ | + ]]) + feed(' ') + screen:expect([[ + | + {1:~ }|*8 + :term ^ | + ]]) + feed('foo') + screen:expect([[ + | + {1:~ }|*8 + :term foo^ | + ]]) + end) end) describe('cmdwin', function() diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim @@ -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() @@ -5016,4 +5053,23 @@ func Test_skip_wildtrigger_hist_navigation() cunmap <Down> endfunc +" Issue 18298: wildmenu should be dismissed after wildtrigger and whitespace +func Test_update_screen_after_wildtrigger() + CheckScreendump + let lines =<< trim [SCRIPT] + call test_override("char_avail", 1) + set wildmode=noselect:lastused,full wildmenu wildoptions=pum + autocmd CmdlineChanged : if getcmdcompltype() != 'shellcmd' | call wildtrigger() | endif + [SCRIPT] + call writefile(lines, 'XTest_wildtrigger', 'D') + let buf = RunVimInTerminal('-S XTest_wildtrigger', {'rows': 10}) + + call term_sendkeys(buf, ":term foo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_update_screen_wildtrigger_1', {}) + + call term_sendkeys(buf, "\<esc>") + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim @@ -5359,7 +5359,7 @@ func Test_autocomplete_trigger() call feedkeys("Sazx\<Left>\<BS>\<F2>\<Esc>0", 'tx!') call assert_equal(['and', 'afoo'], b:matches->mapnew('v:val.word')) - " Test 6: <BS> should clear the selected item + " Test 6: <BS> should clear the selected item (PR #18265) %d call setline(1, ["foobarfoo", "foobar", "foobarbaz"]) call feedkeys("Gofo\<C-N>\<C-N>\<F2>\<F3>\<Esc>0", 'tx!') @@ -5374,6 +5374,13 @@ func Test_autocomplete_trigger() call assert_equal(0, b:selected) call assert_equal('foobarbaz', getline(4)) + " Test 7: Remove selection when menu contents change (PR #18265) + %d + call setline(1, ["foobar", "fodxyz", "fodabc"]) + call feedkeys("Gofoo\<C-N>\<BS>\<BS>\<BS>\<BS>d\<F2>\<F3>\<Esc>0", 'tx!') + call assert_equal(['fodabc', 'fodxyz'], b:matches->mapnew('v:val.word')) + call assert_equal(-1, b:selected) + bw! call Ntest_override("char_avail", 0) delfunc NonKeywordComplete