1
0
forked from aniani/vim

patch 9.1.1214: matchfuzzy() can be improved for camel case matches

Problem:  When searching for "Cur", CamelCase matches like "lCursor" score
          higher than exact prefix matches like Cursor, which is
          counter-intuitive (Maxim Kim).
Solution: Add a 'camelcase' option to matchfuzzy() that lets users disable
          CamelCase bonuses when needed, making prefix matches rank higher.
          (glepnir)

fixes: #16504
closes: #16797

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
glepnir
2025-03-16 21:24:22 +01:00
committed by Christian Brabandt
parent 250739d442
commit 28e40a7b55
6 changed files with 53 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.1. Last change: 2025 Mar 11
*builtin.txt* For Vim version 9.1. Last change: 2025 Mar 16
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -7275,6 +7275,9 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
given sequence.
limit Maximum number of matches in {list} to be
returned. Zero means no limit.
camelcase Use enhanced camel case scoring making results
better suited for completion related to
programming languages. Default is v:true
If {list} is a list of dictionaries, then the optional {dict}
argument supports the following additional items:

View File

@@ -37,7 +37,7 @@ void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_com
spat_T *get_spat(int idx);
int get_spat_last_idx(void);
void f_searchcount(typval_T *argvars, typval_T *rettv);
int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u *matches, int maxMatches);
int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u *matches, int maxMatches, int camelcase);
void f_matchfuzzy(typval_T *argvars, typval_T *rettv);
void f_matchfuzzypos(typval_T *argvars, typval_T *rettv);
int fuzzy_match_str(char_u *str, char_u *pat);

View File

@@ -6246,7 +6246,8 @@ vgr_match_buflines(
// Fuzzy string match
CLEAR_FIELD(matches);
while (fuzzy_match(str + col, spat, FALSE, &score, matches, sz) > 0)
while (fuzzy_match(str + col, spat, FALSE, &score,
matches, sz, TRUE) > 0)
{
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the

View File

@@ -42,11 +42,11 @@ static void find_mps_values(int *initc, int *findc, int *backwards, int switchit
static int is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction);
static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, size_t msgbuflen, int recompute, int maxcount, long timeout);
static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
static int fuzzy_match_compute_score(char_u *fuzpat, char_u *str, int strSz, int_u *matches, int numMatches);
static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, int *outScore, char_u *strBegin, int strLen, int_u *srcMatches, int_u *matches, int maxMatches, int nextMatch, int *recursionCount);
static int fuzzy_match_compute_score(char_u *fuzpat, char_u *str, int strSz, int_u *matches, int numMatches, int camelcase);
static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, int *outScore, char_u *strBegin, int strLen, int_u *srcMatches, int_u *matches, int maxMatches, int nextMatch, int *recursionCount, int camelcase);
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
static int fuzzy_match_item_compare(const void *s1, const void *s2);
static void fuzzy_match_in_list(list_T *l, char_u *str, int matchseq, char_u *key, callback_T *item_cb, int retmatchpos, list_T *fmatchlist, long max_matches);
static void fuzzy_match_in_list(list_T *l, char_u *str, int matchseq, char_u *key, callback_T *item_cb, int retmatchpos, list_T *fmatchlist, long max_matches, int camelcase);
static void do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos);
#endif
static int fuzzy_match_str_compare(const void *s1, const void *s2);
@@ -4388,7 +4388,8 @@ fuzzy_match_compute_score(
char_u *str,
int strSz,
int_u *matches,
int numMatches)
int numMatches,
int camelcase)
{
int score;
int penalty;
@@ -4461,7 +4462,7 @@ fuzzy_match_compute_score(
}
// Enhanced camel case scoring
if (vim_islower(neighbor) && vim_isupper(curr))
if (camelcase && vim_islower(neighbor) && vim_isupper(curr))
{
score += CAMEL_BONUS * 2; // Double the camel case bonus
is_camel = TRUE;
@@ -4544,7 +4545,8 @@ fuzzy_match_recursive(
int_u *matches,
int maxMatches,
int nextMatch,
int *recursionCount)
int *recursionCount,
int camelcase)
{
// Recursion params
int recursiveMatch = FALSE;
@@ -4596,7 +4598,7 @@ fuzzy_match_recursive(
&recursiveScore, strBegin, strLen, matches,
recursiveMatches,
ARRAY_LENGTH(recursiveMatches),
nextMatch, recursionCount))
nextMatch, recursionCount, camelcase))
{
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore)
@@ -4628,7 +4630,7 @@ fuzzy_match_recursive(
// Calculate score
if (matched)
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches,
nextMatch);
nextMatch, camelcase);
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
@@ -4666,7 +4668,8 @@ fuzzy_match(
int matchseq,
int *outScore,
int_u *matches,
int maxMatches)
int maxMatches,
int camelcase)
{
int recursionCount = 0;
int len = MB_CHARLEN(str);
@@ -4714,7 +4717,7 @@ fuzzy_match(
recursionCount = 0;
matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
matches + numMatches, maxMatches - numMatches,
0, &recursionCount);
0, &recursionCount, camelcase);
if (matchCount == 0)
{
numMatches = 0;
@@ -4775,7 +4778,8 @@ fuzzy_match_in_list(
callback_T *item_cb,
int retmatchpos,
list_T *fmatchlist,
long max_matches)
long max_matches,
int camelcase)
{
long len;
fuzzyItem_T *items;
@@ -4836,7 +4840,7 @@ fuzzy_match_in_list(
if (itemstr != NULL
&& fuzzy_match(itemstr, str, matchseq, &score, matches,
MAX_FUZZY_MATCHES))
MAX_FUZZY_MATCHES, camelcase))
{
items[match_count].idx = match_count;
items[match_count].item = li;
@@ -4955,6 +4959,7 @@ do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
int ret;
int matchseq = FALSE;
long max_matches = 0;
int camelcase = TRUE;
if (in_vim9script()
&& (check_for_list_arg(argvars, 0) == FAIL
@@ -5020,6 +5025,16 @@ do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
}
if ((di = dict_find(d, (char_u *)"camelcase", -1)) != NULL)
{
if (di->di_tv.v_type != VAR_BOOL)
{
semsg(_(e_invalid_argument_str), "camelcase");
return;
}
camelcase = tv_get_bool_chk(&di->di_tv, NULL);
}
if (dict_has_key(d, "matchseq"))
matchseq = TRUE;
}
@@ -5063,7 +5078,8 @@ do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
}
fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches);
matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches,
camelcase);
done:
free_callback(&cb);
@@ -5166,7 +5182,7 @@ fuzzy_match_str(char_u *str, char_u *pat)
return 0;
fuzzy_match(str, pat, TRUE, &score, matchpos,
sizeof(matchpos) / sizeof(matchpos[0]));
sizeof(matchpos) / sizeof(matchpos[0]), TRUE);
return score;
}
@@ -5193,7 +5209,7 @@ fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED)
return NULL;
ga_init2(match_positions, sizeof(int_u), 10);
if (!fuzzy_match(str, pat, FALSE, &score, matches, MAX_FUZZY_MATCHES)
if (!fuzzy_match(str, pat, FALSE, &score, matches, MAX_FUZZY_MATCHES, TRUE)
|| score == 0)
{
ga_clear(match_positions);

View File

@@ -87,6 +87,10 @@ func Test_matchfuzzy()
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
" camcelcase
call assert_equal(['Cursor', 'CurSearch', 'CursorLine', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor'], matchfuzzy(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur', {"camelcase": v:false}))
call assert_equal(['lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'Cursor', 'CurSearch', 'CursorLine'], matchfuzzy(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur'))
" Test in latin1 encoding
let save_enc = &encoding
set encoding=latin1
@@ -168,6 +172,15 @@ func Test_matchfuzzypos()
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
"camelcase
call assert_equal([['lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'Cursor', 'CurSearch', 'CursorLine'], [[1, 2, 3], [2, 3, 4], [2, 3, 4], [6, 7, 8], [0, 1, 2], [0, 1, 2], [0, 1, 2]], [318, 311, 308, 303, 267, 264, 263]],
\ matchfuzzypos(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur'))
call assert_equal([['Cursor', 'CurSearch', 'CursorLine', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor'], [[0, 1, 2], [0, 1, 2], [0, 1, 2], [1, 2, 3], [2, 3, 4], [2, 3, 4], [6, 7, 8]], [267, 264, 263, 246, 239, 236, 231]],
\ matchfuzzypos(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur', {"camelcase": v:false}))
call assert_equal([['things', 'sThings', 'thisThings'], [[0, 1, 2, 3], [1, 2, 3, 4], [0, 1, 2, 7]], [333, 287, 279]],
\ matchfuzzypos(['things','sThings', 'thisThings'], 'thin', {'camelcase': v:false}))
endfunc
" Test for matchfuzzy() with multibyte characters

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1214,
/**/
1213,
/**/