From 1359578abbcf56036c46cc424a508a131147218f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 1 Sep 2025 08:09:05 +0800 Subject: [PATCH] vim-patch:9.1.1714: completion: wildmode=longest:full selects wrong item Problem: completion: wildmode=longest:full selects wrong item (zeertzjq) Solution: Fix issue, refactor ex_getln.c slightly (Girish Palya) fixes: vim/vim#18102 closes: vim/vim#18125 https://github.com/vim/vim/commit/2eccb4d0bec87854cfc214e1feb96b5a8f4a69df Co-authored-by: Girish Palya --- src/nvim/cmdexpand.c | 65 +++++++++++--------- src/nvim/ex_getln.c | 83 +++++++++++++------------- test/old/testdir/test_cmdline.vim | 31 +++++++++- test/old/testdir/test_ins_complete.vim | 2 +- 4 files changed, 107 insertions(+), 74 deletions(-) diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index c74318776c..89d3c75bb2 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -381,8 +381,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return OK; } -/// Create and display a cmdline completion popup menu with items from -/// "matches". +/// Create completion popup menu with items from 'matches'. static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches, bool showtail, bool noselect) { @@ -402,19 +401,13 @@ static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, } // Compute the popup menu starting column - char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, true) : xp->xp_pattern; + char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, noselect) : xp->xp_pattern; if (ui_has(kUICmdline) && cmdline_win == NULL) { compl_startcol = (int)(endpos - ccline->cmdbuff); } else { compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); } - // no default selection - compl_selected = noselect ? -1 : 0; - - pum_clear(); - cmdline_pum_display(true); - return EXPAND_OK; } @@ -427,8 +420,7 @@ void cmdline_pum_display(bool changed_array) /// Returns true if the cmdline completion popup menu is being displayed. bool cmdline_pum_active(void) { - // compl_match_array != NULL should already imply pum_visible() in Nvim. - return compl_match_array != NULL; + return pum_visible() && compl_match_array != NULL; } /// Remove the cmdline completion popup menu (if present), free the list of items. @@ -752,11 +744,20 @@ static char *get_next_or_prev_match(int mode, expand_T *xp) } // Display matches on screen - if (compl_match_array) { - compl_selected = findex; - cmdline_pum_display(false); - } else if (p_wmnu) { - redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + if (p_wmnu) { + if (compl_match_array) { + compl_selected = findex; + cmdline_pum_display(false); + } else if (wop_flags & kOptWopFlagPum) { + if (cmdline_pum_create(get_cmdline_info(), xp, xp->xp_files, + xp->xp_numfiles, cmd_showtail, false) == EXPAND_OK) { + compl_selected = findex; + pum_clear(); + cmdline_pum_display(true); + } + } else { + redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + } } xp->xp_selected = findex; @@ -1091,10 +1092,10 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in } } -/// Show all matches for completion on the command line. -/// Returns EXPAND_NOTHING when the character that triggered expansion should -/// be inserted like a normal character. -int showmatches(expand_T *xp, bool wildmenu, bool noselect) +/// Display completion matches. +/// Returns EXPAND_NOTHING when the character that triggered expansion should be +/// inserted as a normal character. +int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool noselect) { CmdlineInfo *const ccline = get_cmdline_info(); int numMatches; @@ -1121,12 +1122,19 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect) showtail = cmd_showtail; } - if (((!ui_has(kUICmdline) || cmdline_win != NULL) && wildmenu && (wop_flags & kOptWopFlagPum)) + if (((!ui_has(kUICmdline) || cmdline_win != NULL) && display_wildmenu && !display_list + && (wop_flags & kOptWopFlagPum)) || ui_has(kUIWildmenu) || (ui_has(kUICmdline) && ui_has(kUIPopupmenu))) { - return cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect); + int retval = cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect); + if (retval == EXPAND_OK) { + compl_selected = noselect ? -1 : 0; + pum_clear(); + cmdline_pum_display(true); + } + return retval; } - if (!wildmenu) { + if (display_list) { msg_didany = false; // lines_left will be set msg_start(); // prepare for paging msg_putchar('\n'); @@ -1138,10 +1146,11 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect) } if (got_int) { - got_int = false; // only int. the completion, not the cmd line - } else if (wildmenu) { - redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0, showtail); - } else { + got_int = false; // only interrupt the completion, not the cmd line + } else if (display_wildmenu && !display_list) { + redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0, + showtail); // display statusbar menu + } else if (display_list) { // find the length of the longest file name maxlen = 0; for (int i = 0; i < numMatches; i++) { @@ -3570,7 +3579,7 @@ int wildmenu_translate_key(CmdlineInfo *cclp, int key, expand_T *xp, bool did_wi { int c = key; - if (did_wild_list) { + if (cmdline_pum_active() || did_wild_list || wild_menu_showing) { if (c == K_LEFT) { c = Ctrl_P; } else if (c == K_RIGHT) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 48367bb902..85af5647f2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1120,32 +1120,34 @@ static int command_line_wildchar_complete(CommandLineState *s) { int res; int options = WILD_NO_BEEP; - bool noselect = p_wmnu && (wim_flags[0] & kOptWimFlagNoselect) != 0; + bool escape = s->firstc != '@'; + bool wim_noselect = p_wmnu && (wim_flags[0] & kOptWimFlagNoselect) != 0; if (wim_flags[s->wim_index] & kOptWimFlagLastused) { options |= WILD_BUFLASTUSED; } if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice - // if 'wildmode' contains "list" may still need to list + // If "list" is present, list matches unless already listed if (s->xpc.xp_numfiles > 1 && !s->did_wild_list - && ((wim_flags[s->wim_index] & kOptWimFlagList) - || (p_wmnu && (wim_flags[s->wim_index] & kOptWimFlagFull) != 0))) { - showmatches(&s->xpc, - p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0), - noselect); + && (wim_flags[s->wim_index] & kOptWimFlagList)) { + showmatches(&s->xpc, false, true, wim_noselect); redrawcmd(); s->did_wild_list = true; } - if (wim_flags[s->wim_index] & kOptWimFlagLongest) { - res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + res = nextwild(&s->xpc, WILD_LONGEST, options, escape); } else if (wim_flags[s->wim_index] & kOptWimFlagFull) { - res = nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); + res = nextwild(&s->xpc, WILD_NEXT, options, escape); } else { res = OK; // don't insert 'wildchar' now } } else { // typed p_wc first time + bool wim_longest = (wim_flags[0] & kOptWimFlagLongest); + bool wim_list = (wim_flags[0] & kOptWimFlagList); + bool wim_full = (wim_flags[0] & kOptWimFlagFull); + + s->wim_index = 0; if (s->c == p_wc || s->c == p_wcm || s->c == K_WILD || s->c == Ctrl_Z) { options |= WILD_MAY_EXPAND_PATTERN; if (s->c == K_WILD) { @@ -1153,18 +1155,17 @@ static int command_line_wildchar_complete(CommandLineState *s) } s->xpc.xp_pre_incsearch_pos = s->is_state.search_start; } - s->wim_index = 0; - int j = ccline.cmdpos; + int cmdpos_before = ccline.cmdpos; // if 'wildmode' first contains "longest", get longest // common part - if (wim_flags[0] & kOptWimFlagLongest) { - res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + if (wim_longest) { + res = nextwild(&s->xpc, WILD_LONGEST, options, escape); } else { - if (noselect || (wim_flags[s->wim_index] & kOptWimFlagList)) { + if (wim_noselect || wim_list) { options |= WILD_NOSELECT; } - res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); + res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, escape); } // if interrupted while completing, behave like it failed @@ -1176,29 +1177,28 @@ static int command_line_wildchar_complete(CommandLineState *s) return CMDLINE_CHANGED; } - // when more than one match, and 'wildmode' first contains - // "list", or no change and 'wildmode' contains "longest,list", - // list all matches - if (res == OK - && s->xpc.xp_numfiles > (noselect ? 0 : 1)) { - // a "longest" that didn't do anything is skipped (but not - // "list:longest") - if (wim_flags[0] == kOptWimFlagLongest && ccline.cmdpos == j) { - s->wim_index = 1; - } - if ((wim_flags[s->wim_index] & kOptWimFlagList) - || (p_wmnu && (wim_flags[s->wim_index] & (kOptWimFlagFull|kOptWimFlagNoselect)))) { - showmatches(&s->xpc, - p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0), - noselect); - redrawcmd(); - s->did_wild_list = true; - - if (wim_flags[s->wim_index] & kOptWimFlagLongest) { - nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + // Display matches + if (res == OK && s->xpc.xp_numfiles > (wim_noselect ? 0 : 1)) { + // If "longest" fails to identify the longest item, try other + // 'wim' values if available + if (wim_longest && ccline.cmdpos == cmdpos_before) { + if (wim_full) { + nextwild(&s->xpc, WILD_NEXT, options, escape); } - } else { - vim_beep(kOptBoFlagWildmode); + if (wim_list || (p_wmnu && wim_full)) { + showmatches(&s->xpc, p_wmnu, wim_list, false); + } + } else if (!wim_longest) { + if (wim_list || (p_wmnu && (wim_full || wim_noselect))) { + showmatches(&s->xpc, p_wmnu, wim_list, wim_noselect); + } else { + vim_beep(kOptBoFlagWildmode); + } + } + + redrawcmd(); + if (wim_list) { + s->did_wild_list = true; } } else if (s->xpc.xp_numfiles == -1) { s->xpc.xp_context = EXPAND_NOTHING; @@ -1339,7 +1339,7 @@ static int command_line_execute(VimState *state, int key) int wild_type = 0; const bool key_is_wc = (s->c == p_wc && KeyTyped) || s->c == p_wcm; - if ((cmdline_pum_active() || s->did_wild_list) && !key_is_wc) { + if ((cmdline_pum_active() || wild_menu_showing || s->did_wild_list) && !key_is_wc) { // Ctrl-Y: Accept the current selection and close the popup menu. // Ctrl-E: cancel the cmdline popup menu and return the original text. if (s->c == Ctrl_E || s->c == Ctrl_Y) { @@ -1464,8 +1464,7 @@ static int command_line_execute(VimState *state, int key) if (s->xpc.xp_numfiles > 1 && ((!s->did_wild_list && (wim_flags[s->wim_index] & kOptWimFlagList)) || p_wmnu)) { // Trigger the popup menu when wildoptions=pum - showmatches(&s->xpc, - p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0), + showmatches(&s->xpc, p_wmnu, wim_flags[s->wim_index] & kOptWimFlagList, wim_flags[0] & kOptWimFlagNoselect); } nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); @@ -2033,7 +2032,7 @@ static int command_line_handle_key(CommandLineState *s) } case Ctrl_D: - if (showmatches(&s->xpc, false, wim_flags[0] & kOptWimFlagNoselect) + if (showmatches(&s->xpc, false, true, wim_flags[0] & kOptWimFlagNoselect) == EXPAND_NOTHING) { break; // Use ^D as normal char instead } diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index c685acb43d..4cac0e1eba 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -2896,11 +2896,11 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "sign xyz\:sign \\\") call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) - " Check "list" still works + " Check that when "longest" produces no result, "list" works call term_sendkeys(buf, "\set wildmode=longest,list\") call term_sendkeys(buf, ":cn\") call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) - call term_sendkeys(buf, "s") + call term_sendkeys(buf, "\") call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {}) " Tests a directory name contained full-width characters. @@ -3000,7 +3000,32 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "\:set wildchazz\\\\") call VerifyScreenDump(buf, 'Test_wildmenu_pum_53', {}) - call term_sendkeys(buf, "\\") + call term_sendkeys(buf, "\:set showtabline& laststatus& lazyredraw&\") + + " Verify that if "longest" finds nothing, wildmenu is still shown + call term_sendkeys(buf, ":set wildmode=longest:full,full wildoptions&\") + call term_sendkeys(buf, ":cn\") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_54', {}) + + " Verify that if "longest" finds nothing, "list" is still shown + call term_sendkeys(buf, "\:set wildmode=longest:list,full\") + call term_sendkeys(buf, ":cn\") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_55', {}) + call term_sendkeys(buf, "\") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_56', {}) + + " Verify that if "longest" finds a candidate, wildmenu is not shown + call term_sendkeys(buf, "\:set wildmode=longest:full,full wildoptions&\") + call term_sendkeys(buf, ":sign u\") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_57', {}) + " Subsequent wildchar shows wildmenu + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_58', {}) + + call term_sendkeys(buf, "\\") call StopVimInTerminal(buf) endfunc diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 9c92a49445..39a83ca0e5 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -5620,7 +5620,7 @@ func Test_completetimeout_autocompletetimeout() set completetimeout=1 call feedkeys("Gof\\\0", 'xt!') let match_count = len(b:matches->mapnew('v:val.word')) - call assert_true(match_count < 2000) + call assert_true(match_count < 4000) set completetimeout=1000 call feedkeys("\Sf\\\0", 'xt!')