patch 9.1.1416: completion limits not respected for fuzzy completions

Commit: 
https://github.com/vim/vim/commit/19ef6b0b4b11a9775f9c90edc68c896034fd2a9d
Author: Girish Palya <giris...@gmail.com>
Date:   Wed May 28 20:28:21 2025 +0200

    patch 9.1.1416: completion limits not respected for fuzzy completions
    
    Problem:  completion limits not respected when using fuzzy completion
              (Maxim Kim)
    Solution: trim completion array (Girish Palya)
    
    fixes: #17379
    closes: #17386
    
    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 3592fdcaf..1de15c3ff 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -1438,6 +1438,71 @@ 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.
+ */
+    static void
+trim_compl_match_array(void)
+{
+    int                i, src_idx, limit, new_size = 0, *match_counts = NULL;
+    pumitem_T  *trimmed = NULL;
+    int                trimmed_idx = 0;
+
+    // Count current matches per source.
+    match_counts = ALLOC_CLEAR_MULT(int, cpt_sources_count);
+    if (match_counts == NULL)
+       return;
+    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].max_matches;
+       new_size += (limit > 0 && match_counts[i] > limit)
+           ? limit : match_counts[i];
+    }
+
+    if (new_size == compl_match_arraysize)
+       goto theend;
+
+    // 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].max_matches;
+           if (limit <= 0 || match_counts[src_idx] < limit)
+           {
+               trimmed[trimmed_idx++] = compl_match_array[i];
+               match_counts[src_idx]++;
+           }
+       }
+       else
+           trimmed[trimmed_idx++] = compl_match_array[i];
+    }
+    vim_free(compl_match_array);
+    compl_match_array = trimmed;
+    compl_match_arraysize = new_size;
+
+theend:
+    vim_free(match_counts);
+}
+
 /*
  * pumitem qsort compare func
  */
@@ -1477,7 +1542,7 @@ ins_compl_build_pum(void)
     int                match_count = 0;
     int                cur_source = -1;
     int                max_matches_found = FALSE;
-    int                is_forward = compl_shows_dir_forward() && !fuzzy_filter;
+    int                is_forward = compl_shows_dir_forward();
 
     // Need to build the popup menu list.
     compl_match_arraysize = 0;
@@ -1506,7 +1571,7 @@ ins_compl_build_pum(void)
        if (fuzzy_filter && compl_leader.string != NULL && compl_leader.length 
> 0)
            compl->cp_score = fuzzy_match_str(compl->cp_str.string, 
compl_leader.string);
 
-       if (is_forward && compl->cp_cpt_source_idx != -1)
+       if (is_forward && !fuzzy_sort && compl->cp_cpt_source_idx != -1)
        {
            if (cur_source != compl->cp_cpt_source_idx)
            {
@@ -1579,7 +1644,7 @@ ins_compl_build_pum(void)
                    shown_match_ok = TRUE;
                }
            }
-           if (is_forward && compl->cp_cpt_source_idx != -1)
+           if (is_forward && !fuzzy_sort && compl->cp_cpt_source_idx != -1)
                match_count++;
            i++;
        }
@@ -1627,6 +1692,7 @@ ins_compl_build_pum(void)
        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;
        compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU] != NULL
@@ -1646,6 +1712,9 @@ ins_compl_build_pum(void)
        shown_match_ok = TRUE;
     }
 
+    if (is_forward && fuzzy_sort && cpt_sources_array != NULL)
+       trim_compl_match_array(); // Truncate by max_matches in 'cpt'
+
     if (!shown_match_ok)    // no displayed match at all
        cur = -1;
 
diff --git a/src/structs.h b/src/structs.h
index 00b074643..30e20c5a6 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4566,6 +4566,7 @@ typedef struct
     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
 } pumitem_T;
diff --git a/src/testdir/test_ins_complete.vim 
b/src/testdir/test_ins_complete.vim
index ffe549ab5..4bb8b40b3 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -4078,7 +4078,7 @@ func Test_complete_multiline_marks()
 endfunc
 
 func Test_complete_match_count()
-  func PrintMenuWords()
+  func! PrintMenuWords()
     let info = complete_info(["selected", "matches"])
     call map(info.matches, {_, v -> v.word})
     return info
@@ -4198,8 +4198,50 @@ func Test_complete_match_count()
   call assert_equal(3, g:CallCount)
   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>"
+  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
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+  set complete=.^2
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+  set complete=.^3
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+  set complete=.^4
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+
+  func! ComplFunc(findstart, base)
+    if a:findstart
+      return col(".")
+    endif
+    return ["abcde", "abacr"]
+  endfunc
+
+  set complete=.,FComplFunc^1
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>"
+  call assert_equal('abacr', getline(4))
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+  set complete=.^1,FComplFunc^1
+  execute "normal Sa\<c-n>c\<c-n>\<c-n>\<c-n>\<c-n>"
+  call assert_equal('abac', getline(4))
+  bw!
+
   set completeopt& complete&
   delfunc PrintMenuWords
+  delfunc ComplFunc
+  delfunc CompleteItemsSelect
 endfunc
 
 func Test_complete_append_selected_match_default()
@@ -4321,7 +4363,7 @@ endfunc
 " Test 'nearest' flag of 'completeopt'
 func Test_nearest_cpt_option()
 
-  func PrintMenuWords()
+  func! PrintMenuWords()
     let info = complete_info(["selected", "matches"])
     call map(info.matches, {_, v -> v.word})
     return info
diff --git a/src/version.c b/src/version.c
index a788db1af..6941380a7 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 */
+/**/
+    1416,
 /**/
     1415,
 /**/

-- 
-- 
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/E1uKLlx-003cMy-6W%40256bit.org.

Raspunde prin e-mail lui