internal/lsp: improve link in documentation on hover

This change refactors some of the logic that builds a link anchor for
a given symbol, pushing the actual Link into the HoverInformation struct.
This is necessary because type information is needed to build up that
link in certain cases, like methods.

The last step will be to correctly display struct fields.

Updates golang/go#34240
Fixes golang/go#36031

Change-Id: I7f989faddbaa07f91838a870b4477bf78ce8ddf7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/210201
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 291ef41..e0737ad 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -168,7 +168,7 @@
 	case "":
 		connection := newConnection(app)
 		ctx, connection.Server = lsp.NewClientServer(ctx, cache.New(app.options), connection.Client)
-		return connection, connection.initialize(ctx)
+		return connection, connection.initialize(ctx, app.options)
 	case "internal":
 		internalMu.Lock()
 		defer internalMu.Unlock()
@@ -186,7 +186,7 @@
 			ctx, srv := lsp.NewServer(ctx, cache.New(app.options), jsonrpc2.NewHeaderStream(sr, sw))
 			srv.Run(ctx)
 		}()
-		if err := connection.initialize(ctx); err != nil {
+		if err := connection.initialize(ctx, app.options); err != nil {
 			return nil, err
 		}
 		internalConnections[app.wd] = connection
@@ -201,17 +201,24 @@
 		var jc *jsonrpc2.Conn
 		ctx, jc, connection.Server = protocol.NewClient(ctx, stream, connection.Client)
 		go jc.Run(ctx)
-		return connection, connection.initialize(ctx)
+		return connection, connection.initialize(ctx, app.options)
 	}
 }
 
-func (c *connection) initialize(ctx context.Context) error {
+func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
 	params := &protocol.ParamInitialize{}
 	params.RootURI = string(span.FileURI(c.Client.app.wd))
 	params.Capabilities.Workspace.Configuration = true
-	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
-		ContentFormat: []protocol.MarkupKind{protocol.PlainText},
+
+	// Make sure to respect configured options when sending initialize request.
+	opts := source.DefaultOptions
+	if options != nil {
+		options(&opts)
 	}
+	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
+		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
+	}
+
 	if _, err := c.Server.Initialize(ctx, params); err != nil {
 		return err
 	}
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
index c804aec..5cabac2 100644
--- a/internal/lsp/cmd/definition.go
+++ b/internal/lsp/cmd/definition.go
@@ -14,6 +14,7 @@
 
 	guru "golang.org/x/tools/cmd/guru/serial"
 	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/tool"
 	errors "golang.org/x/xerrors"
@@ -60,6 +61,15 @@
 	if len(args) != 1 {
 		return tool.CommandLineErrorf("definition expects 1 argument")
 	}
+	// Plaintext makes more sense for the command line.
+	opts := d.query.app.options
+	d.query.app.options = func(o *source.Options) {
+		opts(o)
+		o.PreferredContentFormat = protocol.PlainText
+		if d.query.MarkdownSupported {
+			o.PreferredContentFormat = protocol.Markdown
+		}
+	}
 	conn, err := d.query.app.connect(ctx)
 	if err != nil {
 		return err
diff --git a/internal/lsp/cmd/query.go b/internal/lsp/cmd/query.go
index 037b4d0..747b90e 100644
--- a/internal/lsp/cmd/query.go
+++ b/internal/lsp/cmd/query.go
@@ -23,8 +23,9 @@
 
 // query implements the query command.
 type query struct {
-	JSON    bool   `flag:"json" help:"emit output in JSON format"`
-	Emulate string `flag:"emulate" help:"compatibility mode, causes gopls to emulate another tool.\nvalues depend on the operation being performed"`
+	JSON              bool   `flag:"json" help:"emit output in JSON format"`
+	MarkdownSupported bool   `flag:"markdown" help:"support markdown in responses"`
+	Emulate           string `flag:"emulate" help:"compatibility mode, causes gopls to emulate another tool.\nvalues depend on the operation being performed"`
 
 	app *Application
 }
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index cc0c3ba..d8341d0 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -6,13 +6,10 @@
 
 import (
 	"context"
-	"encoding/json"
-	"fmt"
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
-	"golang.org/x/tools/internal/telemetry/log"
 )
 
 func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
@@ -33,7 +30,7 @@
 	if err != nil {
 		return nil, nil
 	}
-	hover, err := ident.Hover(ctx)
+	h, err := ident.Hover(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -41,59 +38,15 @@
 	if err != nil {
 		return nil, err
 	}
-	contents := s.toProtocolHoverContents(ctx, hover, view.Options())
+	hover, err := source.FormatHover(h, view.Options())
+	if err != nil {
+		return nil, err
+	}
 	return &protocol.Hover{
-		Contents: contents,
-		Range:    rng,
+		Contents: protocol.MarkupContent{
+			Kind:  view.Options().PreferredContentFormat,
+			Value: hover,
+		},
+		Range: rng,
 	}, nil
 }
-
-func (s *Server) toProtocolHoverContents(ctx context.Context, h *source.HoverInformation, options source.Options) protocol.MarkupContent {
-	content := protocol.MarkupContent{
-		Kind: options.PreferredContentFormat,
-	}
-	signature := h.Signature
-	if content.Kind == protocol.Markdown {
-		signature = fmt.Sprintf("```go\n%s\n```", h.Signature)
-	}
-
-	switch options.HoverKind {
-	case source.SingleLine:
-		doc := h.SingleLine
-		if content.Kind == protocol.Markdown {
-			doc = source.CommentToMarkdown(doc)
-		}
-		content.Value = doc
-	case source.NoDocumentation:
-		content.Value = signature
-	case source.SynopsisDocumentation:
-		if h.Synopsis != "" {
-			doc := h.Synopsis
-			if content.Kind == protocol.Markdown {
-				doc = source.CommentToMarkdown(h.Synopsis)
-			}
-			content.Value = fmt.Sprintf("%s\n%s", doc, signature)
-		} else {
-			content.Value = signature
-		}
-		content.Value += "\n" + h.DocumentationLink(options)
-	case source.FullDocumentation:
-		if h.FullDocumentation != "" {
-			doc := h.FullDocumentation
-			if content.Kind == protocol.Markdown {
-				doc = source.CommentToMarkdown(h.FullDocumentation)
-			}
-			content.Value = fmt.Sprintf("%s\n%s", signature, doc)
-		} else {
-			content.Value = signature
-		}
-		content.Value += "\n" + h.DocumentationLink(options)
-	case source.Structured:
-		b, err := json.Marshal(h)
-		if err != nil {
-			log.Error(ctx, "failed to marshal structured hover", err)
-		}
-		content.Value = string(b)
-	}
-	return content
-}
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 54b1070..f16620f 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -6,6 +6,7 @@
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"go/ast"
 	"go/doc"
@@ -13,6 +14,7 @@
 	"go/types"
 	"strings"
 
+	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/telemetry/trace"
 	errors "golang.org/x/xerrors"
 )
@@ -31,23 +33,17 @@
 	// FullDocumentation is the symbol's full documentation.
 	FullDocumentation string `json:"fullDocumentation"`
 
-	// pkgPath holds the package path of the hovered symbol.
-	pkgPath string
+	// Link is the pkg.go.dev anchor for the given symbol.
+	// For example, "go/ast#Node".
+	Link string `json:"link"`
 
-	// symbolName holds the symbol name without any package prefix.
-	symbolName string
+	// SymbolName is the types.Object.Name for the given symbol.
+	SymbolName string
 
 	source  interface{}
 	comment *ast.CommentGroup
 }
 
-func (h *HoverInformation) DocumentationLink(options Options) string {
-	if h.symbolName == "" || h.pkgPath == "" || options.LinkTarget == "" {
-		return ""
-	}
-	return fmt.Sprintf("[%s on %s](https://%s/%s#%s)", h.symbolName, options.LinkTarget, options.LinkTarget, h.pkgPath, h.symbolName)
-}
-
 func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Hover")
 	defer done()
@@ -68,16 +64,9 @@
 		h.Signature = objectString(x, i.qf)
 	}
 	if obj := i.Declaration.obj; obj != nil {
-		// Only show the documentation links for symbols in the package scope.
-		// TODO(https://golang.org/issue/34240): Handle other symbols.
-		if obj.Exported() && obj.Parent() == obj.Pkg().Scope() {
-			h.pkgPath = obj.Pkg().Path()
-			h.symbolName = obj.Name()
-		}
-		// Set the documentation.
 		h.SingleLine = objectString(obj, i.qf)
 	}
-
+	h.Link, h.SymbolName = i.linkAndSymbolName()
 	if h.comment != nil {
 		h.FullDocumentation = h.comment.Text()
 		h.Synopsis = doc.Synopsis(h.FullDocumentation)
@@ -85,6 +74,57 @@
 	return h, nil
 }
 
+func (i *IdentifierInfo) linkAndSymbolName() (string, string) {
+	obj := i.Declaration.obj
+	if obj == nil {
+		return "", ""
+	}
+	switch obj := obj.(type) {
+	case *types.PkgName:
+		return obj.Imported().Path(), obj.Name()
+	case *types.Builtin:
+		return fmt.Sprintf("builtin#%s", obj.Name()), obj.Name()
+	}
+	// Don't return links for other unexported types.
+	if !obj.Exported() {
+		return "", ""
+	}
+	var rTypeName string
+	switch obj := obj.(type) {
+	case *types.Var:
+		if obj.IsField() {
+			// If the object is a field, and we have an associated selector,
+			// we can determine the struct.
+			if selection, ok := i.pkg.GetTypesInfo().Selections[i.selector]; ok {
+				switch rtyp := deref(selection.Recv()).(type) {
+				case *types.Named:
+					rTypeName = rtyp.Obj().Name()
+				}
+			}
+		}
+	case *types.Func:
+		typ, ok := obj.Type().(*types.Signature)
+		if !ok {
+			return "", ""
+		}
+		if r := typ.Recv(); r != nil {
+			switch rtyp := deref(r.Type()).(type) {
+			case *types.Struct:
+				rTypeName = r.Name()
+			case *types.Named:
+				rTypeName = rtyp.Obj().Name()
+			}
+		}
+	}
+	if rTypeName != "" {
+		link := fmt.Sprintf("%s#%s.%s", obj.Pkg().Path(), rTypeName, obj.Name())
+		symbol := fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name())
+		return link, symbol
+	}
+	// For most cases, the link is "package/path#symbol".
+	return fmt.Sprintf("%s#%s", obj.Pkg().Path(), obj.Name()), fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
+}
+
 // 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) string {
@@ -199,3 +239,91 @@
 	// If we weren't able to find documentation for the object.
 	return &HoverInformation{source: obj}
 }
+
+func FormatHover(h *HoverInformation, options Options) (string, error) {
+	signature := formatSignature(h.Signature, 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)
+	switch options.HoverKind {
+	case SynopsisDocumentation:
+		doc := formatDoc(h.Synopsis, options)
+		return formatHover(options, doc, link, signature), nil
+	case FullDocumentation:
+		doc := formatDoc(h.FullDocumentation, options)
+		return formatHover(options, signature, link, doc), nil
+	}
+	return "", errors.Errorf("no hover for %v", h.source)
+}
+
+func formatLink(h *HoverInformation, options Options) string {
+	if options.LinkTarget == "" || h.Link == "" {
+		return ""
+	}
+	plainLink := fmt.Sprintf("https://%s/%s", options.LinkTarget, h.Link)
+	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
+	}
+}
+
+func formatSignature(signature string, options Options) string {
+	if options.PreferredContentFormat == protocol.Markdown {
+		signature = fmt.Sprintf("```go\n%s\n```", signature)
+	}
+	return signature
+}
+
+func formatDoc(doc string, options Options) string {
+	if options.PreferredContentFormat == protocol.Markdown {
+		return CommentToMarkdown(doc)
+	}
+	return doc
+}
+
+func formatHover(options Options, x ...string) string {
+	var b strings.Builder
+	for i, el := range x {
+		if el != "" {
+			b.WriteString(el)
+
+			// Don't write out final newline.
+			if i == len(x) {
+				continue
+			}
+			// If any elements of the remainder of the list are non-empty,
+			// write a newline.
+			if anyNonEmpty(x[i+1:]) {
+				if options.PreferredContentFormat == protocol.Markdown {
+					b.WriteString("\n\n")
+				} else {
+					b.WriteRune('\n')
+				}
+			}
+		}
+	}
+	return b.String()
+}
+
+func anyNonEmpty(x []string) bool {
+	for _, el := range x {
+		if el != "" {
+			return true
+		}
+	}
+	return false
+}
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 26d8183..2cfdf72 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -33,9 +33,11 @@
 
 	Declaration Declaration
 
-	pkg   Package
-	ident *ast.Ident
-	qf    types.Qualifier
+	ident    *ast.Ident
+	selector *ast.SelectorExpr
+
+	pkg Package
+	qf  types.Qualifier
 }
 
 type Declaration struct {
@@ -123,7 +125,9 @@
 		qf:       qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
 		pkg:      pkg,
 		ident:    searchForIdent(path[0]),
+		selector: searchForSelector(path),
 	}
+
 	// No identifier at the given position.
 	if result.ident == nil {
 		return nil, nil
@@ -228,6 +232,16 @@
 	return nil
 }
 
+func searchForSelector(path []ast.Node) *ast.SelectorExpr {
+	for _, n := range path {
+		switch node := n.(type) {
+		case *ast.SelectorExpr:
+			return node
+		}
+	}
+	return nil
+}
+
 func typeToObject(typ types.Type) types.Object {
 	switch typ := typ.(type) {
 	case *types.Named:
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 4770351..21ac309 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -46,7 +46,7 @@
 		TextDocumentSyncKind:   protocol.Incremental,
 		HoverKind:              SynopsisDocumentation,
 		InsertTextFormat:       protocol.PlainTextTextFormat,
-		PreferredContentFormat: protocol.PlainText,
+		PreferredContentFormat: protocol.Markdown,
 		SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
 			Go: {
 				protocol.SourceOrganizeImports: true,
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 9c7a5cd..fbed335 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -519,12 +519,10 @@
 	if err != nil {
 		t.Fatalf("failed for %v: %v", d.Src, err)
 	}
-	var hover string
-	if h.Synopsis != "" {
-		hover += h.Synopsis + "\n"
+	hover, err := source.FormatHover(h, r.view.Options())
+	if err != nil {
+		t.Fatal(err)
 	}
-	hover += h.Signature
-	hover += "\n" + h.DocumentationLink(r.view.Options())
 	rng, err := ident.Declaration.Range()
 	if err != nil {
 		t.Fatal(err)
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index f9b321f..a84c70c 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -388,12 +388,16 @@
 	return ok
 }
 
-// deref returns a pointer's element type; otherwise it returns typ.
+// deref returns a pointer's element type, traversing as many levels as needed.
+// Otherwise it returns typ.
 func deref(typ types.Type) types.Type {
-	if p, ok := typ.Underlying().(*types.Pointer); ok {
-		return p.Elem()
+	for {
+		p, ok := typ.Underlying().(*types.Pointer)
+		if !ok {
+			return typ
+		}
+		typ = p.Elem()
 	}
-	return typ
 }
 
 func isTypeName(obj types.Object) bool {
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.golden b/internal/lsp/testdata/cgoimport/usecgo.go.golden
index 27b66ad..14e2077 100644
--- a/internal/lsp/testdata/cgoimport/usecgo.go.golden
+++ b/internal/lsp/testdata/cgoimport/usecgo.go.golden
@@ -1,6 +1,9 @@
 -- funccgoexample-definition --
-cgo/declarecgo.go:17:6-13: defined here as func cgo.Example()
-[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+cgo/declarecgo.go:17:6-13: defined here as [`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+
+```go
+func cgo.Example()
+```
 -- funccgoexample-definition-json --
 {
 	"span": {
@@ -16,9 +19,12 @@
 			"offset": 160
 		}
 	},
-	"description": "func cgo.Example()\n[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)"
+	"description": "[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)\n\n```go\nfunc cgo.Example()\n```"
 }
 
 -- funccgoexample-hover --
+[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+
+```go
 func cgo.Example()
-[Example on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+```
diff --git a/internal/lsp/testdata/godef/a/a.go b/internal/lsp/testdata/godef/a/a.go
index 6d81a8b..9db1dea 100644
--- a/internal/lsp/testdata/godef/a/a.go
+++ b/internal/lsp/testdata/godef/a/a.go
@@ -2,7 +2,10 @@
 
 package a
 
-import "fmt"
+import (
+	"fmt"
+	"sync"
+)
 
 type A string //@A
 
@@ -16,4 +19,7 @@
 
 	var y string       //@string,hover("string", string)
 	_ = make([]int, 0) //@make,hover("make", make)
+
+	var mu sync.Mutex
+	mu.Lock() //@Lock,hover("Lock", Lock)
 }
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
index 260c44b..304e361 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/godef/a/a.go.golden
@@ -1,6 +1,17 @@
+-- Lock-hover --
+Lock locks m\.
+
+[`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync#Mutex.Lock)
+
+```go
+func (*sync.Mutex).Lock()
+```
 -- Random-definition --
-godef/a/random.go:3:6-12: defined here as func Random() int
-[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
+godef/a/random.go:3:6-12: defined here as [`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
+
+```go
+func Random() int
+```
 -- Random-definition-json --
 {
 	"span": {
@@ -16,15 +27,21 @@
 			"offset": 22
 		}
 	},
-	"description": "func Random() int\n[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)"
+	"description": "[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)\n\n```go\nfunc Random() int\n```"
 }
 
 -- Random-hover --
+[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
+
+```go
 func Random() int
-[Random on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
+```
 -- Random2-definition --
-godef/a/random.go:8:6-13: defined here as func Random2(y int) int
-[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
+godef/a/random.go:8:6-13: defined here as [`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
+
+```go
+func Random2(y int) int
+```
 -- Random2-definition-json --
 {
 	"span": {
@@ -40,39 +57,50 @@
 			"offset": 78
 		}
 	},
-	"description": "func Random2(y int) int\n[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)"
+	"description": "[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)\n\n```go\nfunc Random2(y int) int\n```"
 }
 
 -- Random2-hover --
+[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
+
+```go
 func Random2(y int) int
-[Random2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
+```
 -- err-definition --
-godef/a/a.go:14:6-9: defined here as var err error
+godef/a/a.go:17:6-9: defined here as ```go
+var err error
+```
 -- err-definition-json --
 {
 	"span": {
 		"uri": "file://godef/a/a.go",
 		"start": {
-			"line": 14,
+			"line": 17,
 			"column": 6,
-			"offset": 203
+			"offset": 216
 		},
 		"end": {
-			"line": 14,
+			"line": 17,
 			"column": 9,
-			"offset": 206
+			"offset": 219
 		}
 	},
-	"description": "var err error"
+	"description": "```go\nvar err error\n```"
 }
 
 -- err-hover --
+```go
 var err error
-
+```
 -- make-hover --
-The make built-in function allocates and initializes an object of type slice, map, or chan (only).
+The make built\-in function allocates and initializes an object of type slice, map, or chan \(only\)\.
+
+[`make` on pkg.go.dev](https://pkg.go.dev/builtin#make)
+
+```go
 func(t Type, size ...IntegerType) Type
-
+```
 -- string-hover --
+```go
 string
-
+```
diff --git a/internal/lsp/testdata/godef/a/d.go.golden b/internal/lsp/testdata/godef/a/d.go.golden
index e048ff9..7d2d244 100644
--- a/internal/lsp/testdata/godef/a/d.go.golden
+++ b/internal/lsp/testdata/godef/a/d.go.golden
@@ -1,6 +1,11 @@
 -- Member-definition --
-godef/a/d.go:6:2-8: defined here as @Member
+godef/a/d.go:6:2-8: defined here as \@Member
+
+[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
+
+```go
 field Member string
+```
 -- Member-definition-json --
 {
 	"span": {
@@ -16,15 +21,23 @@
 			"offset": 61
 		}
 	},
-	"description": "@Member\nfield Member string"
+	"description": "\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n```go\nfield Member string\n```"
 }
 
 -- Member-hover --
-@Member
-field Member string
+\@Member
 
+[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
+
+```go
+field Member string
+```
 -- Method-definition --
-godef/a/d.go:15:16-22: defined here as func (Thing).Method(i int) string
+godef/a/d.go:15:16-22: defined here as [`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)
+
+```go
+func (Thing).Method(i int) string
+```
 -- Method-definition-json --
 {
 	"span": {
@@ -40,15 +53,21 @@
 			"offset": 190
 		}
 	},
-	"description": "func (Thing).Method(i int) string"
+	"description": "[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)\n\n```go\nfunc (Thing).Method(i int) string\n```"
 }
 
 -- Method-hover --
-func (Thing).Method(i int) string
+[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)
 
+```go
+func (Thing).Method(i int) string
+```
 -- Other-definition --
-godef/a/d.go:9:5-10: defined here as var Other Thing
-[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+godef/a/d.go:9:5-10: defined here as [`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+
+```go
+var Other Thing
+```
 -- Other-definition-json --
 {
 	"span": {
@@ -64,17 +83,23 @@
 			"offset": 91
 		}
 	},
-	"description": "var Other Thing\n[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
+	"description": "[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)\n\n```go\nvar Other Thing\n```"
 }
 
 -- Other-hover --
+[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+
+```go
 var Other Thing
-[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+```
 -- Thing-definition --
-godef/a/d.go:5:6-11: defined here as Thing struct {
+godef/a/d.go:5:6-11: defined here as [`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+
+```go
+Thing struct {
 	Member string //@Member
 }
-[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+```
 -- Thing-definition-json --
 {
 	"span": {
@@ -90,17 +115,23 @@
 			"offset": 35
 		}
 	},
-	"description": "Thing struct {\n\tMember string //@Member\n}\n[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
+	"description": "[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)\n\n```go\nThing struct {\n\tMember string //@Member\n}\n```"
 }
 
 -- Thing-hover --
+[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+
+```go
 Thing struct {
 	Member string //@Member
 }
-[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+```
 -- Things-definition --
-godef/a/d.go:11:6-12: defined here as func Things(val []string) []Thing
-[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+godef/a/d.go:11:6-12: defined here as [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+
+```go
+func Things(val []string) []Thing
+```
 -- Things-definition-json --
 {
 	"span": {
@@ -116,9 +147,12 @@
 			"offset": 119
 		}
 	},
-	"description": "func Things(val []string) []Thing\n[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
+	"description": "[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)\n\n```go\nfunc Things(val []string) []Thing\n```"
 }
 
 -- Things-hover --
+[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+
+```go
 func Things(val []string) []Thing
-[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+```
diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/godef/a/f.go.golden
index bdf4b99..1a220e0 100644
--- a/internal/lsp/testdata/godef/a/f.go.golden
+++ b/internal/lsp/testdata/godef/a/f.go.golden
@@ -1,5 +1,7 @@
 -- switchStringY-definition --
-godef/a/f.go:8:9-10: defined here as var y string
+godef/a/f.go:8:9-10: defined here as ```go
+var y string
+```
 -- switchStringY-definition-json --
 {
 	"span": {
@@ -15,14 +17,17 @@
 			"offset": 77
 		}
 	},
-	"description": "var y string"
+	"description": "```go\nvar y string\n```"
 }
 
 -- switchStringY-hover --
+```go
 var y string
-
+```
 -- switchY-definition --
-godef/a/f.go:8:9-10: defined here as var y int
+godef/a/f.go:8:9-10: defined here as ```go
+var y int
+```
 -- switchY-definition-json --
 {
 	"span": {
@@ -38,9 +43,10 @@
 			"offset": 77
 		}
 	},
-	"description": "var y int"
+	"description": "```go\nvar y int\n```"
 }
 
 -- switchY-hover --
+```go
 var y int
-
+```
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/godef/a/random.go.golden
index 502e93a..32d7403 100644
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ b/internal/lsp/testdata/godef/a/random.go.golden
@@ -1,5 +1,9 @@
 -- PosSum-definition --
-godef/a/random.go:16:15-18: defined here as func (*Pos).Sum() int
+godef/a/random.go:16:15-18: defined here as [`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)
+
+```go
+func (*Pos).Sum() int
+```
 -- PosSum-definition-json --
 {
 	"span": {
@@ -15,15 +19,21 @@
 			"offset": 251
 		}
 	},
-	"description": "func (*Pos).Sum() int"
+	"description": "[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)\n\n```go\nfunc (*Pos).Sum() int\n```"
 }
 
 -- PosSum-hover --
-func (*Pos).Sum() int
+[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)
 
+```go
+func (*Pos).Sum() int
+```
 -- PosX-definition --
-godef/a/random.go:13:2-3: defined here as @mark(PosX, "x"),mark(PosY, "y")
+godef/a/random.go:13:2-3: defined here as \@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
+
+```go
 field x int
+```
 -- PosX-definition-json --
 {
 	"span": {
@@ -39,15 +49,19 @@
 			"offset": 188
 		}
 	},
-	"description": "@mark(PosX, \"x\"),mark(PosY, \"y\")\nfield x int"
+	"description": "\\@mark\\(PosX, \\\"x\\\"\\),mark\\(PosY, \\\"y\\\"\\)\n\n```go\nfield x int\n```"
 }
 
 -- PosX-hover --
-@mark(PosX, "x"),mark(PosY, "y")
-field x int
+\@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
 
+```go
+field x int
+```
 -- RandomParamY-definition --
-godef/a/random.go:8:14-15: defined here as var y int
+godef/a/random.go:8:14-15: defined here as ```go
+var y int
+```
 -- RandomParamY-definition-json --
 {
 	"span": {
@@ -63,9 +77,10 @@
 			"offset": 80
 		}
 	},
-	"description": "var y int"
+	"description": "```go\nvar y int\n```"
 }
 
 -- RandomParamY-hover --
+```go
 var y int
-
+```
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
index 3869da5..f298f15 100644
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ b/internal/lsp/testdata/godef/b/b.go.golden
@@ -1,31 +1,41 @@
 -- A-definition --
-godef/a/a.go:7:6-7: defined here as A string //@A
+godef/a/a.go:10:6-7: defined here as [`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
 
-[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
+```go
+A string //@A
+
+```
 -- A-definition-json --
 {
 	"span": {
 		"uri": "file://godef/a/a.go",
 		"start": {
-			"line": 7,
+			"line": 10,
 			"column": 6,
-			"offset": 75
+			"offset": 88
 		},
 		"end": {
-			"line": 7,
+			"line": 10,
 			"column": 7,
-			"offset": 76
+			"offset": 89
 		}
 	},
-	"description": "A string //@A\n\n[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)"
+	"description": "[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)\n\n```go\nA string //@A\n\n```"
 }
 
 -- A-hover --
+[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
+
+```go
 A string //@A
 
-[A on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
+```
 -- AImport-definition --
-godef/b/b.go:5:2-43: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
+godef/b/b.go:5:2-43: defined here as [`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
+
+```go
+package a ("golang.org/x/tools/internal/lsp/godef/a")
+```
 -- AImport-definition-json --
 {
 	"span": {
@@ -41,36 +51,45 @@
 			"offset": 153
 		}
 	},
-	"description": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"
+	"description": "[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)\n\n```go\npackage a (\"golang.org/x/tools/internal/lsp/godef/a\")\n```"
 }
 
 -- AImport-hover --
-package a ("golang.org/x/tools/internal/lsp/godef/a")
+[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
 
+```go
+package a ("golang.org/x/tools/internal/lsp/godef/a")
+```
 -- AStuff-definition --
-godef/a/a.go:9:6-12: defined here as func a.AStuff()
-[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+godef/a/a.go:12:6-12: defined here as [`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+
+```go
+func a.AStuff()
+```
 -- AStuff-definition-json --
 {
 	"span": {
 		"uri": "file://godef/a/a.go",
 		"start": {
-			"line": 9,
+			"line": 12,
 			"column": 6,
-			"offset": 95
+			"offset": 108
 		},
 		"end": {
-			"line": 9,
+			"line": 12,
 			"column": 12,
-			"offset": 101
+			"offset": 114
 		}
 	},
-	"description": "func a.AStuff()\n[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)"
+	"description": "[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)\n\n```go\nfunc a.AStuff()\n```"
 }
 
 -- AStuff-hover --
+[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+
+```go
 func a.AStuff()
-[AStuff on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+```
 -- PackageFoo-definition --
 foo/foo.go:1:1-30:16: defined here as myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("foo", PackageFoo),godef("myFoo", myFoo)
 -- PackageFoo-definition-json --
@@ -95,12 +114,15 @@
 myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("foo", PackageFoo),godef("myFoo", myFoo)
 
 -- S1-definition --
-godef/b/b.go:8:6-8: defined here as S1 struct {
+godef/b/b.go:8:6-8: defined here as [`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
+S1 struct {
 	F1  int //@mark(S1F1, "F1")
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
-[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+```
 -- S1-definition-json --
 {
 	"span": {
@@ -116,19 +138,27 @@
 			"offset": 195
 		}
 	},
-	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
+	"description": "[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)\n\n```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n```"
 }
 
 -- S1-hover --
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
 S1 struct {
 	F1  int //@mark(S1F1, "F1")
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
-[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+```
 -- S1F1-definition --
-godef/b/b.go:9:2-4: defined here as @mark(S1F1, "F1")
+godef/b/b.go:9:2-4: defined here as \@mark\(S1F1, \"F1\"\)
+
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+```go
 field F1 int
+```
 -- S1F1-definition-json --
 {
 	"span": {
@@ -144,16 +174,25 @@
 			"offset": 214
 		}
 	},
-	"description": "@mark(S1F1, \"F1\")\nfield F1 int"
+	"description": "\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n```go\nfield F1 int\n```"
 }
 
 -- S1F1-hover --
-@mark(S1F1, "F1")
-field F1 int
+\@mark\(S1F1, \"F1\"\)
 
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+```go
+field F1 int
+```
 -- S1S2-definition --
-godef/b/b.go:10:2-4: defined here as @godef("S2", S2), mark(S1S2, "S2")
+godef/b/b.go:10:2-4: defined here as \@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
+
+[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
+
+```go
 field S2 S2
+```
 -- S1S2-definition-json --
 {
 	"span": {
@@ -169,20 +208,27 @@
 			"offset": 243
 		}
 	},
-	"description": "@godef(\"S2\", S2), mark(S1S2, \"S2\")\nfield S2 S2"
+	"description": "\\@godef\\(\\\"S2\\\", S2\\), mark\\(S1S2, \\\"S2\\\"\\)\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)\n\n```go\nfield S2 S2\n```"
 }
 
 -- S1S2-hover --
-@godef("S2", S2), mark(S1S2, "S2")
-field S2 S2
+\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
 
+[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
+
+```go
+field S2 S2
+```
 -- S2-definition --
-godef/b/b.go:14:6-8: defined here as S2 struct {
+godef/b/b.go:14:6-8: defined here as [`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+
+```go
+S2 struct {
 	F1   string //@mark(S2F1, "F1")
 	F2   int    //@mark(S2F2, "F2")
 	*a.A        //@godef("A", A),godef("a",AImport)
 }
-[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+```
 -- S2-definition-json --
 {
 	"span": {
@@ -198,19 +244,27 @@
 			"offset": 322
 		}
 	},
-	"description": "S2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", A),godef(\"a\",AImport)\n}\n[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)"
+	"description": "[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)\n\n```go\nS2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", A),godef(\"a\",AImport)\n}\n```"
 }
 
 -- S2-hover --
+[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+
+```go
 S2 struct {
 	F1   string //@mark(S2F1, "F1")
 	F2   int    //@mark(S2F2, "F2")
 	*a.A        //@godef("A", A),godef("a",AImport)
 }
-[S2 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+```
 -- S2F1-definition --
-godef/b/b.go:15:2-4: defined here as @mark(S2F1, "F1")
+godef/b/b.go:15:2-4: defined here as \@mark\(S2F1, \"F1\"\)
+
+[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
+
+```go
 field F1 string
+```
 -- S2F1-definition-json --
 {
 	"span": {
@@ -226,16 +280,25 @@
 			"offset": 341
 		}
 	},
-	"description": "@mark(S2F1, \"F1\")\nfield F1 string"
+	"description": "\\@mark\\(S2F1, \\\"F1\\\"\\)\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)\n\n```go\nfield F1 string\n```"
 }
 
 -- S2F1-hover --
-@mark(S2F1, "F1")
-field F1 string
+\@mark\(S2F1, \"F1\"\)
 
+[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
+
+```go
+field F1 string
+```
 -- S2F2-definition --
-godef/b/b.go:16:2-4: defined here as @mark(S2F2, "F2")
+godef/b/b.go:16:2-4: defined here as \@mark\(S2F2, \"F2\"\)
+
+[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
+
+```go
 field F2 int
+```
 -- S2F2-definition-json --
 {
 	"span": {
@@ -251,13 +314,17 @@
 			"offset": 374
 		}
 	},
-	"description": "@mark(S2F2, \"F2\")\nfield F2 int"
+	"description": "\\@mark\\(S2F2, \\\"F2\\\"\\)\n\n[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)\n\n```go\nfield F2 int\n```"
 }
 
 -- S2F2-hover --
-@mark(S2F2, "F2")
-field F2 int
+\@mark\(S2F2, \"F2\"\)
 
+[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
+
+```go
+field F2 int
+```
 -- Stuff-definition --
 godef/a/a.go:9:6-11: defined here as func a.Stuff()
 -- Stuff-definition-json --
@@ -281,8 +348,11 @@
 -- Stuff-hover --
 func a.Stuff()
 -- X-definition --
-godef/b/b.go:37:7-8: defined here as const X untyped int = 0
-[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+godef/b/b.go:37:7-8: defined here as [`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+
+```go
+const X untyped int = 0
+```
 -- X-definition-json --
 {
 	"span": {
@@ -298,14 +368,21 @@
 			"offset": 796
 		}
 	},
-	"description": "const X untyped int = 0\n[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)"
+	"description": "[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)\n\n```go\nconst X untyped int = 0\n```"
 }
 
 -- X-hover --
+[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+
+```go
 const X untyped int = 0
-[X on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+```
 -- myFoo-definition --
-godef/b/b.go:4:2-7: defined here as package myFoo ("golang.org/x/tools/internal/lsp/foo")
+godef/b/b.go:4:2-7: defined here as [`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
+
+```go
+package myFoo ("golang.org/x/tools/internal/lsp/foo")
+```
 -- myFoo-definition-json --
 {
 	"span": {
@@ -321,9 +398,12 @@
 			"offset": 26
 		}
 	},
-	"description": "package myFoo (\"golang.org/x/tools/internal/lsp/foo\")"
+	"description": "[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)\n\n```go\npackage myFoo (\"golang.org/x/tools/internal/lsp/foo\")\n```"
 }
 
 -- myFoo-hover --
-package myFoo ("golang.org/x/tools/internal/lsp/foo")
+[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
 
+```go
+package myFoo ("golang.org/x/tools/internal/lsp/foo")
+```
diff --git a/internal/lsp/testdata/godef/b/c.go.golden b/internal/lsp/testdata/godef/b/c.go.golden
index c5f2741..30fb109 100644
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ b/internal/lsp/testdata/godef/b/c.go.golden
@@ -1,10 +1,13 @@
 -- S1-definition --
-godef/b/b.go:8:6-8: defined here as S1 struct {
+godef/b/b.go:8:6-8: defined here as [`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
+S1 struct {
 	F1  int //@mark(S1F1, "F1")
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
-[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+```
 -- S1-definition-json --
 {
 	"span": {
@@ -20,19 +23,27 @@
 			"offset": 195
 		}
 	},
-	"description": "S1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
+	"description": "[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)\n\n```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n```"
 }
 
 -- S1-hover --
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
 S1 struct {
 	F1  int //@mark(S1F1, "F1")
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
 	a.A     //@godef("A", A)
 }
-[S1 on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+```
 -- S1F1-definition --
-godef/b/b.go:9:2-4: defined here as @mark(S1F1, "F1")
+godef/b/b.go:9:2-4: defined here as \@mark\(S1F1, \"F1\"\)
+
+[`b.F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#F1)
+
+```go
 field F1 int
+```
 -- S1F1-definition-json --
 {
 	"span": {
@@ -48,10 +59,14 @@
 			"offset": 214
 		}
 	},
-	"description": "@mark(S1F1, \"F1\")\nfield F1 int"
+	"description": "\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`b.F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#F1)\n\n```go\nfield F1 int\n```"
 }
 
 -- S1F1-hover --
-@mark(S1F1, "F1")
-field F1 int
+\@mark\(S1F1, \"F1\"\)
 
+[`b.F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#F1)
+
+```go
+field F1 int
+```
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/godef/b/e.go.golden
index 074fce3..df38081 100644
--- a/internal/lsp/testdata/godef/b/e.go.golden
+++ b/internal/lsp/testdata/godef/b/e.go.golden
@@ -1,6 +1,11 @@
 -- Member-definition --
-godef/a/d.go:6:2-8: defined here as @Member
+godef/a/d.go:6:2-8: defined here as \@Member
+
+[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
+
+```go
 field Member string
+```
 -- Member-definition-json --
 {
 	"span": {
@@ -16,16 +21,23 @@
 			"offset": 61
 		}
 	},
-	"description": "@Member\nfield Member string"
+	"description": "\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n```go\nfield Member string\n```"
 }
 
 -- Member-hover --
-@Member
-field Member string
+\@Member
 
+[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
+
+```go
+field Member string
+```
 -- Other-definition --
-godef/a/d.go:9:5-10: defined here as var a.Other a.Thing
-[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+godef/a/d.go:9:5-10: defined here as [`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+
+```go
+var a.Other a.Thing
+```
 -- Other-definition-json --
 {
 	"span": {
@@ -41,17 +53,23 @@
 			"offset": 91
 		}
 	},
-	"description": "var a.Other a.Thing\n[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
+	"description": "[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)\n\n```go\nvar a.Other a.Thing\n```"
 }
 
 -- Other-hover --
+[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+
+```go
 var a.Other a.Thing
-[Other on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
+```
 -- Thing-definition --
-godef/a/d.go:5:6-11: defined here as Thing struct {
+godef/a/d.go:5:6-11: defined here as [`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+
+```go
+Thing struct {
 	Member string //@Member
 }
-[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+```
 -- Thing-definition-json --
 {
 	"span": {
@@ -67,17 +85,23 @@
 			"offset": 35
 		}
 	},
-	"description": "Thing struct {\n\tMember string //@Member\n}\n[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
+	"description": "[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)\n\n```go\nThing struct {\n\tMember string //@Member\n}\n```"
 }
 
 -- Thing-hover --
+[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+
+```go
 Thing struct {
 	Member string //@Member
 }
-[Thing on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
+```
 -- Things-definition --
-godef/a/d.go:11:6-12: defined here as func a.Things(val []string) []a.Thing
-[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+godef/a/d.go:11:6-12: defined here as [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+
+```go
+func a.Things(val []string) []a.Thing
+```
 -- Things-definition-json --
 {
 	"span": {
@@ -93,9 +117,12 @@
 			"offset": 119
 		}
 	},
-	"description": "func a.Things(val []string) []a.Thing\n[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
+	"description": "[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)\n\n```go\nfunc a.Things(val []string) []a.Thing\n```"
 }
 
 -- Things-hover --
+[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+
+```go
 func a.Things(val []string) []a.Thing
-[Things on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
+```
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
index 822aab0..43d5597 100644
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
+++ b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
@@ -1,5 +1,7 @@
 -- myUnclosedIf-definition --
-godef/broken/unclosedIf.go:7:7-19: defined here as var myUnclosedIf string
+godef/broken/unclosedIf.go:7:7-19: defined here as ```go
+var myUnclosedIf string
+```
 -- myUnclosedIf-definition-json --
 {
 	"span": {
@@ -15,9 +17,10 @@
 			"offset": 80
 		}
 	},
-	"description": "var myUnclosedIf string"
+	"description": "```go\nvar myUnclosedIf string\n```"
 }
 
 -- myUnclosedIf-hover --
+```go
 var myUnclosedIf string
-
+```
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index ae7ecf1..31e701c 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -11,7 +11,7 @@
 FormatCount = 6
 ImportCount = 7
 SuggestedFixCount = 1
-DefinitionsCount = 38
+DefinitionsCount = 39
 TypeDefinitionsCount = 2
 HighlightsCount = 41
 ReferencesCount = 7