patch 9.1.1435: completion: various flaws in fuzzy completion

Commit: 
https://github.com/vim/vim/commit/8cd42a58b49c948ab59ced6ca5f5ccfae5d9ecea
Author: Girish Palya <giris...@gmail.com>
Date:   Thu Jun 5 21:04:29 2025 +0200

    patch 9.1.1435: completion: various flaws in fuzzy completion
    
    Problem:  completion: various flaws in fuzzy completion
    Solution: fix the issues (Girish Palya)
    
    - Remove the brittle `qsort()` on `compl_match_array`.
    - Add a stable, non-recursive `mergesort` for the internal doubly
      linked list of matches.
    - The sort now happens directly on the internal representation (`compl_T`),
      preserving sync with external structures and making sorting stable.
    - Update fuzzy match logic to enforce `max_matches` limits after
      sorting.
    - Remove `trim_compl_match_array()`, which is no longer necessary.
    - Fixe test failures by correctly setting `selected` index and
      maintaining match consistency.
    - Introduce `mergesort_list()` in `misc2.c`, which operates generically
      over doubly linked lists.
    - Remove `pum_score` and `pum_idx` variables
    
    fixes: #17387
    closes: #17430
    
    Signed-off-by: Girish Palya <giris...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/insexpand.c b/src/insexpand.c
index 7ecac6e85..33d00e0c6 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -265,6 +265,8 @@ static unsigned  quote_meta(char_u *dest, char_u *str, int 
len);
 static int ins_compl_has_multiple(void);
 static void ins_compl_expand_multiple(char_u *str);
 static void ins_compl_longest_insert(char_u *prefix);
+static void ins_compl_make_linear(void);
+static int ins_compl_make_cyclic(void);
 
 #ifdef FEAT_SPELL
 static void spell_back_to_badword(void);
@@ -1440,86 +1442,38 @@ trigger_complete_changed_event(int cur)
 #endif
 
 /*
- * Trim compl_match_array to enforce max_matches per completion source.
- *
- * Note: This special-case trimming is a workaround because compl_match_array
- * becomes inconsistent with compl_first_match (list) after former is sorted by
- * fuzzy score. The two structures end up in different orders.
- * Ideally, compl_first_match list should have been sorted instead.
- *
- * Returns recalculated index of shown match.
+ * Helper functions for mergesort_list().
  */
-    static int
-trim_compl_match_array(int shown_match_idx)
+    static void*
+cp_get_next(void *node)
 {
-    int                i, src_idx, limit, new_size = 0, *match_counts = NULL;
-    pumitem_T  *trimmed = NULL;
-    int                trimmed_idx = 0, remove_count = 0;
-
-    // Count current matches per source.
-    match_counts = ALLOC_CLEAR_MULT(int, cpt_sources_count);
-    if (match_counts == NULL)
-       return shown_match_idx;
-    for (i = 0; i < compl_match_arraysize; i++)
-    {
-       src_idx = compl_match_array[i].pum_cpt_source_idx;
-       if (src_idx != -1)
-           match_counts[src_idx]++;
-    }
-
-    // Calculate size of trimmed array, respecting max_matches per source.
-    for (i = 0; i < cpt_sources_count; i++)
-    {
-       limit = cpt_sources_array[i].cs_max_matches;
-       new_size += (limit > 0 && match_counts[i] > limit)
-           ? limit : match_counts[i];
-    }
+    return ((compl_T*)node)->cp_next;
+}
 
-    if (new_size == compl_match_arraysize)
-       goto theend;
+    static void
+cp_set_next(void *node, void *next)
+{
+    ((compl_T*)node)->cp_next = (compl_T*)next;
+}
 
-    // Create trimmed array while enforcing per-source limits
-    trimmed = ALLOC_CLEAR_MULT(pumitem_T, new_size);
-    if (trimmed == NULL)
-       goto theend;
-    vim_memset(match_counts, 0, sizeof(int) * cpt_sources_count);
-    for (i = 0; i < compl_match_arraysize; i++)
-    {
-       src_idx = compl_match_array[i].pum_cpt_source_idx;
-       if (src_idx != -1)
-       {
-           limit = cpt_sources_array[src_idx].cs_max_matches;
-           if (limit <= 0 || match_counts[src_idx] < limit)
-           {
-               trimmed[trimmed_idx++] = compl_match_array[i];
-               match_counts[src_idx]++;
-           }
-           else if (i < shown_match_idx)
-               remove_count++;
-       }
-       else
-           trimmed[trimmed_idx++] = compl_match_array[i];
-    }
-    vim_free(compl_match_array);
-    compl_match_array = trimmed;
-    compl_match_arraysize = new_size;
+    static void*
+cp_get_prev(void* node)
+{
+    return ((compl_T*)node)->cp_prev;
+}
 
-theend:
-    vim_free(match_counts);
-    return shown_match_idx - remove_count;
+    static void
+cp_set_prev(void* node, void* prev)
+{
+    ((compl_T*)node)->cp_prev = (compl_T*)prev;
 }
 
-/*
- * pumitem qsort compare func
- */
     static int
-ins_compl_fuzzy_cmp(const void *a, const void *b)
+cp_compare_fuzzy(const void* a, const void* b)
 {
-    const int sa = (*(pumitem_T *)a).pum_score;
-    const int sb = (*(pumitem_T *)b).pum_score;
-    const int ia = (*(pumitem_T *)a).pum_idx;
-    const int ib = (*(pumitem_T *)b).pum_idx;
-    return sa == sb ? (ia == ib ? 0 : (ia < ib ? -1 : 1)) : (sa < sb ? 1 : -1);
+    int score_a = ((compl_T*)a)->cp_score;
+    int score_b = ((compl_T*)b)->cp_score;
+    return (score_b > score_a) ? 1 : (score_b < score_a) ? -1 : 0;
 }
 
 /*
@@ -1536,7 +1490,6 @@ ins_compl_build_pum(void)
     int                shown_match_ok = FALSE;
     int                i = 0;
     int                cur = -1;
-    int                max_fuzzy_score = 0;
     unsigned int cur_cot_flags = get_cot_flags();
     int                compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
     int                fuzzy_filter = (cur_cot_flags & COT_FUZZY) != 0;
@@ -1544,54 +1497,74 @@ ins_compl_build_pum(void)
     compl_T    *match_head = NULL;
     compl_T    *match_tail = NULL;
     compl_T    *match_next = NULL;
-    int                update_shown_match = fuzzy_filter;
-    int                match_count = 0;
-    int                cur_source = -1;
-    int                max_matches_found = FALSE;
+    int                *match_count = NULL;
     int                is_forward = compl_shows_dir_forward();
+    int                is_cpt_completion = (cpt_sources_array != NULL);
 
     // Need to build the popup menu list.
     compl_match_arraysize = 0;
-    compl = compl_first_match;
 
     // If the current match is the original text don't find the first
     // match after it, don't highlight anything.
     if (match_at_original_text(compl_shown_match))
        shown_match_ok = TRUE;
 
-    if (fuzzy_filter && ctrl_x_mode_normal() && compl_leader.string == NULL
-           && compl_shown_match->cp_score > 0)
-       update_shown_match = FALSE;
-
     if (compl_leader.string != NULL
            && STRCMP(compl_leader.string, compl_orig_text.string) == 0
            && shown_match_ok == FALSE)
        compl_shown_match = compl_no_select ? compl_first_match
                                            : compl_first_match->cp_next;
 
-    do
+    // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
+    // set the cp_score for later comparisons.
+    if (fuzzy_filter && compl_leader.string != NULL && compl_leader.length > 0)
     {
-       compl->cp_in_match_array = FALSE;
-       // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
-       // set the cp_score for later comparisons.
-       if (fuzzy_filter && compl_leader.string != NULL && compl_leader.length 
> 0)
+       compl = compl_first_match;
+       do
+       {
            compl->cp_score = fuzzy_match_str(compl->cp_str.string, 
compl_leader.string);
+           compl = compl->cp_next;
+       } while (compl != NULL && !is_first_match(compl));
+    }
 
-       if (is_forward && !fuzzy_sort && compl->cp_cpt_source_idx != -1)
+    // Sort the linked list based on fuzzy score
+    if (fuzzy_sort && compl_leader.string != NULL && compl_leader.length > 0
+           && !is_first_match(compl_first_match->cp_next))
+    {
+       compl = compl_first_match->cp_prev;
+       ins_compl_make_linear();
+       if (is_forward)
        {
-           if (cur_source != compl->cp_cpt_source_idx)
-           {
-               cur_source = compl->cp_cpt_source_idx;
-               match_count = 1;
-               max_matches_found = FALSE;
-           }
-           else if (cpt_sources_array != NULL && !max_matches_found)
-           {
-               int max_matches = cpt_sources_array[cur_source].cs_max_matches;
-               if (max_matches > 0 && match_count > max_matches)
-                   max_matches_found = TRUE;
-           }
+           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, cp_get_prev, cp_set_prev, cp_compare_fuzzy);
+           compl_first_match->cp_next->cp_prev = compl_first_match;
+       }
+       else
+       {
+           compl->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, cp_compare_fuzzy);
+           compl_T     *tail = compl_first_match;
+           while (tail->cp_next != NULL)
+               tail = tail->cp_next;
+           tail->cp_next = compl;
+           compl->cp_prev = tail;
        }
+       (void)ins_compl_make_cyclic();
+    }
+
+    if (is_cpt_completion)
+    {
+       match_count = ALLOC_CLEAR_MULT(int, cpt_sources_count);
+       if (match_count == NULL)
+           return -1;
+    }
+
+    compl = compl_first_match;
+    do
+    {
+       compl->cp_in_match_array = FALSE;
 
        // Apply 'smartcase' behavior during normal mode
        if (ctrl_x_mode_normal() && !p_inf && compl_leader.string
@@ -1599,60 +1572,61 @@ ins_compl_build_pum(void)
            compl->cp_flags &= ~CP_ICASE;
 
        if (!match_at_original_text(compl)
-               && !max_matches_found
                && (compl_leader.string == NULL
                    || ins_compl_equal(compl, compl_leader.string,
                        (int)compl_leader.length)
                    || (fuzzy_filter && compl->cp_score > 0)))
        {
-           ++compl_match_arraysize;
-           compl->cp_in_match_array = TRUE;
-           if (match_head == NULL)
-               match_head = compl;
-           else
-               match_tail->cp_match_next = compl;
-           match_tail = compl;
-
-           if (!shown_match_ok && !fuzzy_filter)
+           // Limit number of items from each source if max_items is set.
+           int match_limit_exceeded = FALSE;
+           int cur_source = compl->cp_cpt_source_idx;
+           if (is_forward && cur_source != -1 && is_cpt_completion)
            {
-               if (compl == compl_shown_match || did_find_shown_match)
-               {
-                   // This item is the shown match or this is the
-                   // first displayed item after the shown match.
-                   compl_shown_match = compl;
-                   did_find_shown_match = TRUE;
-                   shown_match_ok = TRUE;
-               }
-               else
-                   // Remember this displayed match for when the
-                   // shown match is just below it.
-                   shown_compl = compl;
-               cur = i;
+               match_count[cur_source]++;
+               int max_matches = cpt_sources_array[cur_source].cs_max_matches;
+               if (max_matches > 0 && match_count[cur_source] > max_matches)
+                   match_limit_exceeded = TRUE;
            }
-           else if (fuzzy_filter)
+
+           if (!match_limit_exceeded)
            {
-               if (i == 0)
-                   shown_compl = compl;
-               // Update the maximum fuzzy score and the shown match
-               // if the current item's score is higher
-               if (fuzzy_sort && compl->cp_score > max_fuzzy_score
-                       && update_shown_match)
+               ++compl_match_arraysize;
+               compl->cp_in_match_array = TRUE;
+               if (match_head == NULL)
+                   match_head = compl;
+               else
+                   match_tail->cp_match_next = compl;
+               match_tail = compl;
+
+               if (!shown_match_ok && !fuzzy_filter)
                {
-                   did_find_shown_match = TRUE;
-                   max_fuzzy_score = compl->cp_score;
-                   if (!compl_no_select)
+                   if (compl == compl_shown_match || did_find_shown_match)
+                   {
+                       // This item is the shown match or this is the
+                       // first displayed item after the shown match.
                        compl_shown_match = compl;
+                       did_find_shown_match = TRUE;
+                       shown_match_ok = TRUE;
+                   }
+                   else
+                       // Remember this displayed match for when the
+                       // shown match is just below it.
+                       shown_compl = compl;
+                   cur = i;
                }
-
-               if (!shown_match_ok && compl == compl_shown_match)
+               else if (fuzzy_filter)
                {
-                   cur = i;
-                   shown_match_ok = TRUE;
+                   if (i == 0)
+                       shown_compl = compl;
+
+                   if (!shown_match_ok && compl == compl_shown_match)
+                   {
+                       cur = i;
+                       shown_match_ok = TRUE;
+                   }
                }
+               i++;
            }
-           if (is_forward && !fuzzy_sort && compl->cp_cpt_source_idx != -1)
-               match_count++;
-           i++;
        }
 
        if (compl == compl_shown_match && !fuzzy_filter)
@@ -1675,10 +1649,12 @@ ins_compl_build_pum(void)
        compl = compl->cp_next;
     } while (compl != NULL && !is_first_match(compl));
 
+    vim_free(match_count);
+
     if (compl_match_arraysize == 0)
        return -1;
 
-    if (fuzzy_filter && !fuzzy_sort && !compl_no_select && !shown_match_ok)
+    if (fuzzy_filter && !compl_no_select && !shown_match_ok)
     {
        compl_shown_match = shown_compl;
        shown_match_ok = TRUE;
@@ -1697,7 +1673,6 @@ ins_compl_build_pum(void)
                            ? compl->cp_text[CPT_ABBR] : compl->cp_str.string;
        compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND];
        compl_match_array[i].pum_info = compl->cp_text[CPT_INFO];
-       compl_match_array[i].pum_score = compl->cp_score;
        compl_match_array[i].pum_cpt_source_idx = compl->cp_cpt_source_idx;
        compl_match_array[i].pum_user_abbr_hlattr = compl->cp_user_abbr_hlattr;
        compl_match_array[i].pum_user_kind_hlattr = compl->cp_user_kind_hlattr;
@@ -1708,19 +1683,6 @@ ins_compl_build_pum(void)
        compl = match_next;
     }
 
-    if (fuzzy_sort && compl_leader.string != NULL && compl_leader.length > 0)
-    {
-       for (i = 0; i < compl_match_arraysize; i++)
-           compl_match_array[i].pum_idx = i;
-       // sort by the largest score of fuzzy match
-       qsort(compl_match_array, (size_t)compl_match_arraysize,
-                                      sizeof(pumitem_T), ins_compl_fuzzy_cmp);
-       shown_match_ok = TRUE;
-    }
-
-    if (fuzzy_sort && cpt_sources_array != NULL)
-       cur = trim_compl_match_array(cur); // Truncate by max_matches in 'cpt'
-
     if (!shown_match_ok)    // no displayed match at all
        cur = -1;
 
@@ -3888,7 +3850,6 @@ get_complete_info(list_T *what_list, dict_T *retdict)
 #define CI_WHAT_MATCHES                0x20
 #define CI_WHAT_ALL            0xff
     int                what_flag;
-    int                compl_fuzzy_match = (get_cot_flags() & COT_FUZZY) != 0;
 
     if (what_list == NULL)
        what_flag = CI_WHAT_ALL & ~(CI_WHAT_MATCHES | CI_WHAT_COMPLETED);
@@ -3965,7 +3926,7 @@ get_complete_info(list_T *what_list, dict_T *retdict)
                    if (compl_curr_match != NULL
                            && compl_curr_match->cp_number == match->cp_number)
                        selected_idx = list_idx;
-                   if (compl_fuzzy_match || match->cp_in_match_array)
+                   if (match->cp_in_match_array)
                        list_idx += 1;
                }
                match = match->cp_next;
@@ -5550,44 +5511,6 @@ ins_compl_show_filename(void)
     redraw_cmdline = FALSE;        // don't overwrite!
 }
 
-/*
- * Find a completion item when 'completeopt' contains "fuzzy".
- */
-    static compl_T *
-find_comp_when_fuzzy(void)
-{
-    int                score;
-    char_u*    str;
-    int                target_idx = -1;
-    int                is_forward = compl_shows_dir_forward();
-    int                is_backward = compl_shows_dir_backward();
-    compl_T    *comp = NULL;
-
-    if ((is_forward && compl_selected_item == compl_match_arraysize - 1)
-           || (is_backward && compl_selected_item == 0))
-       return match_at_original_text(compl_first_match)
-                           ? compl_first_match : compl_first_match->cp_prev;
-
-    if (is_forward)
-       target_idx = compl_selected_item + 1;
-    else if (is_backward)
-       target_idx = compl_selected_item == -1 ? compl_match_arraysize - 1
-                                               : compl_selected_item - 1;
-
-    score = compl_match_array[target_idx].pum_score;
-    str = compl_match_array[target_idx].pum_text;
-
-    comp = compl_first_match;
-    do
-    {
-       if (comp->cp_score == score && (str == comp->cp_str.string || str == 
comp->cp_text[CPT_ABBR]))
-           return comp;
-       comp = comp->cp_next;
-    } while (comp != NULL && !is_first_match(comp));
-
-    return NULL;
-}
-
 /*
  * Find the appropriate completion item when 'complete' ('cpt') includes
  * a 'max_matches' postfix. In this case, we search for a match where
@@ -5638,9 +5561,7 @@ find_next_completion_match(
     {
        if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL)
        {
-           if (compl_match_array != NULL && compl_fuzzy_match)
-               compl_shown_match = find_comp_when_fuzzy();
-           else if (cpt_sources_active)
+           if (cpt_sources_active)
                compl_shown_match = find_comp_when_cpt_sources();
            else
                compl_shown_match = compl_shown_match->cp_next;
@@ -5652,9 +5573,7 @@ find_next_completion_match(
                && compl_shown_match->cp_prev != NULL)
        {
            found_end = is_first_match(compl_shown_match);
-           if (compl_match_array != NULL && compl_fuzzy_match)
-               compl_shown_match = find_comp_when_fuzzy();
-           else if (cpt_sources_active)
+           if (cpt_sources_active)
                compl_shown_match = find_comp_when_cpt_sources();
            else
                compl_shown_match = compl_shown_match->cp_prev;
@@ -7006,7 +6925,6 @@ is_cpt_func_refresh_always(void)
 /*
  * Make the completion list non-cyclic.
  */
-#ifdef FEAT_COMPL_FUNC
     static void
 ins_compl_make_linear(void)
 {
@@ -7018,7 +6936,6 @@ ins_compl_make_linear(void)
     m->cp_next = NULL;
     compl_first_match->cp_prev = NULL;
 }
-#endif
 
 /*
  * Remove the matches linked to the current completion source (as indicated by
diff --git a/src/misc2.c b/src/misc2.c
index 11eb51bcf..9b8147cbb 100644
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -3202,3 +3202,127 @@ cmp_keyvalue_value_ni(const void *a, const void *b)
                    kv2->value.length));
 }
 
+/*
+ * Iterative merge sort for doubly linked list.
+ * O(NlogN) worst case, and stable.
+ *  - The list is divided into blocks of increasing size (1, 2, 4, 8, ...).
+ *  - Each pair of blocks is merged in sorted order.
+ *  - Merged blocks are reconnected to build the sorted list.
+ */
+    void *
+mergesort_list(
+    void       *head,
+    void       *(*get_next)(void *),
+    void       (*set_next)(void *, void *),
+    void       *(*get_prev)(void *),
+    void       (*set_prev)(void *, void *),
+    int                (*compare)(const void *, const void *))
+{
+    if (!head || !get_next(head))
+       return head;
+
+    // Count length
+    int            n = 0;
+    void*   curr = head;
+    while (curr)
+    {
+       n++;
+       curr = get_next(curr);
+    }
+
+    int        size;
+    for (size = 1; size < n; size *= 2)
+    {
+       void*   new_head = NULL;
+       void*   tail = NULL;
+       curr = head;
+
+       while (curr)
+       {
+           // Split two runs
+           void    *left = curr;
+           void    *right = left;
+           int     i;
+           for (i = 0; i < size && right; ++i)
+               right = get_next(right);
+
+           void    *next = right;
+           for (i = 0; i < size && next; ++i)
+               next = get_next(next);
+
+           // Break links
+           void    *l_end = right ? get_prev(right) : NULL;
+           if (l_end)
+               set_next(l_end, NULL);
+           if (right)
+               set_prev(right, NULL);
+
+           void    *r_end = next ? get_prev(next) : NULL;
+           if (r_end)
+               set_next(r_end, NULL);
+           if (next)
+               set_prev(next, NULL);
+
+           // Merge
+           void    *merged = NULL;
+           void    *merged_tail = NULL;
+
+           while (left || right)
+           {
+               void    *chosen = NULL;
+               if (!left)
+               {
+                   chosen = right;
+                   right = get_next(right);
+               }
+               else if (!right)
+               {
+                   chosen = left;
+                   left = get_next(left);
+               }
+               else if (compare(left, right) <= 0)
+               {
+                   chosen = left;
+                   left = get_next(left);
+               }
+               else
+               {
+                   chosen = right;
+                   right = get_next(right);
+               }
+
+               if (merged_tail)
+               {
+                   set_next(merged_tail, chosen);
+                   set_prev(chosen, merged_tail);
+                   merged_tail = chosen;
+               }
+               else
+               {
+                   merged = merged_tail = chosen;
+                   set_prev(chosen, NULL);
+               }
+           }
+
+           // Connect to full list
+           if (!new_head)
+               new_head = merged;
+           else
+           {
+               set_next(tail, merged);
+               set_prev(merged, tail);
+           }
+
+           // Move tail to end
+           while (get_next(merged_tail))
+               merged_tail = get_next(merged_tail);
+           tail = merged_tail;
+
+           curr = next;
+       }
+
+       head = new_head;
+    }
+
+    return head;
+}
diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro
index 7a5b36736..af779259a 100644
--- a/src/proto/misc2.pro
+++ b/src/proto/misc2.pro
@@ -65,4 +65,5 @@ int cmp_keyvalue_value(const void *a, const void *b);
 int cmp_keyvalue_value_n(const void *a, const void *b);
 int cmp_keyvalue_value_i(const void *a, const void *b);
 int cmp_keyvalue_value_ni(const void *a, const void *b);
+void *mergesort_list(void *head, void *(*get_next)(void *), void 
(*set_next)(void *, void *), void *(*get_prev)(void *), void (*set_prev)(void 
*, void *), int (*compare)(const void *, const void *));
 /* vim: set ft=c : */
diff --git a/src/structs.h b/src/structs.h
index 30e20c5a6..55181eb42 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4564,8 +4564,6 @@ typedef struct
     char_u     *pum_kind;              // extra kind text (may be truncated)
     char_u     *pum_extra;             // extra menu text (may be truncated)
     char_u     *pum_info;              // extra info
-    int                pum_score;              // fuzzy match score
-    int                pum_idx;                // index of item before sorting 
by score
     int                pum_cpt_source_idx;     // index of completion source 
in 'cpt'
     int                pum_user_abbr_hlattr;   // highlight attribute for abbr
     int                pum_user_kind_hlattr;   // highlight attribute for kind
diff --git a/src/testdir/test_ins_complete.vim 
b/src/testdir/test_ins_complete.vim
index 7d67e9f05..fcd2e6c6b 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -3466,7 +3466,7 @@ func Test_complete_opt_fuzzy()
   set cot+=noinsert
   call feedkeys("i\<C-R>=CompAnother()\<CR>f", 'tx')
   call assert_equal("for", g:abbr)
-  call assert_equal(2, g:selected)
+  call assert_equal(0, g:selected)
 
   set cot=menu,menuone,noselect,fuzzy
   call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>\<C-N>\<C-N>", 'tx')
@@ -3703,7 +3703,7 @@ func Test_cfc_with_longest()
   call assert_equal('hello', getline('.'))
 
   " continue search for new leader after insert common prefix
-  exe "normal ohellokate\<CR>h\<C-X>\<C-N>k\<C-y>\<esc>"
+  exe "normal ohellokate\<CR>h\<C-X>\<C-N>k\<C-N>\<C-y>\<esc>"
   call assert_equal('hellokate', getline('.'))
 
   bw!
@@ -4110,6 +4110,12 @@ func Test_complete_match_count()
   set cpt=.^1,w
   exe "normal! Gof\<c-n>\<c-r>=PrintMenuWords()\<cr>"
   call assert_equal('f{''matches'': [''fo''], ''selected'': -1}', getline(5))
+  " With non-matching items
+  %d
+  call setline(1, ["free", "freebar", "foo", "fobarbaz"])
+  set cpt=.^2,w
+  exe "normal! Gofo\<c-n>\<c-r>=PrintMenuWords()\<cr>"
+  call assert_equal('fo{''matches'': [''foo'', ''fobarbaz''], ''selected'': 
-1}', getline(5))
   set cot&
 
   func ComplFunc(findstart, base)
@@ -4199,16 +4205,21 @@ func Test_complete_match_count()
   bw!
 
   " Test 'fuzzy' with max_items
-  " XXX: Cannot use complete_info() since it is broken for 'fuzzy'
   new
   set completeopt=menu,noselect,fuzzy
   set complete=.
   call setline(1, ["abcd", "abac", "abdc"])
-  execute "normal Goa\<c-n>c\<c-n>"
+  exe "normal! Goa\<c-n>c\<c-r>=PrintMenuWords()\<cr>"
+  call assert_equal('ac{''matches'': [''abac'', ''abcd'', ''abdc''], 
''selected'': -1}', getline(4))
+  exe "normal! Sa\<c-n>c\<c-n>\<c-n>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+  call assert_equal('abac{''matches'': [''abac'', ''abcd'', ''abdc''], 
''selected'': 0}', getline(4))
+  execute "normal Sa\<c-n>c\<c-n>"
   call assert_equal('abac', getline(4))
   execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>"
   call assert_equal('abac', getline(4))
   set complete=.^1
+  exe "normal! Sa\<c-n>c\<c-n>\<c-n>\<c-p>\<c-r>=PrintMenuWords()\<cr>"
+  call assert_equal('abac{''matches'': [''abac''], ''selected'': 0}', 
getline(4))
   execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>"
   call assert_equal('abac', getline(4))
   set complete=.^2
diff --git a/src/version.c b/src/version.c
index bfbe09d0d..0117df0b6 100644
--- a/src/version.c
+++ b/src/version.c
@@ -709,6 +709,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1435,
 /**/
     1434,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1uNG3L-0020rT-6V%40256bit.org.

Raspunde prin e-mail lui