vim-patch:8.2.4760: using matchfuzzy() on a long list can take a while

Problem:    Using matchfuzzy() on a long list can take a while.
Solution:   Add a limit to the number of matches. (Yasuhiro Matsumoto,
            closes vim/vim#10189)
9029a6e993
This commit is contained in:
zeertzjq
2022-04-25 08:02:52 +08:00
parent af82eab946
commit e6974114fb
3 changed files with 43 additions and 8 deletions

View File

@@ -4975,7 +4975,7 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
If {list} is a list of dictionaries, then the optional {dict} If {list} is a list of dictionaries, then the optional {dict}
argument supports the following additional items: argument supports the following additional items:
key key of the item which is fuzzy matched against key Key of the item which is fuzzy matched against
{str}. The value of this item should be a {str}. The value of this item should be a
string. string.
text_cb |Funcref| that will be called for every item text_cb |Funcref| that will be called for every item
@@ -4983,6 +4983,8 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
This should accept a dictionary item as the This should accept a dictionary item as the
argument and return the text for that item to argument and return the text for that item to
use for fuzzy matching. use for fuzzy matching.
limit Maximum number of matches in {list} to be
returned. Zero means no limit.
{str} is treated as a literal string and regular expression {str} is treated as a literal string and regular expression
matching is NOT supported. The maximum supported {str} length matching is NOT supported. The maximum supported {str} length
@@ -4995,6 +4997,9 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
empty list is returned. If length of {str} is greater than empty list is returned. If length of {str} is greater than
256, then returns an empty list. 256, then returns an empty list.
When {limit} is given, matchfuzzy() will find up to this
number of matches in {list} and return them in sorted order.
Refer to |fuzzy-matching| for more information about fuzzy Refer to |fuzzy-matching| for more information about fuzzy
matching strings. matching strings.

View File

@@ -5095,7 +5095,8 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
/// matches for each item. /// matches for each item.
static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq, static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq,
const char_u *const key, Callback *const item_cb, const char_u *const key, Callback *const item_cb,
const bool retmatchpos, list_T *const fmatchlist) const bool retmatchpos, list_T *const fmatchlist,
const long max_matches)
FUNC_ATTR_NONNULL_ARG(2, 5, 7) FUNC_ATTR_NONNULL_ARG(2, 5, 7)
{ {
const long len = tv_list_len(items); const long len = tv_list_len(items);
@@ -5103,9 +5104,10 @@ static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bo
return; return;
} }
// TODO(vim): when using a limit use that instead of "len"
fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T)); fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T));
long i = 0; long i = 0;
bool found_match = false; long found_match = 0;
uint32_t matches[MAX_FUZZY_MATCHES]; uint32_t matches[MAX_FUZZY_MATCHES];
// For all the string items in items, get the fuzzy matching score // For all the string items in items, get the fuzzy matching score
@@ -5113,6 +5115,14 @@ static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bo
ptrs[i].idx = i; ptrs[i].idx = i;
ptrs[i].item = li; ptrs[i].item = li;
ptrs[i].score = SCORE_NONE; ptrs[i].score = SCORE_NONE;
// TODO(vim): instead of putting all items in ptrs[] should only add
// matching items there.
if (max_matches > 0 && found_match >= max_matches) {
i++;
continue;
}
char_u *itemstr = NULL; char_u *itemstr = NULL;
typval_T rettv; typval_T rettv;
rettv.v_type = VAR_UNKNOWN; rettv.v_type = VAR_UNKNOWN;
@@ -5159,13 +5169,13 @@ static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bo
} }
} }
ptrs[i].score = score; ptrs[i].score = score;
found_match = true; found_match++;
} }
i++; i++;
tv_clear(&rettv); tv_clear(&rettv);
}); });
if (found_match) { if (found_match > 0) {
// Sort the list by the descending order of the match score // Sort the list by the descending order of the match score
qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare); qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare);
@@ -5239,6 +5249,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
Callback cb = CALLBACK_NONE; Callback cb = CALLBACK_NONE;
const char_u *key = NULL; const char_u *key = NULL;
bool matchseq = false; bool matchseq = false;
long max_matches = 0;
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
emsg(_(e_dictreq)); emsg(_(e_dictreq));
@@ -5248,8 +5259,8 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
// To search a dict, either a callback function or a key can be // To search a dict, either a callback function or a key can be
// specified. // specified.
dict_T *const d = argvars[2].vval.v_dict; dict_T *const d = argvars[2].vval.v_dict;
const dictitem_T *const di = tv_dict_find(d, "key", -1); const dictitem_T *di;
if (di != NULL) { if ((di = tv_dict_find(d, "key", -1)) != NULL) {
if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL
|| *di->di_tv.vval.v_string == NUL) { || *di->di_tv.vval.v_string == NUL) {
semsg(_(e_invarg2), tv_get_string(&di->di_tv)); semsg(_(e_invarg2), tv_get_string(&di->di_tv));
@@ -5259,7 +5270,14 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
} else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) {
semsg(_(e_invargval), "text_cb"); semsg(_(e_invargval), "text_cb");
return; return;
} else if ((di = tv_dict_find(d, "limit", -1)) != NULL) {
if (di->di_tv.v_type != VAR_NUMBER) {
semsg(_(e_invarg2), tv_get_string(&di->di_tv));
return;
}
max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
} }
if (tv_dict_find(d, "matchseq", -1) != NULL) { if (tv_dict_find(d, "matchseq", -1) != NULL) {
matchseq = true; matchseq = true;
} }
@@ -5278,7 +5296,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
} }
fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key, fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key,
&cb, retmatchpos, rettv->vval.v_list); &cb, retmatchpos, rettv->vval.v_list, max_matches);
callback_free(&cb); callback_free(&cb);
} }

View File

@@ -245,4 +245,16 @@ func Test_matchfuzzypos_mbyte()
call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд')) call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
endfunc endfunc
" Test for matchfuzzy() with limit
func Test_matchfuzzy_limit()
let x = ['1', '2', '3', '2']
call assert_equal(['2', '2'], x->matchfuzzy('2'))
call assert_equal(['2', '2'], x->matchfuzzy('2', #{}))
call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 0}))
call assert_equal(['2'], x->matchfuzzy('2', #{limit: 1}))
call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 2}))
call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 3}))
call assert_fails("call matchfuzzy(x, '2', #{limit: '2'})", 'E475:')
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab