1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package cmd 6 7import ( 8 "context" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "os" 13 "strings" 14 15 guru "golang.org/x/tools/cmd/guru/serial" 16 "golang.org/x/tools/internal/lsp/protocol" 17 "golang.org/x/tools/internal/span" 18 "golang.org/x/tools/internal/tool" 19) 20 21// A Definition is the result of a 'definition' query. 22type Definition struct { 23 Span span.Span `json:"span"` // span of the definition 24 Description string `json:"description"` // description of the denoted object 25} 26 27// These constant is printed in the help, and then used in a test to verify the 28// help is still valid. 29// They refer to "Set" in "flag.FlagSet" from the DetailedHelp method below. 30const ( 31 exampleLine = 44 32 exampleColumn = 47 33 exampleOffset = 1270 34) 35 36// definition implements the definition noun for the query command. 37type definition struct { 38 query *query 39} 40 41func (d *definition) Name() string { return "definition" } 42func (d *definition) Usage() string { return "<position>" } 43func (d *definition) ShortHelp() string { return "show declaration of selected identifier" } 44func (d *definition) DetailedHelp(f *flag.FlagSet) { 45 fmt.Fprintf(f.Output(), ` 46Example: show the definition of the identifier at syntax at offset %[1]v in this file (flag.FlagSet): 47 48$ gopls definition internal/lsp/cmd/definition.go:%[1]v:%[2]v 49$ gopls definition internal/lsp/cmd/definition.go:#%[3]v 50 51 gopls query definition flags are: 52`, exampleLine, exampleColumn, exampleOffset) 53 f.PrintDefaults() 54} 55 56// Run performs the definition query as specified by args and prints the 57// results to stdout. 58func (d *definition) Run(ctx context.Context, args ...string) error { 59 if len(args) != 1 { 60 return tool.CommandLineErrorf("definition expects 1 argument") 61 } 62 conn, err := d.query.app.connect(ctx) 63 if err != nil { 64 return err 65 } 66 defer conn.terminate(ctx) 67 from := span.Parse(args[0]) 68 file := conn.AddFile(ctx, from.URI()) 69 if file.err != nil { 70 return file.err 71 } 72 loc, err := file.mapper.Location(from) 73 if err != nil { 74 return err 75 } 76 p := protocol.TextDocumentPositionParams{ 77 TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, 78 Position: loc.Range.Start, 79 } 80 locs, err := conn.Definition(ctx, &p) 81 if err != nil { 82 return fmt.Errorf("%v: %v", from, err) 83 } 84 85 if len(locs) == 0 { 86 return fmt.Errorf("%v: not an identifier", from) 87 } 88 hover, err := conn.Hover(ctx, &p) 89 if err != nil { 90 return fmt.Errorf("%v: %v", from, err) 91 } 92 if hover == nil { 93 return fmt.Errorf("%v: not an identifier", from) 94 } 95 file = conn.AddFile(ctx, span.NewURI(locs[0].URI)) 96 if file.err != nil { 97 return fmt.Errorf("%v: %v", from, file.err) 98 } 99 definition, err := file.mapper.Span(locs[0]) 100 if err != nil { 101 return fmt.Errorf("%v: %v", from, err) 102 } 103 description := strings.TrimSpace(hover.Contents.Value) 104 var result interface{} 105 switch d.query.Emulate { 106 case "": 107 result = &Definition{ 108 Span: definition, 109 Description: description, 110 } 111 case emulateGuru: 112 pos := span.New(definition.URI(), definition.Start(), definition.Start()) 113 result = &guru.Definition{ 114 ObjPos: fmt.Sprint(pos), 115 Desc: description, 116 } 117 default: 118 return fmt.Errorf("unknown emulation for definition: %s", d.query.Emulate) 119 } 120 if err != nil { 121 return err 122 } 123 if d.query.JSON { 124 enc := json.NewEncoder(os.Stdout) 125 enc.SetIndent("", "\t") 126 return enc.Encode(result) 127 } 128 switch d := result.(type) { 129 case *Definition: 130 fmt.Printf("%v: defined here as %s", d.Span, d.Description) 131 case *guru.Definition: 132 fmt.Printf("%s: defined here as %s", d.ObjPos, d.Desc) 133 default: 134 return fmt.Errorf("no printer for type %T", result) 135 } 136 return nil 137} 138