Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / mod / hover.go
1 package mod
2
3 import (
4         "bytes"
5         "context"
6         "fmt"
7         "go/token"
8         "strings"
9
10         "golang.org/x/mod/modfile"
11         "golang.org/x/tools/internal/event"
12         "golang.org/x/tools/internal/lsp/protocol"
13         "golang.org/x/tools/internal/lsp/source"
14         "golang.org/x/tools/internal/span"
15         errors "golang.org/x/xerrors"
16 )
17
18 func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
19         var found bool
20         for _, uri := range snapshot.ModFiles() {
21                 if fh.URI() == uri {
22                         found = true
23                         break
24                 }
25         }
26
27         // We only provide hover information for the view's go.mod files.
28         if !found {
29                 return nil, nil
30         }
31
32         ctx, done := event.Start(ctx, "mod.Hover")
33         defer done()
34
35         // Get the position of the cursor.
36         pm, err := snapshot.ParseMod(ctx, fh)
37         if err != nil {
38                 return nil, errors.Errorf("getting modfile handle: %w", err)
39         }
40         spn, err := pm.Mapper.PointSpan(position)
41         if err != nil {
42                 return nil, errors.Errorf("computing cursor position: %w", err)
43         }
44         hoverRng, err := spn.Range(pm.Mapper.Converter)
45         if err != nil {
46                 return nil, errors.Errorf("computing hover range: %w", err)
47         }
48
49         // Confirm that the cursor is at the position of a require statement.
50         var req *modfile.Require
51         var startPos, endPos int
52         for _, r := range pm.File.Require {
53                 dep := []byte(r.Mod.Path)
54                 s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte
55                 i := bytes.Index(pm.Mapper.Content[s:e], dep)
56                 if i == -1 {
57                         continue
58                 }
59                 // Shift the start position to the location of the
60                 // dependency within the require statement.
61                 startPos, endPos = s+i, s+i+len(dep)
62                 if token.Pos(startPos) <= hoverRng.Start && hoverRng.Start <= token.Pos(endPos) {
63                         req = r
64                         break
65                 }
66         }
67
68         // The cursor position is not on a require statement.
69         if req == nil {
70                 return nil, nil
71         }
72
73         // Get the `go mod why` results for the given file.
74         why, err := snapshot.ModWhy(ctx, fh)
75         if err != nil {
76                 return nil, err
77         }
78         explanation, ok := why[req.Mod.Path]
79         if !ok {
80                 return nil, nil
81         }
82
83         // Get the range to highlight for the hover.
84         line, col, err := pm.Mapper.Converter.ToPosition(startPos)
85         if err != nil {
86                 return nil, err
87         }
88         start := span.NewPoint(line, col, startPos)
89
90         line, col, err = pm.Mapper.Converter.ToPosition(endPos)
91         if err != nil {
92                 return nil, err
93         }
94         end := span.NewPoint(line, col, endPos)
95
96         spn = span.New(fh.URI(), start, end)
97         rng, err := pm.Mapper.Range(spn)
98         if err != nil {
99                 return nil, err
100         }
101         options := snapshot.View().Options()
102         isPrivate := snapshot.View().IsGoPrivatePath(req.Mod.Path)
103         explanation = formatExplanation(explanation, req, options, isPrivate)
104         return &protocol.Hover{
105                 Contents: protocol.MarkupContent{
106                         Kind:  options.PreferredContentFormat,
107                         Value: explanation,
108                 },
109                 Range: rng,
110         }, nil
111 }
112
113 func formatExplanation(text string, req *modfile.Require, options *source.Options, isPrivate bool) string {
114         text = strings.TrimSuffix(text, "\n")
115         splt := strings.Split(text, "\n")
116         length := len(splt)
117
118         var b strings.Builder
119         // Write the heading as an H3.
120         b.WriteString("##" + splt[0])
121         if options.PreferredContentFormat == protocol.Markdown {
122                 b.WriteString("\n\n")
123         } else {
124                 b.WriteRune('\n')
125         }
126
127         // If the explanation is 2 lines, then it is of the form:
128         // # golang.org/x/text/encoding
129         // (main module does not need package golang.org/x/text/encoding)
130         if length == 2 {
131                 b.WriteString(splt[1])
132                 return b.String()
133         }
134
135         imp := splt[length-1] // import path
136         reference := imp
137         // See golang/go#36998: don't link to modules matching GOPRIVATE.
138         if !isPrivate && options.PreferredContentFormat == protocol.Markdown {
139                 target := imp
140                 if strings.ToLower(options.LinkTarget) == "pkg.go.dev" {
141                         target = strings.Replace(target, req.Mod.Path, req.Mod.String(), 1)
142                 }
143                 reference = fmt.Sprintf("[%s](https://%s/%s)", imp, options.LinkTarget, target)
144         }
145         b.WriteString("This module is necessary because " + reference + " is imported in")
146
147         // If the explanation is 3 lines, then it is of the form:
148         // # golang.org/x/tools
149         // modtest
150         // golang.org/x/tools/go/packages
151         if length == 3 {
152                 msg := fmt.Sprintf(" `%s`.", splt[1])
153                 b.WriteString(msg)
154                 return b.String()
155         }
156
157         // If the explanation is more than 3 lines, then it is of the form:
158         // # golang.org/x/text/language
159         // rsc.io/quote
160         // rsc.io/sampler
161         // golang.org/x/text/language
162         b.WriteString(":\n```text")
163         dash := ""
164         for _, imp := range splt[1 : length-1] {
165                 dash += "-"
166                 b.WriteString("\n" + dash + " " + imp)
167         }
168         b.WriteString("\n```")
169         return b.String()
170 }