From f97bcbdf7794e7fbc91f2c4dcd848b2609a5166a Mon Sep 17 00:00:00 2001 From: glepnir Date: Wed, 26 Aug 2026 12:32:09 +0800 Subject: [PATCH] vim-patch:9.2.1007: fuzzy completion list wrongly sorted after complete() (#41498) Problem: With 'completeopt' "fuzzy", a re-sort after complete() leaves the last match unsorted at the end of the list. Solution: Find the original text by its flag instead of assuming compl_shows_dir points at it. closes: vim/vim#21103 https://github.com/vim/vim/commit/7e747229993bf784768fbb22b4bdd3a13dbb3d13 --- src/nvim/insexpand.c | 48 ++++++++++++++++++-------- test/old/testdir/test_ins_complete.vim | 17 +++++++++ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index af8f4e8940..0969441a0d 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -671,6 +671,23 @@ static bool is_first_match(const compl_T *const match) return match == compl_first_match; } +/// Return the entry holding the original text, NULL if not found. +/// It is the first item, or the last one for backward completion. +static compl_T *find_original_text_match(void) +{ + if (compl_first_match == NULL) { + return NULL; + } + if (match_at_original_text(compl_first_match)) { + return compl_first_match; + } + if (compl_first_match->cp_prev != NULL + && match_at_original_text(compl_first_match->cp_prev)) { + return compl_first_match->cp_prev; + } + return NULL; +} + static void do_autocmd_completedone(int c, int mode, String *word) { save_v_event_T save_v_event; @@ -1617,16 +1634,20 @@ static void set_fuzzy_score(void) } while (comp != NULL && !is_first_match(comp)); } -/// Sort completion matches, excluding the node that contains the leader. +/// Sort completion matches, leaving the entry with the original text in place. static void sort_compl_match_list(MergeSortCompareFunc compare) { if (!compl_first_match || is_first_match(compl_first_match->cp_next)) { return; } - compl_T *comp = compl_first_match->cp_prev; + compl_T *orig_text = find_original_text_match(); + if (orig_text == NULL) { + return; + } + ins_compl_make_linear(); - if (compl_shows_dir_forward()) { + if (orig_text == compl_first_match) { compl_first_match->cp_next->cp_prev = NULL; compl_first_match->cp_next = mergesort_list(compl_first_match->cp_next, cp_get_next, cp_set_next, @@ -1634,15 +1655,15 @@ static void sort_compl_match_list(MergeSortCompareFunc compare) compare); compl_first_match->cp_next->cp_prev = compl_first_match; } else { - comp->cp_prev->cp_next = NULL; + orig_text->cp_prev->cp_next = NULL; compl_first_match = mergesort_list(compl_first_match, cp_get_next, cp_set_next, cp_get_prev, cp_set_prev, compare); compl_T *tail = compl_first_match; while (tail->cp_next != NULL) { tail = tail->cp_next; } - tail->cp_next = comp; - comp->cp_prev = tail; + tail->cp_next = orig_text; + orig_text->cp_prev = tail; } (void)ins_compl_make_cyclic(); } @@ -2573,16 +2594,13 @@ static void ins_compl_set_original_text(char *str, size_t len) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. - // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly - // be at the last item for backward completion - if (match_at_original_text(compl_first_match)) { // safety check - API_CLEAR_STRING(compl_first_match->cp_str); - compl_first_match->cp_str = cbuf_to_string(str, len); - } else if (compl_first_match->cp_prev != NULL - && match_at_original_text(compl_first_match->cp_prev)) { - API_CLEAR_STRING(compl_first_match->cp_prev->cp_str); - compl_first_match->cp_prev->cp_str = cbuf_to_string(str, len); + compl_T *match = find_original_text_match(); + if (match == NULL) { + return; } + + API_CLEAR_STRING(match->cp_str); + match->cp_str = cbuf_to_string(str, len); } /// Append one character to the match leader. May reduce the number of diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index a597419f1b..d9541fc919 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -6851,4 +6851,21 @@ func Test_complete_info_hlgroup() bwipe! endfunc +func Test_complete_fuzzy_resort() + new + set completeopt=menu,menuone,noselect,fuzzy + + inoremap call complete(1, ['xxxb', 'xb', 'b']) + call feedkeys("i\b\=string(map(complete_info(['items']).items, 'v:val.word'))\\", 'tx') + call assert_equal("b['b', 'xb', 'xxxb']", getline(1)) + + %d _ + call setline(1, ['xxxbar', 'xbar', 'bar']) + call feedkeys("Go\b\=string(map(complete_info(['items']).items, 'v:val.word'))\\", 'tx') + call assert_equal("b['bar', 'xbar', 'xxxbar']", getline('$')) + + bwipe! + set completeopt& +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable