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:
committed by
Christian Brabandt
parent
250739d442
commit
28e40a7b55
@@ -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
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -7275,6 +7275,9 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
|
|||||||
given sequence.
|
given sequence.
|
||||||
limit Maximum number of matches in {list} to be
|
limit Maximum number of matches in {list} to be
|
||||||
returned. Zero means no limit.
|
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}
|
If {list} is a list of dictionaries, then the optional {dict}
|
||||||
argument supports the following additional items:
|
argument supports the following additional items:
|
||||||
|
@@ -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);
|
spat_T *get_spat(int idx);
|
||||||
int get_spat_last_idx(void);
|
int get_spat_last_idx(void);
|
||||||
void f_searchcount(typval_T *argvars, typval_T *rettv);
|
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_matchfuzzy(typval_T *argvars, typval_T *rettv);
|
||||||
void f_matchfuzzypos(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);
|
int fuzzy_match_str(char_u *str, char_u *pat);
|
||||||
|
@@ -6246,7 +6246,8 @@ vgr_match_buflines(
|
|||||||
|
|
||||||
// Fuzzy string match
|
// Fuzzy string match
|
||||||
CLEAR_FIELD(matches);
|
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
|
// Pass the buffer number so that it gets used even for a
|
||||||
// dummy buffer, unless duplicate_name is set, then the
|
// dummy buffer, unless duplicate_name is set, then the
|
||||||
|
46
src/search.c
46
src/search.c
@@ -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 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 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 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_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);
|
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)
|
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
|
||||||
static int fuzzy_match_item_compare(const void *s1, const void *s2);
|
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);
|
static void do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos);
|
||||||
#endif
|
#endif
|
||||||
static int fuzzy_match_str_compare(const void *s1, const void *s2);
|
static int fuzzy_match_str_compare(const void *s1, const void *s2);
|
||||||
@@ -4388,7 +4388,8 @@ fuzzy_match_compute_score(
|
|||||||
char_u *str,
|
char_u *str,
|
||||||
int strSz,
|
int strSz,
|
||||||
int_u *matches,
|
int_u *matches,
|
||||||
int numMatches)
|
int numMatches,
|
||||||
|
int camelcase)
|
||||||
{
|
{
|
||||||
int score;
|
int score;
|
||||||
int penalty;
|
int penalty;
|
||||||
@@ -4461,7 +4462,7 @@ fuzzy_match_compute_score(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced camel case scoring
|
// 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
|
score += CAMEL_BONUS * 2; // Double the camel case bonus
|
||||||
is_camel = TRUE;
|
is_camel = TRUE;
|
||||||
@@ -4544,7 +4545,8 @@ fuzzy_match_recursive(
|
|||||||
int_u *matches,
|
int_u *matches,
|
||||||
int maxMatches,
|
int maxMatches,
|
||||||
int nextMatch,
|
int nextMatch,
|
||||||
int *recursionCount)
|
int *recursionCount,
|
||||||
|
int camelcase)
|
||||||
{
|
{
|
||||||
// Recursion params
|
// Recursion params
|
||||||
int recursiveMatch = FALSE;
|
int recursiveMatch = FALSE;
|
||||||
@@ -4596,7 +4598,7 @@ fuzzy_match_recursive(
|
|||||||
&recursiveScore, strBegin, strLen, matches,
|
&recursiveScore, strBegin, strLen, matches,
|
||||||
recursiveMatches,
|
recursiveMatches,
|
||||||
ARRAY_LENGTH(recursiveMatches),
|
ARRAY_LENGTH(recursiveMatches),
|
||||||
nextMatch, recursionCount))
|
nextMatch, recursionCount, camelcase))
|
||||||
{
|
{
|
||||||
// Pick best recursive score
|
// Pick best recursive score
|
||||||
if (!recursiveMatch || recursiveScore > bestRecursiveScore)
|
if (!recursiveMatch || recursiveScore > bestRecursiveScore)
|
||||||
@@ -4628,7 +4630,7 @@ fuzzy_match_recursive(
|
|||||||
// Calculate score
|
// Calculate score
|
||||||
if (matched)
|
if (matched)
|
||||||
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches,
|
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches,
|
||||||
nextMatch);
|
nextMatch, camelcase);
|
||||||
|
|
||||||
// Return best result
|
// Return best result
|
||||||
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
|
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
|
||||||
@@ -4666,7 +4668,8 @@ fuzzy_match(
|
|||||||
int matchseq,
|
int matchseq,
|
||||||
int *outScore,
|
int *outScore,
|
||||||
int_u *matches,
|
int_u *matches,
|
||||||
int maxMatches)
|
int maxMatches,
|
||||||
|
int camelcase)
|
||||||
{
|
{
|
||||||
int recursionCount = 0;
|
int recursionCount = 0;
|
||||||
int len = MB_CHARLEN(str);
|
int len = MB_CHARLEN(str);
|
||||||
@@ -4714,7 +4717,7 @@ fuzzy_match(
|
|||||||
recursionCount = 0;
|
recursionCount = 0;
|
||||||
matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
|
matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
|
||||||
matches + numMatches, maxMatches - numMatches,
|
matches + numMatches, maxMatches - numMatches,
|
||||||
0, &recursionCount);
|
0, &recursionCount, camelcase);
|
||||||
if (matchCount == 0)
|
if (matchCount == 0)
|
||||||
{
|
{
|
||||||
numMatches = 0;
|
numMatches = 0;
|
||||||
@@ -4775,7 +4778,8 @@ fuzzy_match_in_list(
|
|||||||
callback_T *item_cb,
|
callback_T *item_cb,
|
||||||
int retmatchpos,
|
int retmatchpos,
|
||||||
list_T *fmatchlist,
|
list_T *fmatchlist,
|
||||||
long max_matches)
|
long max_matches,
|
||||||
|
int camelcase)
|
||||||
{
|
{
|
||||||
long len;
|
long len;
|
||||||
fuzzyItem_T *items;
|
fuzzyItem_T *items;
|
||||||
@@ -4836,7 +4840,7 @@ fuzzy_match_in_list(
|
|||||||
|
|
||||||
if (itemstr != NULL
|
if (itemstr != NULL
|
||||||
&& fuzzy_match(itemstr, str, matchseq, &score, matches,
|
&& fuzzy_match(itemstr, str, matchseq, &score, matches,
|
||||||
MAX_FUZZY_MATCHES))
|
MAX_FUZZY_MATCHES, camelcase))
|
||||||
{
|
{
|
||||||
items[match_count].idx = match_count;
|
items[match_count].idx = match_count;
|
||||||
items[match_count].item = li;
|
items[match_count].item = li;
|
||||||
@@ -4955,6 +4959,7 @@ do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
|
|||||||
int ret;
|
int ret;
|
||||||
int matchseq = FALSE;
|
int matchseq = FALSE;
|
||||||
long max_matches = 0;
|
long max_matches = 0;
|
||||||
|
int camelcase = TRUE;
|
||||||
|
|
||||||
if (in_vim9script()
|
if (in_vim9script()
|
||||||
&& (check_for_list_arg(argvars, 0) == FAIL
|
&& (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);
|
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"))
|
if (dict_has_key(d, "matchseq"))
|
||||||
matchseq = TRUE;
|
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]),
|
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:
|
done:
|
||||||
free_callback(&cb);
|
free_callback(&cb);
|
||||||
@@ -5166,7 +5182,7 @@ fuzzy_match_str(char_u *str, char_u *pat)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fuzzy_match(str, pat, TRUE, &score, matchpos,
|
fuzzy_match(str, pat, TRUE, &score, matchpos,
|
||||||
sizeof(matchpos) / sizeof(matchpos[0]));
|
sizeof(matchpos) / sizeof(matchpos[0]), TRUE);
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
@@ -5193,7 +5209,7 @@ fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED)
|
|||||||
return NULL;
|
return NULL;
|
||||||
ga_init2(match_positions, sizeof(int_u), 10);
|
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)
|
|| score == 0)
|
||||||
{
|
{
|
||||||
ga_clear(match_positions);
|
ga_clear(match_positions);
|
||||||
|
@@ -87,6 +87,10 @@ func Test_matchfuzzy()
|
|||||||
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
|
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
|
||||||
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:')
|
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
|
" Test in latin1 encoding
|
||||||
let save_enc = &encoding
|
let save_enc = &encoding
|
||||||
set encoding=latin1
|
set encoding=latin1
|
||||||
@@ -168,6 +172,15 @@ func Test_matchfuzzypos()
|
|||||||
|
|
||||||
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
|
let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}]
|
||||||
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:')
|
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
|
endfunc
|
||||||
|
|
||||||
" Test for matchfuzzy() with multibyte characters
|
" Test for matchfuzzy() with multibyte characters
|
||||||
|
@@ -704,6 +704,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1214,
|
||||||
/**/
|
/**/
|
||||||
1213,
|
1213,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user