diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go index d631cacbb10..828f3bb334d 100644 --- a/internal/lsp/source/workspace_symbol.go +++ b/internal/lsp/source/workspace_symbol.go @@ -6,9 +6,11 @@ package source import ( "context" + "fmt" "go/ast" "go/token" "go/types" + "sort" "strings" "golang.org/x/tools/internal/event" @@ -16,6 +18,8 @@ import ( "golang.org/x/tools/internal/lsp/protocol" ) +// maxSymbols defines the maximum number of symbol results that should ever be +// sent in response to a client. const maxSymbols = 100 // WorkspaceSymbols matches symbols across views using the given query, @@ -41,172 +45,282 @@ func WorkspaceSymbols(ctx context.Context, matcherType SymbolMatcher, style Symb if query == "" { return nil, nil } + sc := newSymbolCollector(matcherType, style, query) + return sc.walk(ctx, views) +} - queryMatcher := makeQueryMatcher(matcherType, query) - seen := make(map[string]struct{}) - var symbols []protocol.SymbolInformation -outer: - for _, view := range views { - snapshot, release := view.Snapshot(ctx) - defer release() // TODO: refactor so this runs promptly instead of at the end of the function - knownPkgs, err := snapshot.KnownPackages(ctx) - if err != nil { - return nil, err - } - // TODO: apply some kind of ordering to the search, and sort the results. - for _, pkg := range knownPkgs { - symbolMatcher := makePackageSymbolMatcher(style, pkg, queryMatcher) - if err != nil { - return nil, err - } - if _, ok := seen[pkg.PkgPath()]; ok { - continue - } - seen[pkg.PkgPath()] = struct{}{} - for _, pgf := range pkg.CompiledGoFiles() { - for _, si := range findSymbol(pgf.File.Decls, pkg.GetTypesInfo(), symbolMatcher) { - mrng, err := posToMappedRange(snapshot, pkg, si.node.Pos(), si.node.End()) - if err != nil { - event.Error(ctx, "Error getting mapped range for node", err) - continue - } - rng, err := mrng.Range() - if err != nil { - event.Error(ctx, "Error getting range from mapped range", err) - continue - } - symbols = append(symbols, protocol.SymbolInformation{ - Name: si.name, - Kind: si.kind, - Location: protocol.Location{ - URI: protocol.URIFromSpanURI(mrng.URI()), - Range: rng, - }, - ContainerName: pkg.PkgPath(), - }) - if len(symbols) > maxSymbols { - break outer - } - } - } - } +// A matcherFunc determines the matching score of a symbol. +// +// See the comment for symbolCollector for more information. +type matcherFunc func(name string) float64 + +// A symbolizer returns the best symbol match for name with pkg, according to +// some heuristic. +// +// See the comment for symbolCollector for more information. +type symbolizer func(name string, pkg Package, m matcherFunc) (string, float64) + +func fullyQualifiedSymbolMatch(name string, pkg Package, matcher matcherFunc) (string, float64) { + fullyQualified := pkg.PkgPath() + "." + name + if matcher(fullyQualified) > 0 { + return fullyQualified, 1 } - return symbols, nil + return "", 0 } -type symbolInformation struct { - name string - kind protocol.SymbolKind - node ast.Node +func dynamicSymbolMatch(name string, pkg Package, matcher matcherFunc) (string, float64) { + // Prefer any package-qualified match. + pkgQualified := pkg.Name() + "." + name + if match, score := bestMatch(pkgQualified, matcher); match != "" { + return match, score + } + fullyQualified := pkg.PkgPath() + "." + name + if match, score := bestMatch(fullyQualified, matcher); match != "" { + return match, score + } + return "", 0 } -type matcherFunc func(string) bool +func packageSymbolMatch(name string, pkg Package, matcher matcherFunc) (string, float64) { + qualified := pkg.Name() + "." + name + if matcher(qualified) > 0 { + return qualified, 1 + } + return "", 0 +} -func makeQueryMatcher(m SymbolMatcher, query string) matcherFunc { - switch m { - case SymbolFuzzy: - fm := fuzzy.NewMatcher(query) - return func(s string) bool { - return fm.Score(s) > 0 - } - case SymbolCaseSensitive: - return func(s string) bool { - return strings.Contains(s, query) +// bestMatch returns the highest scoring symbol suffix of fullPath, starting +// from the right and splitting on selectors and path components. +// +// e.g. given a symbol path of the form 'host.com/dir/pkg.type.field', we +// check the match quality of the following: +// - field +// - type.field +// - pkg.type.field +// - dir/pkg.type.field +// - host.com/dir/pkg.type.field +// +// and return the best match, along with its score. +// +// This is used to implement the 'dynamic' symbol style. +func bestMatch(fullPath string, matcher matcherFunc) (string, float64) { + pathParts := strings.Split(fullPath, "/") + dottedParts := strings.Split(pathParts[len(pathParts)-1], ".") + + var best string + var score float64 + + for i := 0; i < len(dottedParts); i++ { + path := strings.Join(dottedParts[len(dottedParts)-1-i:], ".") + if match := matcher(path); match > score { + best = path + score = match } - default: - q := strings.ToLower(query) - return func(s string) bool { - return strings.Contains(strings.ToLower(s), q) + } + for i := 0; i < len(pathParts); i++ { + path := strings.Join(pathParts[len(pathParts)-1-i:], "/") + if match := matcher(path); match > score { + best = path + score = match } } + return best, score } -// packageSymbolMatcher matches (possibly partially) qualified symbols within a -// package scope. +// symbolCollector holds context as we walk Packages, gathering symbols that +// match a given query. // -// The given symbolizer controls how symbol names are extracted from the -// package scope. -type packageSymbolMatcher struct { - queryMatcher matcherFunc - pkg Package - symbolize symbolizer -} +// How we match symbols is parameterized by two interfaces: +// * A matcherFunc determines how well a string symbol matches a query. It +// returns a non-negative score indicating the quality of the match. A score +// of zero indicates no match. +// * A symbolizer determines how we extract the symbol for an object. This +// enables the 'symbolStyle' configuration option. +type symbolCollector struct { + // query is the user-supplied query passed to the Symbol method. + query string + + // These types parameterize the symbol-matching pass. + matcher matcherFunc + symbolizer symbolizer -// symbolMatch returns the package symbol for name that matches the underlying -// query, or the empty string if no match is found. -func (s packageSymbolMatcher) symbolMatch(name string) string { - return s.symbolize(name, s.pkg, s.queryMatcher) + // current holds metadata for the package we are currently walking. + current *pkgView + curFile *ParsedGoFile + + res [maxSymbols]symbolInformation } -func makePackageSymbolMatcher(style SymbolStyle, pkg Package, matcher matcherFunc) func(string) string { +func newSymbolCollector(matcher SymbolMatcher, style SymbolStyle, query string) *symbolCollector { + var m matcherFunc + switch matcher { + case SymbolFuzzy: + fm := fuzzy.NewMatcher(query) + m = func(s string) float64 { + return float64(fm.Score(s)) + } + case SymbolCaseSensitive: + m = func(s string) float64 { + if strings.Contains(s, query) { + return 1 + } + return 0 + } + case SymbolCaseInsensitive: + q := strings.ToLower(query) + m = func(s string) float64 { + if strings.Contains(strings.ToLower(s), q) { + return 1 + } + return 0 + } + default: + panic(fmt.Errorf("unknown symbol matcher: %v", matcher)) + } var s symbolizer switch style { case DynamicSymbols: s = dynamicSymbolMatch case FullyQualifiedSymbols: s = fullyQualifiedSymbolMatch - default: + case PackageQualifiedSymbols: s = packageSymbolMatch + default: + panic(fmt.Errorf("unknown symbol style: %v", style)) } - return packageSymbolMatcher{queryMatcher: matcher, pkg: pkg, symbolize: s}.symbolMatch -} - -// A symbolizer returns a qualified symbol match for the unqualified name -// within pkg, if one exists, or the empty string if no match is found. -type symbolizer func(name string, pkg Package, m matcherFunc) string - -func fullyQualifiedSymbolMatch(name string, pkg Package, matcher matcherFunc) string { - // TODO: this should probably include pkg.Name() as well. - fullyQualified := pkg.PkgPath() + "." + name - if matcher(fullyQualified) { - return fullyQualified + return &symbolCollector{ + matcher: m, + symbolizer: s, } - return "" } -func dynamicSymbolMatch(name string, pkg Package, matcher matcherFunc) string { - pkgQualified := pkg.Name() + "." + name - if match := shortestMatch(pkgQualified, matcher); match != "" { - return match +// walk walks views, gathers symbols, and returns the results. +func (sc *symbolCollector) walk(ctx context.Context, views []View) (_ []protocol.SymbolInformation, err error) { + toWalk, release, err := sc.collectPackages(ctx, views) + defer release() + if err != nil { + return nil, err } - fullyQualified := pkg.PkgPath() + "." + name - if match := shortestMatch(fullyQualified, matcher); match != "" { - return match + // Make sure we only walk files once (we might see them more than once due to + // build constraints). + seen := make(map[*ast.File]bool) + for _, pv := range toWalk { + sc.current = pv + for _, pgf := range pv.pkg.CompiledGoFiles() { + if seen[pgf.File] { + continue + } + sc.curFile = pgf + sc.walkFilesDecls(pgf.File.Decls) + } } - return "" + return sc.results(), nil } -func packageSymbolMatch(name string, pkg Package, matcher matcherFunc) string { - qualified := pkg.Name() + "." + name - if matcher(qualified) { - return qualified +func (sc *symbolCollector) results() []protocol.SymbolInformation { + var res []protocol.SymbolInformation + for _, si := range sc.res { + if si.score <= 0 { + return res + } + res = append(res, si.asProtocolSymbolInformation()) } - return "" + return res } -func shortestMatch(fullPath string, matcher func(string) bool) string { - pathParts := strings.Split(fullPath, "/") - dottedParts := strings.Split(pathParts[len(pathParts)-1], ".") - // First match the smallest package identifier. - if m := matchRight(dottedParts, ".", matcher); m != "" { - return m +// collectPackages gathers the packages we are going to inspect for symbols. +// This pre-step is required in order to filter out any "duplicate" +// *types.Package. The duplicates arise for packages that have test variants. +// For example, if package mod.com/p has test files, then we will visit two +// packages that have the PkgPath() mod.com/p: the first is the actual package +// mod.com/p, the second is a special version that includes the non-XTest +// _test.go files. If we were to walk both of of these packages, then we would +// get duplicate matching symbols and we would waste effort. Therefore where +// test variants exist we walk those (because they include any symbols defined +// in non-XTest _test.go files). +// +// One further complication is that even after this filtering, packages between +// views might not be "identical" because they can be built using different +// build constraints (via the "env" config option). +// +// Therefore on a per view basis we first build up a map of package path -> +// *types.Package preferring the test variants if they exist. Then we merge the +// results between views, de-duping by *types.Package. +func (sc *symbolCollector) collectPackages(ctx context.Context, views []View) ([]*pkgView, func(), error) { + gathered := make(map[string]map[*types.Package]*pkgView) + var releaseFuncs []func() + release := func() { + for _, releaseFunc := range releaseFuncs { + releaseFunc() + } } - // Then match the shortest subpath. - return matchRight(pathParts, "/", matcher) -} - -func matchRight(parts []string, sep string, matcher func(string) bool) string { - for i := 0; i < len(parts); i++ { - path := strings.Join(parts[len(parts)-1-i:], sep) - if matcher(path) { - return path + var toWalk []*pkgView + for _, v := range views { + seen := make(map[string]*pkgView) + snapshot, release := v.Snapshot(ctx) + releaseFuncs = append(releaseFuncs, release) + knownPkgs, err := snapshot.KnownPackages(ctx) + if err != nil { + return nil, release, err + } + workspacePackages, err := snapshot.WorkspacePackages(ctx) + if err != nil { + return nil, release, err + } + isWorkspacePkg := make(map[Package]bool) + for _, wp := range workspacePackages { + isWorkspacePkg[wp] = true + } + var forTests []*pkgView + for _, pkg := range knownPkgs { + toAdd := &pkgView{ + pkg: pkg, + snapshot: snapshot, + isWorkspace: isWorkspacePkg[pkg], + } + // Defer test packages, so that they overwrite seen for this package + // path. + if pkg.ForTest() != "" { + forTests = append(forTests, toAdd) + } else { + seen[pkg.PkgPath()] = toAdd + } + } + for _, pkg := range forTests { + seen[pkg.pkg.PkgPath()] = pkg + } + for _, pkg := range seen { + pm, ok := gathered[pkg.pkg.PkgPath()] + if !ok { + pm = make(map[*types.Package]*pkgView) + gathered[pkg.pkg.PkgPath()] = pm + } + pm[pkg.pkg.GetTypes()] = pkg + } + } + for _, pm := range gathered { + for _, pkg := range pm { + toWalk = append(toWalk, pkg) } } - return "" + // Now sort for stability of results. We order by + // (pkgView.isWorkspace, pkgView.p.ID()) + sort.Slice(toWalk, func(i, j int) bool { + lhs := toWalk[i] + rhs := toWalk[j] + switch { + case lhs.isWorkspace == rhs.isWorkspace: + return lhs.pkg.ID() < rhs.pkg.ID() + case lhs.isWorkspace: + return true + default: + return false + } + }) + return toWalk, release, nil } -func findSymbol(decls []ast.Decl, info *types.Info, symbolMatch func(string) string) []symbolInformation { - var result []symbolInformation +func (sc *symbolCollector) walkFilesDecls(decls []ast.Decl) { for _, decl := range decls { switch decl := decl.(type) { case *ast.FuncDecl: @@ -221,29 +335,17 @@ func findSymbol(decls []ast.Decl, info *types.Info, symbolMatch func(string) str fn = typ.Name + "." + fn } } - if m := symbolMatch(fn); m != "" { - result = append(result, symbolInformation{ - name: m, - kind: kind, - node: decl.Name, - }) - } + sc.match(fn, kind, decl.Name) case *ast.GenDecl: for _, spec := range decl.Specs { switch spec := spec.(type) { case *ast.TypeSpec: target := spec.Name.Name - if m := symbolMatch(target); m != "" { - result = append(result, symbolInformation{ - name: m, - kind: typeToKind(info.TypeOf(spec.Type)), - node: spec.Name, - }) - } + sc.match(target, typeToKind(sc.current.pkg.GetTypesInfo().TypeOf(spec.Type)), spec.Name) switch st := spec.Type.(type) { case *ast.StructType: for _, field := range st.Fields.List { - result = append(result, findFieldSymbol(field, protocol.Field, symbolMatch, target)...) + sc.walkField(field, protocol.Field, target) } case *ast.InterfaceType: for _, field := range st.Methods.List { @@ -251,28 +353,35 @@ func findSymbol(decls []ast.Decl, info *types.Info, symbolMatch func(string) str if len(field.Names) == 0 { kind = protocol.Interface } - result = append(result, findFieldSymbol(field, kind, symbolMatch, target)...) + sc.walkField(field, kind, target) } } case *ast.ValueSpec: for _, name := range spec.Names { - if m := symbolMatch(name.Name); m != "" { - kind := protocol.Variable - if decl.Tok == token.CONST { - kind = protocol.Constant - } - result = append(result, symbolInformation{ - name: m, - kind: kind, - node: name, - }) + target := name.Name + kind := protocol.Variable + if decl.Tok == token.CONST { + kind = protocol.Constant } + sc.match(target, kind, name) } } } } } - return result +} + +func (sc *symbolCollector) walkField(field *ast.Field, kind protocol.SymbolKind, prefix string) { + if len(field.Names) == 0 { + name := types.ExprString(field.Type) + target := prefix + "." + name + sc.match(target, kind, field) + return + } + for _, name := range field.Names { + target := prefix + "." + name.Name + sc.match(target, kind, name) + } } func typeToKind(typ types.Type) protocol.SymbolKind { @@ -302,32 +411,68 @@ func typeToKind(typ types.Type) protocol.SymbolKind { return protocol.Variable } -func findFieldSymbol(field *ast.Field, kind protocol.SymbolKind, symbolMatch func(string) string, prefix string) []symbolInformation { - var result []symbolInformation - - if len(field.Names) == 0 { - name := types.ExprString(field.Type) - target := prefix + "." + name - if m := symbolMatch(target); m != "" { - result = append(result, symbolInformation{ - name: m, - kind: kind, - node: field, - }) - } - return result +// match finds matches and gathers the symbol identified by name, kind and node +// via the symbolCollector's matcher after first de-duping against previously +// seen symbols. +func (sc *symbolCollector) match(name string, kind protocol.SymbolKind, node ast.Node) { + if !node.Pos().IsValid() || !node.End().IsValid() { + return } - - for _, name := range field.Names { - target := prefix + "." + name.Name - if m := symbolMatch(target); m != "" { - result = append(result, symbolInformation{ - name: m, - kind: kind, - node: name, - }) - } + symbol, score := sc.symbolizer(name, sc.current.pkg, sc.matcher) + if score <= sc.res[len(sc.res)-1].score { + return } + mrng := newMappedRange(sc.current.snapshot.FileSet(), sc.curFile.Mapper, node.Pos(), node.End()) + rng, err := mrng.Range() + if err != nil { + return + } + si := symbolInformation{ + score: score, + name: name, + symbol: symbol, + container: sc.current.pkg.PkgPath(), + kind: kind, + location: protocol.Location{ + URI: protocol.URIFromSpanURI(mrng.URI()), + Range: rng, + }, + } + insertAt := sort.Search(len(sc.res), func(i int) bool { + return sc.res[i].score < score + }) + if insertAt < len(sc.res)-1 { + copy(sc.res[insertAt+1:], sc.res[insertAt:len(sc.res)-1]) + } + sc.res[insertAt] = si +} - return result +// pkgView holds information related to a package that we are going to walk. +type pkgView struct { + pkg Package + snapshot Snapshot + isWorkspace bool +} + +// symbolInformation is a cut-down version of protocol.SymbolInformation that +// allows struct values of this type to be used as map keys. +type symbolInformation struct { + score float64 + name string + symbol string + container string + kind protocol.SymbolKind + location protocol.Location +} + +// asProtocolSymbolInformation converts s to a protocol.SymbolInformation value. +// +// TODO: work out how to handle tags if/when they are needed. +func (s symbolInformation) asProtocolSymbolInformation() protocol.SymbolInformation { + return protocol.SymbolInformation{ + Name: s.symbol, + Kind: s.kind, + Location: s.location, + ContainerName: s.container, + } } diff --git a/internal/lsp/source/workspace_symbol_test.go b/internal/lsp/source/workspace_symbol_test.go new file mode 100644 index 00000000000..0d3a9cd7cc2 --- /dev/null +++ b/internal/lsp/source/workspace_symbol_test.go @@ -0,0 +1,60 @@ +// Copyright 2020 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. + +package source + +import ( + "strings" + "testing" +) + +func TestBestMatch(t *testing.T) { + tests := []struct { + desc string + symbol string + matcher matcherFunc + wantMatch string + wantScore float64 + }{ + { + desc: "shortest match", + symbol: "foo/bar/baz.quux", + matcher: func(string) float64 { return 1.0 }, + wantMatch: "quux", + wantScore: 1.0, + }, + { + desc: "partial match", + symbol: "foo/bar/baz.quux", + matcher: func(s string) float64 { + if strings.HasPrefix(s, "bar") { + return 1.0 + } + return 0.0 + }, + wantMatch: "bar/baz.quux", + wantScore: 1.0, + }, + { + desc: "longest match", + symbol: "foo/bar/baz.quux", + matcher: func(s string) float64 { + parts := strings.Split(s, "/") + return float64(len(parts)) + }, + wantMatch: "foo/bar/baz.quux", + wantScore: 3.0, + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + gotMatch, gotScore := bestMatch(test.symbol, test.matcher) + if gotMatch != test.wantMatch || gotScore != test.wantScore { + t.Errorf("bestMatch(%q, matcher) = (%q, %.2g), want (%q, %.2g)", test.symbol, gotMatch, gotScore, test.wantMatch, test.wantScore) + } + }) + } +} diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go new file mode 100644 index 00000000000..30d5340970a --- /dev/null +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go @@ -0,0 +1,3 @@ +package a + +var RandomGopherTestVariableA = "a" //@symbol("RandomGopherTestVariableA", "RandomGopherTestVariableA", "Variable", "", "a.RandomGopherTestVariableA") diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go.golden new file mode 100644 index 00000000000..af74619439a --- /dev/null +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_test.go.golden @@ -0,0 +1,3 @@ +-- symbols -- +RandomGopherTestVariableA Variable 3:5-3:30 + diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go new file mode 100644 index 00000000000..76eb8487d8e --- /dev/null +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go @@ -0,0 +1,3 @@ +package a_test + +var RandomGopherXTestVariableA = "a" //@symbol("RandomGopherXTestVariableA", "RandomGopherXTestVariableA", "Variable", "", "a_test.RandomGopherXTestVariableA") diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go.golden new file mode 100644 index 00000000000..dfd02a5c449 --- /dev/null +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a_x_test.go.golden @@ -0,0 +1,3 @@ +-- symbols -- +RandomGopherXTestVariableA Variable 3:5-3:31 + diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go index f4a8821c75d..929bb3bed38 100644 --- a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go @@ -2,25 +2,31 @@ package fuzzy /*@ workspacesymbolfuzzy("rgop", - RandomGopherVariableA, - RandomGopherConstantA, + bBar, randomgopherinvariable, + RandomGopherVariableA, + RandomGopherXTestVariableA, RandomGopherVariableB, RandomGopherStructB, - bBar, + RandomGopherTestVariableA, + RandomGopherConstantA, ) workspacesymbolfuzzy("randoma", - RandomGopherVariableA, - RandomGopherConstantA, - randomgopherinvariable, - RandomGopherVariableB, bBar, + RandomGopherVariableB, + randomgopherinvariable, + RandomGopherXTestVariableA, + RandomGopherTestVariableA, + RandomGopherConstantA, + RandomGopherVariableA, ) workspacesymbolfuzzy("randomb", - RandomGopherVariableA, + bBar, randomgopherinvariable, - RandomGopherVariableB, + RandomGopherTestVariableA, + RandomGopherXTestVariableA, + RandomGopherVariableA, RandomGopherStructB, - bBar, + RandomGopherVariableB, ) */ diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randoma.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randoma.golden index 8423a577480..5e3d1eb9509 100644 --- a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randoma.golden +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randoma.golden @@ -2,5 +2,7 @@ workspacesymbol/a/a.go:3:5-26 a.RandomGopherVariableA Variable workspacesymbol/a/a.go:5:7-28 a.RandomGopherConstantA Constant workspacesymbol/a/a.go:8:2-24 a.randomgopherinvariable Constant +workspacesymbol/a/a_test.go:3:5-30 a.RandomGopherTestVariableA Variable +workspacesymbol/a/a_x_test.go:3:5-31 a_test.RandomGopherXTestVariableA Variable workspacesymbol/b/b.go:3:5-26 b.RandomGopherVariableB Variable workspacesymbol/b/b.go:6:2-5 b.RandomGopherStructB.Bar Field diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randomb.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randomb.golden index 7c3426602c0..46aafd5dc18 100644 --- a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randomb.golden +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/randomb.golden @@ -1,6 +1,8 @@ -- workspace_symbol -- workspacesymbol/a/a.go:3:5-26 a.RandomGopherVariableA Variable workspacesymbol/a/a.go:8:2-24 a.randomgopherinvariable Constant +workspacesymbol/a/a_test.go:3:5-30 a.RandomGopherTestVariableA Variable +workspacesymbol/a/a_x_test.go:3:5-31 a_test.RandomGopherXTestVariableA Variable workspacesymbol/b/b.go:3:5-26 b.RandomGopherVariableB Variable workspacesymbol/b/b.go:5:6-25 b.RandomGopherStructB Struct workspacesymbol/b/b.go:6:2-5 b.RandomGopherStructB.Bar Field diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/rgop.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/rgop.golden index fa43cc99926..1d106f04050 100644 --- a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/rgop.golden +++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/rgop.golden @@ -2,6 +2,8 @@ workspacesymbol/a/a.go:3:5-26 a.RandomGopherVariableA Variable workspacesymbol/a/a.go:5:7-28 a.RandomGopherConstantA Constant workspacesymbol/a/a.go:8:2-24 a.randomgopherinvariable Constant +workspacesymbol/a/a_test.go:3:5-30 a.RandomGopherTestVariableA Variable +workspacesymbol/a/a_x_test.go:3:5-31 a_test.RandomGopherXTestVariableA Variable workspacesymbol/b/b.go:3:5-26 b.RandomGopherVariableB Variable workspacesymbol/b/b.go:5:6-25 b.RandomGopherStructB Struct workspacesymbol/b/b.go:6:2-5 b.RandomGopherStructB.Bar Field diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden index 416c54934bb..f625017c64a 100644 --- a/internal/lsp/testdata/lsp/summary.txt.golden +++ b/internal/lsp/testdata/lsp/summary.txt.golden @@ -20,7 +20,7 @@ HighlightsCount = 69 ReferencesCount = 11 RenamesCount = 29 PrepareRenamesCount = 7 -SymbolsCount = 3 +SymbolsCount = 5 WorkspaceSymbolsCount = 2 FuzzyWorkspaceSymbolsCount = 3 CaseSensitiveWorkspaceSymbolsCount = 2