diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 1a94684951..758c2f84dc 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -6465,73 +6465,68 @@ static void ins_compl_make_linear(void) /// Remove the matches linked to the current completion source (as indicated by /// cpt_sources_index) from the completion list. -static compl_T *remove_old_matches(void) +static void remove_old_matches(void) { - compl_T *sublist_start = NULL, *sublist_end = NULL, *insert_at = NULL; - compl_T *current, *next; - bool compl_shown_removed = false; + bool shown_match_removed = false; bool forward = (compl_first_match->cp_cpt_source_idx < 0); + if (cpt_sources_index < 0) { + return; + } + compl_direction = forward ? FORWARD : BACKWARD; compl_shows_dir = compl_direction; - // Identify the sublist of old matches that needs removal - for (current = compl_first_match; current != NULL; current = current->cp_next) { - if (current->cp_cpt_source_idx < cpt_sources_index - && (forward || (!forward && !insert_at))) { - insert_at = current; - } - + // When 'fuzzy' is enabled, items are not ordered by their original source + // order (cpt_sources_index). So, remove items one by one. + for (compl_T *current = compl_first_match; current != NULL;) { if (current->cp_cpt_source_idx == cpt_sources_index) { - if (!sublist_start) { - sublist_start = current; - } - sublist_end = current; - if (!compl_shown_removed && compl_shown_match == current) { - compl_shown_removed = true; - } - } + compl_T *to_delete = current; - if ((forward && current->cp_cpt_source_idx > cpt_sources_index) - || (!forward && insert_at)) { - break; + if (!shown_match_removed && compl_shown_match == current) { + shown_match_removed = true; + } + + current = current->cp_next; + + if (to_delete == compl_first_match) { // node to remove is at head + compl_first_match = to_delete->cp_next; + compl_first_match->cp_prev = NULL; + } else if (to_delete->cp_next == NULL) { // node to remove is at tail + to_delete->cp_prev->cp_next = NULL; + } else { // node is in the moddle + to_delete->cp_prev->cp_next = to_delete->cp_next; + to_delete->cp_next->cp_prev = to_delete->cp_prev; + } + ins_compl_item_free(to_delete); + } else { + current = current->cp_next; } } // Re-assign compl_shown_match if necessary - if (compl_shown_removed) { + if (shown_match_removed) { if (forward) { compl_shown_match = compl_first_match; } else { // Last node will have the prefix that is being completed + compl_T *current; for (current = compl_first_match; current->cp_next != NULL; current = current->cp_next) {} compl_shown_match = current; } } - if (!sublist_start) { // No nodes to remove - return insert_at; + // Re-assign compl_curr_match + compl_curr_match = compl_first_match; + for (compl_T *current = compl_first_match; current != NULL;) { + if ((forward ? current->cp_cpt_source_idx < cpt_sources_index + : current->cp_cpt_source_idx > cpt_sources_index)) { + compl_curr_match = forward ? current : current->cp_next; + current = current->cp_next; + } else { + break; + } } - - // Update links to remove sublist - if (sublist_start->cp_prev) { - sublist_start->cp_prev->cp_next = sublist_end->cp_next; - } else { - compl_first_match = sublist_end->cp_next; - } - - if (sublist_end->cp_next) { - sublist_end->cp_next->cp_prev = sublist_start->cp_prev; - } - - // Free all nodes in the sublist - sublist_end->cp_next = NULL; - for (current = sublist_start; current != NULL; current = next) { - next = current->cp_next; - ins_compl_item_free(current); - } - - return insert_at; } /// Retrieve completion matches using the callback function "cb" and store the @@ -6573,7 +6568,7 @@ static void cpt_compl_refresh(void) if (cpt_sources_array[cpt_sources_index].cs_refresh_always) { Callback *cb = get_callback_if_cpt_func(p, cpt_sources_index); if (cb) { - compl_curr_match = remove_old_matches(); + remove_old_matches(); int startcol; int ret = get_userdefined_compl_info(curwin->w_cursor.col, cb, &startcol); if (ret == FAIL) { diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index e34e39bd3e..60407ad270 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -6080,4 +6080,33 @@ func Test_fuzzy_select_item_when_acl() call StopVimInTerminal(buf) endfunc +" Issue #18378: crash when fuzzy reorders items during refresh:always +func Test_refresh_always_with_fuzzy() + func ComplFunc1(findstart, base) + if a:findstart + return 1 + else + return ['foo', 'foobar'] + endif + endfunc + func ComplFunc2(findstart, base) + if a:findstart + return 1 + else + return #{words: ['foo'], refresh: 'always'} + endif + endfunc + set complete=.,FComplFunc1,FComplFunc2 + set autocomplete + call Ntest_override("char_avail", 1) + new + call setline(1, ['fox']) + exe "normal! Gofo" + bw! + delfunc ComplFunc1 + delfunc ComplFunc2 + set complete& autocomplete& + call Ntest_override("char_avail", 0) +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable