1
0
mirror of https://github.com/golang/go synced 2024-11-18 11:14:39 -07:00

internal/lsp/fake: define Symbol method on Editor

In preparation for later changes to the implementation of the workspace
Symbol method, we add the Symbol method to fake.Editor. This requires
the definition of a number of associated fake types (editor-friendly,
byte-offset-based versions of protocol UTF16-based types) for example
fake.SymbolInformation and the types it references.

We also implement a basic regtest for the Symbol method, exposing Symbol
on regtest.Env like other LSP server methods. To aid with the writing of
Symbol result assertions, we provide some helper functions to simplify
the process of defining matches that are evaluated against the result
set.

Change-Id: If73b493e1e791c8201423a303af8041f5a15ccfc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/228121
Run-TryBot: Paul Jolly <paul@myitcv.org.uk>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Paul Jolly 2020-04-10 14:38:38 +01:00 committed by Paul Jolly
parent ca5866bcf9
commit 0bd3dbed90
5 changed files with 236 additions and 0 deletions

View File

@ -18,6 +18,13 @@ type Pos struct {
Line, Column int
}
// Range corresponds to protocol.Range, but uses the editor friend Pos
// instead of UTF-16 oriented protocol.Position
type Range struct {
Start Pos
End Pos
}
func (p Pos) toProtocolPosition() protocol.Position {
return protocol.Position{
Line: float64(p.Line),
@ -38,6 +45,21 @@ type Edit struct {
Text string
}
// Location is the editor friendly equivalent of protocol.Location
type Location struct {
Path string
Range Range
}
// SymbolInformation is an editor friendly version of
// protocol.SymbolInformation, with location information transformed to byte
// offsets. Field names correspond to the protocol type.
type SymbolInformation struct {
Name string
Kind protocol.SymbolKind
Location Location
}
// NewEdit creates an edit replacing all content between
// (startLine, startColumn) and (endLine, endColumn) with text.
func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {

View File

@ -526,6 +526,38 @@ func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (stri
return newPath, newPos, nil
}
// Symbol performs a workspace symbol search using query
func (e *Editor) Symbol(ctx context.Context, query string) ([]SymbolInformation, error) {
params := &protocol.WorkspaceSymbolParams{}
params.Query = query
resp, err := e.server.Symbol(ctx, params)
if err != nil {
return nil, fmt.Errorf("symbol: %w", err)
}
var res []SymbolInformation
for _, si := range resp {
ploc := si.Location
path := e.sandbox.Workdir.URIToPath(ploc.URI)
start := fromProtocolPosition(ploc.Range.Start)
end := fromProtocolPosition(ploc.Range.End)
rnge := Range{
Start: start,
End: end,
}
loc := Location{
Path: path,
Range: rnge,
}
res = append(res, SymbolInformation{
Name: si.Name,
Kind: si.Kind,
Location: loc,
})
}
return res, nil
}
// OrganizeImports requests and performs the source.organizeImports codeAction.
func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
return e.codeAction(ctx, path, nil, protocol.SourceOrganizeImports)

View File

@ -0,0 +1,114 @@
// 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 regtest
import (
"encoding/json"
"fmt"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
)
// expSymbolInformation and the types it references are pointer-based versions
// of fake.SymbolInformation, used to make it easier to partially assert
// against values of type fake.SymbolInformation
// expSymbolInformation is a pointer-based version of fake.SymbolInformation
type expSymbolInformation struct {
Name *string
Kind *protocol.SymbolKind
Location *expLocation
}
func (e *expSymbolInformation) matchAgainst(sis []fake.SymbolInformation) bool {
for _, si := range sis {
if e.match(si) {
return true
}
}
return false
}
func (e *expSymbolInformation) match(si fake.SymbolInformation) bool {
if e.Name != nil && *e.Name != si.Name {
return false
}
if e.Kind != nil && *e.Kind != si.Kind {
return false
}
if e.Location != nil && !e.Location.match(si.Location) {
return false
}
return true
}
func (e *expSymbolInformation) String() string {
byts, err := json.MarshalIndent(e, "", " ")
if err != nil {
panic(fmt.Errorf("failed to json.Marshal *expSymbolInformation: %v", err))
}
return string(byts)
}
// expLocation is a pointer-based version of fake.Location
type expLocation struct {
Path *string
Range *expRange
}
func (e *expLocation) match(l fake.Location) bool {
if e.Path != nil && *e.Path != l.Path {
return false
}
if e.Range != nil && !e.Range.match(l.Range) {
return false
}
return true
}
// expRange is a pointer-based version of fake.Range
type expRange struct {
Start *expPos
End *expPos
}
func (e *expRange) match(l fake.Range) bool {
if e.Start != nil && !e.Start.match(l.Start) {
return false
}
if e.End != nil && !e.End.match(l.End) {
return false
}
return true
}
// expPos is a pointer-based version of fake.Pos
type expPos struct {
Line *int
Column *int
}
func (e *expPos) match(l fake.Pos) bool {
if e.Line != nil && *e.Line != l.Line {
return false
}
if e.Column != nil && *e.Column != l.Column {
return false
}
return true
}
func pString(s string) *string {
return &s
}
func pInt(i int) *int {
return &i
}
func pKind(k protocol.SymbolKind) *protocol.SymbolKind {
return &k
}

View File

@ -0,0 +1,58 @@
// 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 regtest
import (
"testing"
"golang.org/x/tools/internal/lsp/fake"
)
const symbolSetup = `
-- go.mod --
module mod.com
go 1.12
-- main.go --
package main
import "fmt"
func main() {
fmt.Println(Message)
}
-- const.go --
package main
const Message = "Hello World."
`
// TestSymbolPos tests that, at a basic level, we get the correct position
// information for symbols matches that are returned.
func TestSymbolPos(t *testing.T) {
matcher := "caseSensitive"
opts := []RunOption{
WithEditorConfig(fake.EditorConfig{SymbolMatcher: &matcher}),
}
runner.Run(t, symbolSetup, func(t *testing.T, env *Env) {
res := env.Symbol("main")
exp := &expSymbolInformation{
Name: pString("main"),
Location: &expLocation{
Path: pString("main.go"),
Range: &expRange{
Start: &expPos{
Line: pInt(4),
Column: pInt(5),
},
},
},
}
if !exp.matchAgainst(res) {
t.Fatalf("failed to find match for main function")
}
}, opts...)
}

View File

@ -106,6 +106,16 @@ func (e *Env) GoToDefinition(name string, pos fake.Pos) (string, fake.Pos) {
return n, p
}
// Symbol returns symbols matching query
func (e *Env) Symbol(query string) []fake.SymbolInformation {
e.T.Helper()
r, err := e.Editor.Symbol(e.Ctx, query)
if err != nil {
e.T.Fatal(err)
}
return r
}
// FormatBuffer formats the editor buffer, calling t.Fatal on any error.
func (e *Env) FormatBuffer(name string) {
e.T.Helper()