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