mirror of
https://github.com/golang/go
synced 2024-11-18 19:24:39 -07:00
internal/lsp/cmd: add the definition mode
Change-Id: Ib171016fb1bb063a6424677458b554a08144465c Reviewed-on: https://go-review.googlesource.com/c/159438 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
8dbcc66f33
commit
067a2f313b
@ -11,39 +11,45 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/internal/tool"
|
"golang.org/x/tools/internal/tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Application is the main application as passed to tool.Main
|
// Application is the main application as passed to tool.Main
|
||||||
// It handles the main command line parsing and dispatch to the sub commands.
|
// It handles the main command line parsing and dispatch to the sub commands.
|
||||||
type Application struct {
|
type Application struct {
|
||||||
|
// Core application flags
|
||||||
|
|
||||||
// Embed the basic profiling flags supported by the tool package
|
// Embed the basic profiling flags supported by the tool package
|
||||||
tool.Profile
|
tool.Profile
|
||||||
|
|
||||||
// we also include the server directly for now, so the flags work even without
|
// We include the server directly for now, so the flags work even without the verb.
|
||||||
// the verb. We should remove this when we stop allowing the server verb by
|
// TODO: Remove this when we stop allowing the server verb by default.
|
||||||
// default
|
|
||||||
Server Server
|
Server Server
|
||||||
|
|
||||||
|
// An initial, common go/packages configuration
|
||||||
|
Config packages.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements tool.Application returning the binary name.
|
// Name implements tool.Application returning the binary name.
|
||||||
func (app *Application) Name() string { return "gopls" }
|
func (app *Application) Name() string { return "gopls" }
|
||||||
|
|
||||||
// Usage implements tool.Application returning empty extra argument usage.
|
// Usage implements tool.Application returning empty extra argument usage.
|
||||||
func (app *Application) Usage() string { return "<mode> [mode-flags] [mode-args]" }
|
func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
|
||||||
|
|
||||||
// ShortHelp implements tool.Application returning the main binary help.
|
// ShortHelp implements tool.Application returning the main binary help.
|
||||||
func (app *Application) ShortHelp() string {
|
func (app *Application) ShortHelp() string {
|
||||||
return "The Go Language Smartness Provider."
|
return "The Go Language source tools."
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetailedHelp implements tool.Application returning the main binary help.
|
// DetailedHelp implements tool.Application returning the main binary help.
|
||||||
// This includes the short help for all the sub commands.
|
// This includes the short help for all the sub commands.
|
||||||
func (app *Application) DetailedHelp(f *flag.FlagSet) {
|
func (app *Application) DetailedHelp(f *flag.FlagSet) {
|
||||||
fmt.Fprint(f.Output(), `
|
fmt.Fprint(f.Output(), `
|
||||||
Available modes are:
|
Available commands are:
|
||||||
`)
|
`)
|
||||||
for _, c := range app.modes() {
|
for _, c := range app.commands() {
|
||||||
fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
|
fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
|
||||||
}
|
}
|
||||||
fmt.Fprint(f.Output(), `
|
fmt.Fprint(f.Output(), `
|
||||||
@ -61,21 +67,27 @@ func (app *Application) Run(ctx context.Context, args ...string) error {
|
|||||||
tool.Main(ctx, &app.Server, args)
|
tool.Main(ctx, &app.Server, args)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
mode, args := args[0], args[1:]
|
app.Config.Mode = packages.LoadSyntax
|
||||||
for _, m := range app.modes() {
|
app.Config.Tests = true
|
||||||
if m.Name() == mode {
|
if app.Config.Fset == nil {
|
||||||
tool.Main(ctx, m, args)
|
app.Config.Fset = token.NewFileSet()
|
||||||
|
}
|
||||||
|
command, args := args[0], args[1:]
|
||||||
|
for _, c := range app.commands() {
|
||||||
|
if c.Name() == command {
|
||||||
|
tool.Main(ctx, c, args)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tool.CommandLineErrorf("Unknown mode %v", mode)
|
return tool.CommandLineErrorf("Unknown command %v", command)
|
||||||
}
|
}
|
||||||
|
|
||||||
// modes returns the set of command modes supported by the gopls tool on the
|
// commands returns the set of commands supported by the gopls tool on the
|
||||||
// command line.
|
// command line.
|
||||||
// The mode is specified by the first non flag argument.
|
// The command is specified by the first non flag argument.
|
||||||
func (app *Application) modes() []tool.Application {
|
func (app *Application) commands() []tool.Application {
|
||||||
return []tool.Application{
|
return []tool.Application{
|
||||||
&app.Server,
|
&app.Server,
|
||||||
|
&query{app: app},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
179
internal/lsp/cmd/definition.go
Normal file
179
internal/lsp/cmd/definition.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Copyright 2019 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
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/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Definition is the result of a 'definition' query.
|
||||||
|
type Definition struct {
|
||||||
|
Location Location `json:"location"` // location of the definition
|
||||||
|
Description string `json:"description"` // description of the denoted object
|
||||||
|
}
|
||||||
|
|
||||||
|
// This constant is printed in the help, and then used in a test to verify the
|
||||||
|
// help is still valid.
|
||||||
|
// It should be the byte offset in this file of the "Set" in "flag.FlagSet" from
|
||||||
|
// the DetailedHelp method below.
|
||||||
|
const exampleOffset = 1277
|
||||||
|
|
||||||
|
// definition implements the definition noun for the query command.
|
||||||
|
type definition struct {
|
||||||
|
query *query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *definition) Name() string { return "definition" }
|
||||||
|
func (d *definition) Usage() string { return "<position>" }
|
||||||
|
func (d *definition) ShortHelp() string { return "show declaration of selected identifier" }
|
||||||
|
func (d *definition) DetailedHelp(f *flag.FlagSet) {
|
||||||
|
fmt.Fprintf(f.Output(), `
|
||||||
|
Example: show the definition of the identifier at syntax at offset %[1]v in this file (flag.FlagSet):
|
||||||
|
|
||||||
|
$ gopls definition internal/lsp/cmd/definition.go:#%[1]v
|
||||||
|
|
||||||
|
gopls definition flags are:
|
||||||
|
`, exampleOffset)
|
||||||
|
f.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run performs the definition query as specified by args and prints the
|
||||||
|
// results to stdout.
|
||||||
|
func (d *definition) Run(ctx context.Context, args ...string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return tool.CommandLineErrorf("definition expects 1 argument")
|
||||||
|
}
|
||||||
|
view := cache.NewView(&d.query.app.Config)
|
||||||
|
from, err := parseLocation(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := view.GetFile(ctx, source.ToURI(from.Filename))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tok, err := f.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pos := tok.Pos(from.Start.Offset)
|
||||||
|
ident, err := source.Identifier(ctx, view, f, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ident == nil {
|
||||||
|
return fmt.Errorf("not an identifier")
|
||||||
|
}
|
||||||
|
var result interface{}
|
||||||
|
switch d.query.Emulate {
|
||||||
|
case "":
|
||||||
|
result, err = buildDefinition(view, ident)
|
||||||
|
case emulateGuru:
|
||||||
|
result, err = buildGuruDefinition(view, ident)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.query.JSON {
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
enc.SetIndent("", "\t")
|
||||||
|
return enc.Encode(result)
|
||||||
|
}
|
||||||
|
switch d := result.(type) {
|
||||||
|
case *Definition:
|
||||||
|
fmt.Printf("%v: defined here as %s", d.Location, d.Description)
|
||||||
|
case *guru.Definition:
|
||||||
|
fmt.Printf("%s: defined here as %s", d.ObjPos, d.Desc)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("no printer for type %T", result)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDefinition(view source.View, ident *source.IdentifierInfo) (*Definition, error) {
|
||||||
|
content, err := ident.Hover(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Definition{
|
||||||
|
Location: newLocation(view.FileSet(), ident.Declaration.Range),
|
||||||
|
Description: content,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildGuruDefinition(view source.View, ident *source.IdentifierInfo) (*guru.Definition, error) {
|
||||||
|
loc := newLocation(view.FileSet(), ident.Declaration.Range)
|
||||||
|
pkg, err := ident.File.GetPackage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// guru does not support ranges
|
||||||
|
loc.End = loc.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.Types)
|
||||||
|
qualifyName := ident.Declaration.Object.Pkg() != pkg.Types
|
||||||
|
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(loc),
|
||||||
|
Desc: buf.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
163
internal/lsp/cmd/definition_test.go
Normal file
163
internal/lsp/cmd/definition_test.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2019 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 cmd_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages/packagestest"
|
||||||
|
"golang.org/x/tools/internal/lsp/cmd"
|
||||||
|
"golang.org/x/tools/internal/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var verifyGuru = flag.Bool("verify-guru", false, "Check that the guru compatability matches")
|
||||||
|
|
||||||
|
func TestDefinitionHelpExample(t *testing.T) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not get wd: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thisFile := filepath.Join(dir, "definition.go")
|
||||||
|
args := []string{"query", "definition", fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)}
|
||||||
|
expect := regexp.MustCompile(`^[\w/\\:_]+flag[/\\]flag.go:\d+:\d+,\d+:\d+: defined here as type flag.FlagSet struct{.*}$`)
|
||||||
|
got := captureStdOut(t, func() {
|
||||||
|
tool.Main(context.Background(), &cmd.Application{}, args)
|
||||||
|
})
|
||||||
|
if !expect.MatchString(got) {
|
||||||
|
t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefinition(t *testing.T) {
|
||||||
|
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
|
||||||
|
Name: "golang.org/fake",
|
||||||
|
Files: packagestest.MustCopyFileTree("testdata"),
|
||||||
|
}})
|
||||||
|
defer exported.Cleanup()
|
||||||
|
count := 0
|
||||||
|
if err := exported.Expect(map[string]interface{}{
|
||||||
|
"definition": func(fset *token.FileSet, src token.Pos, flags string, def packagestest.Range, match string) {
|
||||||
|
count++
|
||||||
|
args := []string{"query"}
|
||||||
|
if flags != "" {
|
||||||
|
args = append(args, strings.Split(flags, " ")...)
|
||||||
|
}
|
||||||
|
args = append(args, "definition")
|
||||||
|
f := fset.File(src)
|
||||||
|
loc := cmd.Location{
|
||||||
|
Filename: f.Name(),
|
||||||
|
Start: cmd.Position{
|
||||||
|
Offset: f.Offset(src),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
loc.End = loc.Start
|
||||||
|
args = append(args, fmt.Sprint(loc))
|
||||||
|
app := &cmd.Application{}
|
||||||
|
app.Config = *exported.Config
|
||||||
|
got := captureStdOut(t, func() {
|
||||||
|
tool.Main(context.Background(), app, args)
|
||||||
|
})
|
||||||
|
start := fset.Position(def.Start)
|
||||||
|
end := fset.Position(def.End)
|
||||||
|
expect := os.Expand(match, func(name string) string {
|
||||||
|
switch name {
|
||||||
|
case "file":
|
||||||
|
return start.Filename
|
||||||
|
case "efile":
|
||||||
|
qfile := strconv.Quote(start.Filename)
|
||||||
|
return qfile[1 : len(qfile)-1]
|
||||||
|
case "line":
|
||||||
|
return fmt.Sprint(start.Line)
|
||||||
|
case "col":
|
||||||
|
return fmt.Sprint(start.Column)
|
||||||
|
case "offset":
|
||||||
|
return fmt.Sprint(start.Offset)
|
||||||
|
case "eline":
|
||||||
|
return fmt.Sprint(end.Line)
|
||||||
|
case "ecol":
|
||||||
|
return fmt.Sprint(end.Column)
|
||||||
|
case "eoffset":
|
||||||
|
return fmt.Sprint(end.Offset)
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if *verifyGuru {
|
||||||
|
var guruArgs []string
|
||||||
|
runGuru := false
|
||||||
|
for _, arg := range args {
|
||||||
|
switch {
|
||||||
|
case arg == "query":
|
||||||
|
// just ignore this one
|
||||||
|
case arg == "-json":
|
||||||
|
guruArgs = append(guruArgs, arg)
|
||||||
|
case arg == "-emulate=guru":
|
||||||
|
// if we don't see this one we should not run guru
|
||||||
|
runGuru = true
|
||||||
|
case strings.HasPrefix(arg, "-"):
|
||||||
|
// unknown flag, ignore it
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
guruArgs = append(guruArgs, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if runGuru {
|
||||||
|
cmd := exec.Command("guru", guruArgs...)
|
||||||
|
cmd.Env = exported.Config.Env
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not run guru %v: %v\n%s", guruArgs, err, out)
|
||||||
|
} else {
|
||||||
|
guru := strings.TrimSpace(string(out))
|
||||||
|
if !strings.HasPrefix(expect, guru) {
|
||||||
|
t.Errorf("definition %v\nexpected:\n%s\nguru gave:\n%s", args, expect, guru)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expect != got {
|
||||||
|
t.Errorf("definition %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
t.Fatalf("No tests were run")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureStdOut(t testing.TB, f func()) string {
|
||||||
|
r, out, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
old := os.Stdout
|
||||||
|
defer func() {
|
||||||
|
os.Stdout = old
|
||||||
|
out.Close()
|
||||||
|
r.Close()
|
||||||
|
}()
|
||||||
|
os.Stdout = out
|
||||||
|
f()
|
||||||
|
out.Close()
|
||||||
|
data, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(data))
|
||||||
|
}
|
7
internal/lsp/cmd/export_test.go
Normal file
7
internal/lsp/cmd/export_test.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2019 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 cmd
|
||||||
|
|
||||||
|
const ExampleOffset = exampleOffset
|
161
internal/lsp/cmd/location.go
Normal file
161
internal/lsp/cmd/location.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2019 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Location struct {
|
||||||
|
Filename string `json:"file"`
|
||||||
|
Start Position `json:"start"`
|
||||||
|
End Position `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Position struct {
|
||||||
|
Line int `json:"line"`
|
||||||
|
Column int `json:"column"`
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocation(fset *token.FileSet, r source.Range) Location {
|
||||||
|
start := fset.Position(r.Start)
|
||||||
|
end := fset.Position(r.End)
|
||||||
|
// it should not be possible the following line to fail
|
||||||
|
filename, _ := source.ToURI(start.Filename).Filename()
|
||||||
|
return Location{
|
||||||
|
Filename: filename,
|
||||||
|
Start: Position{
|
||||||
|
Line: start.Line,
|
||||||
|
Column: start.Column,
|
||||||
|
Offset: fset.File(r.Start).Offset(r.Start),
|
||||||
|
},
|
||||||
|
End: Position{
|
||||||
|
Line: end.Line,
|
||||||
|
Column: end.Column,
|
||||||
|
Offset: fset.File(r.End).Offset(r.End),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var posRe = regexp.MustCompile(
|
||||||
|
`(?P<file>.*):(?P<start>(?P<sline>\d+):(?P<scol>\d)+|#(?P<soff>\d+))(?P<end>:(?P<eline>\d+):(?P<ecol>\d+)|#(?P<eoff>\d+))?$`)
|
||||||
|
|
||||||
|
const (
|
||||||
|
posReAll = iota
|
||||||
|
posReFile
|
||||||
|
posReStart
|
||||||
|
posReSLine
|
||||||
|
posReSCol
|
||||||
|
posReSOff
|
||||||
|
posReEnd
|
||||||
|
posReELine
|
||||||
|
posReECol
|
||||||
|
posReEOff
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
names := posRe.SubexpNames()
|
||||||
|
// verify all our submatch offsets are correct
|
||||||
|
for name, index := range map[string]int{
|
||||||
|
"file": posReFile,
|
||||||
|
"start": posReStart,
|
||||||
|
"sline": posReSLine,
|
||||||
|
"scol": posReSCol,
|
||||||
|
"soff": posReSOff,
|
||||||
|
"end": posReEnd,
|
||||||
|
"eline": posReELine,
|
||||||
|
"ecol": posReECol,
|
||||||
|
"eoff": posReEOff,
|
||||||
|
} {
|
||||||
|
if names[index] == name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// try to find it
|
||||||
|
for test := range names {
|
||||||
|
if names[test] == name {
|
||||||
|
panic(fmt.Errorf("Index for %s incorrect, wanted %v have %v", name, index, test))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("Subexp %s does not exist", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLocation parses a string of the form "file:pos" or
|
||||||
|
// file:start,end" where pos, start, end match either a byte offset in the
|
||||||
|
// form #%d or a line and column in the form %d,%d.
|
||||||
|
func parseLocation(value string) (Location, error) {
|
||||||
|
var loc Location
|
||||||
|
m := posRe.FindStringSubmatch(value)
|
||||||
|
if m == nil {
|
||||||
|
return loc, fmt.Errorf("bad location syntax %q", value)
|
||||||
|
}
|
||||||
|
loc.Filename = m[posReFile]
|
||||||
|
if m[posReSLine] != "" {
|
||||||
|
if v, err := strconv.ParseInt(m[posReSLine], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.Start.Line = int(v)
|
||||||
|
}
|
||||||
|
if v, err := strconv.ParseInt(m[posReSCol], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.Start.Column = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v, err := strconv.ParseInt(m[posReSOff], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.Start.Offset = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m[posReEnd] == "" {
|
||||||
|
loc.End = loc.Start
|
||||||
|
} else {
|
||||||
|
if m[posReELine] != "" {
|
||||||
|
if v, err := strconv.ParseInt(m[posReELine], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.End.Line = int(v)
|
||||||
|
}
|
||||||
|
if v, err := strconv.ParseInt(m[posReECol], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.End.Column = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v, err := strconv.ParseInt(m[posReEOff], 10, 32); err != nil {
|
||||||
|
return loc, err
|
||||||
|
} else {
|
||||||
|
loc.End.Offset = int(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Format(f fmt.State, c rune) {
|
||||||
|
// we should always have a filename
|
||||||
|
fmt.Fprint(f, l.Filename)
|
||||||
|
// are we in line:column format or #offset format
|
||||||
|
fmt.Fprintf(f, ":%v", l.Start)
|
||||||
|
if l.End != l.Start {
|
||||||
|
fmt.Fprintf(f, ",%v", l.End)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Position) Format(f fmt.State, c rune) {
|
||||||
|
// are we in line:column format or #offset format
|
||||||
|
if p.Line > 0 {
|
||||||
|
fmt.Fprintf(f, "%d:%d", p.Line, p.Column)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(f, "#%d", p.Offset)
|
||||||
|
}
|
71
internal/lsp/cmd/query.go
Normal file
71
internal/lsp/cmd/query.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2019 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The set of possible options that can be passed through the -emulate flag,
|
||||||
|
// which causes query to adjust its output to match that of the binary being
|
||||||
|
// emulated.
|
||||||
|
|
||||||
|
// emulateGuru tells query to emulate the output format of the guru tool.
|
||||||
|
emulateGuru = "guru"
|
||||||
|
)
|
||||||
|
|
||||||
|
// query implements the query command.
|
||||||
|
type query struct {
|
||||||
|
JSON bool `flag:"json" help:"emit output in JSON format"`
|
||||||
|
Emulate string `flag:"emulate" help:"compatability mode, causes gopls to emulate another tool.\nvalues depend on the operation being performed"`
|
||||||
|
|
||||||
|
app *Application
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *query) Name() string { return "query" }
|
||||||
|
func (q *query) Usage() string { return "query [flags] <mode> <mode args>" }
|
||||||
|
func (q *query) ShortHelp() string {
|
||||||
|
return "answer queries about go source code"
|
||||||
|
}
|
||||||
|
func (q *query) DetailedHelp(f *flag.FlagSet) {
|
||||||
|
fmt.Fprint(f.Output(), `
|
||||||
|
The mode argument determines the query to perform:
|
||||||
|
`)
|
||||||
|
for _, m := range q.modes() {
|
||||||
|
fmt.Fprintf(f.Output(), " %s : %v\n", m.Name(), m.ShortHelp())
|
||||||
|
}
|
||||||
|
fmt.Fprint(f.Output(), `
|
||||||
|
query flags are:
|
||||||
|
`)
|
||||||
|
f.PrintDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run takes the args after command flag processing, and invokes the correct
|
||||||
|
// query mode as specified by the first argument.
|
||||||
|
func (q *query) Run(ctx context.Context, args ...string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return tool.CommandLineErrorf("query must be supplied a mode")
|
||||||
|
}
|
||||||
|
mode, args := args[0], args[1:]
|
||||||
|
for _, m := range q.modes() {
|
||||||
|
if m.Name() == mode {
|
||||||
|
tool.Main(ctx, m, args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tool.CommandLineErrorf("unknown command %v", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// modes returns the set of modes supported by the query command.
|
||||||
|
func (q *query) modes() []tool.Application {
|
||||||
|
return []tool.Application{
|
||||||
|
&definition{query: q},
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,10 @@ func (s *Server) DetailedHelp(f *flag.FlagSet) {
|
|||||||
fmt.Fprint(f.Output(), `
|
fmt.Fprint(f.Output(), `
|
||||||
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
|
The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
|
||||||
a child of an editor process.
|
a child of an editor process.
|
||||||
|
|
||||||
|
gopls server flags are:
|
||||||
`)
|
`)
|
||||||
|
f.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run configures a server based on the flags, and then runs it.
|
// Run configures a server based on the flags, and then runs it.
|
||||||
|
68
internal/lsp/cmd/testdata/a/a.go
vendored
Normal file
68
internal/lsp/cmd/testdata/a/a.go
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
type Thing struct { //@Thing
|
||||||
|
Member string //@Member
|
||||||
|
}
|
||||||
|
|
||||||
|
var Other Thing //@Other
|
||||||
|
|
||||||
|
func Things(val []string) []Thing { //@Things
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Thing) Method(i int) string { //@Method
|
||||||
|
return t.Member
|
||||||
|
}
|
||||||
|
|
||||||
|
func useThings() {
|
||||||
|
t := Thing{} //@mark(aStructType, "ing")
|
||||||
|
fmt.Print(t.Member) //@mark(aMember, "ember")
|
||||||
|
fmt.Print(Other) //@mark(aVar, "ther")
|
||||||
|
Things() //@mark(aFunc, "ings")
|
||||||
|
t.Method() //@mark(aMethod, "eth")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@
|
||||||
|
definition(aStructType, "", Thing, "$file:$line:$col,$eline:$ecol: defined here as type Thing struct{Member string}")
|
||||||
|
definition(aStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type Thing")
|
||||||
|
|
||||||
|
definition(aMember, "", Member, "$file:$line:$col,$eline:$ecol: defined here as field Member string")
|
||||||
|
definition(aMember, "-emulate=guru", Member, "$file:$line:$col: defined here as field Member string")
|
||||||
|
|
||||||
|
definition(aVar, "", Other, "$file:$line:$col,$eline:$ecol: defined here as var Other Thing")
|
||||||
|
definition(aVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var Other")
|
||||||
|
|
||||||
|
definition(aFunc, "", Things, "$file:$line:$col,$eline:$ecol: defined here as func Things(val []string) []Thing")
|
||||||
|
definition(aFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func Things(val []string) []Thing")
|
||||||
|
|
||||||
|
definition(aMethod, "", Method, "$file:$line:$col,$eline:$ecol: defined here as func (Thing).Method(i int) string")
|
||||||
|
definition(aMethod, "-emulate=guru", Method, "$file:$line:$col: defined here as func (Thing).Method(i int) string")
|
||||||
|
|
||||||
|
//param
|
||||||
|
//package name
|
||||||
|
//const
|
||||||
|
//anon field
|
||||||
|
|
||||||
|
// JSON tests
|
||||||
|
|
||||||
|
definition(aStructType, "-json", Thing, `{
|
||||||
|
"location": {
|
||||||
|
"file": "$efile",
|
||||||
|
"start": {
|
||||||
|
"line": $line,
|
||||||
|
"column": $col,
|
||||||
|
"offset": $offset
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": $eline,
|
||||||
|
"column": $ecol,
|
||||||
|
"offset": $eoffset
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "type Thing struct{Member string}"
|
||||||
|
}`)
|
||||||
|
definition(aStructType, "-json -emulate=guru", Thing, `{
|
||||||
|
"objpos": "$efile:$line:$col",
|
||||||
|
"desc": "type Thing"
|
||||||
|
}`)
|
||||||
|
*/
|
26
internal/lsp/cmd/testdata/b/b.go
vendored
Normal file
26
internal/lsp/cmd/testdata/b/b.go
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/fake/a"
|
||||||
|
)
|
||||||
|
|
||||||
|
func useThings() {
|
||||||
|
t := a.Thing{} //@mark(bStructType, "ing")
|
||||||
|
fmt.Print(t.Member) //@mark(bMember, "ember")
|
||||||
|
fmt.Print(a.Other) //@mark(bVar, "ther")
|
||||||
|
a.Things() //@mark(bFunc, "ings")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@
|
||||||
|
definition(bStructType, "", Thing, "$file:$line:$col,$eline:$ecol: defined here as type a.Thing struct{Member string}")
|
||||||
|
definition(bStructType, "-emulate=guru", Thing, "$file:$line:$col: defined here as type golang.org/fake/a.Thing")
|
||||||
|
|
||||||
|
definition(bMember, "", Member, "$file:$line:$col,$eline:$ecol: defined here as field Member string")
|
||||||
|
definition(bMember, "-emulate=guru", Member, "$file:$line:$col: defined here as field Member string")
|
||||||
|
|
||||||
|
definition(bVar, "", Other, "$file:$line:$col,$eline:$ecol: defined here as var a.Other a.Thing")
|
||||||
|
definition(bVar, "-emulate=guru", Other, "$file:$line:$col: defined here as var golang.org/fake/a.Other")
|
||||||
|
|
||||||
|
definition(bFunc, "", Things, "$file:$line:$col,$eline:$ecol: defined here as func a.Things(val []string) []a.Thing")
|
||||||
|
definition(bFunc, "-emulate=guru", Things, "$file:$line:$col: defined here as func golang.org/fake/a.Things(val []string) []golang.org/fake/a.Thing")
|
||||||
|
*/
|
Loading…
Reference in New Issue
Block a user