]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1799: completion: crash with autcompletion v9.1.1799
authorGirish Palya <girishji@gmail.com>
Sun, 28 Sep 2025 17:07:29 +0000 (17:07 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 28 Sep 2025 17:07:29 +0000 (17:07 +0000)
Problem:  completion: crash with autcompletion
          (Maxim Kim)
Solution: Rework remove_old_matches (Girish Palya)

fixes: #18378
fixes: #18390
fixes: #18391
closes: #18427

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/insexpand.c
src/testdir/test_ins_complete.vim
src/version.c

index bf28bb2c13c9df9b7407a31d9e7f80fd51579891..2aabacd83aa7fb9e1779accd5ada166a06b58df5 100644 (file)
@@ -7590,40 +7590,52 @@ ins_compl_make_linear(void)
  * cpt_sources_index) from the completion list.
  */
 #ifdef FEAT_COMPL_FUNC
-    static compl_T *
+    static void
 remove_old_matches(void)
 {
-    compl_T *sublist_start = NULL, *sublist_end = NULL, *insert_at = NULL;
-    compl_T *current, *next;
-    int            compl_shown_removed = FALSE;
+    compl_T *current;
+    int            shown_match_removed = FALSE;
     int            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)
+    // When 'fuzzy' is enabled, items are not ordered by their original source
+    // order (cpt_sources_index). So, remove items one by one.
+    for (current = compl_first_match; current != NULL; )
     {
-       if (current->cp_cpt_source_idx < cpt_sources_index &&
-               (forward || (!forward && !insert_at)))
-           insert_at = current;
-
        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;
@@ -7636,27 +7648,19 @@ remove_old_matches(void)
        }
     }
 
-    if (!sublist_start) // No nodes to remove
-       return insert_at;
-
-    // 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)
+    // Re-assign compl_curr_match
+    compl_curr_match = compl_first_match;
+    for (current = compl_first_match; current != NULL; )
     {
-       next = current->cp_next;
-       ins_compl_item_free(current);
+       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;
     }
-
-    return insert_at;
 }
 #endif
 
@@ -7716,7 +7720,7 @@ cpt_compl_refresh(void)
            cb = get_callback_if_cpt_func(p, cpt_sources_index);
            if (cb)
            {
-               compl_curr_match = remove_old_matches();
+               remove_old_matches();
                ret = get_userdefined_compl_info(curwin->w_cursor.col, cb,
                        &startcol);
                if (ret == FAIL)
index ba449f2dbc157054225941a3649fb2b1a67c1e57..48623f26a740536fd1120d9e1dc9af38545766fd 100644 (file)
@@ -5942,4 +5942,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 test_override("char_avail", 1)
+  new
+  call setline(1, ['fox'])
+  exe "normal! Gofo"
+  bw!
+  delfunc ComplFunc1
+  delfunc ComplFunc2
+  set complete& autocomplete&
+  call test_override("char_avail", 0)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab nofoldenable
index 490b882f9ef0404af6a445cce81484f0487021e2..5c1424ed323a04fa4dfb5cf592211544ab0aaf85 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1799,
 /**/
     1798,
 /**/