From 3b5337ab6c0a34d6acc92ee45beffa2a1f754185 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 24 Aug 2025 18:35:11 +0800 Subject: [PATCH] vim-patch:9.1.1676: completion: long line shown twice Problem: completion: long line shown twice (Maxim Kim) Solution: Fix the issue, disable an incorrect test. (Girish Palya) fixes: vim/vim#18035 closes: vim/vim#18088 https://github.com/vim/vim/commit/57379302aa2a82ee0c9aca49ddd681308cf1483c Omit removal of blank line in Test_noselect_expand_env_var() as it's added again in patch 9.1.1682. Cherry-pick two blank lines in Test_long_line_noselect() from patch 9.1.1682. Co-authored-by: Girish Palya --- src/nvim/cmdexpand.c | 37 +++++++++++----------- src/nvim/cmdexpand.h | 2 +- src/nvim/ex_getln.c | 19 +++--------- test/functional/legacy/cmdline_spec.lua | 41 +++++++++++++++++++++++++ test/old/testdir/test_cmdline.vim | 40 +++++++++++++++++++++--- 5 files changed, 101 insertions(+), 38 deletions(-) diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index e61f739ee9..c74318776c 100644 --- 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); } @@ -377,7 +384,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape) /// Create and display a cmdline 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 @@ -403,7 +410,7 @@ static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, } // no default selection - compl_selected = -1; + compl_selected = noselect ? -1 : 0; pum_clear(); cmdline_pum_display(true); @@ -925,7 +932,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; @@ -1097,12 +1104,6 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect) 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) { @@ -1122,7 +1123,7 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect) if (((!ui_has(kUICmdline) || cmdline_win != NULL) && wildmenu && (wop_flags & kOptWopFlagPum)) || ui_has(kUIWildmenu) || (ui_has(kUICmdline) && ui_has(kUIPopupmenu))) { - return cmdline_pum_create(ccline, xp, matches, numMatches, showtail); + return cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect); } if (!wildmenu) { @@ -1139,7 +1140,7 @@ 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, -1, showtail); + redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0, showtail); } else { // find the length of the longest file name maxlen = 0; diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 7d6c6f27ba..51b90b311e 100644 --- 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 index a4220225b3..48367bb902 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1120,14 +1120,11 @@ static int command_line_wildchar_complete(CommandLineState *s) { int res; int options = WILD_NO_BEEP; - bool noselect = (wim_flags[0] & kOptWimFlagNoselect) != 0; + bool noselect = p_wmnu && (wim_flags[0] & kOptWimFlagNoselect) != 0; if (wim_flags[s->wim_index] & kOptWimFlagLastused) { options |= WILD_BUFLASTUSED; } - if (noselect) { - 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 (s->xpc.xp_numfiles > 1 @@ -1164,6 +1161,9 @@ static int command_line_wildchar_complete(CommandLineState *s) if (wim_flags[0] & kOptWimFlagLongest) { res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); } else { + if (noselect || (wim_flags[s->wim_index] & kOptWimFlagList)) { + options |= WILD_NOSELECT; + } res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); } @@ -1188,14 +1188,6 @@ static int command_line_wildchar_complete(CommandLineState *s) } 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; - } - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0), noselect); @@ -1204,9 +1196,6 @@ static int command_line_wildchar_complete(CommandLineState *s) 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); diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 5e11f47967..75eb234c6d 100644 --- 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 ') + screen:expect([[ + | + {1:~ }|*5 + {1:~ }{4: loooooooooooooooong quite loooooooooooong, real}| + :DoubleEntry ^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }|*3 + {3: }| + :DoubleEntry loooooooooooooooong quite loooooooooooong, real| + ly loooooooo{12: loooooooooooooooong quite loooooooooooong, real}| + ong entry^ | + ]]) + + feed('') + screen:expect([[ + | + {1:~ }|*3 + {3: }{4: loooooooooooooooong quite loooooooooooong, real}| + :DoubleEntry ^ | + |*2 + ]]) + + feed('') + end) end) describe('cmdwin', function() diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index bb37d010da..c685acb43d 100644 --- 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\\"\", '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\\"\", 'xt') + " call assert_equal('"help a', @:) " non existing file call feedkeys(":e a1b2y3z4\t\\"\", 'xt') call assert_equal('"e a1b2y3z4', @:) @@ -4406,7 +4409,7 @@ func Test_cmdcomplete_info() call feedkeys(":h echom\", "tx") " No expansion call assert_equal('{}', g:cmdcomplete_info) call feedkeys($":h echoms{trig}\", "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}\", "tx") call assert_equal( \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', @@ -4422,7 +4425,7 @@ func Test_cmdcomplete_info() set wildoptions=pum call feedkeys($":h echoms{trig}\", "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}\", "tx") call assert_equal( \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', @@ -4874,4 +4877,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 \") + call VerifyScreenDump(buf, 'Test_long_line_noselect_1', {}) + + call term_sendkeys(buf, "\:DoubleEntry \\") + call VerifyScreenDump(buf, 'Test_long_line_noselect_2', {}) + + call term_sendkeys(buf, "\:DoubleEntry \\\") + call VerifyScreenDump(buf, 'Test_long_line_noselect_3', {}) + + " clean up + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab