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