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