Merge pull request #32953 from glepnir/vim-9.1.1214

vim-patch:9.1.{1214,1217,1219}: matchfuzzy() "camelcase"
This commit is contained in:
zeertzjq
2025-03-27 08:54:32 +08:00
committed by GitHub
6 changed files with 59 additions and 15 deletions

View File

@ -6410,6 +6410,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. Defaults to v:true.
If {list} is a list of dictionaries, then the optional {dict}
argument supports the following additional items:

View File

@ -5815,6 +5815,9 @@ function vim.fn.matchend(expr, pat, start, count) end
--- 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. Defaults to v:true.
---
--- If {list} is a list of dictionaries, then the optional {dict}
--- argument supports the following additional items:

View File

@ -7150,6 +7150,9 @@ M.funcs = {
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. Defaults to v:true.
If {list} is a list of dictionaries, then the optional {dict}
argument supports the following additional items:

View File

@ -5356,7 +5356,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
// Fuzzy string match
CLEAR_FIELD(matches);
while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz, true) > 0) {
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.

View File

@ -2996,7 +2996,7 @@ typedef struct {
/// are in "matches".
static int fuzzy_match_compute_score(const char *const fuzpat, const char *const str,
const int strSz, const uint32_t *const matches,
const int numMatches)
const int numMatches, bool camelcase)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
assert(numMatches > 0); // suppress clang "result of operation is garbage"
@ -3055,7 +3055,7 @@ static int fuzzy_match_compute_score(const char *const fuzpat, const char *const
curr = utf_ptr2char(p);
// Enhanced camel case scoring
if (mb_islower(neighbor) && mb_isupper(curr)) {
if (camelcase && mb_islower(neighbor) && mb_isupper(curr)) {
score += CAMEL_BONUS * 2; // Double the camel case bonus
is_camel = true;
consecutive_camel++;
@ -3114,7 +3114,8 @@ static int fuzzy_match_compute_score(const char *const fuzpat, const char *const
static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t strIdx,
int *const outScore, const char *const strBegin, const int strLen,
const uint32_t *const srcMatches, uint32_t *const matches,
const int maxMatches, int nextMatch, int *const recursionCount)
const int maxMatches, int nextMatch, int *const recursionCount,
bool camelcase)
FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT
{
// Recursion params
@ -3161,7 +3162,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen,
matches, recursiveMatches,
sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch,
recursionCount)) {
recursionCount, camelcase)) {
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
memcpy(bestRecursiveMatches, recursiveMatches,
@ -3184,7 +3185,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
// Calculate score
if (matched) {
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches, nextMatch);
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches, nextMatch, camelcase);
}
// Return best result
@ -3213,7 +3214,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
/// @return true if "pat_arg" matches "str". Also returns the match score in
/// "outScore" and the matching character positions in "matches".
bool fuzzy_match(char *const str, const char *const pat_arg, const bool matchseq,
int *const outScore, uint32_t *const matches, const int maxMatches)
int *const outScore, uint32_t *const matches, const int maxMatches, bool camelcase)
FUNC_ATTR_NONNULL_ALL
{
const int len = mb_charlen(str);
@ -3251,7 +3252,7 @@ bool fuzzy_match(char *const str, const char *const pat_arg, const bool matchseq
const int matchCount
= fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
matches + numMatches,
maxMatches - numMatches, 0, &recursionCount);
maxMatches - numMatches, 0, &recursionCount, camelcase);
if (matchCount == 0) {
numMatches = 0;
break;
@ -3301,7 +3302,7 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq,
const char *const key, Callback *const item_cb,
const bool retmatchpos, list_T *const fmatchlist,
const int max_matches)
const int max_matches, bool camelcase)
FUNC_ATTR_NONNULL_ARG(2, 5, 7)
{
int len = tv_list_len(l);
@ -3352,7 +3353,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
int score;
if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches,
MAX_FUZZY_MATCHES)) {
MAX_FUZZY_MATCHES, camelcase)) {
items[match_count].idx = (int)match_count;
items[match_count].item = li;
items[match_count].score = score;
@ -3452,6 +3453,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
const char *key = NULL;
bool matchseq = false;
int max_matches = 0;
bool camelcase = true;
if (argvars[2].v_type != VAR_UNKNOWN) {
if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
@ -3464,7 +3466,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
if ((di = tv_dict_find(d, "key", -1)) != NULL) {
if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL
|| *di->di_tv.vval.v_string == NUL) {
semsg(_(e_invarg2), tv_get_string(&di->di_tv));
semsg(_(e_invargNval), "key", tv_get_string(&di->di_tv));
return;
}
key = tv_get_string(&di->di_tv);
@ -3475,12 +3477,20 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
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));
semsg(_(e_invargval), "limit");
return;
}
max_matches = (int)tv_get_number_chk(&di->di_tv, NULL);
}
if ((di = tv_dict_find(d, "camelcase", -1)) != NULL) {
if (di->di_tv.v_type != VAR_BOOL) {
semsg(_(e_invargval), "camelcase");
return;
}
camelcase = tv_get_bool_chk(&di->di_tv, NULL);
}
if (tv_dict_find(d, "matchseq", -1) != NULL) {
matchseq = true;
}
@ -3500,7 +3510,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
fuzzy_match_in_list(argvars[0].vval.v_list,
(char *)tv_get_string(&argvars[1]), matchseq, key,
&cb, retmatchpos, rettv->vval.v_list, max_matches);
&cb, retmatchpos, rettv->vval.v_list, max_matches, camelcase);
callback_free(&cb);
}
@ -3584,7 +3594,7 @@ int fuzzy_match_str(char *const str, const char *const pat)
int score = 0;
uint32_t matchpos[MAX_FUZZY_MATCHES];
fuzzy_match(str, pat, true, &score, matchpos, sizeof(matchpos) / sizeof(matchpos[0]));
fuzzy_match(str, pat, true, &score, matchpos, sizeof(matchpos) / sizeof(matchpos[0]), true);
return score;
}
@ -3602,7 +3612,7 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat)
unsigned matches[MAX_FUZZY_MATCHES];
int score = 0;
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);
xfree(match_positions);

View File

@ -78,6 +78,7 @@ func Test_matchfuzzy()
" Nvim's callback implementation is different, so E6000 is expected instead,
" call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:')
call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:')
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 123})", 'E475: Invalid value for argument key: 123')
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:')
call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E1297:')
call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:')
@ -90,6 +91,17 @@ 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:')
" camelcase
call assert_equal(['lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'Cursor', 'CurSearch', 'CursorLine'],
\ matchfuzzy(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur'))
call assert_equal(['lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'Cursor', 'CurSearch', 'CursorLine'],
\ matchfuzzy(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur', {"camelcase": v:true}))
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(['things', 'sThings', 'thisThings'],
\ matchfuzzy(['things','sThings', 'thisThings'], 'thin', {'camelcase': v:false}))
call assert_fails("let x = matchfuzzy([], 'foo', {'camelcase': []})", 'E475: Invalid value for argument camelcase')
" Test in latin1 encoding
let save_enc = &encoding
" Nvim supports utf-8 encoding only
@ -155,6 +167,7 @@ func Test_matchfuzzypos()
" Nvim's callback implementation is different, so E6000 is expected instead,
" call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 123})", 'E475: Invalid value for argument key: 123')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E1297:')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:')
@ -175,6 +188,17 @@ 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([['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', {"camelcase": v:true}))
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}))
call assert_fails("let x = matchfuzzypos([], 'foo', {'camelcase': []})", 'E475: Invalid value for argument camelcase')
endfunc
" Test for matchfuzzy() with multibyte characters
@ -265,6 +289,7 @@ func Test_matchfuzzy_limit()
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:')
call assert_fails("call matchfuzzy(x, '2', #{limit: []})", 'E475:')
let l = [{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}]
call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}}))