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.