+++ /dev/null
-package mod
-
-import (
- "bytes"
- "context"
- "fmt"
- "go/token"
- "strings"
-
- "golang.org/x/mod/modfile"
- "golang.org/x/tools/internal/event"
- "golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
- errors "golang.org/x/xerrors"
-)
-
-func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
- var found bool
- for _, uri := range snapshot.ModFiles() {
- if fh.URI() == uri {
- found = true
- break
- }
- }
-
- // We only provide hover information for the view's go.mod files.
- if !found {
- return nil, nil
- }
-
- ctx, done := event.Start(ctx, "mod.Hover")
- defer done()
-
- // Get the position of the cursor.
- pm, err := snapshot.ParseMod(ctx, fh)
- if err != nil {
- return nil, errors.Errorf("getting modfile handle: %w", err)
- }
- spn, err := pm.Mapper.PointSpan(position)
- if err != nil {
- return nil, errors.Errorf("computing cursor position: %w", err)
- }
- hoverRng, err := spn.Range(pm.Mapper.Converter)
- if err != nil {
- return nil, errors.Errorf("computing hover range: %w", err)
- }
-
- // Confirm that the cursor is at the position of a require statement.
- var req *modfile.Require
- var startPos, endPos int
- for _, r := range pm.File.Require {
- dep := []byte(r.Mod.Path)
- s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte
- i := bytes.Index(pm.Mapper.Content[s:e], dep)
- if i == -1 {
- continue
- }
- // Shift the start position to the location of the
- // dependency within the require statement.
- startPos, endPos = s+i, s+i+len(dep)
- if token.Pos(startPos) <= hoverRng.Start && hoverRng.Start <= token.Pos(endPos) {
- req = r
- break
- }
- }
-
- // The cursor position is not on a require statement.
- if req == nil {
- return nil, nil
- }
-
- // Get the `go mod why` results for the given file.
- why, err := snapshot.ModWhy(ctx, fh)
- if err != nil {
- return nil, err
- }
- explanation, ok := why[req.Mod.Path]
- if !ok {
- return nil, nil
- }
-
- // Get the range to highlight for the hover.
- line, col, err := pm.Mapper.Converter.ToPosition(startPos)
- if err != nil {
- return nil, err
- }
- start := span.NewPoint(line, col, startPos)
-
- line, col, err = pm.Mapper.Converter.ToPosition(endPos)
- if err != nil {
- return nil, err
- }
- end := span.NewPoint(line, col, endPos)
-
- spn = span.New(fh.URI(), start, end)
- rng, err := pm.Mapper.Range(spn)
- if err != nil {
- return nil, err
- }
- options := snapshot.View().Options()
- isPrivate := snapshot.View().IsGoPrivatePath(req.Mod.Path)
- explanation = formatExplanation(explanation, req, options, isPrivate)
- return &protocol.Hover{
- Contents: protocol.MarkupContent{
- Kind: options.PreferredContentFormat,
- Value: explanation,
- },
- Range: rng,
- }, nil
-}
-
-func formatExplanation(text string, req *modfile.Require, options *source.Options, isPrivate bool) string {
- text = strings.TrimSuffix(text, "\n")
- splt := strings.Split(text, "\n")
- length := len(splt)
-
- var b strings.Builder
- // Write the heading as an H3.
- b.WriteString("##" + splt[0])
- if options.PreferredContentFormat == protocol.Markdown {
- b.WriteString("\n\n")
- } else {
- b.WriteRune('\n')
- }
-
- // If the explanation is 2 lines, then it is of the form:
- // # golang.org/x/text/encoding
- // (main module does not need package golang.org/x/text/encoding)
- if length == 2 {
- b.WriteString(splt[1])
- return b.String()
- }
-
- imp := splt[length-1] // import path
- reference := imp
- // See golang/go#36998: don't link to modules matching GOPRIVATE.
- if !isPrivate && options.PreferredContentFormat == protocol.Markdown {
- target := imp
- if strings.ToLower(options.LinkTarget) == "pkg.go.dev" {
- target = strings.Replace(target, req.Mod.Path, req.Mod.String(), 1)
- }
- reference = fmt.Sprintf("[%s](https://%s/%s)", imp, options.LinkTarget, target)
- }
- b.WriteString("This module is necessary because " + reference + " is imported in")
-
- // If the explanation is 3 lines, then it is of the form:
- // # golang.org/x/tools
- // modtest
- // golang.org/x/tools/go/packages
- if length == 3 {
- msg := fmt.Sprintf(" `%s`.", splt[1])
- b.WriteString(msg)
- return b.String()
- }
-
- // If the explanation is more than 3 lines, then it is of the form:
- // # golang.org/x/text/language
- // rsc.io/quote
- // rsc.io/sampler
- // golang.org/x/text/language
- b.WriteString(":\n```text")
- dash := ""
- for _, imp := range splt[1 : length-1] {
- dash += "-"
- b.WriteString("\n" + dash + " " + imp)
- }
- b.WriteString("\n```")
- return b.String()
-}