patch 9.1.1627: fuzzy matching can be improved

Problem:  fuzzy-matching can be improved
Solution: Implement a better fuzzy matching algorithm
          (Girish Palya)

Replace fuzzy matching algorithm with improved fzy-based implementation

The
[current](https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/)
fuzzy matching algorithm has several accuracy issues:

* It struggles with CamelCase
* It fails to prioritize matches at the beginning of strings, often
  ranking middle matches higher.

After evaluating alternatives (see my comments
[here](https://github.com/vim/vim/issues/17531#issuecomment-3112046897)
and
[here](https://github.com/vim/vim/issues/17531#issuecomment-3121593900)),
I chose to adopt the [fzy](https://github.com/jhawthorn/fzy) algorithm,
which:

* Resolves the aforementioned issues.
* Performs better.

Implementation details

This version is based on the original fzy
[algorithm](https://github.com/jhawthorn/fzy/blob/master/src/match.c),
with one key enhancement: **multibyte character support**.

* The original implementation supports only ASCII.
* This patch replaces ascii lookup tables with function calls, making it
  compatible with multibyte character sets.
* Core logic (`match_row()` and `match_positions()`) remains faithful to
  the original, but now operates on codepoints rather than single-byte
  characters.

Performance

Tested against a dataset of **90,000 Linux kernel filenames**. Results
(in milliseconds) show a **\~2x performance improvement** over the
current fuzzy matching algorithm.

```
Search String            Current Algo    FZY Algo
-------------------------------------------------
init                          131.759    66.916
main                          83.688     40.861
sig                           98.348     39.699
index                         109.222    30.738
ab                            72.222     44.357
cd                            83.036     54.739
a                             58.94      62.242
b                             43.612     43.442
c                             64.39      67.442
k                             40.585     36.371
z                             34.708     22.781
w                             38.033     30.109
cpa                           82.596     38.116
arz                           84.251     23.964
zzzz                          35.823     22.75
dimag                         110.686    29.646
xa                            43.188     29.199
nha                           73.953     31.001
nedax                         94.775     29.568
dbue                          79.846     25.902
fp                            46.826     31.641
tr                            90.951     55.883
kw                            38.875     23.194
rp                            101.575    55.775
kkkkkkkkkkkkkkkkkkkkkkkkkkkkk 48.519     30.921
```

```vim
vim9script

var haystack = readfile('/Users/gp/linux.files')

var needles = ['init', 'main', 'sig', 'index', 'ab', 'cd', 'a', 'b',
'c', 'k',
    'z', 'w', 'cpa', 'arz', 'zzzz', 'dimag', 'xa', 'nha', 'nedax',
'dbue',
    'fp', 'tr', 'kw', 'rp', 'kkkkkkkkkkkkkkkkkkkkkkkkkkkkk']
for needle in needles
    var start = reltime()
    var tmp = matchfuzzy(haystack, needle)
    echom $'{needle}' (start->reltime()->reltimefloat() * 1000)
endfor
```

Additional changes

* Removed the "camelcase" option from both matchfuzzy() and
  matchfuzzypos(), as it's now obsolete with the improved algorithm.

related: neovim/neovim#34101
fixes #17531
closes: #17900

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Girish Palya
2025-08-12 22:22:52 +02:00
committed by Christian Brabandt
parent 5ba6e41d37
commit 7e0df5eee9
25 changed files with 1289 additions and 1311 deletions

View File

@ -82,6 +82,7 @@ SRC_ALL = \
src/findfile.c \
src/float.c \
src/fold.c \
src/fuzzy.c \
src/getchar.c \
src/gc.c \
src/globals.h \
@ -291,6 +292,7 @@ SRC_ALL = \
src/proto/findfile.pro \
src/proto/float.pro \
src/proto/fold.pro \
src/proto/fuzzy.pro \
src/proto/getchar.pro \
src/proto/gc.pro \
src/proto/gui.pro \

View File

@ -1,4 +1,4 @@
*builtin.txt* For Vim version 9.1. Last change: 2025 Aug 10
*builtin.txt* For Vim version 9.1. Last change: 2025 Aug 12
VIM REFERENCE MANUAL by Bram Moolenaar
@ -7421,9 +7421,6 @@ 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

@ -1,4 +1,4 @@
*pattern.txt* For Vim version 9.1. Last change: 2025 Aug 06
*pattern.txt* For Vim version 9.1. Last change: 2025 Aug 12
VIM REFERENCE MANUAL by Bram Moolenaar
@ -1509,6 +1509,9 @@ characters in the search string. If the search string has multiple words, then
each word is matched separately. So the words in the search string can be
present in any order in a string.
Vim uses the same improved algorithm as the fzy project:
https://github.com/jhawthorn/fzy
Fuzzy matching assigns a score for each matched string based on the following
criteria:
- The number of sequentially matching characters.

View File

@ -41723,6 +41723,8 @@ Functions: ~
- Add the optional {opts} |Dict| argument to |getchar()| to control: cursor
behaviour, return type and whether or not to simplify the returned key
- |chdir()| allows to optionally specify a scope argument
- |matchfuzzy()| and |matchfuzzypos()| use an improved fuzzy matching
algorithm (same as fzy).
Others: ~
- the regex engines match correctly case-insensitive multi-byte characters

View File

@ -113,6 +113,7 @@ SRC += \
findfile.c \
float.c \
fold.c \
fuzzy.c \
getchar.c \
gc.c \
hardcopy.c \

View File

@ -823,6 +823,7 @@ OBJ = \
$(OUTDIR)/findfile.o \
$(OUTDIR)/float.o \
$(OUTDIR)/fold.o \
$(OUTDIR)/fuzzy.o \
$(OUTDIR)/getchar.o \
$(OUTDIR)/gc.o \
$(OUTDIR)/gui_xim.o \

View File

@ -732,6 +732,7 @@ OBJ = \
$(OUTDIR)\findfile.obj \
$(OUTDIR)\float.obj \
$(OUTDIR)\fold.obj \
$(OUTDIR)\fuzzy.obj \
$(OUTDIR)\getchar.obj \
$(OUTDIR)\gc.obj \
$(OUTDIR)\gui_xim.obj \
@ -1616,6 +1617,8 @@ $(OUTDIR)/float.obj: $(OUTDIR) float.c $(INCL)
$(OUTDIR)/fold.obj: $(OUTDIR) fold.c $(INCL)
$(OUTDIR)/fuzzy.obj: $(OUTDIR) fuzzy.c $(INCL)
$(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL)
$(OUTDIR)/gc.obj: $(OUTDIR) gc.c $(INCL)
@ -1961,6 +1964,7 @@ proto.h: \
proto/filepath.pro \
proto/findfile.pro \
proto/float.pro \
proto/fuzzy.pro \
proto/getchar.pro \
proto/gc.pro \
proto/gui_xim.pro \

View File

@ -529,6 +529,7 @@ SRC = \
findfile.c \
float.c \
fold.c \
fuzzy.c \
getchar.c \
gc.c \
gui_xim.c \
@ -665,6 +666,7 @@ OBJ = \
[.$(DEST)]findfile.obj \
[.$(DEST)]float.obj \
[.$(DEST)]fold.obj \
[.$(DEST)]fuzzy.obj \
[.$(DEST)]getchar.obj \
[.$(DEST)]gc.obj \
[.$(DEST)]gui_xim.obj \
@ -1141,6 +1143,10 @@ lua_env :
[.$(DEST)]fold.obj : fold.c vim.h [.$(DEST)]config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
[.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
[.$(DEST)]fuzzy.obj : fuzzy.c vim.h [.$(DEST)]config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \
gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
errors.h globals.h
[.$(DEST)]getchar.obj : getchar.c vim.h [.$(DEST)]config.h feature.h os_unix.h \
ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \
gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \

View File

@ -1523,6 +1523,7 @@ BASIC_SRC = \
findfile.c \
float.c \
fold.c \
fuzzy.c \
getchar.c \
gc.c \
gui_xim.c \
@ -1701,6 +1702,7 @@ OBJ_COMMON = \
objects/findfile.o \
objects/float.o \
objects/fold.o \
objects/fuzzy.o \
objects/getchar.o \
objects/gc.o \
objects/gui_xim.o \
@ -1886,6 +1888,7 @@ PRO_AUTO = \
findfile.pro \
float.pro \
fold.pro \
fuzzy.pro \
getchar.pro \
gc.pro \
gui_xim.pro \
@ -3309,6 +3312,9 @@ objects/float.o: float.c
objects/fold.o: fold.c
$(CCC) -o $@ fold.c
objects/fuzzy.o: fuzzy.c
$(CCC) -o $@ fuzzy.c
objects/getchar.o: getchar.c
$(CCC) -o $@ getchar.c
@ -3988,6 +3994,11 @@ objects/fold.o: fold.c vim.h protodef.h auto/config.h feature.h os_unix.h \
proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
globals.h errors.h
objects/fuzzy.o: fuzzy.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
globals.h errors.h
objects/getchar.o: getchar.c vim.h protodef.h auto/config.h feature.h os_unix.h \
auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \

View File

@ -48,6 +48,7 @@ fileio.c | reading and writing files
filepath.c | dealing with file names and paths
findfile.c | search for files in 'path'
fold.c | folding
fuzzy.c | fuzzy matching
getchar.c | getting characters and key mapping
gc.c | garbage collection
help.c | vim help related functions

View File

@ -2945,12 +2945,14 @@ ExpandBufnames(
{
p = NULL;
// first try matching with the short file name
if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0)
if ((score = fuzzy_match_str(buf->b_sfname, pat))
!= FUZZY_SCORE_NONE)
p = buf->b_sfname;
if (p == NULL)
{
// next try matching with the full path file name
if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0)
if ((score = fuzzy_match_str(buf->b_ffname, pat))
!= FUZZY_SCORE_NONE)
p = buf->b_ffname;
}
}

View File

@ -3612,7 +3612,7 @@ ExpandGenericExt(
else
{
score = fuzzy_match_str(str, pat);
match = (score != 0);
match = (score != FUZZY_SCORE_NONE);
}
}
else
@ -4022,7 +4022,7 @@ ExpandUserDefined(
else
{
score = fuzzy_match_str(s, pat);
match = (score != 0);
match = (score != FUZZY_SCORE_NONE);
}
}
else

1141
src/fuzzy.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -966,7 +966,7 @@ ins_compl_add(
// current match in the list of matches .
if (compl_first_match == NULL)
match->cp_next = match->cp_prev = NULL;
else if (cfc_has_mode() && score > 0 && compl_get_longest)
else if (cfc_has_mode() && score != FUZZY_SCORE_NONE && compl_get_longest)
{
current = compl_first_match->cp_next;
prev = compl_first_match;
@ -1193,7 +1193,8 @@ ins_compl_add_matches(
for (int i = 0; i < num_matches && add_r != FAIL; i++)
{
add_r = ins_compl_add(matches[i], -1, NULL, NULL, NULL, dir,
CP_FAST | (icase ? CP_ICASE : 0), FALSE, NULL, 0);
CP_FAST | (icase ? CP_ICASE : 0), FALSE, NULL,
FUZZY_SCORE_NONE);
if (add_r == OK)
// if dir was BACKWARD then honor it just once
dir = FORWARD;
@ -1430,7 +1431,7 @@ cp_compare_nearest(const void* a, const void* b)
{
int score_a = ((compl_T*)a)->cp_score;
int score_b = ((compl_T*)b)->cp_score;
if (score_a == 0 || score_b == 0)
if (score_a == FUZZY_SCORE_NONE || score_b == FUZZY_SCORE_NONE)
return 0;
return (score_a > score_b) ? 1 : (score_a < score_b) ? -1 : 0;
}
@ -1627,7 +1628,7 @@ ins_compl_build_pum(void)
&& (leader->string == NULL
|| ins_compl_equal(compl, leader->string,
(int)leader->length)
|| (fuzzy_filter && compl->cp_score > 0)))
|| (fuzzy_filter && compl->cp_score != FUZZY_SCORE_NONE)))
{
// Limit number of items from each source if max_items is set.
int match_limit_exceeded = FALSE;
@ -2001,7 +2002,7 @@ thesaurus_add_words_in_line(
if (wstart != skip_word)
{
status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic,
fname, dir, FALSE, 0);
fname, dir, FALSE, FUZZY_SCORE_NONE);
if (status == FAIL)
break;
}
@ -2072,7 +2073,7 @@ ins_compl_files(
: find_word_end(ptr);
add_r = ins_compl_add_infercase(regmatch->startp[0],
(int)(ptr - regmatch->startp[0]),
p_ic, files[i], *dir, FALSE, 0);
p_ic, files[i], *dir, FALSE, FUZZY_SCORE_NONE);
if (thesaurus)
{
// For a thesaurus, add all the words in the line
@ -3662,7 +3663,7 @@ ins_compl_add_tv(typval_T *tv, int dir, int fast)
return FAIL;
}
status = ins_compl_add(word, -1, NULL, cptext,
&user_data, dir, flags, dup, user_hl, 0);
&user_data, dir, flags, dup, user_hl, FUZZY_SCORE_NONE);
if (status != OK)
clear_tv(&user_data);
return status;
@ -3757,7 +3758,7 @@ set_completion(colnr_T startcol, list_T *list)
compl_orig_text.length = (size_t)compl_length;
if (ins_compl_add(compl_orig_text.string,
(int)compl_orig_text.length, NULL, NULL, NULL, 0,
flags | CP_FAST, FALSE, NULL, 0) != OK)
flags | CP_FAST, FALSE, NULL, FUZZY_SCORE_NONE) != OK)
return;
ctrl_x_mode = CTRL_X_EVAL;
@ -4747,7 +4748,7 @@ get_next_filename_completion(void)
{
ptr = matches[i];
score = fuzzy_match_str(ptr, leader);
if (score > 0)
if (score != FUZZY_SCORE_NONE)
{
if (ga_grow(&fuzzy_indices, 1) == OK)
{
@ -4959,7 +4960,7 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
int in_fuzzy_collect = (cfc_has_mode() && compl_length > 0)
|| ((get_cot_flags() & COT_FUZZY) && compl_autocomplete);
char_u *leader = ins_compl_leader();
int score = 0;
int score = FUZZY_SCORE_NONE;
int in_curbuf = st->ins_buf == curbuf;
// If 'infercase' is set, don't use 'smartcase' here
@ -5053,7 +5054,6 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
score = st->cur_match_pos->lnum - curwin->w_cursor.lnum;
if (score < 0)
score = -score;
score++;
}
if (ins_compl_add_infercase(ptr, len, p_ic,
@ -5159,7 +5159,7 @@ get_register_completion(void)
compl_orig_text.length) == 0))
{
if (ins_compl_add_infercase(str, str_len, p_ic, NULL,
dir, FALSE, 0) == OK)
dir, FALSE, FUZZY_SCORE_NONE) == OK)
dir = FORWARD;
}
}
@ -5198,7 +5198,7 @@ get_register_completion(void)
compl_orig_text.length) == 0)))
{
if (ins_compl_add_infercase(p, len, p_ic, NULL,
dir, FALSE, 0) == OK)
dir, FALSE, FUZZY_SCORE_NONE) == OK)
dir = FORWARD;
}
@ -5568,7 +5568,8 @@ ins_compl_get_exp(pos_T *ini)
// For `^P` completion, reset `compl_curr_match` to the head to avoid
// mixing matches from different sources.
if (!compl_dir_forward())
while (compl_curr_match->cp_prev)
while (compl_curr_match->cp_prev
&& !match_at_original_text(compl_curr_match->cp_prev))
compl_curr_match = compl_curr_match->cp_prev;
}
cpt_sources_index = -1;
@ -5966,7 +5967,8 @@ find_next_completion_match(
&& leader->string != NULL
&& !ins_compl_equal(compl_shown_match,
leader->string, (int)leader->length)
&& !(compl_fuzzy_match && compl_shown_match->cp_score > 0))
&& !(compl_fuzzy_match
&& compl_shown_match->cp_score != FUZZY_SCORE_NONE))
++todo;
else
// Remember a matching item.
@ -6947,7 +6949,7 @@ ins_compl_start(void)
if (compl_orig_text.string == NULL
|| ins_compl_add(compl_orig_text.string,
(int)compl_orig_text.length,
NULL, NULL, NULL, 0, flags, FALSE, NULL, 0) != OK)
NULL, NULL, NULL, 0, flags, FALSE, NULL, FUZZY_SCORE_NONE) != OK)
{
VIM_CLEAR_STRING(compl_pattern);
VIM_CLEAR_STRING(compl_orig_text);

View File

@ -1433,7 +1433,7 @@ ExpandMappings(
else
{
score = fuzzy_match_str(p, pat);
match = (score != 0);
match = (score != FUZZY_SCORE_NONE);
}
if (!match)
@ -1480,7 +1480,7 @@ ExpandMappings(
else
{
score = fuzzy_match_str(p, pat);
match = (score != 0);
match = (score != FUZZY_SCORE_NONE);
}
if (!match)

View File

@ -7956,7 +7956,7 @@ match_str(
int score;
score = fuzzy_match_str(str, fuzzystr);
if (score != 0)
if (score != FUZZY_SCORE_NONE)
{
if (!test_only)
{

View File

@ -188,6 +188,7 @@ void mbyte_im_set_active(int active_arg);
# if defined(FEAT_CRYPT) || defined(FEAT_PERSISTENT_UNDO)
# include "sha256.pro"
# endif
# include "fuzzy.pro"
# include "search.pro"
# ifdef FEAT_SIGNS
# include "sign.pro"

11
src/proto/fuzzy.pro Normal file
View File

@ -0,0 +1,11 @@
/* fuzzy.c */
int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u *matches, int maxMatches);
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);
garray_T *fuzzy_match_str_with_pos(char_u *str, char_u *pat);
int fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos, int *score);
int search_for_fuzzy_match(buf_T *buf, pos_T *pos, char_u *pattern, int dir, pos_T *start_pos, int *len, char_u **ptr, int *score);
void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count);
int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort);
/* vim: set ft=c : */

View File

@ -37,13 +37,4 @@ 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 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);
garray_T *fuzzy_match_str_with_pos(char_u *str, char_u *pat);
int fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos, int *score);
int search_for_fuzzy_match(buf_T *buf, pos_T *pos, char_u *pattern, int dir, pos_T *start_pos, int *len, char_u **ptr, int *score);
void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count);
int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort);
/* vim: set ft=c : */

View File

@ -6429,8 +6429,8 @@ vgr_match_buflines(
long lnum;
colnr_T col;
int pat_len = (int)STRLEN(spat);
if (pat_len > MAX_FUZZY_MATCHES)
pat_len = MAX_FUZZY_MATCHES;
if (pat_len > FUZZY_MATCH_MAX_LEN)
pat_len = FUZZY_MATCH_MAX_LEN;
for (lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; ++lnum)
{
@ -6483,13 +6483,13 @@ vgr_match_buflines(
char_u *str = ml_get_buf(buf, lnum, FALSE);
colnr_T linelen = ml_get_buf_len(buf, lnum);
int score;
int_u matches[MAX_FUZZY_MATCHES];
int_u matches[FUZZY_MATCH_MAX_LEN];
int_u sz = ARRAY_LENGTH(matches);
// Fuzzy string match
CLEAR_FIELD(matches);
while (fuzzy_match(str + col, spat, FALSE, &score,
matches, sz, TRUE) > 0)
matches, sz) > 0)
{
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the

File diff suppressed because it is too large Load Diff

View File

@ -3224,7 +3224,7 @@ func Test_fuzzy_completion_bufname_fullpath()
call assert_equal('"b CmdStateFile', @:)
set wildoptions=fuzzy
call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_match('Xcmd/Xstate/Xfile.js$', @:)
call assert_equal('"b CmdStateFile', @:)
cd -
set wildoptions&
endfunc
@ -3502,7 +3502,7 @@ func Test_fuzzy_completion_mapname()
nmap <Plug>state :
nmap <Plug>FendingOff :
call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:)
call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>fendoff <Plug>goformat", @:)
nunmap <Plug>format
nunmap <Plug>goformat
nunmap <Plug>TestFOrmat
@ -3674,7 +3674,7 @@ func Test_fuzzy_completion_cmd_sort_results()
command T123FendingOff :
set wildoptions=fuzzy
call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:)
call assert_equal('"T123format T123TestFOrmat T123FendingOff T123fendoff T123goformat', @:)
delcommand T123format
delcommand T123goformat
delcommand T123TestFOrmat

View File

@ -14,11 +14,11 @@ func Test_matchfuzzy()
call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra'))
call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa'))
call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one'))
call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
call assert_equal(['oneTwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo'))
call assert_equal([], matchfuzzy(['onetwo', 'one_two'], 'oneTwo'))
call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa'))
call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len())
call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
call assert_equal([repeat('a', 300)], matchfuzzy([repeat('a', 300)], repeat('a', 257)))
" full match has highest score
call assert_equal(['Cursor', 'lCursor'], matchfuzzy(["hello", "lCursor", "Cursor"], "Cursor"))
" matches with same score should not be reordered
@ -26,8 +26,7 @@ func Test_matchfuzzy()
call assert_equal(l, l->matchfuzzy('abc'))
" Tests for match preferences
" preference for camel case match
call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
call assert_equal(['onetwo', 'oneTwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo'))
" preference for match after a separator (_ or space)
call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo'))
" preference for leading letter match
@ -43,7 +42,7 @@ func Test_matchfuzzy()
" gap penalty
call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab'))
" path separator vs word separator
call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
call assert_equal(['color/setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim', 'color\\setup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim'))
" match multiple words (separated by space)
call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo'))
@ -85,15 +84,16 @@ func Test_matchfuzzy()
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}))
\ matchfuzzy(['Cursor', 'lCursor', 'shCurlyIn', 'shCurlyError', 'TracesCursor', 'CurSearch', 'CursorLine'], 'Cur'))
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')
\ matchfuzzy(['things','sThings', 'thisThings'], 'thin'))
call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['mytestcase', 'MyTestCase'], 'mtc'))
call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['MyTestCase', 'mytestcase'], 'mtc'))
call assert_equal(['MyTest'], matchfuzzy(['Mytest', 'mytest', 'MyTest'], 'MyT'))
call assert_equal(['CamelCaseMatchIngAlg'],
\ matchfuzzy(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'], 'CamelCase'))
call assert_equal(['SomeWord', 'PrefixSomeWord'], matchfuzzy(['PrefixSomeWord', 'SomeWord'], 'somewor'))
" Test in latin1 encoding
let save_enc = &encoding
@ -104,50 +104,57 @@ endfunc
" Test for the matchfuzzypos() function
func Test_matchfuzzypos()
call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'curl'], 'rl'))
call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
call assert_equal([['curl', 'world'], [[2,3], [2,3]], [990, 985]], matchfuzzypos(['world', 'curl'], 'rl'))
call assert_equal([['curl', 'world'], [[2,3], [2,3]], [990, 985]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
call assert_equal([['hello', 'hello world hello world'],
\ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [500, 382]],
\ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [2147483647, 4810]],
\ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
call assert_equal([['aaaaaaa'], [[0, 1, 2]], [266]], matchfuzzypos(['aaaaaaa'], 'aaa'))
call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0]], [137]], matchfuzzypos(['a b'], ' a '))
call assert_equal([['aaaaaaa'], [[0, 1, 2]], [2880]], matchfuzzypos(['aaaaaaa'], 'aaa'))
call assert_equal([['a b'], [[0, 3]], [1670]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0, 3]], [1670]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0]], [885]], matchfuzzypos(['a b'], ' a '))
call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
call assert_equal(range(256), x[1][0])
call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
" fuzzy matches limited to 1024 chars
let matches = matchfuzzypos([repeat('a', 1030)], repeat('a', 1025))
call assert_equal([repeat('a', 1030)], matches[0])
call assert_equal(1024, len(matches[1][0]))
call assert_equal(1023, matches[1][0][1023])
call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
call assert_equal([[], [], []], matchfuzzypos(['abc'], ''))
" match in a long string
call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [155]],
call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [500]],
\ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
" preference for camel case match
call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [269]], matchfuzzypos(['xabcxxaBc'], 'abc'))
call assert_equal([['xabcxxaBc'], [[7, 8]], [1665]], matchfuzzypos(['xabcxxaBc'], 'bc'))
" preference for match after a separator (_ or space)
call assert_equal([['xabx_ab'], [[5, 6]], [195]], matchfuzzypos(['xabx_ab'], 'ab'))
call assert_equal([['xabx_ab'], [[5, 6]], [1775]], matchfuzzypos(['xabx_ab'], 'ab'))
" preference for leading letter match
call assert_equal([['abcxabc'], [[0, 1]], [200]], matchfuzzypos(['abcxabc'], 'ab'))
call assert_equal([['abcxabc'], [[0, 1]], [1875]], matchfuzzypos(['abcxabc'], 'ab'))
" preference for sequential match
call assert_equal([['aobncedone'], [[7, 8, 9]], [233]], matchfuzzypos(['aobncedone'], 'one'))
call assert_equal([['aobncedone'], [[7, 8, 9]], [1965]], matchfuzzypos(['aobncedone'], 'one'))
" best recursive match
call assert_equal([['xoone'], [[2, 3, 4]], [243]], matchfuzzypos(['xoone'], 'one'))
call assert_equal([['xoone'], [[2, 3, 4]], [1990]], matchfuzzypos(['xoone'], 'one'))
" match multiple words (separated by space)
call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [5620]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1}))
call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz'))
call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [476]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [5620]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz'))
call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 9, 10]], [6660]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1}))
call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [1057]], ['grace']->matchfuzzypos('race ace grace'))
call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [2147483647]], ['grace']->matchfuzzypos('race ace grace'))
let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]],
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [2885]],
\ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]],
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [2885]],
\ matchfuzzypos(l, 'cam', {'key' : 'val'}))
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
@ -164,30 +171,17 @@ func Test_matchfuzzypos()
call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:')
" case match
call assert_equal([['Match', 'match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['match', 'Match'], 'Ma'))
call assert_equal([['match', 'Match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['Match', 'match'], 'ma'))
" CamelCase has high weight even case match
call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['mytestcase', 'MyTestCase'], 'mtc'))
call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['MyTestCase', 'mytestcase'], 'mtc'))
call assert_equal(['MyTest', 'Mytest', 'mytest', ],matchfuzzy(['Mytest', 'mytest', 'MyTest'], 'MyT'))
call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'],
\ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelCase'))
call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'],
\ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelcaseM'))
call assert_equal([['Match'], [[0, 1]], [1885]], matchfuzzypos(['match', 'Match'], 'Ma'))
call assert_equal([['match', 'Match'], [[0, 1], [0, 1]], [1885, 1885]], matchfuzzypos(['Match', 'match'], 'ma'))
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]],
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]], [2885, 2870, 2865, 2680, 2670, 2655, 2655]],
\ 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')
call assert_equal([['things', 'sThings', 'thisThings'], [[0, 1, 2, 3], [1, 2, 3, 4], [0, 1, 6, 7]], [3890, 3685, 3670]],
\ matchfuzzypos(['things','sThings', 'thisThings'], 'thin'))
endfunc
" Test for matchfuzzy() with multibyte characters
@ -205,9 +199,14 @@ func Test_matchfuzzy_mbyte()
call assert_equal(['세 마리의 작은 돼지'], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('돼지 마리의'))
call assert_equal([], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('파란 하늘'))
" preference for camel case match
" camel case match
call assert_equal(['oneąwo', 'oneĄwo'],
\ ['oneĄwo', 'oneąwo']->matchfuzzy('oneąwo'))
call assert_equal(['oneĄwo'],
\ ['oneĄwo', 'oneąwo']->matchfuzzy('Ąwo'))
" prefer camelcase when searched first character matches upper case
call assert_equal(['oneĄwo', 'oneąwo'],
\ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo'))
\ ['oneĄwo', 'oneąwo']->matchfuzzy('ąw'))
" preference for complete match then match after separator (_ or space)
call assert_equal(['Ⅱabㄟㄠ'] + sort(['Ⅱa_bㄟㄠ', 'Ⅱa bㄟㄠ']),
\ ['Ⅱabㄟㄠ', 'Ⅱa bㄟㄠ', 'Ⅱa_bㄟㄠ']->matchfuzzy('Ⅱabㄟㄠ'))
@ -231,41 +230,39 @@ endfunc
" Test for matchfuzzypos() with multibyte characters
func Test_matchfuzzypos_mbyte()
CheckFeature multi_lang
call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]],
call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [4890]],
\ matchfuzzypos(['こんにちは世界'], 'こんにちは'))
call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [-20]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
" reverse the order of characters
call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [252, 143]],
call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [2885, 670]],
\ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
\ [[0, 1], [0, 1], [0, 1], [0, 2]], [176, 173, 170, 135]],
\ [[0, 1], [0, 1], [0, 1], [0, 2]], [1880, 1865, 1850, 890]],
\ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
call assert_equal([['ααααααα'], [[0, 1, 2]], [216]],
call assert_equal([['ααααααα'], [[0, 1, 2]], [2880]],
\ matchfuzzypos(['ααααααα'], 'ααα'))
call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl'))
let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
call assert_equal(range(256), x[1][0])
call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
call assert_equal([repeat('✓', 300)], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257))[0])
" match multiple words (separated by space)
call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [4515]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘'))
" match in a long string
call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [105]],
call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [500]],
\ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
" preference for camel case match
call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [219]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ'))
" preference for match after a separator (_ or space)
call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
call assert_equal([['xちだx_ちだ'], [[5, 6]], [1775]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
" preference for leading letter match
call assert_equal([[ѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos([ѵҁxѳѵҁ'], 'ѳѵ'))
call assert_equal([[ѵҁxѳѵҁ'], [[0, 1]], [1875]], matchfuzzypos([ѵҁxѳѵҁ'], 'ѳѵ'))
" preference for sequential match
call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [1965]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
" best recursive match
call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
call assert_equal([['xффйд'], [[2, 3, 4]], [1990]], matchfuzzypos(['xффйд'], 'фйд'))
endfunc
" Test for matchfuzzy() with limit

View File

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

View File

@ -3036,8 +3036,9 @@ long elapsed(DWORD start_tick);
#define EVAL_VAR_IMPORT 4 // may return special variable for import
#define EVAL_VAR_NO_FUNC 8 // do not look for a function
// Maximum number of characters that can be fuzzy matched
#define MAX_FUZZY_MATCHES 256
// Fuzzy matching
#define FUZZY_MATCH_MAX_LEN 1024 // max characters that can be matched
#define FUZZY_SCORE_NONE INT_MIN // invalid fuzzy score
// flags for equal_type()
#define ETYPE_ARG_UNKNOWN 1