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:
committed by
Christian Brabandt
parent
5ba6e41d37
commit
7e0df5eee9
2
Filelist
2
Filelist
@ -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 \
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -113,6 +113,7 @@ SRC += \
|
||||
findfile.c \
|
||||
float.c \
|
||||
fold.c \
|
||||
fuzzy.c \
|
||||
getchar.c \
|
||||
gc.c \
|
||||
hardcopy.c \
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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 \
|
||||
|
||||
11
src/Makefile
11
src/Makefile
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
1141
src/fuzzy.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
11
src/proto/fuzzy.pro
Normal 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 : */
|
||||
@ -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 : */
|
||||
|
||||
@ -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
|
||||
|
||||
1198
src/search.c
1198
src/search.c
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -719,6 +719,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1627,
|
||||
/**/
|
||||
1626,
|
||||
/**/
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user