commit d9153d620ade3c7590d4fb99e6e8a3c827f2a299
parent facde3dcbe6fd303a347276d27924b8754c43c42
Author: zeertzjq <zeertzjq@outlook.com>
Date: Tue, 7 Oct 2025 10:02:11 +0800
vim-patch:9.1.1833: completion: fuzzy candidates are not sorted (#36062)
Problem: completion: fuzzy candidates are not sorted
(ddad431)
Solution: Always sort fuzzy candidates (Girish Palya)
fixes: vim/vim#18488
closes: vim/vim#18497
https://github.com/vim/vim/commit/10aa04e3d432de1d7328ed44b6fa7456750ea3e2
Co-authored-by: Girish Palya <girishji@gmail.com>
Diffstat:
2 files changed, 68 insertions(+), 39 deletions(-)
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
@@ -1460,19 +1460,35 @@ theend:
return &compl_leader;
}
-/// Set fuzzy score.
+/// Set fuzzy score for completion matches.
static void set_fuzzy_score(void)
{
- if (!compl_first_match || compl_leader.data == NULL || compl_leader.size == 0) {
+ if (compl_first_match == NULL) {
return;
}
- (void)get_leader_for_startcol(NULL, true); // Clear the cache
+ // Determine the pattern to match against
+ bool use_leader = (compl_leader.data != NULL && compl_leader.size > 0);
+ char *pattern;
+ if (!use_leader) {
+ if (compl_orig_text.data == NULL || compl_orig_text.size == 0) {
+ return;
+ }
+ pattern = compl_orig_text.data;
+ } else {
+ // Clear the leader cache once before the loop
+ (void)get_leader_for_startcol(NULL, true);
+ pattern = NULL; // Will be computed per-completion
+ }
+ // Score all completion matches
compl_T *comp = compl_first_match;
do {
- comp->cp_score = fuzzy_match_str(comp->cp_str.data,
- get_leader_for_startcol(comp, true)->data);
+ if (use_leader) {
+ pattern = get_leader_for_startcol(comp, true)->data;
+ }
+
+ comp->cp_score = fuzzy_match_str(comp->cp_str.data, pattern);
comp = comp->cp_next;
} while (comp != NULL && !is_first_match(comp));
}
@@ -2244,13 +2260,31 @@ bool ins_compl_has_autocomplete(void)
return curbuf->b_p_ac >= 0 ? curbuf->b_p_ac : p_ac;
}
+/// Cacluate fuzzy score and sort completion matches unless sorting is disabled.
+static void ins_compl_fuzzy_sort(void)
+{
+ unsigned cur_cot_flags = get_cot_flags();
+
+ // set the fuzzy score in cp_score
+ set_fuzzy_score();
+ // Sort the matches linked list based on fuzzy score
+ if (!(cur_cot_flags & kOptCotFlagNosort)) {
+ sort_compl_match_list(cp_compare_fuzzy);
+ if ((cur_cot_flags & (kOptCotFlagNoinsert|kOptCotFlagNoselect)) == kOptCotFlagNoinsert
+ && compl_first_match) {
+ compl_shown_match = compl_first_match;
+ if (compl_shows_dir_forward() && !compl_autocomplete) {
+ compl_shown_match = compl_first_match->cp_next;
+ }
+ }
+ }
+}
+
/// Called after changing "compl_leader".
/// Show the popup menu with a different set of matches.
/// May also search for matches again if the previous search was interrupted.
static void ins_compl_new_leader(void)
{
- unsigned cur_cot_flags = get_cot_flags();
-
ins_compl_del_pum();
ins_compl_delete(true);
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);
@@ -2268,6 +2302,9 @@ static void ins_compl_new_leader(void)
if (is_cpt_func_refresh_always()) {
cpt_compl_refresh();
}
+ if (get_cot_flags() & kOptCotFlagFuzzy) {
+ ins_compl_fuzzy_sort();
+ }
} else {
spell_bad_len = 0; // need to redetect bad word
// Matches were cleared, need to search for them now.
@@ -2284,22 +2321,6 @@ static void ins_compl_new_leader(void)
compl_restarting = false;
}
- // When 'cot' contains "fuzzy" set the cp_score and maybe sort
- if (cur_cot_flags & kOptCotFlagFuzzy) {
- set_fuzzy_score();
- // Sort the matches linked list based on fuzzy score
- if (!(cur_cot_flags & kOptCotFlagNosort)) {
- sort_compl_match_list(cp_compare_fuzzy);
- if ((cur_cot_flags & (kOptCotFlagNoinsert|kOptCotFlagNoselect)) == kOptCotFlagNoinsert
- && compl_first_match) {
- compl_shown_match = compl_first_match;
- if (compl_shows_dir_forward() && !compl_autocomplete) {
- compl_shown_match = compl_first_match->cp_next;
- }
- }
- }
- }
-
compl_enter_selects = !compl_used_match && compl_selected_item != -1;
// Show the popup menu with a different set of matches.
@@ -4909,6 +4930,10 @@ static int ins_compl_get_exp(pos_T *ini)
sort_compl_match_list(cp_compare_nearest);
}
+ if ((get_cot_flags() & kOptCotFlagFuzzy) && ins_compl_leader_len() > 0) {
+ ins_compl_fuzzy_sort();
+ }
+
return match_count;
}
@@ -5551,7 +5576,9 @@ void ins_compl_check_keys(int frequency, bool in_compl_func)
}
}
- if (compl_pending && !got_int && !(cot_flags & kOptCotFlagNoinsert)
+ if (compl_pending
+ && !got_int
+ && !(cot_flags & (kOptCotFlagNoinsert | kOptCotFlagFuzzy))
&& (!compl_autocomplete || ins_compl_has_preinsert())) {
// Insert the first match immediately and advance compl_shown_match,
// before finding other matches.
diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
@@ -3641,9 +3641,9 @@ func Test_complete_opt_fuzzy()
set cot=menu,fuzzy
call feedkeys("Sblue\<CR>bar\<CR>b\<C-X>\<C-P>\<C-Y>\<ESC>", 'tx')
- call assert_equal('bar', getline('.'))
- call feedkeys("Sb\<C-X>\<C-N>\<C-Y>\<ESC>", 'tx')
call assert_equal('blue', getline('.'))
+ call feedkeys("Sb\<C-X>\<C-N>\<C-Y>\<ESC>", 'tx')
+ call assert_equal('bar', getline('.'))
call feedkeys("Sb\<C-X>\<C-P>\<C-N>\<C-Y>\<ESC>", 'tx')
call assert_equal('b', getline('.'))
@@ -3668,15 +3668,29 @@ func Test_complete_opt_fuzzy()
call feedkeys("S\<C-X>\<C-O>c\<C-Y>", 'tx')
call assert_equal('cp_str', getline('.'))
+ " Issue 18488: sort after collection when "fuzzy" (unless "nosort")
+ %d
+ set completeopt&
+ set completeopt+=fuzzy,noselect completefuzzycollect=keyword
+ func! PrintMenuWords()
+ let info = complete_info(["items"])
+ call map(info.items, {_, v -> v.word})
+ return info
+ endfunc
+ call setline(1, ['func1', 'xfunc', 'func2'])
+ call feedkeys("Gof\<C-N>\<C-R>=PrintMenuWords()\<CR>\<Esc>0", 'tx')
+ call assert_equal('f{''items'': [''func1'', ''func2'', ''xfunc'']}', getline('.'))
+
" clean up
set omnifunc=
bw!
- set complete& completeopt&
+ set complete& completeopt& completefuzzycollect&
autocmd! AAAAA_Group
augroup! AAAAA_Group
delfunc OnPumChange
delfunc Omni_test
delfunc Comp
+ delfunc PrintMenuWords
unlet g:item
unlet g:word
unlet g:abbr
@@ -4845,18 +4859,6 @@ func Test_nearest_cpt_option()
call setline(1, ["fo", "foo", "foobar", "foobarbaz"])
exe "normal! 2jof\<c-p>\<c-r>=PrintMenuWords()\<cr>"
call assert_equal('fo{''matches'': [''foobarbaz'', ''foobar'', ''foo'', ''fo''], ''selected'': -1}', getline(4))
-
- " No effect if 'fuzzy' is present
- set completeopt&
- set completeopt+=fuzzy,nearest
- %d
- call setline(1, ["foo", "fo", "foobarbaz", "foobar"])
- exe "normal! of\<c-n>\<c-r>=PrintMenuWords()\<cr>"
- call assert_equal('fo{''matches'': [''fo'', ''foobarbaz'', ''foobar'', ''foo''], ''selected'': 0}', getline(2))
- %d
- call setline(1, ["fo", "foo", "foobar", "foobarbaz"])
- exe "normal! 2jof\<c-p>\<c-r>=PrintMenuWords()\<cr>"
- call assert_equal('foobar{''matches'': [''foobarbaz'', ''fo'', ''foo'', ''foobar''], ''selected'': 3}', getline(4))
bw!
set completeopt&