neovim

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

commit f0bf6d7647a4dabad0855d10bff63816b06eb24e
parent ad22d0ace9965907a83088fb52dd407b92222992
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Mon,  8 Sep 2025 10:38:37 +0800

Merge pull request #35673 from zeertzjq/vim-9.1.1676

vim-patch: cmdline completion fixes
Diffstat:
Msrc/nvim/cmdexpand.c | 114+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/nvim/cmdexpand.h | 2+-
Msrc/nvim/ex_getln.c | 101++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mtest/functional/legacy/cmdline_spec.lua | 41+++++++++++++++++++++++++++++++++++++++++
Mtest/functional/ui/cmdline_spec.lua | 30+++++++++++++++++++++++++++++-
Mtest/functional/ui/popupmenu_spec.lua | 155++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtest/old/testdir/test_cmdline.vim | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mtest/old/testdir/test_ins_complete.vim | 2+-
8 files changed, 437 insertions(+), 113 deletions(-)

diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c @@ -254,6 +254,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape) CmdlineInfo *const ccline = get_cmdline_info(); char *p; bool from_wildtrigger_func = options & WILD_FUNC_TRIGGER; + bool wild_navigate = (type == WILD_NEXT || type == WILD_PREV + || type == WILD_PAGEUP || type == WILD_PAGEDOWN + || type == WILD_PUM_WANT); if (xp->xp_numfiles == -1) { pre_incsearch_pos = xp->xp_pre_incsearch_pos; @@ -292,15 +295,13 @@ int nextwild(expand_T *xp, int type, int options, bool escape) // If cmd_silent is set then don't show the dots, because redrawcmd() below // won't remove them. - if (!cmd_silent && !from_wildtrigger_func + if (!cmd_silent && !from_wildtrigger_func && !wild_navigate && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { msg_puts("..."); // show that we are busy ui_flush(); } - if (type == WILD_NEXT || type == WILD_PREV - || type == WILD_PAGEUP || type == WILD_PAGEDOWN - || type == WILD_PUM_WANT) { + if (wild_navigate) { // Get next/previous match for a previous expanded pattern. p = ExpandOne(xp, NULL, NULL, 0, type); } else { @@ -313,7 +314,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) tmp = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); } // Translate string into pattern and expand it. - const int use_options = ((options & ~WILD_KEEP_SOLE_ITEM) + const int use_options = (options | WILD_HOME_REPLACE | WILD_ADD_SLASH | WILD_SILENT @@ -337,7 +338,13 @@ int nextwild(expand_T *xp, int type, int options, bool escape) } } - if (p != NULL && !got_int) { + // Save cmdline before inserting selected item + if (!wild_navigate && ccline->cmdbuff != NULL) { + xfree(cmdline_orig); + cmdline_orig = xstrnsave(ccline->cmdbuff, (size_t)ccline->cmdlen); + } + + if (p != NULL && !got_int && !(options & WILD_NOSELECT)) { size_t plen = strlen(p); int difflen = (int)plen - (int)(xp->xp_pattern_len); if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { @@ -364,7 +371,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) if (xp->xp_numfiles <= 0 && p == NULL) { beep_flush(); - } else if (xp->xp_numfiles == 1 && !(options & WILD_KEEP_SOLE_ITEM)) { + } else if (xp->xp_numfiles == 1 && !(options & WILD_NOSELECT) && !wild_navigate) { // free expanded pattern ExpandOne(xp, NULL, NULL, 0, WILD_FREE); } @@ -374,10 +381,9 @@ 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 showtail, bool noselect) { assert(numMatches >= 0); // Add all the completion matches @@ -395,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 = -1; - - pum_clear(); - cmdline_pum_display(true); - return EXPAND_OK; } @@ -420,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. @@ -745,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; @@ -925,7 +933,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) cmdline_pum_remove(); } } - xp->xp_selected = 0; + xp->xp_selected = (options & WILD_NOSELECT) ? -1 : 0; if (mode == WILD_FREE) { // only release file name return NULL; @@ -1084,49 +1092,49 @@ 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) +/// 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; char **matches; - int j; int maxlen; int lines; int columns; bool showtail; - // Save cmdline before expansion - if (ccline->cmdbuff != NULL) { - xfree(cmdline_orig); - cmdline_orig = xstrnsave(ccline->cmdbuff, (size_t)ccline->cmdlen); - } - if (xp->xp_numfiles == -1) { set_expand_context(xp); if (xp->xp_context == EXPAND_LUA) { nlua_expand_pat(xp); } - int i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, - &numMatches, &matches); - showtail = expand_showtail(xp); - if (i != EXPAND_OK) { - return i; + int retval = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, + &numMatches, &matches); + if (retval != EXPAND_OK) { + return retval; } + showtail = expand_showtail(xp); } else { numMatches = xp->xp_numfiles; matches = xp->xp_files; 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); + 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,22 +1146,24 @@ int showmatches(expand_T *xp, bool wildmenu) } if (got_int) { - got_int = false; // only int. the completion, not the cmd line - } else if (wildmenu) { - redraw_wildmenu(xp, numMatches, matches, -1, 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++) { + int len; if (!showtail && (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_SHELLCMD || xp->xp_context == EXPAND_BUFFERS)) { home_replace(NULL, matches[i], NameBuff, MAXPATHL, true); - j = vim_strsize(NameBuff); + len = vim_strsize(NameBuff); } else { - j = vim_strsize(SHOW_MATCH(i)); + len = vim_strsize(SHOW_MATCH(i)); } - maxlen = MAX(maxlen, j); + maxlen = MAX(maxlen, len); } if (xp->xp_context == EXPAND_TAGS_LISTFILES) { @@ -3569,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/cmdexpand.h b/src/nvim/cmdexpand.h @@ -40,7 +40,7 @@ enum { WILD_NOERROR = 0x800, ///< sets EW_NOERROR WILD_BUFLASTUSED = 0x1000, BUF_DIFF_FILTER = 0x2000, - WILD_KEEP_SOLE_ITEM = 0x4000, + WILD_NOSELECT = 0x4000, WILD_MAY_EXPAND_PATTERN = 0x8000, WILD_FUNC_TRIGGER = 0x10000, ///< called from wildtrigger() }; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c @@ -1120,31 +1120,34 @@ static int command_line_wildchar_complete(CommandLineState *s) { int res; int options = WILD_NO_BEEP; + 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 (wim_flags[0] & kOptWimFlagNoselect) { - options |= WILD_KEEP_SOLE_ITEM; - } 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)); + && (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) { @@ -1152,15 +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 { - res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); + if (wim_noselect || wim_list) { + options |= WILD_NOSELECT; + } + res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, escape); } // if interrupted while completing, behave like it failed @@ -1172,38 +1177,38 @@ 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 > ((wim_flags[s->wim_index] & kOptWimFlagNoselect) ? 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)))) { - if (!(wim_flags[0] & kOptWimFlagLongest)) { - int p_wmnu_save = p_wmnu; - p_wmnu = 0; - // remove match - nextwild(&s->xpc, WILD_PREV, options, s->firstc != '@'); - p_wmnu = p_wmnu_save; + // Display matches + if (res == OK && s->xpc.xp_numfiles > (wim_noselect ? 0 : 1)) { + if (wim_longest) { + bool found_longest_prefix = (ccline.cmdpos != cmdpos_before); + if (wim_list || (p_wmnu && wim_full)) { + showmatches(&s->xpc, p_wmnu, wim_list, true); + } else if (!found_longest_prefix) { + bool wim_list_next = (wim_flags[1] & kOptWimFlagList); + bool wim_full_next = (wim_flags[1] & kOptWimFlagFull); + bool wim_noselect_next = (wim_flags[1] & kOptWimFlagNoselect); + if (wim_list_next || (p_wmnu && (wim_full_next || wim_noselect_next))) { + if (wim_full_next && !wim_noselect_next) { + nextwild(&s->xpc, WILD_NEXT, options, escape); + } else { + showmatches(&s->xpc, p_wmnu, wim_list_next, wim_noselect_next); + } + if (wim_list_next) { + s->did_wild_list = true; + } + } } + } else { + if (wim_list || (p_wmnu && (wim_full || wim_noselect))) { + showmatches(&s->xpc, p_wmnu, wim_list, wim_noselect); + } else { + vim_beep(kOptBoFlagWildmode); + } + } - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0)); - redrawcmd(); + redrawcmd(); + if (wim_list) { s->did_wild_list = true; - - if (wim_flags[s->wim_index] & kOptWimFlagLongest) { - nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); - } else if ((wim_flags[s->wim_index] & kOptWimFlagFull) - && !(wim_flags[s->wim_index] & kOptWimFlagNoselect)) { - nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); - } - } else { - vim_beep(kOptBoFlagWildmode); } } else if (s->xpc.xp_numfiles == -1) { s->xpc.xp_context = EXPAND_NOTHING; @@ -1344,7 +1349,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) { @@ -1469,7 +1474,8 @@ 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 != '@'); nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); @@ -2036,7 +2042,8 @@ static int command_line_handle_key(CommandLineState *s) } case Ctrl_D: - if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { + if (showmatches(&s->xpc, false, true, wim_flags[0] & kOptWimFlagNoselect) + == EXPAND_NOTHING) { break; // Use ^D as normal char instead } diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua @@ -530,6 +530,47 @@ describe('cmdline', function() /global^ | ]]) end) + + -- oldtest: Test_long_line_noselect() + it("long line is shown properly with noselect in 'wildmode'", function() + local screen = Screen.new(60, 8) + exec([[ + set wildmenu wildoptions=pum wildmode=noselect,full + command -nargs=1 -complete=custom,Entries DoubleEntry echo + func Entries(a, b, c) + return 'loooooooooooooooong quite loooooooooooong, really loooooooooooong, probably too looooooooooooooooooooooooooong entry' + endfunc + ]]) + + feed(':DoubleEntry <Tab>') + screen:expect([[ + | + {1:~ }|*5 + {1:~ }{4: loooooooooooooooong quite loooooooooooong, real}| + :DoubleEntry ^ | + ]]) + + feed('<C-N>') + screen:expect([[ + | + {1:~ }|*3 + {3: }| + :DoubleEntry loooooooooooooooong quite loooooooooooong, real| + ly loooooooo{12: loooooooooooooooong quite loooooooooooong, real}| + ong entry^ | + ]]) + + feed('<C-N>') + screen:expect([[ + | + {1:~ }|*3 + {3: }{4: loooooooooooooooong quite loooooooooooong, real}| + :DoubleEntry ^ | + |*2 + ]]) + + feed('<Esc>') + end) end) describe('cmdwin', function() diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua @@ -535,12 +535,40 @@ local function test_cmdline(linegrid) {2:långfile1 }| | ]], + cmdline = { { content = { { 'b långfile1' } }, firstc = ':', pos = 12 } }, popupmenu = { anchor = { -1, 0, 2 }, items = { { 'långfile1', '', '', '' }, { 'långfile2', '', '', '' } }, pos = 0, }, - cmdline = { { content = { { 'b långfile1' } }, firstc = ':', pos = 12 } }, + } + + feed('<Esc>') + command('silent %bwipe') + + command('set shellslash') + -- position is correct when expanding environment variable #20348 + command('silent cd test/functional/fixtures') + n.fn.setenv('XNDIR', 'wildpum/Xnamedir') + feed(':e $XNDIR/<Tab>') + screen:expect { + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { + { + content = { { 'e wildpum/Xnamedir/XdirA/' } }, + firstc = ':', + pos = 25, + }, + }, + popupmenu = { + anchor = { -1, 0, 19 }, + items = { { 'XdirA/', '', '', '' }, { 'XfileA', '', '', '' } }, + pos = 0, + }, } end) diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua @@ -1060,6 +1060,7 @@ describe('builtin popupmenu', function() [110] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkYellow }, [111] = { background = Screen.colors.Plum1, foreground = Screen.colors.DarkBlue }, [112] = { background = Screen.colors.Plum1, foreground = Screen.colors.DarkGreen }, + [113] = { background = Screen.colors.Yellow, foreground = Screen.colors.Black }, -- popup non-selected item n = { background = Screen.colors.Plum1 }, -- popup scrollbar knob @@ -4516,7 +4517,7 @@ describe('builtin popupmenu', function() feed('<esc>') - -- Check "list" still works + -- Check that when "longest" produces no result, "list" works command('set wildmode=longest,list') feed(':cn<Tab>') screen:expect([[ @@ -4529,6 +4530,8 @@ describe('builtin popupmenu', function() cnfile cnoremenu | :cn^ | ]]) + feed('<Tab>') + screen:expect_unchanged() feed('s') screen:expect([[ | @@ -4694,6 +4697,108 @@ describe('builtin popupmenu', function() feed('<Esc>') + -- "longest:list" shows list whether it finds a candidate or not + command('set wildmode=longest:list,full wildoptions=') + feed(':cn<Tab>') + screen:expect([[ + | + {1:~ }|*3 + {3: }| + :cn | + cnewer cnoreabbrev | + cnext cnoremap | + cnfile cnoremenu | + :cn^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*2 + {3: }| + :cn | + cnewer cnoreabbrev | + cnext cnoremap | + cnfile cnoremenu | + {113:cnewer}{3: cnext cnfile > }| + :cnewer^ | + ]]) + feed('<Esc>:sign u<Tab>') + screen:expect([[ + | + {1:~ }|*5 + {3: }| + :sign un | + undefine unplace | + :sign un^ | + ]]) + + -- "longest:full" shows wildmenu whether it finds a candidate or not; + -- item not selected + feed('<Esc>') + command('set wildmode=longest:full,full') + feed(':sign u<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {3:undefine unplace }| + :sign un^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {113:undefine}{3: unplace }| + :sign undefine^ | + ]]) + + feed('<Esc>:cn<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {3:cnewer cnext cnfile > }| + :cn^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {113:cnewer}{3: cnext cnfile > }| + :cnewer^ | + ]]) + + -- If "longest,full" finds a candidate, wildmenu is not shown + feed('<Esc>') + command('set wildmode=longest,full') + feed(':sign u<Tab>') + screen:expect([[ + | + {1:~ }|*8 + :sign un^ | + ]]) + -- Subsequent wildchar shows wildmenu + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {113:undefine}{3: unplace }| + :sign undefine^ | + ]]) + + -- 'longest' does not find candidate, and displays menu without selecting item + feed('<Esc>') + command('set wildmode=longest,noselect') + feed(':cn<Tab>') + screen:expect([[ + | + {1:~ }|*7 + {3:cnewer cnext cnfile > }| + :cn^ | + ]]) + + command('set wildmode& wildoptions=pum') + + feed('<C-U><Esc>') + -- check positioning with multibyte char in pattern command('e långfile1') command('sp långfile2') @@ -4748,7 +4853,7 @@ describe('builtin popupmenu', function() ]]) feed('<esc>') - command('close') + command('%bwipe') command('set wildmode=full') -- special case: when patterns ends with "/", show menu items aligned @@ -4761,6 +4866,18 @@ describe('builtin popupmenu', function() {1:~ }{n: file2 }{1: }| :e compdir/file1^ | ]]) + + -- position is correct when expanding environment variable #20348 + command('cd ..') + fn.setenv('XNDIR', 'wildpum/Xnamedir') + feed('<C-U>e $XNDIR/<Tab>') + screen:expect([[ + | + {1:~ }|*11 + {1:~ }{12: XdirA/ }{1: }| + {1:~ }{n: XfileA }{1: }| + :e wildpum/Xnamedir/XdirA/^ | + ]]) end) end @@ -4877,24 +4994,52 @@ describe('builtin popupmenu', function() -- pressing <Tab> should display the wildmenu feed('<Tab>') - screen:expect([[ + local s1 = [[ | {1:~ }|*4 {1:~ }{12: undefine }{1: }| {1:~ }{n: unplace }{1: }| :sign undefine^ | - ]]) + ]] + screen:expect(s1) eq(1, fn.wildmenumode()) -- pressing <Tab> second time should select the next entry in the menu feed('<Tab>') - screen:expect([[ + local s2 = [[ | {1:~ }|*4 {1:~ }{n: undefine }{1: }| {1:~ }{12: unplace }{1: }| :sign unplace^ | + ]] + screen:expect(s2) + eq(1, fn.wildmenumode()) + + -- If "longest" finds no candidate in "longest,full", "full" is used + feed('<Esc>') + command('set wildmode=longest,full') + command('set wildoptions=pum') + feed(':sign un<Tab>') + screen:expect(s1) + feed('<Tab>') + screen:expect(s2) + + -- Similarly for "longest,noselect:full" + feed('<Esc>') + command('set wildmode=longest,noselect:full') + feed(':sign un<Tab>') + screen:expect([[ + | + {1:~ }|*4 + {1:~ }{n: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + :sign un^ | ]]) + feed('<Tab>') + screen:expect(s1) + feed('<Tab>') + screen:expect(s2) end) it('wildoptions=pum with a wrapped line in buffer vim-patch:8.2.4655', function() diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim @@ -2286,8 +2286,11 @@ func Wildmode_tests() " when using longest completion match, matches shorter than the argument " should be ignored (happens with :help) set wildmode=longest,full - call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') - call assert_equal('"help a', @:) + " XXX: This test is incorrect. ':help a*' will never yield 'help a' + " because '`a' exists as a menu item. The intent was to test a case + " handled by nextwild(). + " call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') + " call assert_equal('"help a', @:) " non existing file call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') call assert_equal('"e a1b2y3z4', @:) @@ -2893,10 +2896,12 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>") call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) - " Check "list" still works + " Check that when "longest" produces no result, "list" works call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>") call term_sendkeys(buf, ":cn\<Tab>") call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) call term_sendkeys(buf, "s") call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {}) @@ -2997,7 +3002,66 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "\<Esc>:set wildchazz\<Left>\<Left>\<Tab>\<C-Y>") call VerifyScreenDump(buf, 'Test_wildmenu_pum_53', {}) - call term_sendkeys(buf, "\<C-U>\<CR>") + call term_sendkeys(buf, "\<Esc>:set showtabline& laststatus& lazyredraw&\<CR>") + + " "longest:list" shows list whether it finds a candidate or not + call term_sendkeys(buf, ":set wildmode=longest:list,full wildoptions&\<CR>") + call term_sendkeys(buf, ":cn\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_55', {}) + call term_sendkeys(buf, "\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_56', {}) + call term_sendkeys(buf, "\<Esc>:sign u\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_57', {}) + + " "longest:full" shows wildmenu whether it finds a candidate or not; item not selected + call term_sendkeys(buf, "\<Esc>:set wildmode=longest:full,full\<CR>") + call term_sendkeys(buf, ":sign u\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_58', {}) + call term_sendkeys(buf, "\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_59', {}) + call term_sendkeys(buf, "\<Esc>:cn\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_60', {}) + call term_sendkeys(buf, "\<Tab>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_61', {}) + + " If "longest,full" finds a candidate, wildmenu is not shown + call term_sendkeys(buf, "\<Esc>:set wildmode=longest,full\<CR>") + call term_sendkeys(buf, ":sign u\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_62', {}) + " Subsequent wildchar shows wildmenu + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_63', {}) + + " 'longest' does not find candidate, and displays menu without selecting item + call term_sendkeys(buf, "\<Esc>:set wildmode=longest,noselect\<CR>") + call term_sendkeys(buf, ":cn\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_64', {}) + + " If "longest" finds no candidate in "longest,full", "full" is used + call term_sendkeys(buf, "\<Esc>:set wildmode=longest,full\<CR>") + call term_sendkeys(buf, ":set wildoptions=pum\<CR>") + call term_sendkeys(buf, ":sign un\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) + + " Similarly for "longest,noselect:full" + call term_sendkeys(buf, "\<Esc>:set wildmode=longest,noselect:full\<CR>") + call term_sendkeys(buf, ":sign un\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_65', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) + + call term_sendkeys(buf, "\<C-U>\<Esc>") call StopVimInTerminal(buf) endfunc @@ -4406,7 +4470,7 @@ func Test_cmdcomplete_info() call feedkeys(":h echom\<cr>", "tx") " No expansion call assert_equal('{}', g:cmdcomplete_info) call feedkeys($":h echoms{trig}\<cr>", "tx") - call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) + call assert_equal('{''cmdline_orig'': ''h echoms'', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) call feedkeys($":h echom{trig}\<cr>", "tx") call assert_equal( \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', @@ -4422,7 +4486,7 @@ func Test_cmdcomplete_info() set wildoptions=pum call feedkeys($":h echoms{trig}\<cr>", "tx") - call assert_equal('{''cmdline_orig'': '''', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) + call assert_equal('{''cmdline_orig'': ''h echoms'', ''pum_visible'': 0, ''matches'': [], ''selected'': 0}', g:cmdcomplete_info) call feedkeys($":h echom{trig}\<cr>", "tx") call assert_equal( \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', @@ -4874,4 +4938,33 @@ func Test_cmdline_changed() call Ntest_override("char_avail", 0) endfunc +" Issue #18035: long lines should not get listed twice in the menu when +" 'wildmode' contains 'noselect' +func Test_long_line_noselect() + CheckScreendump + + let lines =<< trim [SCRIPT] + set wildmenu wildoptions=pum wildmode=noselect,full + command -nargs=1 -complete=custom,Entries DoubleEntry echo + func Entries(a, b, c) + return 'loooooooooooooooong quite loooooooooooong, really loooooooooooong, probably too looooooooooooooooooooooooooong entry' + endfunc + [SCRIPT] + call writefile(lines, 'XTest_wildmenu', 'D') + let buf = RunVimInTerminal('-S XTest_wildmenu', {'rows': 8, 'cols': 60}) + + call term_sendkeys(buf, ":DoubleEntry \<Tab>") + call VerifyScreenDump(buf, 'Test_long_line_noselect_1', {}) + + call term_sendkeys(buf, "\<Esc>:DoubleEntry \<Tab>\<C-N>") + call VerifyScreenDump(buf, 'Test_long_line_noselect_2', {}) + + call term_sendkeys(buf, "\<Esc>:DoubleEntry \<Tab>\<C-N>\<C-N>") + call VerifyScreenDump(buf, 'Test_long_line_noselect_3', {}) + + " clean up + 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 @@ -5620,7 +5620,7 @@ func Test_completetimeout_autocompletetimeout() set completetimeout=1 call feedkeys("Gof\<C-N>\<F2>\<Esc>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("\<Esc>Sf\<C-N>\<F2>\<Esc>0", 'xt!')