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