diff options
Diffstat (limited to 'internal/lsp/source/hover.go')
-rw-r--r-- | internal/lsp/source/hover.go | 870 |
1 files changed, 0 insertions, 870 deletions
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go deleted file mode 100644 index b6fd9acf9..000000000 --- a/internal/lsp/source/hover.go +++ /dev/null @@ -1,870 +0,0 @@ -// Copyright 2019 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 source - -import ( - "context" - "encoding/json" - "fmt" - "go/ast" - "go/constant" - "go/doc" - "go/format" - "go/token" - "go/types" - "strconv" - "strings" - "time" - "unicode/utf8" - - "golang.org/x/text/unicode/runenames" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/typeparams" - errors "golang.org/x/xerrors" -) - -// HoverContext contains context extracted from the syntax and type information -// of a given node, for use in various summaries (hover, autocomplete, -// signature help). -type HoverContext struct { - // signatureSource is the object or node use to derive the hover signature. - // - // It may also hold a precomputed string. - // TODO(rfindley): pre-compute all signatures to avoid this indirection. - signatureSource interface{} - - // comment is the most relevant comment group associated with the hovered object. - Comment *ast.CommentGroup -} - -// HoverJSON contains information used by hover. It is also the JSON returned -// for the "structured" hover format -type HoverJSON struct { - // Synopsis is a single sentence synopsis of the symbol's documentation. - Synopsis string `json:"synopsis"` - - // FullDocumentation is the symbol's full documentation. - FullDocumentation string `json:"fullDocumentation"` - - // Signature is the symbol's signature. - Signature string `json:"signature"` - - // SingleLine is a single line describing the symbol. - // This is recommended only for use in clients that show a single line for hover. - SingleLine string `json:"singleLine"` - - // SymbolName is the types.Object.Name for the given symbol. - SymbolName string `json:"symbolName"` - - // LinkPath is the pkg.go.dev link for the given symbol. - // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node". - LinkPath string `json:"linkPath"` - - // LinkAnchor is the pkg.go.dev link anchor for the given symbol. - // For example, the "Node" part of "pkg.go.dev/go/ast#Node". - LinkAnchor string `json:"linkAnchor"` -} - -func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) { - ident, err := Identifier(ctx, snapshot, fh, position) - if err != nil { - if hover, innerErr := hoverRune(ctx, snapshot, fh, position); innerErr == nil { - return hover, nil - } - return nil, nil - } - h, err := HoverIdentifier(ctx, ident) - if err != nil { - return nil, err - } - rng, err := ident.Range() - if err != nil { - return nil, err - } - hover, err := FormatHover(h, snapshot.View().Options()) - if err != nil { - return nil, err - } - return &protocol.Hover{ - Contents: protocol.MarkupContent{ - Kind: snapshot.View().Options().PreferredContentFormat, - Value: hover, - }, - Range: rng, - }, nil -} - -func hoverRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) { - ctx, done := event.Start(ctx, "source.hoverRune") - defer done() - - r, mrng, err := findRune(ctx, snapshot, fh, position) - if err != nil { - return nil, err - } - rng, err := mrng.Range() - if err != nil { - return nil, err - } - - var desc string - runeName := runenames.Name(r) - if len(runeName) > 0 && runeName[0] == '<' { - // Check if the rune looks like an HTML tag. If so, trim the surrounding <> - // characters to work around https://github.com/microsoft/vscode/issues/124042. - runeName = strings.TrimRight(runeName[1:], ">") - } - if strconv.IsPrint(r) { - desc = fmt.Sprintf("'%s', U+%04X, %s", string(r), uint32(r), runeName) - } else { - desc = fmt.Sprintf("U+%04X, %s", uint32(r), runeName) - } - return &protocol.Hover{ - Contents: protocol.MarkupContent{ - Kind: snapshot.View().Options().PreferredContentFormat, - Value: desc, - }, - Range: rng, - }, nil -} - -// ErrNoRuneFound is the error returned when no rune is found at a particular position. -var ErrNoRuneFound = errors.New("no rune found") - -// findRune returns rune information for a position in a file. -func findRune(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (rune, MappedRange, error) { - pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) - if err != nil { - return 0, MappedRange{}, err - } - spn, err := pgf.Mapper.PointSpan(position) - if err != nil { - return 0, MappedRange{}, err - } - rng, err := spn.Range(pgf.Mapper.Converter) - if err != nil { - return 0, MappedRange{}, err - } - pos := rng.Start - - // Find the basic literal enclosing the given position, if there is one. - var lit *ast.BasicLit - var found bool - ast.Inspect(pgf.File, func(n ast.Node) bool { - if found { - return false - } - if n, ok := n.(*ast.BasicLit); ok && pos >= n.Pos() && pos <= n.End() { - lit = n - found = true - } - return !found - }) - if !found { - return 0, MappedRange{}, ErrNoRuneFound - } - - var r rune - var start, end token.Pos - switch lit.Kind { - case token.CHAR: - s, err := strconv.Unquote(lit.Value) - if err != nil { - // If the conversion fails, it's because of an invalid syntax, therefore - // there is no rune to be found. - return 0, MappedRange{}, ErrNoRuneFound - } - r, _ = utf8.DecodeRuneInString(s) - if r == utf8.RuneError { - return 0, MappedRange{}, fmt.Errorf("rune error") - } - start, end = lit.Pos(), lit.End() - case token.INT: - // It's an integer, scan only if it is a hex litteral whose bitsize in - // ranging from 8 to 32. - if !(strings.HasPrefix(lit.Value, "0x") && len(lit.Value[2:]) >= 2 && len(lit.Value[2:]) <= 8) { - return 0, MappedRange{}, ErrNoRuneFound - } - v, err := strconv.ParseUint(lit.Value[2:], 16, 32) - if err != nil { - return 0, MappedRange{}, err - } - r = rune(v) - if r == utf8.RuneError { - return 0, MappedRange{}, fmt.Errorf("rune error") - } - start, end = lit.Pos(), lit.End() - case token.STRING: - // It's a string, scan only if it contains a unicode escape sequence under or before the - // current cursor position. - var found bool - litOffset, err := Offset(pgf.Tok, lit.Pos()) - if err != nil { - return 0, MappedRange{}, err - } - offset, err := Offset(pgf.Tok, pos) - if err != nil { - return 0, MappedRange{}, err - } - for i := offset - litOffset; i > 0; i-- { - // Start at the cursor position and search backward for the beginning of a rune escape sequence. - rr, _ := utf8.DecodeRuneInString(lit.Value[i:]) - if rr == utf8.RuneError { - return 0, MappedRange{}, fmt.Errorf("rune error") - } - if rr == '\\' { - // Got the beginning, decode it. - var tail string - r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"') - if err != nil { - // If the conversion fails, it's because of an invalid syntax, therefore is no rune to be found. - return 0, MappedRange{}, ErrNoRuneFound - } - // Only the rune escape sequence part of the string has to be highlighted, recompute the range. - runeLen := len(lit.Value) - (int(i) + len(tail)) - start = token.Pos(int(lit.Pos()) + int(i)) - end = token.Pos(int(start) + runeLen) - found = true - break - } - } - if !found { - // No escape sequence found - return 0, MappedRange{}, ErrNoRuneFound - } - default: - return 0, MappedRange{}, ErrNoRuneFound - } - - mappedRange, err := posToMappedRange(snapshot, pkg, start, end) - if err != nil { - return 0, MappedRange{}, err - } - return r, mappedRange, nil -} - -func HoverIdentifier(ctx context.Context, i *IdentifierInfo) (*HoverJSON, error) { - ctx, done := event.Start(ctx, "source.Hover") - defer done() - - hoverCtx, err := FindHoverContext(ctx, i.Snapshot, i.pkg, i.Declaration.obj, i.Declaration.node, i.Declaration.fullDecl) - if err != nil { - return nil, err - } - - h := &HoverJSON{ - FullDocumentation: hoverCtx.Comment.Text(), - Synopsis: doc.Synopsis(hoverCtx.Comment.Text()), - } - - fset := i.Snapshot.FileSet() - // Determine the symbol's signature. - switch x := hoverCtx.signatureSource.(type) { - case string: - h.Signature = x // a pre-computed signature - - case *ast.TypeSpec: - x2 := *x - // Don't duplicate comments when formatting type specs. - x2.Doc = nil - x2.Comment = nil - var b strings.Builder - b.WriteString("type ") - if err := format.Node(&b, fset, &x2); err != nil { - return nil, err - } - h.Signature = b.String() - - case ast.Node: - var b strings.Builder - if err := format.Node(&b, fset, x); err != nil { - return nil, err - } - h.Signature = b.String() - - // Check if the variable is an integer whose value we can present in a more - // user-friendly way, i.e. `var hex = 0xe34e` becomes `var hex = 58190` - if spec, ok := x.(*ast.ValueSpec); ok && len(spec.Values) > 0 { - if lit, ok := spec.Values[0].(*ast.BasicLit); ok && len(spec.Names) > 0 { - val := constant.MakeFromLiteral(types.ExprString(lit), lit.Kind, 0) - h.Signature = fmt.Sprintf("var %s = %s", spec.Names[0], val) - } - } - - case types.Object: - // If the variable is implicitly declared in a type switch, we need to - // manually generate its object string. - if typ := i.Declaration.typeSwitchImplicit; typ != nil { - if v, ok := x.(*types.Var); ok { - h.Signature = fmt.Sprintf("var %s %s", v.Name(), types.TypeString(typ, i.qf)) - break - } - } - h.Signature = objectString(x, i.qf, i.Inferred) - } - if obj := i.Declaration.obj; obj != nil { - h.SingleLine = objectString(obj, i.qf, nil) - } - obj := i.Declaration.obj - if obj == nil { - return h, nil - } - - // Check if the identifier is test-only (and is therefore not part of a - // package's API). This is true if the request originated in a test package, - // and if the declaration is also found in the same test package. - if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" { - if _, err := i.pkg.File(i.Declaration.MappedRange[0].URI()); err == nil { - return h, nil - } - } - - h.SymbolName, h.LinkPath, h.LinkAnchor = linkData(obj, i.enclosing) - - // See golang/go#36998: don't link to modules matching GOPRIVATE. - // - // The path returned by linkData is an import path. - if i.Snapshot.View().IsGoPrivatePath(h.LinkPath) { - h.LinkPath = "" - } else if mod, version, ok := moduleAtVersion(h.LinkPath, i); ok { - h.LinkPath = strings.Replace(h.LinkPath, mod, mod+"@"+version, 1) - } - - return h, nil -} - -// linkData returns the name, import path, and anchor to use in building links -// to obj. -// -// If obj is not visible in documentation, the returned name will be empty. -func linkData(obj types.Object, enclosing *types.TypeName) (name, importPath, anchor string) { - // Package names simply link to the package. - if obj, ok := obj.(*types.PkgName); ok { - return obj.Name(), obj.Imported().Path(), "" - } - - // Builtins link to the special builtin package. - if obj.Parent() == types.Universe { - return obj.Name(), "builtin", obj.Name() - } - - // In all other cases, the object must be exported. - if !obj.Exported() { - return "", "", "" - } - - var recv types.Object // If non-nil, the field or method receiver base. - - switch obj := obj.(type) { - case *types.Var: - // If the object is a field, and we have an associated selector - // composite literal, or struct, we can determine the link. - if obj.IsField() && enclosing != nil { - recv = enclosing - } - case *types.Func: - typ, ok := obj.Type().(*types.Signature) - if !ok { - // Note: this should never happen. go/types guarantees that the type of - // *Funcs are Signatures. - // - // TODO(rfindley): given a 'debug' mode, we should panic here. - return "", "", "" - } - if r := typ.Recv(); r != nil { - if rtyp, _ := Deref(r.Type()).(*types.Named); rtyp != nil { - // If we have an unexported type, see if the enclosing type is - // exported (we may have an interface or struct we can link - // to). If not, don't show any link. - if !rtyp.Obj().Exported() { - if enclosing != nil { - recv = enclosing - } else { - return "", "", "" - } - } else { - recv = rtyp.Obj() - } - } - } - } - - if recv != nil && !recv.Exported() { - return "", "", "" - } - - // Either the object or its receiver must be in the package scope. - scopeObj := obj - if recv != nil { - scopeObj = recv - } - if scopeObj.Pkg() == nil || scopeObj.Pkg().Scope().Lookup(scopeObj.Name()) != scopeObj { - return "", "", "" - } - - importPath = obj.Pkg().Path() - if recv != nil { - anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name()) - name = fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), recv.Name(), obj.Name()) - } else { - // For most cases, the link is "package/path#symbol". - anchor = obj.Name() - name = fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name()) - } - return name, importPath, anchor -} - -func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) { - // TODO(rfindley): moduleAtVersion should not be responsible for deciding - // whether or not the link target supports module version links. - if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" { - return "", "", false - } - impPkg, err := i.pkg.GetImport(path) - if err != nil { - return "", "", false - } - if impPkg.Version() == nil { - return "", "", false - } - version, modpath := impPkg.Version().Version, impPkg.Version().Path - if modpath == "" || version == "" { - return "", "", false - } - return modpath, version, true -} - -// objectString is a wrapper around the types.ObjectString function. -// It handles adding more information to the object string. -func objectString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string { - // If the signature type was inferred, prefer the preferred signature with a - // comment showing the generic signature. - if sig, _ := obj.Type().(*types.Signature); sig != nil && typeparams.ForSignature(sig).Len() > 0 && inferred != nil { - obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred) - str := types.ObjectString(obj2, qf) - // Try to avoid overly long lines. - if len(str) > 60 { - str += "\n" - } else { - str += " " - } - str += "// " + types.TypeString(sig, qf) - return str - } - str := types.ObjectString(obj, qf) - switch obj := obj.(type) { - case *types.Const: - str = fmt.Sprintf("%s = %s", str, obj.Val()) - - // Try to add a formatted duration as an inline comment - typ, ok := obj.Type().(*types.Named) - if !ok { - break - } - pkg := typ.Obj().Pkg() - if pkg.Path() == "time" && typ.Obj().Name() == "Duration" { - if d, ok := constant.Int64Val(obj.Val()); ok { - str += " // " + time.Duration(d).String() - } - } - } - return str -} - -// FindHoverContext returns a HoverContext struct for an AST node and its -// declaration object. node should be the actual node used in type checking, -// while fullNode could be a separate node with more complete syntactic -// information. -func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Object, pkgNode ast.Node, fullDecl ast.Decl) (*HoverContext, error) { - var info *HoverContext - - // Type parameters get their signature from their declaration object. - if _, isTypeName := obj.(*types.TypeName); isTypeName { - if _, isTypeParam := obj.Type().(*typeparams.TypeParam); isTypeParam { - return &HoverContext{signatureSource: obj}, nil - } - } - - // This is problematic for a number of reasons. We really need to have a more - // general mechanism to validate the coherency of AST with type information, - // but absent that we must do our best to ensure that we don't use fullNode - // when we actually need the node that was type checked. - // - // pkgNode may be nil, if it was eliminated from the type-checked syntax. In - // that case, use fullDecl if available. - node := pkgNode - if node == nil && fullDecl != nil { - node = fullDecl - } - - switch node := node.(type) { - case *ast.Ident: - // The package declaration. - for _, f := range pkg.GetSyntax() { - if f.Name == pkgNode { - info = &HoverContext{Comment: f.Doc} - } - } - case *ast.ImportSpec: - // Try to find the package documentation for an imported package. - if pkgName, ok := obj.(*types.PkgName); ok { - imp, err := pkg.GetImport(pkgName.Imported().Path()) - if err != nil { - return nil, err - } - // Assume that only one file will contain package documentation, - // so pick the first file that has a doc comment. - for _, file := range imp.GetSyntax() { - if file.Doc != nil { - info = &HoverContext{signatureSource: obj, Comment: file.Doc} - break - } - } - } - info = &HoverContext{signatureSource: node} - case *ast.GenDecl: - switch obj := obj.(type) { - case *types.TypeName, *types.Var, *types.Const, *types.Func: - // Always use the full declaration here if we have it, because the - // dependent code doesn't rely on pointer identity. This is fragile. - if d, _ := fullDecl.(*ast.GenDecl); d != nil { - node = d - } - // obj may not have been produced by type checking the AST containing - // node, so we need to be careful about using token.Pos. - tok := s.FileSet().File(obj.Pos()) - offset, err := Offset(tok, obj.Pos()) - if err != nil { - return nil, err - } - - // fullTok and fullPos are the *token.File and object position in for the - // full AST. - fullTok := s.FileSet().File(node.Pos()) - fullPos, err := Pos(fullTok, offset) - if err != nil { - return nil, err - } - - var spec ast.Spec - for _, s := range node.Specs { - // Avoid panics by guarding the calls to token.Offset (golang/go#48249). - start, err := Offset(fullTok, s.Pos()) - if err != nil { - return nil, err - } - end, err := Offset(fullTok, s.End()) - if err != nil { - return nil, err - } - if start <= offset && offset <= end { - spec = s - break - } - } - - info, err = hoverGenDecl(node, spec, fullPos, obj) - if err != nil { - return nil, err - } - } - case *ast.TypeSpec: - if obj.Parent() == types.Universe { - if genDecl, ok := fullDecl.(*ast.GenDecl); ok { - info = hoverTypeSpec(node, genDecl) - } - } - case *ast.FuncDecl: - switch obj.(type) { - case *types.Func: - info = &HoverContext{signatureSource: obj, Comment: node.Doc} - case *types.Builtin: - info = &HoverContext{Comment: node.Doc} - if sig, err := NewBuiltinSignature(ctx, s, obj.Name()); err == nil { - info.signatureSource = "func " + sig.name + sig.Format() - } else { - // Fall back on the object as a signature source. - - // TODO(rfindley): refactor so that we can report bugs from the source - // package. - - // debug.Bug(ctx, "invalid builtin hover", "did not find builtin signature: %v", err) - info.signatureSource = obj - } - case *types.Var: - // Object is a function param or the field of an anonymous struct - // declared with ':='. Skip the first one because only fields - // can have docs. - if isFunctionParam(obj, node) { - break - } - - field, err := s.PosToField(ctx, pkg, obj.Pos()) - if err != nil { - return nil, err - } - - if field != nil { - comment := field.Doc - if comment.Text() == "" { - comment = field.Comment - } - info = &HoverContext{signatureSource: obj, Comment: comment} - } - } - } - - if info == nil { - info = &HoverContext{signatureSource: obj} - } - - return info, nil -} - -// isFunctionParam returns true if the passed object is either an incoming -// or an outgoing function param -func isFunctionParam(obj types.Object, node *ast.FuncDecl) bool { - for _, f := range node.Type.Params.List { - if f.Pos() == obj.Pos() { - return true - } - } - if node.Type.Results != nil { - for _, f := range node.Type.Results.List { - if f.Pos() == obj.Pos() { - return true - } - } - } - return false -} - -// hoverGenDecl returns hover information an object declared via spec inside -// of the GenDecl node. obj is the type-checked object corresponding to the -// declaration, but may have been type-checked using a different AST than the -// given nodes; fullPos is the position of obj in node's AST. -func hoverGenDecl(node *ast.GenDecl, spec ast.Spec, fullPos token.Pos, obj types.Object) (*HoverContext, error) { - if spec == nil { - return nil, errors.Errorf("no spec for node %v at position %v", node, fullPos) - } - - // If we have a field or method. - switch obj.(type) { - case *types.Var, *types.Const, *types.Func: - return hoverVar(spec, fullPos, obj, node), nil - } - // Handle types. - switch spec := spec.(type) { - case *ast.TypeSpec: - return hoverTypeSpec(spec, node), nil - case *ast.ValueSpec: - return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil - case *ast.ImportSpec: - return &HoverContext{signatureSource: spec, Comment: spec.Doc}, nil - } - return nil, errors.Errorf("unable to format spec %v (%T)", spec, spec) -} - -// TODO(rfindley): rename this function. -func hoverTypeSpec(spec *ast.TypeSpec, decl *ast.GenDecl) *HoverContext { - comment := spec.Doc - if comment == nil && decl != nil { - comment = decl.Doc - } - if comment == nil { - comment = spec.Comment - } - return &HoverContext{ - signatureSource: spec, - Comment: comment, - } -} - -func hoverVar(node ast.Spec, fullPos token.Pos, obj types.Object, decl *ast.GenDecl) *HoverContext { - var fieldList *ast.FieldList - switch spec := node.(type) { - case *ast.TypeSpec: - switch t := spec.Type.(type) { - case *ast.StructType: - fieldList = t.Fields - case *ast.InterfaceType: - fieldList = t.Methods - } - case *ast.ValueSpec: - // Try to extract the field list of an anonymous struct - if fieldList = extractFieldList(spec.Type); fieldList != nil { - break - } - - comment := spec.Doc - if comment == nil { - comment = decl.Doc - } - if comment == nil { - comment = spec.Comment - } - - // We need the AST nodes for variable declarations of basic literals with - // associated values so that we can augment their hover with more information. - if _, ok := obj.(*types.Var); ok && spec.Type == nil && len(spec.Values) > 0 { - if _, ok := spec.Values[0].(*ast.BasicLit); ok { - return &HoverContext{signatureSource: spec, Comment: comment} - } - } - - return &HoverContext{signatureSource: obj, Comment: comment} - } - - if fieldList != nil { - comment := findFieldComment(fullPos, fieldList) - return &HoverContext{signatureSource: obj, Comment: comment} - } - return &HoverContext{signatureSource: obj, Comment: decl.Doc} -} - -// extractFieldList recursively tries to extract a field list. -// If it is not found, nil is returned. -func extractFieldList(specType ast.Expr) *ast.FieldList { - switch t := specType.(type) { - case *ast.StructType: - return t.Fields - case *ast.InterfaceType: - return t.Methods - case *ast.ArrayType: - return extractFieldList(t.Elt) - case *ast.MapType: - // Map value has a greater chance to be a struct - if fields := extractFieldList(t.Value); fields != nil { - return fields - } - return extractFieldList(t.Key) - case *ast.ChanType: - return extractFieldList(t.Value) - } - return nil -} - -// findFieldComment visits all fields in depth-first order and returns -// the comment of a field with passed position. If no comment is found, -// nil is returned. -func findFieldComment(pos token.Pos, fieldList *ast.FieldList) *ast.CommentGroup { - for _, field := range fieldList.List { - if field.Pos() == pos { - if field.Doc.Text() != "" { - return field.Doc - } - return field.Comment - } - - if nestedFieldList := extractFieldList(field.Type); nestedFieldList != nil { - if c := findFieldComment(pos, nestedFieldList); c != nil { - return c - } - } - } - return nil -} - -func FormatHover(h *HoverJSON, options *Options) (string, error) { - signature := formatSignature(h, options) - - switch options.HoverKind { - case SingleLine: - return h.SingleLine, nil - case NoDocumentation: - return signature, nil - case Structured: - b, err := json.Marshal(h) - if err != nil { - return "", err - } - return string(b), nil - } - - link := formatLink(h, options) - doc := formatDoc(h, options) - - var b strings.Builder - parts := []string{signature, doc, link} - for i, el := range parts { - if el != "" { - b.WriteString(el) - - // Don't write out final newline. - if i == len(parts) { - continue - } - // If any elements of the remainder of the list are non-empty, - // write a newline. - if anyNonEmpty(parts[i+1:]) { - if options.PreferredContentFormat == protocol.Markdown { - b.WriteString("\n\n") - } else { - b.WriteRune('\n') - } - } - } - } - return b.String(), nil -} - -func formatSignature(h *HoverJSON, options *Options) string { - signature := h.Signature - if signature != "" && options.PreferredContentFormat == protocol.Markdown { - signature = fmt.Sprintf("```go\n%s\n```", signature) - } - return signature -} - -func formatLink(h *HoverJSON, options *Options) string { - if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" { - return "" - } - plainLink := BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) - switch options.PreferredContentFormat { - case protocol.Markdown: - return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink) - case protocol.PlainText: - return "" - default: - return plainLink - } -} - -// BuildLink constructs a link with the given target, path, and anchor. -func BuildLink(target, path, anchor string) string { - link := fmt.Sprintf("https://%s/%s", target, path) - if target == "pkg.go.dev" { - link += "?utm_source=gopls" - } - if anchor == "" { - return link - } - return link + "#" + anchor -} - -func formatDoc(h *HoverJSON, options *Options) string { - var doc string - switch options.HoverKind { - case SynopsisDocumentation: - doc = h.Synopsis - case FullDocumentation: - doc = h.FullDocumentation - } - if options.PreferredContentFormat == protocol.Markdown { - return CommentToMarkdown(doc) - } - return doc -} - -func anyNonEmpty(x []string) bool { - for _, el := range x { - if el != "" { - return true - } - } - return false -} |