mirror of
https://github.com/golang/go
synced 2024-11-18 18:14:43 -07:00
ca5866bcf9
In preparation for later changes to the workspace Symbol method, we add a separate configuration option keyed by "symbolMatcher" that specifies the type of matcher to use for workspace symbol requests. We also define a new type SymbolMatcher, the type of this new option. We require SymbolMatcher to be a separate type from Matcher because a later CL adds a type of symbol matcher that does not make sense in the context of other uses of Matcher, e.g. completion. Change-Id: Icde7d270b9efb64444f35675a8d0b48ad3b8b3dd Reviewed-on: https://go-review.googlesource.com/c/tools/+/228122 Reviewed-by: Robert Findley <rfindley@google.com>
240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
// 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 (
|
|
"context"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/internal/event"
|
|
"golang.org/x/tools/internal/lsp/fuzzy"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
)
|
|
|
|
const maxSymbols = 100
|
|
|
|
// WorkspaceSymbols matches symbols across views using the given query,
|
|
// according to the SymbolMatcher matcher.
|
|
//
|
|
// The workspace symbol method is defined in the spec as follows:
|
|
//
|
|
// > The workspace symbol request is sent from the client to the server to
|
|
// > list project-wide symbols matching the query string.
|
|
//
|
|
// It is unclear what "project-wide" means here, but given the parameters of
|
|
// workspace/symbol do not include any workspace identifier, then it has to be
|
|
// assumed that "project-wide" means "across all workspaces". Hence why
|
|
// WorkspaceSymbols receives the views []View.
|
|
//
|
|
// However, it then becomes unclear what it would mean to call WorkspaceSymbols
|
|
// with a different configured SymbolMatcher per View. Therefore we assume that
|
|
// Session level configuration will define the SymbolMatcher to be used for the
|
|
// WorkspaceSymbols method.
|
|
func WorkspaceSymbols(ctx context.Context, matcherType SymbolMatcher, views []View, query string) ([]protocol.SymbolInformation, error) {
|
|
ctx, done := event.Start(ctx, "source.WorkspaceSymbols")
|
|
defer done()
|
|
if query == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
matcher := makeMatcher(matcherType, query)
|
|
seen := make(map[string]struct{})
|
|
var symbols []protocol.SymbolInformation
|
|
outer:
|
|
for _, view := range views {
|
|
knownPkgs, err := view.Snapshot().KnownPackages(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ph := range knownPkgs {
|
|
pkg, err := ph.Check(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := seen[pkg.PkgPath()]; ok {
|
|
continue
|
|
}
|
|
seen[pkg.PkgPath()] = struct{}{}
|
|
for _, fh := range pkg.CompiledGoFiles() {
|
|
file, _, _, _, err := fh.Cached()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, si := range findSymbol(file.Decls, pkg.GetTypesInfo(), matcher) {
|
|
mrng, err := posToMappedRange(view, 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,
|
|
},
|
|
})
|
|
if len(symbols) > maxSymbols {
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return symbols, nil
|
|
}
|
|
|
|
type symbolInformation struct {
|
|
name string
|
|
kind protocol.SymbolKind
|
|
node ast.Node
|
|
}
|
|
|
|
type matcherFunc func(string) bool
|
|
|
|
func makeMatcher(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)
|
|
}
|
|
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 {
|
|
switch decl := decl.(type) {
|
|
case *ast.FuncDecl:
|
|
if matcher(decl.Name.Name) {
|
|
kind := protocol.Function
|
|
if decl.Recv != nil {
|
|
kind = protocol.Method
|
|
}
|
|
result = append(result, symbolInformation{
|
|
name: decl.Name.Name,
|
|
kind: kind,
|
|
node: decl.Name,
|
|
})
|
|
}
|
|
case *ast.GenDecl:
|
|
for _, spec := range decl.Specs {
|
|
switch spec := spec.(type) {
|
|
case *ast.TypeSpec:
|
|
if matcher(spec.Name.Name) {
|
|
result = append(result, symbolInformation{
|
|
name: spec.Name.Name,
|
|
kind: typeToKind(info.TypeOf(spec.Type)),
|
|
node: spec.Name,
|
|
})
|
|
}
|
|
switch st := spec.Type.(type) {
|
|
case *ast.StructType:
|
|
for _, field := range st.Fields.List {
|
|
result = append(result, findFieldSymbol(field, protocol.Field, matcher)...)
|
|
}
|
|
case *ast.InterfaceType:
|
|
for _, field := range st.Methods.List {
|
|
kind := protocol.Method
|
|
if len(field.Names) == 0 {
|
|
kind = protocol.Interface
|
|
}
|
|
result = append(result, findFieldSymbol(field, kind, matcher)...)
|
|
}
|
|
}
|
|
case *ast.ValueSpec:
|
|
for _, name := range spec.Names {
|
|
if matcher(name.Name) {
|
|
kind := protocol.Variable
|
|
if decl.Tok == token.CONST {
|
|
kind = protocol.Constant
|
|
}
|
|
result = append(result, symbolInformation{
|
|
name: name.Name,
|
|
kind: kind,
|
|
node: name,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func typeToKind(typ types.Type) protocol.SymbolKind {
|
|
switch typ := typ.Underlying().(type) {
|
|
case *types.Interface:
|
|
return protocol.Interface
|
|
case *types.Struct:
|
|
return protocol.Struct
|
|
case *types.Signature:
|
|
if typ.Recv() != nil {
|
|
return protocol.Method
|
|
}
|
|
return protocol.Function
|
|
case *types.Named:
|
|
return typeToKind(typ.Underlying())
|
|
case *types.Basic:
|
|
i := typ.Info()
|
|
switch {
|
|
case i&types.IsNumeric != 0:
|
|
return protocol.Number
|
|
case i&types.IsBoolean != 0:
|
|
return protocol.Boolean
|
|
case i&types.IsString != 0:
|
|
return protocol.String
|
|
}
|
|
}
|
|
return protocol.Variable
|
|
}
|
|
|
|
func findFieldSymbol(field *ast.Field, kind protocol.SymbolKind, matcher matcherFunc) []symbolInformation {
|
|
var result []symbolInformation
|
|
|
|
if len(field.Names) == 0 {
|
|
name := types.ExprString(field.Type)
|
|
if matcher(name) {
|
|
result = append(result, symbolInformation{
|
|
name: name,
|
|
kind: kind,
|
|
node: field,
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
for _, name := range field.Names {
|
|
if matcher(name.Name) {
|
|
result = append(result, symbolInformation{
|
|
name: name.Name,
|
|
kind: kind,
|
|
node: name,
|
|
})
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|