To: vim_dev@googlegroups.com Subject: Patch 8.2.2280 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2280 Problem: Fuzzy matching doesn't give access to the scores. Solution: Return the scores with a third list. (Yegappan Lakshmanan, closes #7596) Files: runtime/doc/eval.txt, src/search.c, src/testdir/test_matchfuzzy.vim *** ../vim-8.2.2279/runtime/doc/eval.txt 2020-12-28 12:56:54.179617297 +0100 --- runtime/doc/eval.txt 2021-01-02 18:26:56.028148817 +0100 *************** *** 7344,7350 **** matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()* ! If {list} is a list of strings, then returns a list with all the strings in {list} that fuzzy match {str}. The strings in the returned list are sorted based on the matching score. --- 7414,7420 ---- matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()* ! If {list} is a list of strings, then returns a |List| with all the strings in {list} that fuzzy match {str}. The strings in the returned list are sorted based on the matching score. *************** *** 7401,7422 **** matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()* Same as |matchfuzzy()|, but returns the list of matched ! strings and the list of character positions where characters ! in {str} matches. If {str} matches multiple times in a string, then only the positions for the best match is returned. If there are no matching strings or there is an error, then a ! list with two empty list items is returned. Example: > :echo matchfuzzypos(['testing'], 'tsg') ! < results in [['testing'], [[0, 2, 6]]] > :echo matchfuzzypos(['clay', 'lacy'], 'la') ! < results in [['lacy', 'clay'], [[0, 1], [1, 2]]] > :echo [{'text': 'hello', 'id' : 10}]->matchfuzzypos('ll', {'key' : 'text'}) ! < results in [{'id': 10, 'text': 'hello'}] [[2, 3]] matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()* Same as |match()|, but return a |List|. The first item in the --- 7471,7494 ---- matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()* Same as |matchfuzzy()|, but returns the list of matched ! strings, the list of character positions where characters ! in {str} matches and a list of matching scores. You can ! use |byteidx()|to convert a character position to a byte ! position. If {str} matches multiple times in a string, then only the positions for the best match is returned. If there are no matching strings or there is an error, then a ! list with three empty list items is returned. Example: > :echo matchfuzzypos(['testing'], 'tsg') ! < results in [['testing'], [[0, 2, 6]], [99]] > :echo matchfuzzypos(['clay', 'lacy'], 'la') ! < results in [['lacy', 'clay'], [[0, 1], [1, 2]], [153, 133]] > :echo [{'text': 'hello', 'id' : 10}]->matchfuzzypos('ll', {'key' : 'text'}) ! < results in [[{'id': 10, 'text': 'hello'}], [[2, 3]], [127]] matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()* Same as |match()|, but return a |List|. The first item in the *** ../vim-8.2.2279/src/search.c 2020-12-21 19:59:04.569197722 +0100 --- src/search.c 2021-01-02 18:26:56.028148817 +0100 *************** *** 4723,4732 **** // For matchfuzzy(), return a list of matched strings. // ['str1', 'str2', 'str3'] ! // For matchfuzzypos(), return a list with two items. // 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 ! // character positions. // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] if (retmatchpos) { --- 4723,4732 ---- // For matchfuzzy(), return a list of matched strings. // ['str1', 'str2', 'str3'] ! // For matchfuzzypos(), return a list with three items. // 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 ! // character positions. The third item is a list of matching scores. // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] if (retmatchpos) { *************** *** 4749,4755 **** // next copy the list of matching positions if (retmatchpos) { ! li = list_find(fmatchlist, -1); if (li == NULL || li->li_tv.vval.v_list == NULL) goto done; l = li->li_tv.vval.v_list; --- 4749,4755 ---- // next copy the list of matching positions if (retmatchpos) { ! li = list_find(fmatchlist, -2); if (li == NULL || li->li_tv.vval.v_list == NULL) goto done; l = li->li_tv.vval.v_list; *************** *** 4762,4767 **** --- 4762,4780 ---- list_append_list(l, ptrs[i].lmatchpos) == FAIL) 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,4850 **** { list_T *l; ! // For matchfuzzypos(), a list with two items are returned. First item ! // is a list of matching strings and the second item is a list of ! // lists with matching positions within each string. l = list_alloc(); if (l == NULL) goto done; --- 4855,4869 ---- { list_T *l; ! // For matchfuzzypos(), a list with three items are returned. First ! // item is a list of matching strings, the second item is a list of ! // 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(); if (l == NULL) goto done; *** ../vim-8.2.2279/src/testdir/test_matchfuzzy.vim 2020-10-29 18:57:57.791880416 +0100 --- src/testdir/test_matchfuzzy.vim 2021-01-02 18:26:56.028148817 +0100 *************** *** 93,147 **** " Test for the matchfuzzypos() function 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]]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) call assert_equal([['hello', 'hello world hello world'], ! \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]], \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) ! call assert_equal([['aaaaaaa'], [[0, 1, 2]]], matchfuzzypos(['aaaaaaa'], 'aaa')) ! call assert_equal([['a b'], [[0, 3]]], matchfuzzypos(['a b'], 'a b')) ! call assert_equal([['a b'], [[0, 3]]], matchfuzzypos(['a b'], 'a b')) ! call assert_equal([['a b'], [[0]]], 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))) ! call assert_equal([[], []], matchfuzzypos([], 'abc')) " match in a long string ! call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]]], \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) " preference for camel case match ! call assert_equal([['xabcxxaBc'], [[6, 7, 8]]], matchfuzzypos(['xabcxxaBc'], 'abc')) " preference for match after a separator (_ or space) ! call assert_equal([['xabx_ab'], [[5, 6]]], matchfuzzypos(['xabx_ab'], 'ab')) " preference for leading letter match ! call assert_equal([['abcxabc'], [[0, 1]]], matchfuzzypos(['abcxabc'], 'ab')) " preference for sequential match ! call assert_equal([['aobncedone'], [[7, 8, 9]]], matchfuzzypos(['aobncedone'], 'one')) " best recursive match ! call assert_equal([['xoone'], [[2, 3, 4]]], matchfuzzypos(['xoone'], 'one')) " 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', '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]]], ['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]]], \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) ! call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]]], \ matchfuzzypos(l, 'cam', {'key' : 'val'})) ! call assert_equal([[], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) ! call assert_equal([[], []], matchfuzzypos(l, 'day', {'key' : 'val'})) 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 -> 1}})) call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') ! call assert_equal([[], []], matchfuzzypos(l, 'cam')) 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, 'cam', test_null_dict())", 'E715:') --- 93,147 ---- " Test for the matchfuzzypos() function func Test_matchfuzzypos() ! call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', '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'], ! \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]], \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) ! call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa')) ! call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) ! call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) ! call assert_equal([['a b'], [[0]], [112]], 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))) ! call assert_equal([[], [], []], matchfuzzypos([], 'abc')) " match in a long string ! call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]], \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) " preference for camel case match ! call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc')) " preference for match after a separator (_ or space) ! call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab')) " preference for leading letter match ! call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab')) " preference for sequential match ! call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one')) " best recursive match ! call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one')) " match multiple words (separated by space) ! 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']->matchfuzzypos(" \t ")) ! 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'}] ! call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) ! call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], \ matchfuzzypos(l, 'cam', {'key' : 'val'})) ! call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) ! call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'})) 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 -> 1}})) call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') ! call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) 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, 'cam', test_null_dict())", 'E715:') *************** *** 193,233 **** " Test for matchfuzzypos() with multibyte characters func Test_matchfuzzypos_mbyte() CheckFeature multi_lang ! call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]]], \ matchfuzzypos(['こんにちは世界'], 'こんにちは')) ! call assert_equal([['ンヹㄇヺヴ'], [[1, 3]]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) " reverse the order of characters ! call assert_equal([[], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) ! call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]]], \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], ! \ [[0, 1], [0, 1], [0, 1], [0, 2]]], \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) ! call assert_equal([['ααααααα'], [[0, 1, 2]]], \ 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))) " match multiple words (separated by space) ! call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의')) ! call assert_equal([[], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) " match in a long string ! call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]]], \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) " preference for camel case match ! call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) " preference for match after a separator (_ or space) ! call assert_equal([['xちだx_ちだ'], [[5, 6]]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) " preference for leading letter match ! call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ')) " preference for sequential match ! call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ')) " best recursive match ! call assert_equal([['xффйд'], [[2, 3, 4]]], matchfuzzypos(['xффйд'], 'фйд')) endfunc " vim: shiftwidth=2 sts=2 expandtab --- 193,233 ---- " Test for matchfuzzypos() with multibyte characters func Test_matchfuzzypos_mbyte() CheckFeature multi_lang ! call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]], \ matchfuzzypos(['こんにちは世界'], 'こんにちは')) ! call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) " reverse the order of characters ! call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) ! call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]], \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], ! \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]], \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) ! call assert_equal([['ααααααα'], [[0, 1, 2]], [191]], \ 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))) " match multiple words (separated by space) ! call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의')) ! call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) " match in a long string ! call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]], \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) " preference for camel case match ! call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) " preference for match after a separator (_ or space) ! call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) " preference for leading letter match ! call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ')) " preference for sequential match ! call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ')) " best recursive match ! call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд')) endfunc " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.2279/src/version.c 2021-01-02 18:17:22.946191468 +0100 --- src/version.c 2021-01-02 18:28:01.463911156 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2280, /**/ -- You are only young once, but you can stay immature indefinitely. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///