1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"context"
"flag"
"fmt"
"strings"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/tool"
)
// callHierarchy implements the callHierarchy verb for gopls.
type callHierarchy struct {
app *Application
}
func (c *callHierarchy) Name() string { return "call_hierarchy" }
func (c *callHierarchy) Usage() string { return "<position>" }
func (c *callHierarchy) ShortHelp() string { return "display selected identifier's call hierarchy" }
func (c *callHierarchy) DetailedHelp(f *flag.FlagSet) {
fmt.Fprint(f.Output(), `
Example:
$ # 1-indexed location (:line:column or :#offset) of the target identifier
$ gopls call_hierarchy helper/helper.go:8:6
$ gopls call_hierarchy helper/helper.go:#53
gopls call_hierarchy flags are:
`)
f.PrintDefaults()
}
func (c *callHierarchy) Run(ctx context.Context, args ...string) error {
if len(args) != 1 {
return tool.CommandLineErrorf("call_hierarchy expects 1 argument (position)")
}
conn, err := c.app.connect(ctx)
if err != nil {
return err
}
defer conn.terminate(ctx)
from := span.Parse(args[0])
file := conn.AddFile(ctx, from.URI())
if file.err != nil {
return file.err
}
loc, err := file.mapper.Location(from)
if err != nil {
return err
}
p := protocol.CallHierarchyPrepareParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
Position: loc.Range.Start,
},
}
callItems, err := conn.PrepareCallHierarchy(ctx, &p)
if err != nil {
return err
}
if len(callItems) == 0 {
return fmt.Errorf("function declaration identifier not found at %v", args[0])
}
for _, item := range callItems {
incomingCalls, err := conn.IncomingCalls(ctx, &protocol.CallHierarchyIncomingCallsParams{Item: item})
if err != nil {
return err
}
for i, call := range incomingCalls {
// From the spec: CallHierarchyIncomingCall.FromRanges is relative to
// the caller denoted by CallHierarchyIncomingCall.from.
printString, err := callItemPrintString(ctx, conn, call.From, call.From.URI, call.FromRanges)
if err != nil {
return err
}
fmt.Printf("caller[%d]: %s\n", i, printString)
}
printString, err := callItemPrintString(ctx, conn, item, "", nil)
if err != nil {
return err
}
fmt.Printf("identifier: %s\n", printString)
outgoingCalls, err := conn.OutgoingCalls(ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: item})
if err != nil {
return err
}
for i, call := range outgoingCalls {
// From the spec: CallHierarchyOutgoingCall.FromRanges is the range
// relative to the caller, e.g the item passed to
printString, err := callItemPrintString(ctx, conn, call.To, item.URI, call.FromRanges)
if err != nil {
return err
}
fmt.Printf("callee[%d]: %s\n", i, printString)
}
}
return nil
}
// callItemPrintString returns a protocol.CallHierarchyItem object represented as a string.
// item and call ranges (protocol.Range) are converted to user friendly spans (1-indexed).
func callItemPrintString(ctx context.Context, conn *connection, item protocol.CallHierarchyItem, callsURI protocol.DocumentURI, calls []protocol.Range) (string, error) {
itemFile := conn.AddFile(ctx, item.URI.SpanURI())
if itemFile.err != nil {
return "", itemFile.err
}
itemSpan, err := itemFile.mapper.Span(protocol.Location{URI: item.URI, Range: item.Range})
if err != nil {
return "", err
}
callsFile := conn.AddFile(ctx, callsURI.SpanURI())
if callsURI != "" && callsFile.err != nil {
return "", callsFile.err
}
var callRanges []string
for _, rng := range calls {
callSpan, err := callsFile.mapper.Span(protocol.Location{URI: item.URI, Range: rng})
if err != nil {
return "", err
}
spn := fmt.Sprint(callSpan)
callRanges = append(callRanges, fmt.Sprint(spn[strings.Index(spn, ":")+1:]))
}
printString := fmt.Sprintf("function %s in %v", item.Name, itemSpan)
if len(calls) > 0 {
printString = fmt.Sprintf("ranges %s in %s from/to %s", strings.Join(callRanges, ", "), callsURI.SpanURI().Filename(), printString)
}
return printString, nil
}
|