2019-07-01 15:08:29 -06:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Benchmark results:
|
|
|
|
//
|
|
|
|
// BenchmarkMatcher-12 1000000 1615 ns/op 30.95 MB/s 0 B/op 0 allocs/op
|
|
|
|
//
|
|
|
|
package fuzzy_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/tools/internal/lsp/fuzzy"
|
|
|
|
)
|
|
|
|
|
|
|
|
type comparator struct {
|
|
|
|
f func(val, ref float32) bool
|
|
|
|
descr string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
eq = comparator{
|
|
|
|
f: func(val, ref float32) bool {
|
|
|
|
return val == ref
|
|
|
|
},
|
|
|
|
descr: "==",
|
|
|
|
}
|
|
|
|
ge = comparator{
|
|
|
|
f: func(val, ref float32) bool {
|
|
|
|
return val >= ref
|
|
|
|
},
|
|
|
|
descr: ">=",
|
|
|
|
}
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
gt = comparator{
|
|
|
|
f: func(val, ref float32) bool {
|
|
|
|
return val > ref
|
|
|
|
},
|
|
|
|
descr: ">",
|
|
|
|
}
|
2019-07-01 15:08:29 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
func (c comparator) eval(val, ref float32) bool {
|
|
|
|
return c.f(val, ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c comparator) String() string {
|
|
|
|
return c.descr
|
|
|
|
}
|
|
|
|
|
|
|
|
type scoreTest struct {
|
|
|
|
candidate string
|
|
|
|
comparator
|
|
|
|
ref float32
|
|
|
|
}
|
|
|
|
|
|
|
|
var matcherTests = []struct {
|
|
|
|
pattern string
|
|
|
|
tests []scoreTest
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
pattern: "",
|
|
|
|
tests: []scoreTest{
|
|
|
|
{"def", eq, 1},
|
|
|
|
{"Ab stuff c", eq, 1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
pattern: "abc",
|
|
|
|
tests: []scoreTest{
|
|
|
|
{"def", eq, -1},
|
|
|
|
{"abd", eq, -1},
|
|
|
|
{"abc", ge, 0},
|
|
|
|
{"Abc", ge, 0},
|
|
|
|
{"Ab stuff c", ge, 0},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
pattern: "Abc",
|
|
|
|
tests: []scoreTest{
|
|
|
|
{"def", eq, -1},
|
|
|
|
{"abd", eq, -1},
|
|
|
|
{"abc", ge, 0},
|
|
|
|
{"Abc", ge, 0},
|
|
|
|
{"Ab stuff c", ge, 0},
|
|
|
|
},
|
|
|
|
},
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
{
|
|
|
|
pattern: "U",
|
|
|
|
tests: []scoreTest{
|
|
|
|
{"ErrUnexpectedEOF", gt, 0},
|
|
|
|
{"ErrUnexpectedEOF.Error", eq, 0},
|
|
|
|
},
|
|
|
|
},
|
2019-07-01 15:08:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestScore(t *testing.T) {
|
|
|
|
for _, tc := range matcherTests {
|
2019-10-21 22:07:21 -06:00
|
|
|
m := fuzzy.NewMatcher(tc.pattern)
|
2019-07-01 15:08:29 -06:00
|
|
|
for _, sct := range tc.tests {
|
|
|
|
score := m.Score(sct.candidate)
|
|
|
|
if !sct.comparator.eval(score, sct.ref) {
|
|
|
|
t.Errorf("not true that m.Score(%s)[=%v] %s %v", sct.candidate, score, sct.comparator, sct.ref)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var compareCandidatesTestCases = []struct {
|
|
|
|
pattern string
|
|
|
|
orderedCandidates []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
pattern: "Foo",
|
|
|
|
orderedCandidates: []string{
|
|
|
|
"Barfoo",
|
|
|
|
"Faoo",
|
2019-10-21 22:07:21 -06:00
|
|
|
"F_o_o",
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
"FaoFooa",
|
|
|
|
"BarFoo",
|
2019-07-01 15:08:29 -06:00
|
|
|
"F__oo",
|
|
|
|
"F_oo",
|
|
|
|
"FooA",
|
|
|
|
"FooBar",
|
|
|
|
"Foo",
|
|
|
|
},
|
|
|
|
},
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
{
|
|
|
|
pattern: "U",
|
|
|
|
orderedCandidates: []string{
|
|
|
|
"ErrUnexpectedEOF.Error",
|
|
|
|
"ErrUnexpectedEOF",
|
|
|
|
},
|
|
|
|
},
|
2019-07-01 15:08:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCompareCandidateScores(t *testing.T) {
|
|
|
|
for _, tc := range compareCandidatesTestCases {
|
2019-10-21 22:07:21 -06:00
|
|
|
m := fuzzy.NewMatcher(tc.pattern)
|
2019-07-01 15:08:29 -06:00
|
|
|
|
|
|
|
var prevScore float32
|
|
|
|
prevCand := "MIN_SCORE"
|
|
|
|
for _, cand := range tc.orderedCandidates {
|
|
|
|
score := m.Score(cand)
|
|
|
|
if prevScore > score {
|
|
|
|
t.Errorf("%s[=%v] is scored lower than %s[=%v]", cand, score, prevCand, prevScore)
|
|
|
|
}
|
|
|
|
if score < -1 || score > 1 {
|
|
|
|
t.Errorf("%s score is %v; want value between [-1, 1]", cand, score)
|
|
|
|
}
|
|
|
|
prevScore = score
|
|
|
|
prevCand = cand
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var fuzzyMatcherTestCases = []struct {
|
2019-10-21 22:07:21 -06:00
|
|
|
p string
|
|
|
|
str string
|
|
|
|
want string
|
2019-07-01 15:08:29 -06:00
|
|
|
}{
|
2019-10-21 22:07:21 -06:00
|
|
|
{p: "foo", str: "abc::foo", want: "abc::[foo]"},
|
|
|
|
{p: "foo", str: "foo.foo", want: "foo.[foo]"},
|
|
|
|
{p: "foo", str: "fo_oo.o_oo", want: "[fo]_oo.[o]_oo"},
|
|
|
|
{p: "foo", str: "fo_oo.fo_oo", want: "fo_oo.[fo]_[o]o"},
|
|
|
|
{p: "fo_o", str: "fo_oo.o_oo", want: "[f]o_oo.[o_o]o"},
|
|
|
|
{p: "fOO", str: "fo_oo.o_oo", want: "[f]o_oo.[o]_[o]o"},
|
|
|
|
{p: "tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
|
|
|
|
{p: "TEdit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
|
|
|
|
{p: "Tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
|
|
|
|
{p: "Tedit", str: "foo.Textedit", want: "foo.[Te]xte[dit]"},
|
|
|
|
{p: "TEdit", str: "foo.Textedit", want: ""},
|
|
|
|
{p: "te", str: "foo.Textedit", want: "foo.[Te]xtedit"},
|
|
|
|
{p: "ee", str: "foo.Textedit", want: ""}, // short middle of the word match
|
|
|
|
{p: "ex", str: "foo.Textedit", want: "foo.T[ex]tedit"},
|
|
|
|
{p: "exdi", str: "foo.Textedit", want: ""}, // short middle of the word match
|
|
|
|
{p: "exdit", str: "foo.Textedit", want: ""}, // short middle of the word match
|
|
|
|
{p: "extdit", str: "foo.Textedit", want: "foo.T[ext]e[dit]"},
|
|
|
|
{p: "e", str: "foo.Textedit", want: "foo.T[e]xtedit"},
|
|
|
|
{p: "E", str: "foo.Textedit", want: "foo.T[e]xtedit"},
|
|
|
|
{p: "ed", str: "foo.Textedit", want: "foo.Text[ed]it"},
|
|
|
|
{p: "edt", str: "foo.Textedit", want: ""}, // short middle of the word match
|
|
|
|
{p: "edit", str: "foo.Textedit", want: "foo.Text[edit]"},
|
|
|
|
{p: "edin", str: "foo.TexteditNum", want: "foo.Text[edi]t[N]um"},
|
|
|
|
{p: "n", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"},
|
|
|
|
{p: "N", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"},
|
|
|
|
{p: "completio", str: "completion", want: "[completio]n"},
|
|
|
|
{p: "completio", str: "completion.None", want: "[completio]n.None"},
|
2019-07-01 15:08:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestFuzzyMatcherRanges(t *testing.T) {
|
|
|
|
for _, tc := range fuzzyMatcherTestCases {
|
2019-10-21 22:07:21 -06:00
|
|
|
matcher := fuzzy.NewMatcher(tc.p)
|
2019-07-01 15:08:29 -06:00
|
|
|
score := matcher.Score(tc.str)
|
|
|
|
if tc.want == "" {
|
|
|
|
if score >= 0 {
|
|
|
|
t.Errorf("Score(%s, %s) = %v; want: <= 0", tc.p, tc.str, score)
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if score < 0 {
|
|
|
|
t.Errorf("Score(%s, %s) = %v, want: > 0", tc.p, tc.str, score)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
got := highlightMatches(tc.str, matcher)
|
|
|
|
if tc.want != got {
|
|
|
|
t.Errorf("highlightMatches(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var scoreTestCases = []struct {
|
|
|
|
p string
|
|
|
|
str string
|
|
|
|
want float64
|
|
|
|
}{
|
|
|
|
// Score precision up to five digits. Modify if changing the score, but make sure the new values
|
|
|
|
// are reasonable.
|
|
|
|
{p: "abc", str: "abc", want: 1},
|
|
|
|
{p: "abc", str: "Abc", want: 1},
|
|
|
|
{p: "abc", str: "Abcdef", want: 1},
|
|
|
|
{p: "strc", str: "StrCat", want: 1},
|
|
|
|
{p: "abc_def", str: "abc_def_xyz", want: 1},
|
|
|
|
{p: "abcdef", str: "abc_def_xyz", want: 0.91667},
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
{p: "abcxyz", str: "abc_def_xyz", want: 0.91667},
|
2019-07-01 15:08:29 -06:00
|
|
|
{p: "sc", str: "StrCat", want: 0.75},
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
{p: "abc", str: "AbstrBasicCtor", want: 0.83333},
|
|
|
|
{p: "foo", str: "abc::foo", want: 0.91667},
|
2019-07-01 15:08:29 -06:00
|
|
|
{p: "afoo", str: "abc::foo", want: 0.9375},
|
|
|
|
{p: "abr", str: "abc::bar", want: 0.5},
|
internal/lsp: fix fuzzy matcher inconsistency
Originally the fuzzy matcher required a match in the final candidate
segment. For example, to match the candidate "foo.bar", the input had
to have at least one character that matched "bar". I previously
removed this requirement as it is too restrictive for deep completions
to be useful.
However, there was still some lingering final-segment favoritism in
the matching algorithm. In particular, there were penalties for not
matching the final segment's first character and for not matching the
final segment's word initial characters. However, these penalties only
made sense when we also required a final segment match. Consider this
example:
User input: "U"
Candidate "ErrUnexpectedEOF" - with only a single segment, we got big
penalties for not matching the leading "E" (since it is the final
segment).
Candidate "ErrUnexpectedEOF.Error" - "ErrUnexpectedEOF" is no longer
the final segment, so we didn't get penalties. And we didn't get
penalties for the final segment "Error" because we finished matching
after the first "U". As a result, this candidate slips through with a
higher score.
Fix by simplifying the skip penalty. Now we only penalize for skipping
the first character of the first or final segment (and the penalty is
lower). For deep completions, the first and final segment are both
"important" segments, so I think it makes sense to focus on both of
them. We don't want to penalize all segment starts because that makes
it harder to match deeper candidates where you often "ignore"
intermediate segments.
I had to adjust a few scores in the tests, but I don't think the
impact will be too big other than fixing the bug.
Fixes golang/go#35062.
Change-Id: Id17a5c80bf0f80ce252fe990ccfbd51c1bac1c72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/202638
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-10-22 10:28:04 -06:00
|
|
|
{p: "br", str: "abc::bar", want: 0.25},
|
|
|
|
{p: "aar", str: "abc::bar", want: 0.41667},
|
|
|
|
{p: "edin", str: "foo.TexteditNum", want: 0.125},
|
2019-07-01 15:08:29 -06:00
|
|
|
{p: "ediu", str: "foo.TexteditNum", want: 0},
|
|
|
|
// We want the next two items to have roughly similar scores.
|
|
|
|
{p: "up", str: "unique_ptr", want: 0.75},
|
|
|
|
{p: "up", str: "upper_bound", want: 1},
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestScores(t *testing.T) {
|
|
|
|
for _, tc := range scoreTestCases {
|
2019-10-21 22:07:21 -06:00
|
|
|
matcher := fuzzy.NewMatcher(tc.p)
|
2019-07-01 15:08:29 -06:00
|
|
|
got := math.Round(float64(matcher.Score(tc.str))*1e5) / 1e5
|
|
|
|
if got != tc.want {
|
|
|
|
t.Errorf("Score(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func highlightMatches(str string, matcher *fuzzy.Matcher) string {
|
|
|
|
matches := matcher.MatchedRanges()
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
index := 0
|
|
|
|
for i := 0; i < len(matches)-1; i += 2 {
|
|
|
|
s, e := matches[i], matches[i+1]
|
|
|
|
fmt.Fprintf(&buf, "%s[%s]", str[index:s], str[s:e])
|
|
|
|
index = e
|
|
|
|
}
|
|
|
|
buf.WriteString(str[index:])
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkMatcher(b *testing.B) {
|
|
|
|
pattern := "Foo"
|
|
|
|
candidates := []string{
|
|
|
|
"F_o_o",
|
|
|
|
"Barfoo",
|
|
|
|
"Faoo",
|
|
|
|
"F__oo",
|
|
|
|
"F_oo",
|
|
|
|
"FaoFooa",
|
|
|
|
"BarFoo",
|
|
|
|
"FooA",
|
|
|
|
"FooBar",
|
|
|
|
"Foo",
|
|
|
|
}
|
|
|
|
|
2019-10-21 22:07:21 -06:00
|
|
|
matcher := fuzzy.NewMatcher(pattern)
|
2019-07-01 15:08:29 -06:00
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
for _, c := range candidates {
|
|
|
|
matcher.Score(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var numBytes int
|
|
|
|
for _, c := range candidates {
|
|
|
|
numBytes += len(c)
|
|
|
|
}
|
|
|
|
b.SetBytes(int64(numBytes))
|
|
|
|
}
|