commit 3b5337ab6c0a34d6acc92ee45beffa2a1f754185
parent a16064ff298b713bb6ad919538555381d48012da
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sun, 24 Aug 2025 18:35:11 +0800
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 <girishji@gmail.com>
Diffstat:
5 files changed, 101 insertions(+), 38 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);
}
@@ -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
@@ -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,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
@@ -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/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', @:)
@@ -4406,7 +4409,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 +4425,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 +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 \<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