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

332dd22ed4

Co-authored-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
This commit is contained in:
zeertzjq
2026-03-20 08:44:01 +08:00
committed by GitHub
parent f577e05522
commit d36e7787c1
3 changed files with 40 additions and 10 deletions

View File

@@ -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 <expr> 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();

View File

@@ -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

View File

@@ -4410,6 +4410,23 @@ func Test_setcmdline()
call feedkeys(":a\<CR>", '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 "\<Left>"
endfunc
cnoremap <expr> a g:SetCmdLineEmpty()
call feedkeys(":e Xsetcmdlinedir/\<Tab>a\<C-B>\"\<CR>", 'tx')
call assert_equal('"', @:)
cunmap a
delfunc g:SetCmdLineEmpty
set nowildmenu
endfunc
func Test_rulerformat_position()