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	"sort"
13
14	"golang.org/x/tools/internal/lsp/protocol"
15	"golang.org/x/tools/internal/span"
16	"golang.org/x/tools/internal/tool"
17)
18
19// symbols implements the symbols verb for gopls
20type symbols struct {
21	app *Application
22}
23
24func (r *symbols) Name() string      { return "symbols" }
25func (r *symbols) Usage() string     { return "<file>" }
26func (r *symbols) ShortHelp() string { return "display selected file's symbols" }
27func (r *symbols) DetailedHelp(f *flag.FlagSet) {
28	fmt.Fprint(f.Output(), `
29Example:
30  $ gopls symbols helper/helper.go
31`)
32	f.PrintDefaults()
33}
34func (r *symbols) Run(ctx context.Context, args ...string) error {
35	if len(args) != 1 {
36		return tool.CommandLineErrorf("symbols expects 1 argument (position)")
37	}
38
39	conn, err := r.app.connect(ctx)
40	if err != nil {
41		return err
42	}
43	defer conn.terminate(ctx)
44
45	from := span.Parse(args[0])
46	p := protocol.DocumentSymbolParams{
47		TextDocument: protocol.TextDocumentIdentifier{
48			URI: protocol.URIFromSpanURI(from.URI()),
49		},
50	}
51	symbols, err := conn.DocumentSymbol(ctx, &p)
52	if err != nil {
53		return err
54	}
55	for _, s := range symbols {
56		if m, ok := s.(map[string]interface{}); ok {
57			s, err = mapToSymbol(m)
58			if err != nil {
59				return err
60			}
61		}
62		switch t := s.(type) {
63		case protocol.DocumentSymbol:
64			printDocumentSymbol(t)
65		case protocol.SymbolInformation:
66			printSymbolInformation(t)
67		}
68	}
69	return nil
70}
71
72func mapToSymbol(m map[string]interface{}) (interface{}, error) {
73	b, err := json.Marshal(m)
74	if err != nil {
75		return nil, err
76	}
77
78	if _, ok := m["selectionRange"]; ok {
79		var s protocol.DocumentSymbol
80		if err := json.Unmarshal(b, &s); err != nil {
81			return nil, err
82		}
83		return s, nil
84	}
85
86	var s protocol.SymbolInformation
87	if err := json.Unmarshal(b, &s); err != nil {
88		return nil, err
89	}
90	return s, nil
91}
92
93func printDocumentSymbol(s protocol.DocumentSymbol) {
94	fmt.Printf("%s %s %s\n", s.Name, s.Kind, positionToString(s.SelectionRange))
95	// Sort children for consistency
96	sort.Slice(s.Children, func(i, j int) bool {
97		return s.Children[i].Name < s.Children[j].Name
98	})
99	for _, c := range s.Children {
100		fmt.Printf("\t%s %s %s\n", c.Name, c.Kind, positionToString(c.SelectionRange))
101	}
102}
103
104func printSymbolInformation(s protocol.SymbolInformation) {
105	fmt.Printf("%s %s %s\n", s.Name, s.Kind, positionToString(s.Location.Range))
106}
107
108func positionToString(r protocol.Range) string {
109	return fmt.Sprintf("%v:%v-%v:%v",
110		r.Start.Line+1,
111		r.Start.Character+1,
112		r.End.Line+1,
113		r.End.Character+1,
114	)
115}
116