From d0582fcc742ce61e5e22f19d7b097fb79ade40e7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 6 May 2026 07:17:21 +0800 Subject: [PATCH] vim-patch:9.2.0442: completion: i_CTRL-X_CTRL-V doesn't use dict from customlist (#39614) Problem: Completion with i_CTRL-X_CTRL-V doesn't use dict from cmdline "customlist" completion. Solution: Include abbr/kind/menu/info in the completion items (zeertzjq). closes: vim/vim#20139 https://github.com/vim/vim/commit/2bfddbea47e1afa79ecd094040418abdba88bc83 --- src/nvim/cmdexpand.c | 2 +- src/nvim/insexpand.c | 29 +++- test/functional/ui/popupmenu_spec.lua | 219 ++++++++++++++++++++++++++ test/old/testdir/test_cmdline.vim | 31 +++- 4 files changed, 274 insertions(+), 7 deletions(-) diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index 3cc2fe7fc1..e4d1b09cdb 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -873,7 +873,7 @@ static char *find_longest_match(expand_T *xp, int options) return xmemdupz(xp->xp_files[0], len); } -static void free_xp_files_extra(expand_T *xp, int numfiles) +void free_xp_files_extra(expand_T *xp, int numfiles) { if (xp->xp_files_abbr != NULL) { FreeWild(numfiles, xp->xp_files_abbr); diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 3828eafafb..62c1cfd185 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -4154,7 +4154,34 @@ static void get_next_cmdline_completion(void) int num_matches; if (expand_cmdline(&compl_xp, compl_pattern.data, (int)compl_pattern.size, &num_matches, &matches) == EXPAND_OK) { - ins_compl_add_matches(num_matches, matches, false); + int add_r = OK; + Direction dir = compl_direction; + + for (int i = 0; i < num_matches && add_r != FAIL; i++) { + char *(cptext[CPT_COUNT]) = { NULL, NULL, NULL, NULL }; + + if (compl_xp.xp_files_abbr != NULL) { + cptext[CPT_ABBR] = compl_xp.xp_files_abbr[i]; + } + if (compl_xp.xp_files_kind != NULL) { + cptext[CPT_KIND] = compl_xp.xp_files_kind[i]; + } + if (compl_xp.xp_files_menu != NULL) { + cptext[CPT_MENU] = compl_xp.xp_files_menu[i]; + } + if (compl_xp.xp_files_info != NULL) { + cptext[CPT_INFO] = compl_xp.xp_files_info[i]; + } + + add_r = ins_compl_add(matches[i], -1, NULL, cptext, false, NULL, dir, + CP_FAST, false, NULL, FUZZY_SCORE_NONE, false); + if (add_r == OK) { + // if dir was BACKWARD then honor it just once + dir = FORWARD; + } + } + FreeWild(num_matches, matches); + free_xp_files_extra(&compl_xp, num_matches); } } diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index c84a3c8334..2d2224b3e8 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5469,6 +5469,225 @@ describe('builtin popupmenu', function() :DictCmd ^ | ]]) end + + feed('') + + -- Tests for Insert mode i_CTRL-X_CTRL-V + feed('iDictCmd ') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + DictCmd apple^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{6:match 1 of 4} | + ## grid 5 + {12: apple f fruit }| + {n: banana f fruit }| + {n: carrot v vegetable }| + {n: plain }| + ## grid 6 + {n:A red fruit}| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 7, false, 100, 2, 1, 7 }, + [6] = { 1002, 'NW', 1, 1, 27, true, 50, 1, 1, 27 }, + }, + }) + else + screen:expect([[ + DictCmd apple^ | + {1:~ }{12: apple f fruit }{n:A red fruit}{1: }| + {1:~ }{n: banana f fruit }{1: }| + {1:~ }{n: carrot v vegetable }{1: }| + {1:~ }{n: plain }{1: }| + {1:~ }|*6 + {5:-- Command-line completion (^V^N^P) }{6:match 1 of 4} | + ]]) + end + + feed('') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + DictCmd banana^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{6:match 2 of 4} | + ## grid 5 + {n: apple f fruit }| + {12: banana f fruit }| + {n: carrot v vegetable }| + {n: plain }| + ## grid 6 + {n:A yellow fruit}| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 7, false, 100, 2, 1, 7 }, + [6] = { 1002, 'NW', 1, 1, 27, true, 50, 1, 1, 27 }, + }, + }) + else + screen:expect([[ + DictCmd banana^ | + {1:~ }{n: apple f fruit A yellow fruit}{1: }| + {1:~ }{12: banana f fruit }{1: }| + {1:~ }{n: carrot v vegetable }{1: }| + {1:~ }{n: plain }{1: }| + {1:~ }|*6 + {5:-- Command-line completion (^V^N^P) }{6:match 2 of 4} | + ]]) + end + + feed('') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + DictCmd carrot^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{6:match 3 of 4} | + ## grid 5 + {n: apple f fruit }| + {n: banana f fruit }| + {12: carrot v vegetable }| + {n: plain }| + ## grid 6 + {n:An orange vegetable}| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 7, false, 100, 2, 1, 7 }, + [6] = { 1002, 'NW', 1, 1, 27, true, 50, 1, 1, 27 }, + }, + }) + else + screen:expect([[ + DictCmd carrot^ | + {1:~ }{n: apple f fruit An orange vegetable}{1: }| + {1:~ }{n: banana f fruit }{1: }| + {1:~ }{12: carrot v vegetable }{1: }| + {1:~ }{n: plain }{1: }| + {1:~ }|*6 + {5:-- Command-line completion (^V^N^P) }{6:match 3 of 4} | + ]]) + end + + feed('') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + DictCmd plain^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{6:match 4 of 4} | + ## grid 5 + {n: apple f fruit }| + {n: banana f fruit }| + {n: carrot v vegetable }| + {12: plain }| + ## grid 6 (hidden) + {n:An orange vegetable}| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 7, false, 100, 1, 1, 7 }, + }, + }) + else + screen:expect([[ + DictCmd plain^ | + {1:~ }{n: apple f fruit }{1: }| + {1:~ }{n: banana f fruit }{1: }| + {1:~ }{n: carrot v vegetable }{1: }| + {1:~ }{12: plain }{1: }| + {1:~ }|*6 + {5:-- Command-line completion (^V^N^P) }{6:match 4 of 4} | + ]]) + end + + feed('') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + DictCmd ^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{19:Back at original} | + ## grid 5 + {n: apple f fruit }| + {n: banana f fruit }| + {n: carrot v vegetable }| + {n: plain }| + ## grid 6 (hidden) + {n:An orange vegetable}| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 7, false, 100, 1, 1, 7 }, + }, + }) + else + screen:expect([[ + DictCmd ^ | + {1:~ }{n: apple f fruit }{1: }| + {1:~ }{n: banana f fruit }{1: }| + {1:~ }{n: carrot v vegetable }{1: }| + {1:~ }{n: plain }{1: }| + {1:~ }|*6 + {5:-- Command-line completion (^V^N^P) }{19:Back at original} | + ]]) + end + + -- Starting another i_CTRL-X_CTRL-V completion should not leak memory + feed('') + poke_eventloop() -- Allow pum_check_clear() to remove the info popup. + feed('sign un') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:-------------------------------------------------------]|*11 + [3:-------------------------------------------------------]| + ## grid 2 + sign undefine^ | + {1:~ }|*10 + ## grid 3 + {5:-- Command-line completion (^V^N^P) }{6:match 1 of 2} | + ## grid 5 + {12: undefine }| + {n: unplace }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 4, false, 100, 1, 1, 4 }, + }, + }) + else + screen:expect([[ + sign undefine^ | + {1:~ }{12: undefine }{1: }| + {1:~ }{n: unplace }{1: }| + {1:~ }|*8 + {5:-- Command-line completion (^V^N^P) }{6:match 1 of 2} | + ]]) + end end) it("'pumheight'", function() diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 59d0fb68da..a280265902 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -4645,25 +4645,46 @@ func Test_customlist_dict_completion_info_popup() call term_sendkeys(buf, ":DictCmd \") call WaitForTermCurPosAndLinesToMatch(buf, [rows, (strlen(':DictCmd apple') + 1)], g:test_timeout, ((rows - 4), 'A red fruit')) - call VerifyScreenDump(buf, 'Test_customlist_info_popup_1', {}) + call VerifyScreenDump(buf, 'Test_customlist_info_popup_01', {}) call term_sendkeys(buf, "\") call WaitForTermCurPosAndLinesToMatch(buf, [rows, (strlen(':DictCmd banana') + 1)], g:test_timeout, ((rows - 3), 'A yellow fruit')) - call VerifyScreenDump(buf, 'Test_customlist_info_popup_2', {}) + call VerifyScreenDump(buf, 'Test_customlist_info_popup_02', {}) call term_sendkeys(buf, "\") call WaitForTermCurPosAndLinesToMatch(buf, [rows, (strlen(':DictCmd carrot') + 1)], g:test_timeout, ((rows - 2), 'An orange vegetable')) - call VerifyScreenDump(buf, 'Test_customlist_info_popup_3', {}) + call VerifyScreenDump(buf, 'Test_customlist_info_popup_03', {}) call term_sendkeys(buf, "\") call WaitForTermCurPosAndLinesToMatch(buf, [rows, (strlen(':DictCmd plain') + 1)], g:test_timeout, ((rows - 1), '^\~\s\+plain\s\+$')) - call VerifyScreenDump(buf, 'Test_customlist_info_popup_4', {}) + call VerifyScreenDump(buf, 'Test_customlist_info_popup_04', {}) call term_sendkeys(buf, "\") call WaitForTermCurPosAndLinesToMatch(buf, [rows, (strlen(':DictCmd ') + 1)], g:test_timeout) - call VerifyScreenDump(buf, 'Test_customlist_info_popup_5', {}) + call VerifyScreenDump(buf, 'Test_customlist_info_popup_05', {}) call term_sendkeys(buf, "\") + + " Tests for Insert mode i_CTRL-X_CTRL-V + call term_sendkeys(buf, "iDictCmd \\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_06', {}) + + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_07', {}) + + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_08', {}) + + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_09', {}) + + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_10', {}) + + " Starting another i_CTRL-X_CTRL-V completion should not leak memory + call term_sendkeys(buf, "\sign un\\") + call VerifyScreenDump(buf, 'Test_customlist_info_popup_11', {}) + call StopVimInTerminal(buf) endfunc