patch 9.1.1416: completion limits not respected for fuzzy completions
Commit:
https://github.com/vim/vim/commit/19ef6b0b4b11a9775f9c90edc68c896034fd2a9d
Author: Girish Palya <[email protected]>
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 <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
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 [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/vim_dev/E1uKLlx-003cMy-6W%40256bit.org.