From d36e7787c1e71411a394f7e19f78054f8d7005c2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 20 Mar 2026 08:44:01 +0800 Subject: [PATCH] vim-patch:9.2.0209: freeze during wildmenu completion (#38386) Problem: Vim may freeze if setcmdline() is called while the wildmenu or cmdline popup menu is active (rendcrx) Solution: Cleanup completion state if cmdbuff_replaced flag has been set (Yasuhiro Matsumoto) fixes: vim/vim#19742 closes: vim/vim#19744 https://github.com/vim/vim/commit/332dd22ed48244d67524933453049c6c866bcabf Co-authored-by: Yasuhiro Matsumoto --- src/nvim/ex_getln.c | 31 +++++++++++++++++++++---------- src/nvim/ex_getln_defs.h | 2 ++ test/old/testdir/test_cmdline.vim | 17 +++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9433957762..fdf38bb95f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1261,21 +1261,23 @@ static int command_line_wildchar_complete(CommandLineState *s) return (res == OK) ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED; } -static void command_line_end_wildmenu(CommandLineState *s, bool key_is_wc) +static void command_line_end_wildmenu(CommandLineState *s, bool key_is_wc, int c) { if (cmdline_pum_active()) { - 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 (c != -1) { + s->skip_pum_redraw = (s->skip_pum_redraw && !key_is_wc + && !ascii_iswhite(c) + && (vim_isprintc(c) + || c == K_BS || c == Ctrl_H || c == K_DEL + || c == K_KDEL || c == Ctrl_W || c == Ctrl_U)); + } + cmdline_pum_remove(c != -1 && s->skip_pum_redraw); } if (s->xpc.xp_numfiles != -1) { ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); } s->did_wild_list = false; - if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { + if (!p_wmnu || (c != K_UP && c != K_DOWN)) { s->xpc.xp_context = EXPAND_NOTHING; } s->wim_index = 0; @@ -1292,6 +1294,14 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; + // If the cmdline was replaced externally (e.g. by setcmdline() + // during an mapping), clean up the wildmenu completion + // state to avoid using stale completion data. + if (ccline.cmdbuff_replaced && s->xpc.xp_numfiles > 0) { + command_line_end_wildmenu(s, false, -1); + } + ccline.cmdbuff_replaced = false; + // Skip wildmenu during history navigation via Up/Down keys if (s->c == K_WILD && s->did_hist_navigate) { s->did_hist_navigate = false; @@ -1322,7 +1332,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, false); + command_line_end_wildmenu(s, false, s->c); } } pum_want.active = false; @@ -1430,7 +1440,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, key_is_wc); + command_line_end_wildmenu(s, key_is_wc, s->c); } if (p_wmnu) { @@ -4388,6 +4398,7 @@ static int set_cmdline_str(const char *str, int pos) p->cmdpos = pos < 0 || pos > p->cmdlen ? p->cmdlen : pos; new_cmdpos = p->cmdpos; + p->cmdbuff_replaced = true; redrawcmd(); diff --git a/src/nvim/ex_getln_defs.h b/src/nvim/ex_getln_defs.h index e05d8f27db..463c59119e 100644 --- a/src/nvim/ex_getln_defs.h +++ b/src/nvim/ex_getln_defs.h @@ -57,6 +57,8 @@ struct cmdline_info { int xp_context; ///< type of expansion char *xp_arg; ///< user-defined expansion arg int input_fn; ///< when true Invoked for input() function + bool cmdbuff_replaced; ///< when true cmdline was replaced externally + ///< (e.g. by setcmdline()) unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. Callback highlight_callback; ///< Callback used for coloring user input. ColoredCmdline last_colors; ///< Last cmdline colors diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index fc293510c1..f13413fb8d 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -4410,6 +4410,23 @@ func Test_setcmdline() call feedkeys(":a\", 'tx') call assert_equal('let foo=0', @:) cunmap a + + " setcmdline() during wildmenu completion should not freeze. + " Stripping completion state when cmdline was replaced externally. + set wildmenu + call mkdir('Xsetcmdlinedir', 'pR') + call writefile([], 'Xsetcmdlinedir/Xfile1') + call writefile([], 'Xsetcmdlinedir/Xfile2') + func g:SetCmdLineEmpty() + call setcmdline('', 1) + return "\" + endfunc + cnoremap a g:SetCmdLineEmpty() + call feedkeys(":e Xsetcmdlinedir/\a\\"\", 'tx') + call assert_equal('"', @:) + cunmap a + delfunc g:SetCmdLineEmpty + set nowildmenu endfunc func Test_rulerformat_position()