From d9153d620ade3c7590d4fb99e6e8a3c827f2a299 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 7 Oct 2025 10:02:11 +0800 Subject: [PATCH] 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 --- src/nvim/insexpand.c | 75 +++++++++++++++++--------- test/old/testdir/test_ins_complete.vim | 32 +++++------ 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 72ce61a7a3..31ae200866 100644 --- 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 index e228dfb228..b70f6099ea 100644 --- 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\bar\b\\\\", 'tx') - call assert_equal('bar', getline('.')) - call feedkeys("Sb\\\\", 'tx') call assert_equal('blue', getline('.')) + call feedkeys("Sb\\\\", 'tx') + call assert_equal('bar', getline('.')) call feedkeys("Sb\\\\\", 'tx') call assert_equal('b', getline('.')) @@ -3668,15 +3668,29 @@ func Test_complete_opt_fuzzy() call feedkeys("S\\c\", '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\\=PrintMenuWords()\\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\\=PrintMenuWords()\" 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\\=PrintMenuWords()\" - call assert_equal('fo{''matches'': [''fo'', ''foobarbaz'', ''foobar'', ''foo''], ''selected'': 0}', getline(2)) - %d - call setline(1, ["fo", "foo", "foobar", "foobarbaz"]) - exe "normal! 2jof\\=PrintMenuWords()\" - call assert_equal('foobar{''matches'': [''foobarbaz'', ''fo'', ''foo'', ''foobar''], ''selected'': 3}', getline(4)) bw! set completeopt&