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

57379302aa

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>
This commit is contained in:
zeertzjq
2025-08-24 18:35:11 +08:00
parent a16064ff29
commit 3b5337ab6c
5 changed files with 101 additions and 38 deletions

View File

@@ -254,6 +254,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
CmdlineInfo *const ccline = get_cmdline_info(); CmdlineInfo *const ccline = get_cmdline_info();
char *p; char *p;
bool from_wildtrigger_func = options & WILD_FUNC_TRIGGER; 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) { if (xp->xp_numfiles == -1) {
pre_incsearch_pos = xp->xp_pre_incsearch_pos; 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 // If cmd_silent is set then don't show the dots, because redrawcmd() below
// won't remove them. // won't remove them.
if (!cmd_silent && !from_wildtrigger_func if (!cmd_silent && !from_wildtrigger_func && !wild_navigate
&& !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) {
msg_puts("..."); // show that we are busy msg_puts("..."); // show that we are busy
ui_flush(); ui_flush();
} }
if (type == WILD_NEXT || type == WILD_PREV if (wild_navigate) {
|| type == WILD_PAGEUP || type == WILD_PAGEDOWN
|| type == WILD_PUM_WANT) {
// Get next/previous match for a previous expanded pattern. // Get next/previous match for a previous expanded pattern.
p = ExpandOne(xp, NULL, NULL, 0, type); p = ExpandOne(xp, NULL, NULL, 0, type);
} else { } 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); tmp = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
} }
// Translate string into pattern and expand it. // 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_HOME_REPLACE
| WILD_ADD_SLASH | WILD_ADD_SLASH
| WILD_SILENT | 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); size_t plen = strlen(p);
int difflen = (int)plen - (int)(xp->xp_pattern_len); int difflen = (int)plen - (int)(xp->xp_pattern_len);
if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { 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) { if (xp->xp_numfiles <= 0 && p == NULL) {
beep_flush(); 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 // free expanded pattern
ExpandOne(xp, NULL, NULL, 0, WILD_FREE); 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 /// Create and display a cmdline completion popup menu with items from
/// "matches". /// "matches".
static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches, static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches,
bool showtail) bool showtail, bool noselect)
{ {
assert(numMatches >= 0); assert(numMatches >= 0);
// Add all the completion matches // Add all the completion matches
@@ -403,7 +410,7 @@ static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches,
} }
// no default selection // no default selection
compl_selected = -1; compl_selected = noselect ? -1 : 0;
pum_clear(); pum_clear();
cmdline_pum_display(true); cmdline_pum_display(true);
@@ -925,7 +932,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
cmdline_pum_remove(); cmdline_pum_remove();
} }
} }
xp->xp_selected = 0; xp->xp_selected = (options & WILD_NOSELECT) ? -1 : 0;
if (mode == WILD_FREE) { // only release file name if (mode == WILD_FREE) { // only release file name
return NULL; return NULL;
@@ -1097,12 +1104,6 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect)
int columns; int columns;
bool showtail; 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) { if (xp->xp_numfiles == -1) {
set_expand_context(xp); set_expand_context(xp);
if (xp->xp_context == EXPAND_LUA) { 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)) if (((!ui_has(kUICmdline) || cmdline_win != NULL) && wildmenu && (wop_flags & kOptWopFlagPum))
|| ui_has(kUIWildmenu) || (ui_has(kUICmdline) && ui_has(kUIPopupmenu))) { || 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) { if (!wildmenu) {
@@ -1139,7 +1140,7 @@ int showmatches(expand_T *xp, bool wildmenu, bool noselect)
if (got_int) { if (got_int) {
got_int = false; // only int. the completion, not the cmd line got_int = false; // only int. the completion, not the cmd line
} else if (wildmenu) { } else if (wildmenu) {
redraw_wildmenu(xp, numMatches, matches, -1, showtail); redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0, showtail);
} else { } else {
// find the length of the longest file name // find the length of the longest file name
maxlen = 0; maxlen = 0;

View File

@@ -40,7 +40,7 @@ enum {
WILD_NOERROR = 0x800, ///< sets EW_NOERROR WILD_NOERROR = 0x800, ///< sets EW_NOERROR
WILD_BUFLASTUSED = 0x1000, WILD_BUFLASTUSED = 0x1000,
BUF_DIFF_FILTER = 0x2000, BUF_DIFF_FILTER = 0x2000,
WILD_KEEP_SOLE_ITEM = 0x4000, WILD_NOSELECT = 0x4000,
WILD_MAY_EXPAND_PATTERN = 0x8000, WILD_MAY_EXPAND_PATTERN = 0x8000,
WILD_FUNC_TRIGGER = 0x10000, ///< called from wildtrigger() WILD_FUNC_TRIGGER = 0x10000, ///< called from wildtrigger()
}; };

View File

@@ -1120,14 +1120,11 @@ static int command_line_wildchar_complete(CommandLineState *s)
{ {
int res; int res;
int options = WILD_NO_BEEP; 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) { if (wim_flags[s->wim_index] & kOptWimFlagLastused) {
options |= WILD_BUFLASTUSED; options |= WILD_BUFLASTUSED;
} }
if (noselect) {
options |= WILD_KEEP_SOLE_ITEM;
}
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
// if 'wildmode' contains "list" may still need to list // if 'wildmode' contains "list" may still need to list
if (s->xpc.xp_numfiles > 1 if (s->xpc.xp_numfiles > 1
@@ -1164,6 +1161,9 @@ static int command_line_wildchar_complete(CommandLineState *s)
if (wim_flags[0] & kOptWimFlagLongest) { if (wim_flags[0] & kOptWimFlagLongest) {
res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@');
} else { } else {
if (noselect || (wim_flags[s->wim_index] & kOptWimFlagList)) {
options |= WILD_NOSELECT;
}
res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); 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) if ((wim_flags[s->wim_index] & kOptWimFlagList)
|| (p_wmnu && (wim_flags[s->wim_index] & (kOptWimFlagFull|kOptWimFlagNoselect)))) { || (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, showmatches(&s->xpc,
p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0), p_wmnu && ((wim_flags[s->wim_index] & kOptWimFlagList) == 0),
noselect); noselect);
@@ -1204,9 +1196,6 @@ static int command_line_wildchar_complete(CommandLineState *s)
if (wim_flags[s->wim_index] & kOptWimFlagLongest) { if (wim_flags[s->wim_index] & kOptWimFlagLongest) {
nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); 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 { } else {
vim_beep(kOptBoFlagWildmode); vim_beep(kOptBoFlagWildmode);

View File

@@ -530,6 +530,47 @@ describe('cmdline', function()
/global^ | /global^ |
]]) ]])
end) 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) end)
describe('cmdwin', function() describe('cmdwin', function()

View File

@@ -2286,8 +2286,11 @@ func Wildmode_tests()
" when using longest completion match, matches shorter than the argument " when using longest completion match, matches shorter than the argument
" should be ignored (happens with :help) " should be ignored (happens with :help)
set wildmode=longest,full set wildmode=longest,full
call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') " XXX: This test is incorrect. ':help a*' will never yield 'help a'
call assert_equal('"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 " non existing file
call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt')
call assert_equal('"e a1b2y3z4', @:) call assert_equal('"e a1b2y3z4', @:)
@@ -4406,7 +4409,7 @@ func Test_cmdcomplete_info()
call feedkeys(":h echom\<cr>", "tx") " No expansion call feedkeys(":h echom\<cr>", "tx") " No expansion
call assert_equal('{}', g:cmdcomplete_info) call assert_equal('{}', g:cmdcomplete_info)
call feedkeys($":h echoms{trig}\<cr>", "tx") 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 feedkeys($":h echom{trig}\<cr>", "tx")
call assert_equal( call assert_equal(
\ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', \ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 0, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}',
@@ -4422,7 +4425,7 @@ func Test_cmdcomplete_info()
set wildoptions=pum set wildoptions=pum
call feedkeys($":h echoms{trig}\<cr>", "tx") 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 feedkeys($":h echom{trig}\<cr>", "tx")
call assert_equal( call assert_equal(
\ '{''cmdline_orig'': ''h echom'', ''pum_visible'': 1, ''matches'': ['':echom'', '':echomsg''], ''selected'': 0}', \ '{''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) call Ntest_override("char_avail", 0)
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab