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

internal/lsp: make definition use the lsp protocol

instead of driving the source pacakge directly, it indirects through the lsp
protocol (the same way check does)
We are normalizing on all the command lines doing this, so that server mode
is more viable in the future.

Change-Id: Ib5f2a059a44a5c60a53129c554e3cc14ca72c4a8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/170577
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-04-01 20:08:14 -04:00
parent d996c1aa53
commit f558378bf8
4 changed files with 127 additions and 134 deletions

View File

@ -8,8 +8,6 @@ import (
"context"
"flag"
"fmt"
"go/token"
"io/ioutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
@ -54,38 +52,27 @@ func (c *check) Run(ctx context.Context, args ...string) error {
client := &checkClient{
diagnostics: make(chan entry),
}
client.app = c.app
checking := map[span.URI][]byte{}
checking := map[span.URI]*protocol.ColumnMapper{}
// now we ready to kick things off
server, err := c.app.connect(ctx, client)
_, err := c.app.connect(ctx, client)
if err != nil {
return err
}
for _, arg := range args {
uri := span.FileURI(arg)
content, err := ioutil.ReadFile(arg)
m, err := client.AddFile(ctx, uri)
if err != nil {
return err
}
checking[uri] = content
p := &protocol.DidOpenTextDocumentParams{}
p.TextDocument.URI = string(uri)
p.TextDocument.Text = string(content)
if err := server.DidOpen(ctx, p); err != nil {
return err
}
checking[uri] = m
}
// now wait for results
for entry := range client.diagnostics {
//TODO:timeout?
content, found := checking[entry.uri]
m, found := checking[entry.uri]
if !found {
continue
}
fset := token.NewFileSet()
f := fset.AddFile(string(entry.uri), -1, len(content))
f.SetLinesForContent(content)
m := protocol.NewColumnMapper(entry.uri, fset, f, content)
for _, d := range entry.diagnostics {
spn, err := m.RangeSpan(d.Range)
if err != nil {

View File

@ -14,6 +14,7 @@ import (
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"net"
"os"
@ -118,7 +119,13 @@ func (app *Application) commands() []tool.Application {
}
}
func (app *Application) connect(ctx context.Context, client protocol.Client) (protocol.Server, error) {
type cmdClient interface {
protocol.Client
prepare(app *Application, server protocol.Server)
}
func (app *Application) connect(ctx context.Context, client cmdClient) (protocol.Server, error) {
var server protocol.Server
switch app.Remote {
case "":
@ -150,6 +157,7 @@ func (app *Application) connect(ctx context.Context, client protocol.Client) (pr
"configuration": true,
},
}
client.prepare(app, server)
if _, err := server.Initialize(ctx, params); err != nil {
return nil, err
}
@ -161,7 +169,9 @@ func (app *Application) connect(ctx context.Context, client protocol.Client) (pr
type baseClient struct {
protocol.Server
app *Application
app *Application
server protocol.Server
fset *token.FileSet
}
func (c *baseClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
@ -217,3 +227,30 @@ func (c *baseClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEd
func (c *baseClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
return nil
}
func (c *baseClient) prepare(app *Application, server protocol.Server) {
c.app = app
c.server = server
c.fset = token.NewFileSet()
}
func (c *baseClient) AddFile(ctx context.Context, uri span.URI) (*protocol.ColumnMapper, error) {
fname, err := uri.Filename()
if err != nil {
return nil, fmt.Errorf("%v: %v", uri, err)
}
content, err := ioutil.ReadFile(fname)
if err != nil {
return nil, fmt.Errorf("%v: %v", uri, err)
}
f := c.fset.AddFile(fname, -1, len(content))
f.SetLinesForContent(content)
m := protocol.NewColumnMapper(uri, c.fset, f, content)
p := &protocol.DidOpenTextDocumentParams{}
p.TextDocument.URI = string(uri)
p.TextDocument.Text = string(content)
if err := c.server.DidOpen(ctx, p); err != nil {
return nil, fmt.Errorf("%v: %v", uri, err)
}
return m, nil
}

View File

@ -5,18 +5,15 @@
package cmd
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"go/types"
"os"
"strings"
guru "golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/xlog"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/tool"
)
@ -31,9 +28,9 @@ type Definition struct {
// help is still valid.
// They refer to "Set" in "flag.FlagSet" from the DetailedHelp method below.
const (
exampleLine = 47
exampleLine = 44
exampleColumn = 47
exampleOffset = 1359
exampleOffset = 1270
)
// definition implements the definition noun for the query command.
@ -62,31 +59,67 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
if len(args) != 1 {
return tool.CommandLineErrorf("definition expects 1 argument")
}
log := xlog.New(xlog.StdSink{})
view := cache.NewView(ctx, log, "definition_test", span.FileURI(d.query.app.Config.Dir), &d.query.app.Config)
client := &baseClient{}
server, err := d.query.app.connect(ctx, client)
if err != nil {
return err
}
from := span.Parse(args[0])
f, err := view.GetFile(ctx, from.URI())
m, err := client.AddFile(ctx, from.URI())
if err != nil {
return err
}
converter := span.NewTokenConverter(view.FileSet(), f.GetToken(ctx))
rng, err := from.Range(converter)
loc, err := m.Location(from)
if err != nil {
return err
}
ident, err := source.Identifier(ctx, view, f, rng.Start)
p := protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
Position: loc.Range.Start,
}
locs, err := server.Definition(ctx, &p)
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}
if ident == nil {
if len(locs) == 0 {
return fmt.Errorf("%v: not an identifier", from)
}
hover, err := server.Hover(ctx, &p)
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}
if hover == nil {
return fmt.Errorf("%v: not an identifier", from)
}
m, err = client.AddFile(ctx, span.NewURI(locs[0].URI))
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}
definition, err := m.Span(locs[0])
if err != nil {
return fmt.Errorf("%v: %v", from, err)
}
//TODO: either work out how to request plain text, or
//use a less kludgy way of cleaning the markdown
description := hover.Contents.Value
if v := strings.TrimPrefix(description, "```go"); v != description {
description = strings.TrimSuffix(v, "```")
}
description = strings.TrimSpace(description)
var result interface{}
switch d.query.Emulate {
case "":
result, err = buildDefinition(ctx, view, ident)
result = &Definition{
Span: definition,
Description: description,
}
case emulateGuru:
result, err = buildGuruDefinition(ctx, view, ident)
pos := span.New(definition.URI(), definition.Start(), definition.Start())
result = &guru.Definition{
ObjPos: fmt.Sprint(pos),
Desc: description,
}
default:
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
}
@ -108,82 +141,3 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
}
return nil
}
func buildDefinition(ctx context.Context, view source.View, ident *source.IdentifierInfo) (*Definition, error) {
content, err := ident.Hover(ctx, nil)
if err != nil {
return nil, err
}
spn, err := ident.Declaration.Range.Span()
if err != nil {
return nil, err
}
return &Definition{
Span: spn,
Description: content,
}, nil
}
func buildGuruDefinition(ctx context.Context, view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
spn, err := ident.Declaration.Range.Span()
if err != nil {
return nil, err
}
pkg := ident.File.GetPackage(ctx)
// guru does not support ranges
if !spn.IsPoint() {
spn = span.New(spn.URI(), spn.Start(), spn.Start())
}
// Behavior that attempts to match the expected output for guru. For an example
// of the format, see the associated definition tests.
buf := &bytes.Buffer{}
q := types.RelativeTo(pkg.GetTypes())
qualifyName := ident.Declaration.Object.Pkg() != pkg.GetTypes()
name := ident.Name
var suffix interface{}
switch obj := ident.Declaration.Object.(type) {
case *types.TypeName:
fmt.Fprint(buf, "type")
case *types.Var:
if obj.IsField() {
qualifyName = false
fmt.Fprint(buf, "field")
suffix = obj.Type()
} else {
fmt.Fprint(buf, "var")
}
case *types.Func:
fmt.Fprint(buf, "func")
typ := obj.Type()
if obj.Type() != nil {
if sig, ok := typ.(*types.Signature); ok {
buf := &bytes.Buffer{}
if recv := sig.Recv(); recv != nil {
if named, ok := recv.Type().(*types.Named); ok {
fmt.Fprintf(buf, "(%s).%s", named.Obj().Name(), name)
}
}
if buf.Len() == 0 {
buf.WriteString(name)
}
types.WriteSignature(buf, sig, q)
name = buf.String()
}
}
default:
fmt.Fprintf(buf, "unknown [%T]", obj)
}
fmt.Fprint(buf, " ")
if qualifyName {
fmt.Fprintf(buf, "%s.", ident.Declaration.Object.Pkg().Path())
}
fmt.Fprint(buf, name)
if suffix != nil {
fmt.Fprint(buf, " ")
fmt.Fprint(buf, suffix)
}
return &guru.Definition{
ObjPos: fmt.Sprint(spn),
Desc: buf.String(),
}, nil
}

View File

@ -85,25 +85,7 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
for _, diag := range diags {
spn := span.Parse(diag.Pos)
if spn.IsPoint() && diag.Kind == packages.TypeError {
// Don't set a range if it's anything other than a type error.
if diagFile, err := v.GetFile(ctx, spn.URI()); err == nil {
tok := diagFile.GetToken(ctx)
if tok == nil {
v.Logger().Errorf(ctx, "Could not matching tokens for diagnostic: %v", diagFile.URI())
continue
}
content := diagFile.GetContent(ctx)
c := span.NewTokenConverter(diagFile.GetFileSet(ctx), tok)
s, err := spn.WithOffset(c)
//we just don't bother producing an error if this failed
if err == nil {
start := s.Start()
offset := start.Offset()
if l := bytes.IndexAny(content[offset:], " \n,():;[]"); l > 0 {
spn = span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+l, offset+l))
}
}
}
spn = pointToSpan(ctx, v, spn)
}
diagnostic := Diagnostic{
Span: spn,
@ -142,6 +124,39 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
return reports, nil
}
func pointToSpan(ctx context.Context, v View, spn span.Span) span.Span {
// Don't set a range if it's anything other than a type error.
diagFile, err := v.GetFile(ctx, spn.URI())
if err != nil {
v.Logger().Errorf(ctx, "Could find file for diagnostic: %v", spn.URI())
return spn
}
tok := diagFile.GetToken(ctx)
if tok == nil {
v.Logger().Errorf(ctx, "Could not find tokens for diagnostic: %v", spn.URI())
return spn
}
content := diagFile.GetContent(ctx)
if content == nil {
v.Logger().Errorf(ctx, "Could not find content for diagnostic: %v", spn.URI())
return spn
}
c := span.NewTokenConverter(diagFile.GetFileSet(ctx), tok)
s, err := spn.WithOffset(c)
//we just don't bother producing an error if this failed
if err != nil {
v.Logger().Errorf(ctx, "invalid span for diagnostic: %v: %v", spn.URI(), err)
return spn
}
start := s.Start()
offset := start.Offset()
width := bytes.IndexAny(content[offset:], " \n,():;[]")
if width <= 0 {
return spn
}
return span.New(spn.URI(), start, span.NewPoint(start.Line(), start.Column()+width, offset+width))
}
func singleDiagnostic(uri span.URI, format string, a ...interface{}) map[span.URI][]Diagnostic {
return map[span.URI][]Diagnostic{
uri: []Diagnostic{{