1
0
mirror of https://github.com/golang/go synced 2024-11-18 15:04:44 -07:00

internal/lsp/source: make matchers selectable in WorkspaceSymbols

This change allows to use fuzzy or case-sensitive matchers in addition
to case-insensitive when searching for symbols.
Matcher is specified by UserOptions.Matcher just like Completion.

Updates golang/go#33844

Change-Id: I4000fb7984c75f0f41c38d740dbe164398032312
Reviewed-on: https://go-review.googlesource.com/c/tools/+/218737
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Daisuke Suzuki 2020-02-09 21:36:49 +09:00 committed by Rebecca Stambler
parent 695c81b9c6
commit 885dec1b2d
15 changed files with 310 additions and 89 deletions

View File

@ -102,6 +102,14 @@ func (r *runner) WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformati
//TODO: add command line workspace symbol tests when it works
}
func (r *runner) FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) {
//TODO: add command line workspace symbol tests when it works
}
func (r *runner) CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) {
//TODO: add command line workspace symbol tests when it works
}
func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
rStdout, wStdout, err := os.Pipe()
if err != nil {

View File

@ -785,14 +785,10 @@ func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.
}
func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
params := &protocol.WorkspaceSymbolParams{
Query: query,
}
symbols, err := r.server.Symbol(r.ctx, params)
if err != nil {
t.Fatal(err)
}
got := tests.FilterWorkspaceSymbols(symbols, dirs)
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.CaseInsensitive
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
@ -802,6 +798,60 @@ func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []
}
}
func (r *runner) FuzzyWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.Fuzzy
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
}
if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
t.Error(diff)
}
}
func (r *runner) CaseSensitiveWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.CaseSensitive
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
}
if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
t.Error(diff)
}
}
func (r *runner) callWorkspaceSymbols(t *testing.T, query string, options func(*source.Options)) []protocol.SymbolInformation {
t.Helper()
for _, view := range r.server.session.Views() {
original := view.Options()
modified := original
options(&modified)
var err error
view, err = view.SetOptions(r.ctx, modified)
if err != nil {
t.Error(err)
return nil
}
defer view.SetOptions(r.ctx, original)
}
params := &protocol.WorkspaceSymbolParams{
Query: query,
}
symbols, err := r.server.Symbol(r.ctx, params)
if err != nil {
t.Fatal(err)
}
return symbols
}
func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
m, err := r.data.Mapper(spn.URI())
if err != nil {

View File

@ -798,11 +798,10 @@ func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.
}
func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
symbols, err := source.WorkspaceSymbols(r.ctx, []source.View{r.view}, query)
if err != nil {
t.Errorf("symbols failed: %v", err)
}
got := tests.FilterWorkspaceSymbols(symbols, dirs)
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.CaseInsensitive
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
@ -812,6 +811,53 @@ func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []
}
}
func (r *runner) FuzzyWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.Fuzzy
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
}
if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
t.Error(diff)
}
}
func (r *runner) CaseSensitiveWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
opts.Matcher = source.CaseSensitive
})
got = tests.FilterWorkspaceSymbols(got, dirs)
if len(got) != len(expectedSymbols) {
t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
return
}
if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
t.Error(diff)
}
}
func (r *runner) callWorkspaceSymbols(t *testing.T, query string, options func(*source.Options)) []protocol.SymbolInformation {
t.Helper()
original := r.view.Options()
modified := original
options(&modified)
view, err := r.view.SetOptions(r.ctx, modified)
if err != nil {
t.Fatal(err)
}
defer r.view.SetOptions(r.ctx, original)
got, err := source.WorkspaceSymbols(r.ctx, []source.View{view}, query)
if err != nil {
t.Fatal(err)
}
return got
}
func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
_, rng, err := spanToRange(r.data, spn)
if err != nil {

View File

@ -11,6 +11,7 @@ import (
"go/types"
"strings"
"golang.org/x/tools/internal/lsp/fuzzy"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/trace"
@ -20,11 +21,6 @@ func WorkspaceSymbols(ctx context.Context, views []View, query string) ([]protoc
ctx, done := trace.StartSpan(ctx, "source.WorkspaceSymbols")
defer done()
q := strings.ToLower(query)
matcher := func(s string) bool {
return strings.Contains(strings.ToLower(s), q)
}
seen := make(map[string]struct{})
var symbols []protocol.SymbolInformation
for _, view := range views {
@ -32,6 +28,7 @@ func WorkspaceSymbols(ctx context.Context, views []View, query string) ([]protoc
if err != nil {
return nil, err
}
matcher := makeMatcher(view.Options().Matcher, query)
for _, ph := range knownPkgs {
pkg, err := ph.Check(ctx)
if err != nil {
@ -75,6 +72,25 @@ type symbolInformation struct {
type matcherFunc func(string) bool
func makeMatcher(m Matcher, query string) matcherFunc {
switch m {
case Fuzzy:
fm := fuzzy.NewMatcher(query)
return func(s string) bool {
return fm.Score(s) > 0
}
case CaseSensitive:
return func(s string) bool {
return strings.Contains(s, query)
}
default:
q := strings.ToLower(query)
return func(s string) bool {
return strings.Contains(strings.ToLower(s), q)
}
}
}
func findSymbol(decls []ast.Decl, info *types.Info, matcher matcherFunc) []symbolInformation {
var result []symbolInformation
for _, decl := range decls {

View File

@ -19,6 +19,8 @@ RenamesCount = 0
PrepareRenamesCount = 0
SymbolsCount = 0
WorkspaceSymbolsCount = 0
FuzzyWorkspaceSymbolsCount = 0
CaseSensitiveWorkspaceSymbolsCount = 0
SignaturesCount = 0
LinksCount = 0
ImplementationsCount = 0

View File

@ -3,3 +3,7 @@ package a
var WorkspaceSymbolVariableA = "a" //@symbol("WorkspaceSymbolVariableA", "WorkspaceSymbolVariableA", "Variable", "")
const WorkspaceSymbolConstantA = "a" //@symbol("WorkspaceSymbolConstantA", "WorkspaceSymbolConstantA", "Constant", "")
const (
workspacesymbolinvariable = iota //@symbol("workspacesymbolinvariable", "workspacesymbolinvariable", "Constant", "")
)

View File

@ -1,4 +1,5 @@
-- symbols --
WorkspaceSymbolVariableA Variable 3:5-3:29
WorkspaceSymbolConstantA Constant 5:7-5:31
workspacesymbolinvariable Constant 8:2-8:27

View File

@ -0,0 +1,6 @@
package casesensitive
/*@
workspacesymbolcasesensitive("baz", baz)
workspacesymbolcasesensitive("Baz", Baz)
*/

View File

@ -0,0 +1,23 @@
package fuzzy
/*@
workspacesymbolfuzzy("wsym",
WorkspaceSymbolVariableA,
WorkspaceSymbolConstantA,
workspacesymbolinvariable,
WorkspaceSymbolVariableB,
WorkspaceSymbolStructB,
)
workspacesymbolfuzzy("symbola",
WorkspaceSymbolVariableA,
WorkspaceSymbolConstantA,
workspacesymbolinvariable,
WorkspaceSymbolVariableB,
)
workspacesymbolfuzzy("symbolb",
WorkspaceSymbolVariableA,
workspacesymbolinvariable,
WorkspaceSymbolVariableB,
WorkspaceSymbolStructB,
)
*/

View File

@ -31,6 +31,7 @@ workspacesymbol("",
ioWriter,
WorkspaceSymbolVariableA,
WorkspaceSymbolConstantA,
workspacesymbolinvariable,
WorkspaceSymbolVariableB,
WorkspaceSymbolStructB,
bBar,

View File

@ -19,6 +19,8 @@ RenamesCount = 23
PrepareRenamesCount = 7
SymbolsCount = 3
WorkspaceSymbolsCount = 2
FuzzyWorkspaceSymbolsCount = 3
CaseSensitiveWorkspaceSymbolsCount = 2
SignaturesCount = 23
LinksCount = 8
ImplementationsCount = 5

View File

@ -19,6 +19,8 @@ RenamesCount = 0
PrepareRenamesCount = 0
SymbolsCount = 0
WorkspaceSymbolsCount = 0
FuzzyWorkspaceSymbolsCount = 0
CaseSensitiveWorkspaceSymbolsCount = 0
SignaturesCount = 0
LinksCount = 0
ImplementationsCount = 0

View File

@ -19,6 +19,8 @@ RenamesCount = 0
PrepareRenamesCount = 0
SymbolsCount = 0
WorkspaceSymbolsCount = 0
FuzzyWorkspaceSymbolsCount = 0
CaseSensitiveWorkspaceSymbolsCount = 0
SignaturesCount = 0
LinksCount = 0
ImplementationsCount = 0

View File

@ -19,6 +19,8 @@ RenamesCount = 0
PrepareRenamesCount = 0
SymbolsCount = 0
WorkspaceSymbolsCount = 0
FuzzyWorkspaceSymbolsCount = 0
CaseSensitiveWorkspaceSymbolsCount = 0
SignaturesCount = 0
LinksCount = 0
ImplementationsCount = 0

View File

@ -69,33 +69,35 @@ type Signatures map[span.Span]*protocol.SignatureHelp
type Links map[span.URI][]Link
type Data struct {
Config packages.Config
Exported *packagestest.Exported
Diagnostics Diagnostics
CompletionItems CompletionItems
Completions Completions
CompletionSnippets CompletionSnippets
UnimportedCompletions UnimportedCompletions
DeepCompletions DeepCompletions
FuzzyCompletions FuzzyCompletions
CaseSensitiveCompletions CaseSensitiveCompletions
RankCompletions RankCompletions
FoldingRanges FoldingRanges
Formats Formats
Imports Imports
SuggestedFixes SuggestedFixes
Definitions Definitions
Implementations Implementations
Highlights Highlights
References References
Renames Renames
PrepareRenames PrepareRenames
Symbols Symbols
symbolsChildren SymbolsChildren
symbolInformation SymbolInformation
WorkspaceSymbols WorkspaceSymbols
Signatures Signatures
Links Links
Config packages.Config
Exported *packagestest.Exported
Diagnostics Diagnostics
CompletionItems CompletionItems
Completions Completions
CompletionSnippets CompletionSnippets
UnimportedCompletions UnimportedCompletions
DeepCompletions DeepCompletions
FuzzyCompletions FuzzyCompletions
CaseSensitiveCompletions CaseSensitiveCompletions
RankCompletions RankCompletions
FoldingRanges FoldingRanges
Formats Formats
Imports Imports
SuggestedFixes SuggestedFixes
Definitions Definitions
Implementations Implementations
Highlights Highlights
References References
Renames Renames
PrepareRenames PrepareRenames
Symbols Symbols
symbolsChildren SymbolsChildren
symbolInformation SymbolInformation
WorkspaceSymbols WorkspaceSymbols
FuzzyWorkspaceSymbols WorkspaceSymbols
CaseSensitiveWorkspaceSymbols WorkspaceSymbols
Signatures Signatures
Links Links
t testing.TB
fragments map[string]string
@ -130,6 +132,8 @@ type Tests interface {
PrepareRename(*testing.T, span.Span, *source.PrepareItem)
Symbols(*testing.T, span.URI, []protocol.DocumentSymbol)
WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
Link(*testing.T, span.URI, []Link)
}
@ -163,6 +167,19 @@ const (
CompletionRank
)
type WorkspaceSymbolsTestType int
const (
// Default runs the standard workspace symbols tests.
WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota)
// Fuzzy tests workspace symbols with fuzzy matching.
WorkspaceSymbolsFuzzy
// CaseSensitive tests workspace symbols with case sensitive.
WorkspaceSymbolsCaseSensitive
)
type Completion struct {
CompletionItems []token.Pos
}
@ -243,27 +260,29 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) []*Data {
var data []*Data
for _, folder := range folders {
datum := &Data{
Diagnostics: make(Diagnostics),
CompletionItems: make(CompletionItems),
Completions: make(Completions),
CompletionSnippets: make(CompletionSnippets),
UnimportedCompletions: make(UnimportedCompletions),
DeepCompletions: make(DeepCompletions),
FuzzyCompletions: make(FuzzyCompletions),
RankCompletions: make(RankCompletions),
CaseSensitiveCompletions: make(CaseSensitiveCompletions),
Definitions: make(Definitions),
Implementations: make(Implementations),
Highlights: make(Highlights),
References: make(References),
Renames: make(Renames),
PrepareRenames: make(PrepareRenames),
Symbols: make(Symbols),
symbolsChildren: make(SymbolsChildren),
symbolInformation: make(SymbolInformation),
WorkspaceSymbols: make(WorkspaceSymbols),
Signatures: make(Signatures),
Links: make(Links),
Diagnostics: make(Diagnostics),
CompletionItems: make(CompletionItems),
Completions: make(Completions),
CompletionSnippets: make(CompletionSnippets),
UnimportedCompletions: make(UnimportedCompletions),
DeepCompletions: make(DeepCompletions),
FuzzyCompletions: make(FuzzyCompletions),
RankCompletions: make(RankCompletions),
CaseSensitiveCompletions: make(CaseSensitiveCompletions),
Definitions: make(Definitions),
Implementations: make(Implementations),
Highlights: make(Highlights),
References: make(References),
Renames: make(Renames),
PrepareRenames: make(PrepareRenames),
Symbols: make(Symbols),
symbolsChildren: make(SymbolsChildren),
symbolInformation: make(SymbolInformation),
WorkspaceSymbols: make(WorkspaceSymbols),
FuzzyWorkspaceSymbols: make(WorkspaceSymbols),
CaseSensitiveWorkspaceSymbols: make(WorkspaceSymbols),
Signatures: make(Signatures),
Links: make(Links),
t: t,
dir: folder,
@ -396,9 +415,11 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) []*Data {
}
// Collect names for the entries that require golden files.
if err := datum.Exported.Expect(map[string]interface{}{
"godef": datum.collectDefinitionNames,
"hover": datum.collectDefinitionNames,
"workspacesymbol": datum.collectWorkspaceSymbols,
"godef": datum.collectDefinitionNames,
"hover": datum.collectDefinitionNames,
"workspacesymbol": datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
"workspacesymbolfuzzy": datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
"workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
}); err != nil {
t.Fatal(err)
}
@ -428,6 +449,28 @@ func Run(t *testing.T, tests Tests, data *Data) {
}
}
eachWorkspaceSymbols := func(t *testing.T, cases map[string][]protocol.SymbolInformation, test func(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})) {
t.Helper()
for query, expectedSymbols := range cases {
name := query
if name == "" {
name = "EmptyQuery"
}
t.Run(name, func(t *testing.T) {
t.Helper()
dirs := make(map[string]struct{})
for _, si := range expectedSymbols {
d := filepath.Dir(si.Location.URI)
if _, ok := dirs[d]; !ok {
dirs[d] = struct{}{}
}
}
test(t, query, expectedSymbols, dirs)
})
}
}
t.Run("Completion", func(t *testing.T) {
t.Helper()
eachCompletion(t, data.Completions, tests.Completion)
@ -610,23 +653,17 @@ func Run(t *testing.T, tests Tests, data *Data) {
t.Run("WorkspaceSymbols", func(t *testing.T) {
t.Helper()
for query, expectedSymbols := range data.WorkspaceSymbols {
name := query
if name == "" {
name = "EmptyQuery"
}
t.Run(name, func(t *testing.T) {
t.Helper()
dirs := make(map[string]struct{})
for _, si := range expectedSymbols {
d := filepath.Dir(si.Location.URI)
if _, ok := dirs[d]; !ok {
dirs[d] = struct{}{}
}
}
tests.WorkspaceSymbols(t, query, expectedSymbols, dirs)
})
}
eachWorkspaceSymbols(t, data.WorkspaceSymbols, tests.WorkspaceSymbols)
})
t.Run("FuzzyWorkspaceSymbols", func(t *testing.T) {
t.Helper()
eachWorkspaceSymbols(t, data.FuzzyWorkspaceSymbols, tests.FuzzyWorkspaceSymbols)
})
t.Run("CaseSensitiveWorkspaceSymbols", func(t *testing.T) {
t.Helper()
eachWorkspaceSymbols(t, data.CaseSensitiveWorkspaceSymbols, tests.CaseSensitiveWorkspaceSymbols)
})
t.Run("SignatureHelp", func(t *testing.T) {
@ -716,6 +753,8 @@ func checkData(t *testing.T, data *Data) {
fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames))
fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols))
fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", len(data.WorkspaceSymbols))
fmt.Fprintf(buf, "FuzzyWorkspaceSymbolsCount = %v\n", len(data.FuzzyWorkspaceSymbols))
fmt.Fprintf(buf, "CaseSensitiveWorkspaceSymbolsCount = %v\n", len(data.CaseSensitiveWorkspaceSymbols))
fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
@ -998,9 +1037,26 @@ func (data *Data) collectSymbols(name string, spn span.Span, kind string, parent
data.symbolInformation[spn] = si
}
func (data *Data) collectWorkspaceSymbols(query string, targets []span.Span) {
for _, target := range targets {
data.WorkspaceSymbols[query] = append(data.WorkspaceSymbols[query], data.symbolInformation[target])
func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(string, []span.Span) {
switch typ {
case WorkspaceSymbolsFuzzy:
return func(query string, targets []span.Span) {
for _, target := range targets {
data.FuzzyWorkspaceSymbols[query] = append(data.FuzzyWorkspaceSymbols[query], data.symbolInformation[target])
}
}
case WorkspaceSymbolsCaseSensitive:
return func(query string, targets []span.Span) {
for _, target := range targets {
data.CaseSensitiveWorkspaceSymbols[query] = append(data.CaseSensitiveWorkspaceSymbols[query], data.symbolInformation[target])
}
}
default:
return func(query string, targets []span.Span) {
for _, target := range targets {
data.WorkspaceSymbols[query] = append(data.WorkspaceSymbols[query], data.symbolInformation[target])
}
}
}
}