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

Commit: 
https://github.com/vim/vim/commit/28e40a7b55ce471656cccc2260c11a29d5da447e
Author: glepnir <glephun...@gmail.com>
Date:   Sun Mar 16 21:24:22 2025 +0100

    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 <glephun...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index b7df7c561..3456093d2 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -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:
diff --git a/src/proto/search.pro b/src/proto/search.pro
index e9be4533a..1927738d7 100644
--- a/src/proto/search.pro
+++ b/src/proto/search.pro
@@ -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);
diff --git a/src/quickfix.c b/src/quickfix.c
index 1008dd5ab..e498d5eba 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -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
diff --git a/src/search.c b/src/search.c
index 064ed3e1e..c7ae4f8e2 100644
--- a/src/search.c
+++ b/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 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);
diff --git a/src/testdir/test_matchfuzzy.vim b/src/testdir/test_matchfuzzy.vim
index cba08446b..5280f2fa3 100644
--- a/src/testdir/test_matchfuzzy.vim
+++ b/src/testdir/test_matchfuzzy.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index b69d34d26..b633dc847 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1214,
 /**/
     1213,
 /**/

-- 
-- 
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/E1ttucY-00EdJQ-Do%40256bit.org.

Raspunde prin e-mail lui