mirror of https://github.com/golang/go.git
internal/lsp/source: change symbol matcherFuncs to accept chunks
Whenever possible we should avoid doing string operations when computing
workspace symbols. This CL lays the groundwork for optimizations of this
sort by changing the signature of matcherFunc to accept chunks. It is
done in a naive way though, so this doesn't yet improve performance.
Benchmark ("test" in x/tools): 40ms->48ms
Benchmark ("test" in kubernetes): 799ms->868ms
Change-Id: I171c654b914e9764cfb16f14d65ef1aed797df73
Reviewed-on: https://go-review.googlesource.com/c/tools/+/338693
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
0f3931c766
commit
0d28b7d7c5
|
|
@ -61,7 +61,7 @@ func WorkspaceSymbols(ctx context.Context, matcherType SymbolMatcher, style Symb
|
|||
// A matcherFunc returns the index and score of a symbol match.
|
||||
//
|
||||
// See the comment for symbolCollector for more information.
|
||||
type matcherFunc func(name string) (int, float64)
|
||||
type matcherFunc func(chunks []string) (int, float64)
|
||||
|
||||
// A symbolizer returns the best symbol match for a name with pkg, according to
|
||||
// some heuristic. The symbol name is passed as the slice nameParts of logical
|
||||
|
|
@ -87,9 +87,9 @@ func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) (string,
|
|||
// If the package path does not end in the package name, we need to check the
|
||||
// package-qualified symbol as an extra pass first.
|
||||
if !endsInPkgName {
|
||||
pkgQualified := pkg.Name() + "." + name
|
||||
pkgQualified := []string{pkg.Name(), ".", name}
|
||||
idx, score := matcher(pkgQualified)
|
||||
nameStart := len(pkgQualified) - len(name)
|
||||
nameStart := len(pkg.Name()) + 1
|
||||
if score > 0 {
|
||||
// If our match is contained entirely within the unqualified portion,
|
||||
// just return that.
|
||||
|
|
@ -97,16 +97,16 @@ func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) (string,
|
|||
return name, score
|
||||
}
|
||||
// Lower the score for matches that include the package name.
|
||||
return pkgQualified, score * 0.8
|
||||
return strings.Join(pkgQualified, ""), score * 0.8
|
||||
}
|
||||
}
|
||||
|
||||
// Now try matching the fully qualified symbol.
|
||||
fullyQualified := pkg.PkgPath() + "." + name
|
||||
fullyQualified := []string{pkg.PkgPath(), ".", name}
|
||||
idx, score := matcher(fullyQualified)
|
||||
|
||||
// As above, check if we matched just the unqualified symbol name.
|
||||
nameStart := len(fullyQualified) - len(name)
|
||||
nameStart := len(pkg.PkgPath()) + 1
|
||||
if idx >= nameStart {
|
||||
return name, score
|
||||
}
|
||||
|
|
@ -115,21 +115,21 @@ func dynamicSymbolMatch(name string, pkg Metadata, matcher matcherFunc) (string,
|
|||
// initial pass above, so check if we matched just the package-qualified
|
||||
// name.
|
||||
if endsInPkgName && idx >= 0 {
|
||||
pkgStart := len(fullyQualified) - len(name) - 1 - len(pkg.Name())
|
||||
pkgStart := len(pkg.PkgPath()) - len(pkg.Name())
|
||||
if idx >= pkgStart {
|
||||
return fullyQualified[pkgStart:], score
|
||||
return pkg.Name() + "." + name, score
|
||||
}
|
||||
}
|
||||
|
||||
// Our match was not contained within the unqualified or package qualified
|
||||
// symbol. Return the fully qualified symbol but discount the score.
|
||||
return fullyQualified, score * 0.6
|
||||
return strings.Join(fullyQualified, ""), score * 0.6
|
||||
}
|
||||
|
||||
func packageSymbolMatch(name string, pkg Metadata, matcher matcherFunc) (string, float64) {
|
||||
qualified := pkg.Name() + "." + name
|
||||
qualified := []string{pkg.Name(), ".", name}
|
||||
if _, s := matcher(qualified); s > 0 {
|
||||
return qualified, s
|
||||
return strings.Join(qualified, ""), s
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
|
@ -162,9 +162,11 @@ func newSymbolCollector(matcher SymbolMatcher, style SymbolStyle, query string)
|
|||
case SymbolCaseInsensitive:
|
||||
q := strings.ToLower(query)
|
||||
exact := matchExact(q)
|
||||
m = func(s string) (int, float64) {
|
||||
lower := strings.ToLower(s)
|
||||
return exact(lower)
|
||||
wrapper := []string{""}
|
||||
m = func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
wrapper[0] = strings.ToLower(s)
|
||||
return exact(wrapper)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown symbol matcher: %v", matcher))
|
||||
|
|
@ -201,7 +203,7 @@ func newSymbolCollector(matcher SymbolMatcher, style SymbolStyle, query string)
|
|||
func parseQuery(q string) matcherFunc {
|
||||
fields := strings.Fields(q)
|
||||
if len(fields) == 0 {
|
||||
return func(string) (int, float64) { return -1, 0 }
|
||||
return func([]string) (int, float64) { return -1, 0 }
|
||||
}
|
||||
var funcs []matcherFunc
|
||||
for _, field := range fields {
|
||||
|
|
@ -209,7 +211,8 @@ func parseQuery(q string) matcherFunc {
|
|||
switch {
|
||||
case strings.HasPrefix(field, "^"):
|
||||
prefix := field[1:]
|
||||
f = smartCase(prefix, func(s string) (int, float64) {
|
||||
f = smartCase(prefix, func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return 0, 1
|
||||
}
|
||||
|
|
@ -220,7 +223,8 @@ func parseQuery(q string) matcherFunc {
|
|||
f = smartCase(exact, matchExact(exact))
|
||||
case strings.HasSuffix(field, "$"):
|
||||
suffix := field[0 : len(field)-1]
|
||||
f = smartCase(suffix, func(s string) (int, float64) {
|
||||
f = smartCase(suffix, func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
if strings.HasSuffix(s, suffix) {
|
||||
return len(s) - len(suffix), 1
|
||||
}
|
||||
|
|
@ -228,7 +232,8 @@ func parseQuery(q string) matcherFunc {
|
|||
})
|
||||
default:
|
||||
fm := fuzzy.NewMatcher(field)
|
||||
f = func(s string) (int, float64) {
|
||||
f = func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
score := float64(fm.Score(s))
|
||||
ranges := fm.MatchedRanges()
|
||||
if len(ranges) > 0 {
|
||||
|
|
@ -246,7 +251,8 @@ func parseQuery(q string) matcherFunc {
|
|||
}
|
||||
|
||||
func matchExact(exact string) matcherFunc {
|
||||
return func(s string) (int, float64) {
|
||||
return func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
if idx := strings.LastIndex(s, exact); idx >= 0 {
|
||||
return idx, 1
|
||||
}
|
||||
|
|
@ -258,21 +264,24 @@ func matchExact(exact string) matcherFunc {
|
|||
// upper-case characters, and case-insensitive otherwise.
|
||||
func smartCase(q string, m matcherFunc) matcherFunc {
|
||||
insensitive := strings.ToLower(q) == q
|
||||
return func(s string) (int, float64) {
|
||||
wrapper := []string{""}
|
||||
return func(chunks []string) (int, float64) {
|
||||
s := strings.Join(chunks, "")
|
||||
if insensitive {
|
||||
s = strings.ToLower(s)
|
||||
}
|
||||
return m(s)
|
||||
wrapper[0] = s
|
||||
return m(wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
type comboMatcher []matcherFunc
|
||||
|
||||
func (c comboMatcher) match(s string) (int, float64) {
|
||||
func (c comboMatcher) match(chunks []string) (int, float64) {
|
||||
score := 1.0
|
||||
first := 0
|
||||
for _, f := range c {
|
||||
idx, s := f(s)
|
||||
idx, s := f(chunks)
|
||||
if idx < first {
|
||||
first = idx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestParseQuery(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
matcher := parseQuery(test.query)
|
||||
if _, score := matcher(test.s); score > 0 != test.wantMatch {
|
||||
if _, score := matcher([]string{test.s}); score > 0 != test.wantMatch {
|
||||
t.Errorf("parseQuery(%q) match for %q: %.2g, want match: %t", test.query, test.s, score, test.wantMatch)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue