1
0
forked from aniani/vim

patch 8.2.2280: fuzzy matching doesn't give access to the scores

Problem:    Fuzzy matching doesn't give access to the scores.
Solution:   Return the scores with a third list. (Yegappan Lakshmanan,
            closes #7596)
This commit is contained in:
Bram Moolenaar 2021-01-02 18:31:32 +01:00
parent 508b5618ec
commit 9d19e4f4ba
4 changed files with 79 additions and 58 deletions

View File

@ -7471,24 +7471,24 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()* matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()*
Same as |matchfuzzy()|, but returns the list of matched Same as |matchfuzzy()|, but returns the list of matched
strings and the list of character positions where characters strings, the list of character positions where characters
in {str} matches. You can use |byteidx()|to convert a in {str} matches and a list of matching scores. You can
character position to a byte position. use |byteidx()|to convert a character position to a byte
position.
If {str} matches multiple times in a string, then only the If {str} matches multiple times in a string, then only the
positions for the best match is returned. positions for the best match is returned.
If there are no matching strings or there is an error, then a If there are no matching strings or there is an error, then a
list with two empty list items is returned. list with three empty list items is returned.
Example: > Example: >
:echo matchfuzzypos(['testing'], 'tsg') :echo matchfuzzypos(['testing'], 'tsg')
< results in [['testing'], [[0, 2, 6]]] > < results in [['testing'], [[0, 2, 6]], [99]] >
:echo matchfuzzypos(['clay', 'lacy'], 'la') :echo matchfuzzypos(['clay', 'lacy'], 'la')
< results in [['lacy', 'clay'], [[0, 1], [1, 2]]] > < results in [['lacy', 'clay'], [[0, 1], [1, 2]], [153, 133]] >
:echo [{'text': 'hello', 'id' : 10}]->matchfuzzypos('ll', {'key' : 'text'}) :echo [{'text': 'hello', 'id' : 10}]->matchfuzzypos('ll', {'key' : 'text'})
< results in [{'id': 10, 'text': 'hello'}] [[2, 3]] < results in [[{'id': 10, 'text': 'hello'}], [[2, 3]], [127]]
matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()* matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()*
Same as |match()|, but return a |List|. The first item in the Same as |match()|, but return a |List|. The first item in the

View File

@ -4723,10 +4723,10 @@ fuzzy_match_in_list(
// For matchfuzzy(), return a list of matched strings. // For matchfuzzy(), return a list of matched strings.
// ['str1', 'str2', 'str3'] // ['str1', 'str2', 'str3']
// For matchfuzzypos(), return a list with two items. // For matchfuzzypos(), return a list with three items.
// The first item is a list of matched strings. The second item // The first item is a list of matched strings. The second item
// is a list of lists where each list item is a list of matched // is a list of lists where each list item is a list of matched
// character positions. // character positions. The third item is a list of matching scores.
// [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
if (retmatchpos) if (retmatchpos)
{ {
@ -4749,7 +4749,7 @@ fuzzy_match_in_list(
// next copy the list of matching positions // next copy the list of matching positions
if (retmatchpos) if (retmatchpos)
{ {
li = list_find(fmatchlist, -1); li = list_find(fmatchlist, -2);
if (li == NULL || li->li_tv.vval.v_list == NULL) if (li == NULL || li->li_tv.vval.v_list == NULL)
goto done; goto done;
l = li->li_tv.vval.v_list; l = li->li_tv.vval.v_list;
@ -4762,6 +4762,19 @@ fuzzy_match_in_list(
list_append_list(l, ptrs[i].lmatchpos) == FAIL) list_append_list(l, ptrs[i].lmatchpos) == FAIL)
goto done; goto done;
} }
// copy the matching scores
li = list_find(fmatchlist, -1);
if (li == NULL || li->li_tv.vval.v_list == NULL)
goto done;
l = li->li_tv.vval.v_list;
for (i = 0; i < len; i++)
{
if (ptrs[i].score == SCORE_NONE)
break;
if (list_append_number(l, ptrs[i].score) == FAIL)
goto done;
}
} }
} }
@ -4842,9 +4855,15 @@ do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
{ {
list_T *l; list_T *l;
// For matchfuzzypos(), a list with two items are returned. First item // For matchfuzzypos(), a list with three items are returned. First
// is a list of matching strings and the second item is a list of // item is a list of matching strings, the second item is a list of
// lists with matching positions within each string. // lists with matching positions within each string and the third item
// is the list of scores of the matches.
l = list_alloc();
if (l == NULL)
goto done;
if (list_append_list(rettv->vval.v_list, l) == FAIL)
goto done;
l = list_alloc(); l = list_alloc();
if (l == NULL) if (l == NULL)
goto done; goto done;

View File

@ -93,55 +93,55 @@ endfunc
" Test for the matchfuzzypos() function " Test for the matchfuzzypos() function
func Test_matchfuzzypos() func Test_matchfuzzypos()
call assert_equal([['curl', 'world'], [[2,3], [2,3]]], matchfuzzypos(['world', 'curl'], 'rl')) call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl'))
call assert_equal([['curl', 'world'], [[2,3], [2,3]]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl'))
call assert_equal([['hello', 'hello world hello world'], call assert_equal([['hello', 'hello world hello world'],
\ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]], \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]],
\ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello'))
call assert_equal([['aaaaaaa'], [[0, 1, 2]]], matchfuzzypos(['aaaaaaa'], 'aaa')) call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa'))
call assert_equal([['a b'], [[0, 3]]], matchfuzzypos(['a b'], 'a b')) call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0, 3]]], matchfuzzypos(['a b'], 'a b')) call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b'))
call assert_equal([['a b'], [[0]]], matchfuzzypos(['a b'], ' a ')) call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a '))
call assert_equal([[], []], matchfuzzypos(['a b'], ' ')) call assert_equal([[], [], []], matchfuzzypos(['a b'], ' '))
call assert_equal([[], []], matchfuzzypos(['world', 'curl'], 'ab')) call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab'))
let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256)) let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256))
call assert_equal(range(256), x[1][0]) call assert_equal(range(256), x[1][0])
call assert_equal([[], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257))) call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257)))
call assert_equal([[], []], matchfuzzypos([], 'abc')) call assert_equal([[], [], []], matchfuzzypos([], 'abc'))
" match in a long string " match in a long string
call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]]], call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]],
\ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc'))
" preference for camel case match " preference for camel case match
call assert_equal([['xabcxxaBc'], [[6, 7, 8]]], matchfuzzypos(['xabcxxaBc'], 'abc')) call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc'))
" preference for match after a separator (_ or space) " preference for match after a separator (_ or space)
call assert_equal([['xabx_ab'], [[5, 6]]], matchfuzzypos(['xabx_ab'], 'ab')) call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab'))
" preference for leading letter match " preference for leading letter match
call assert_equal([['abcxabc'], [[0, 1]]], matchfuzzypos(['abcxabc'], 'ab')) call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab'))
" preference for sequential match " preference for sequential match
call assert_equal([['aobncedone'], [[7, 8, 9]]], matchfuzzypos(['aobncedone'], 'one')) call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one'))
" best recursive match " best recursive match
call assert_equal([['xoone'], [[2, 3, 4]]], matchfuzzypos(['xoone'], 'one')) call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one'))
" match multiple words (separated by space) " match multiple words (separated by space)
call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo'))
call assert_equal([[], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two')) call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two'))
call assert_equal([[], []], ['foo bar']->matchfuzzypos(" \t ")) call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t "))
call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]]], ['grace']->matchfuzzypos('race ace grace')) call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace'))
let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}]
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]]], call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
\ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}}))
call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]]], call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]],
\ matchfuzzypos(l, 'cam', {'key' : 'val'})) \ matchfuzzypos(l, 'cam', {'key' : 'val'}))
call assert_equal([[], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}}))
call assert_equal([[], []], matchfuzzypos(l, 'day', {'key' : 'val'})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'}))
call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:') call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:')
call assert_equal([[], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}}))
call assert_equal([[], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}}))
call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:')
call assert_equal([[], []], matchfuzzypos(l, 'cam')) call assert_equal([[], [], []], matchfuzzypos(l, 'cam'))
call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:')
call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:')
call assert_fails("let x = matchfuzzypos(l, 'cam', test_null_dict())", 'E715:') call assert_fails("let x = matchfuzzypos(l, 'cam', test_null_dict())", 'E715:')
@ -193,41 +193,41 @@ endfunc
" Test for matchfuzzypos() with multibyte characters " Test for matchfuzzypos() with multibyte characters
func Test_matchfuzzypos_mbyte() func Test_matchfuzzypos_mbyte()
CheckFeature multi_lang CheckFeature multi_lang
call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]]], call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]],
\ matchfuzzypos(['こんにちは世界'], 'こんにちは')) \ matchfuzzypos(['こんにちは世界'], 'こんにちは'))
call assert_equal([['ンヹㄇヺヴ'], [[1, 3]]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ'))
" reverse the order of characters " reverse the order of characters
call assert_equal([[], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ'))
call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]]], call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]],
\ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ'))
call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'],
\ [[0, 1], [0, 1], [0, 1], [0, 2]]], \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]],
\ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ'))
call assert_equal([['ααααααα'], [[0, 1, 2]]], call assert_equal([['ααααααα'], [[0, 1, 2]], [191]],
\ matchfuzzypos(['ααααααα'], 'ααα')) \ matchfuzzypos(['ααααααα'], 'ααα'))
call assert_equal([[], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl')) call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl'))
let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256)) let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256))
call assert_equal(range(256), x[1][0]) call assert_equal(range(256), x[1][0])
call assert_equal([[], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257))) call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257)))
" match multiple words (separated by space) " match multiple words (separated by space)
call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의')) call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의'))
call assert_equal([[], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘'))
" match in a long string " match in a long string
call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]]], call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]],
\ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ'))
" preference for camel case match " preference for camel case match
call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ'))
" preference for match after a separator (_ or space) " preference for match after a separator (_ or space)
call assert_equal([['xちだx_ちだ'], [[5, 6]]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ'))
" preference for leading letter match " preference for leading letter match
call assert_equal([[ѵҁxѳѵҁ'], [[0, 1]]], matchfuzzypos([ѵҁxѳѵҁ'], 'ѳѵ')) call assert_equal([[ѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos([ѵҁxѳѵҁ'], 'ѳѵ'))
" preference for sequential match " preference for sequential match
call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ')) call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ'))
" best recursive match " best recursive match
call assert_equal([['xффйд'], [[2, 3, 4]]], matchfuzzypos(['xффйд'], 'фйд')) call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд'))
endfunc endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

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