internal/lsp: add support for hovering runes

Enable to hover runes found in basic literals in various forms.
When a rune is found, the hover message provides a summary composed of a
printable version (if it exists) of the rune, its codepoint and its name.

Behaviour varies slightly depending on the basic literal: rune literals
always display the summary when hovered, string literals only display it
when an escaped rune sequence is found to avoid providing unnecessary
information, and finally number literals only when expressed as a
hexadecimal number whose size ranges from one to eight bytes.

Fixes golang/go#38239

Change-Id: I024fdd5c511a45c7c285e200ce1eda0669a45491
Reviewed-on: https://go-review.googlesource.com/c/tools/+/321810
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
diff --git a/go.mod b/go.mod
index 1c1dad4..ff8184f 100644
--- a/go.mod
+++ b/go.mod
@@ -8,5 +8,6 @@
 	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e
+	golang.org/x/text v0.3.6
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
 )
diff --git a/go.sum b/go.sum
index a56a130..b4edbe6 100644
--- a/go.sum
+++ b/go.sum
@@ -18,7 +18,10 @@
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index 2e92726..832d794 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -108,6 +108,10 @@
 	//TODO: import addition not supported on command line
 }
 
+func (r *runner) Hover(t *testing.T, spn span.Span, info string) {
+	//TODO: hovering not supported on command line
+}
+
 func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
 	rStdout, wStdout, err := os.Pipe()
 	if err != nil {
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index f095489..d21d71d 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -718,7 +718,7 @@
 	didSomething := false
 	if hover != nil {
 		didSomething = true
-		tag := fmt.Sprintf("%s-hover", d.Name)
+		tag := fmt.Sprintf("%s-hoverdef", d.Name)
 		expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) {
 			return []byte(hover.Contents.Value), nil
 		}))
@@ -840,6 +840,43 @@
 	}
 }
 
+func (r *runner) Hover(t *testing.T, src span.Span, text string) {
+	m, err := r.data.Mapper(src.URI())
+	if err != nil {
+		t.Fatal(err)
+	}
+	loc, err := m.Location(src)
+	if err != nil {
+		t.Fatalf("failed for %v", err)
+	}
+	tdpp := protocol.TextDocumentPositionParams{
+		TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
+		Position:     loc.Range.Start,
+	}
+	params := &protocol.HoverParams{
+		TextDocumentPositionParams: tdpp,
+	}
+	hover, err := r.server.Hover(r.ctx, params)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if text == "" {
+		if hover != nil {
+			t.Errorf("want nil, got %v\n", hover)
+		}
+	} else {
+		if hover == nil {
+			t.Fatalf("want hover result to include %s, but got nil", text)
+		}
+		if got := hover.Contents.Value; got != text {
+			t.Errorf("want %v, got %v\n", text, got)
+		}
+		if want, got := loc.Range, hover.Range; want != got {
+			t.Errorf("want range %v, got %v instead", want, got)
+		}
+	}
+}
+
 func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
 	sm, err := r.data.Mapper(src.URI())
 	if err != nil {
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 0bc92d1..56666d3 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -14,9 +14,12 @@
 	"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"
@@ -66,6 +69,9 @@
 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)
@@ -93,6 +99,155 @@
 	}, 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, pos 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(pos)
+	if err != nil {
+		return 0, MappedRange{}, err
+	}
+	rng, err := spn.Range(pgf.Mapper.Converter)
+	if err != nil {
+		return 0, MappedRange{}, err
+	}
+
+	// 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 && rng.Start >= n.Pos() && rng.Start <= 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
+		strMappedRng, err := posToMappedRange(snapshot, pkg, lit.Pos(), lit.End())
+		if err != nil {
+			return 0, MappedRange{}, err
+		}
+		strRng, err := strMappedRng.Range()
+		if err != nil {
+			return 0, MappedRange{}, err
+		}
+		offset := strRng.Start.Character
+		for i := pos.Character - offset; 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) (*HoverInformation, error) {
 	ctx, done := event.Start(ctx, "source.Hover")
 	defer done()
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index f1ab3ff..83ce712 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -576,12 +576,12 @@
 	didSomething := false
 	if hover != "" {
 		didSomething = true
-		tag := fmt.Sprintf("%s-hover", d.Name)
+		tag := fmt.Sprintf("%s-hoverdef", d.Name)
 		expectHover := string(r.data.Golden(tag, d.Src.URI().Filename(), func() ([]byte, error) {
 			return []byte(hover), nil
 		}))
 		if hover != expectHover {
-			t.Errorf("hover for %s failed:\n%s", d.Src, tests.Diff(t, expectHover, hover))
+			t.Errorf("hoverdef for %s failed:\n%s", d.Src, tests.Diff(t, expectHover, hover))
 		}
 	}
 	if !d.OnlyHover {
@@ -682,6 +682,37 @@
 	}
 }
 
+func (r *runner) Hover(t *testing.T, src span.Span, text string) {
+	ctx := r.ctx
+	_, srcRng, err := spanToRange(r.data, src)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fh, err := r.snapshot.GetFile(r.ctx, src.URI())
+	if err != nil {
+		t.Fatal(err)
+	}
+	hover, err := source.Hover(ctx, r.snapshot, fh, srcRng.Start)
+	if err != nil {
+		t.Errorf("hover failed for %s: %v", src.URI(), err)
+	}
+	if text == "" {
+		if hover != nil {
+			t.Errorf("want nil, got %v\n", hover)
+		}
+	} else {
+		if hover == nil {
+			t.Fatalf("want hover result to not be nil")
+		}
+		if got := hover.Contents.Value; got != text {
+			t.Errorf("want %v, got %v\n", got, text)
+		}
+		if want, got := srcRng, hover.Range; want != got {
+			t.Errorf("want range %v, got %v instead", want, got)
+		}
+	}
+}
+
 func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) {
 	ctx := r.ctx
 	_, srcRng, err := spanToRange(r.data, src)
diff --git a/internal/lsp/testdata/basiclit/basiclit.go b/internal/lsp/testdata/basiclit/basiclit.go
index ab895dc..9829003 100644
--- a/internal/lsp/testdata/basiclit/basiclit.go
+++ b/internal/lsp/testdata/basiclit/basiclit.go
@@ -10,4 +10,47 @@
 	_ = 1. //@complete(".")
 
 	_ = 'a' //@complete("' ")
+
+	_ = 'a' //@hover("'a'", "'a', U+0061, LATIN SMALL LETTER A")
+	_ = 0x61 //@hover("0x61", "'a', U+0061, LATIN SMALL LETTER A")
+
+	_ = '\u2211' //@hover("'\\u2211'", "'∑', U+2211, N-ARY SUMMATION")
+	_ = 0x2211 //@hover("0x2211", "'∑', U+2211, N-ARY SUMMATION")
+	_ = "foo \u2211 bar" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
+
+	_ = '\a' //@hover("'\\a'", "U+0007, control")
+	_ = "foo \a bar" //@hover("\\a", "U+0007, control")
+
+	_ = '\U0001F30A' //@hover("'\\U0001F30A'", "'šŸŒŠ', U+1F30A, WATER WAVE")
+	_ = 0x0001F30A //@hover("0x0001F30A", "'šŸŒŠ', U+1F30A, WATER WAVE")
+	_ = "foo \U0001F30A bar" //@hover("\\U0001F30A", "'šŸŒŠ', U+1F30A, WATER WAVE")
+
+	_ = '\x7E' //@hover("'\\x7E'", "'~', U+007E, TILDE")
+	_ = "foo \x7E bar" //@hover("\\x7E", "'~', U+007E, TILDE")
+	_ = "foo \a bar" //@hover("\\a", "U+0007, control")
+
+	_ = '\173' //@hover("'\\173'", "'{', U+007B, LEFT CURLY BRACKET")
+	_ = "foo \173 bar" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
+	_ = "foo \173 bar \u2211 baz" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
+	_ = "foo \173 bar \u2211 baz" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
+	_ = "foo\173bar\u2211baz" //@hover("\\173", "'{', U+007B, LEFT CURLY BRACKET")
+	_ = "foo\173bar\u2211baz" //@hover("\\u2211", "'∑', U+2211, N-ARY SUMMATION")
+
+	// search for runes in string only if there is an escaped sequence
+	_ = "hello" //@hover("\"hello\"", "")
+
+	// incorrect escaped rune sequences
+	_ = '\0' //@hover("'\\0'", "")
+	_ = '\u22111' //@hover("'\\u22111'", "")
+	_ = '\U00110000' //@hover("'\\U00110000'", "")
+	_ = '\u12e45'//@hover("'\\u12e45'", "")
+	_ = '\xa' //@hover("'\\xa'", "")
+	_ = 'aa' //@hover("'aa'", "")
+
+	// other basic lits
+	_ = 1 //@hover("1", "")
+	_ = 1.2 //@hover("1.2", "")
+	_ = 1.2i //@hover("1.2i", "")
+	_ = 0123 //@hover("0123", "")
+	_ = 0x1234567890 //@hover("0x1234567890", "")
 }
diff --git a/internal/lsp/testdata/cgo/declarecgo.go.golden b/internal/lsp/testdata/cgo/declarecgo.go.golden
index 773f3b7..b6d94d0 100644
--- a/internal/lsp/testdata/cgo/declarecgo.go.golden
+++ b/internal/lsp/testdata/cgo/declarecgo.go.golden
@@ -22,7 +22,7 @@
 	"description": "```go\nfunc Example()\n```\n\n[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)"
 }
 
--- funccgoexample-hover --
+-- funccgoexample-hoverdef --
 ```go
 func Example()
 ```
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.golden b/internal/lsp/testdata/cgoimport/usecgo.go.golden
index 8f7518a..f33f94f 100644
--- a/internal/lsp/testdata/cgoimport/usecgo.go.golden
+++ b/internal/lsp/testdata/cgoimport/usecgo.go.golden
@@ -22,7 +22,7 @@
 	"description": "```go\nfunc cgo.Example()\n```\n\n[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo?utm_source=gopls#Example)"
 }
 
--- funccgoexample-hover --
+-- funccgoexample-hoverdef --
 ```go
 func cgo.Example()
 ```
diff --git a/internal/lsp/testdata/godef/a/a.go b/internal/lsp/testdata/godef/a/a.go
index 993fd86..5cc8552 100644
--- a/internal/lsp/testdata/godef/a/a.go
+++ b/internal/lsp/testdata/godef/a/a.go
@@ -1,5 +1,5 @@
 // Package a is a package for testing go to definition.
-package a //@mark(aPackage, "a "),hover("a ", aPackage)
+package a //@mark(aPackage, "a "),hoverdef("a ", aPackage)
 
 import (
 	"fmt"
@@ -9,19 +9,19 @@
 
 var (
 	// x is a variable.
-	x string //@x,hover("x", x)
+	x string //@x,hoverdef("x", x)
 )
 
 // Constant block. When I hover on h, I should see this comment.
 const (
 	// When I hover on g, I should see this comment.
-	g = 1 //@g,hover("g", g)
+	g = 1 //@g,hoverdef("g", g)
 
-	h = 2 //@h,hover("h", h)
+	h = 2 //@h,hoverdef("h", h)
 )
 
 // z is a variable too.
-var z string //@z,hover("z", z)
+var z string //@z,hoverdef("z", z)
 
 type A string //@mark(AString, "A")
 
@@ -33,14 +33,14 @@
 	var err error         //@err
 	fmt.Printf("%v", err) //@godef("err", err)
 
-	var y string       //@string,hover("string", string)
-	_ = make([]int, 0) //@make,hover("make", make)
+	var y string       //@string,hoverdef("string", string)
+	_ = make([]int, 0) //@make,hoverdef("make", make)
 
 	var mu sync.Mutex
-	mu.Lock() //@Lock,hover("Lock", Lock)
+	mu.Lock() //@Lock,hoverdef("Lock", Lock)
 
-	var typ *types.Named //@mark(typesImport, "types"),hover("types", typesImport)
-	typ.Obj().Name()     //@Name,hover("Name", Name)
+	var typ *types.Named //@mark(typesImport, "types"),hoverdef("types", typesImport)
+	typ.Obj().Name()     //@Name,hoverdef("Name", Name)
 }
 
 type A struct {
@@ -76,7 +76,7 @@
 func _() {
 	// 1st type declaration block
 	type (
-		a struct { //@mark(declBlockA, "a"),hover("a", declBlockA)
+		a struct { //@mark(declBlockA, "a"),hoverdef("a", declBlockA)
 			x string
 		}
 	)
@@ -84,21 +84,21 @@
 	// 2nd type declaration block
 	type (
 		// b has a comment
-		b struct{} //@mark(declBlockB, "b"),hover("b", declBlockB)
+		b struct{} //@mark(declBlockB, "b"),hoverdef("b", declBlockB)
 	)
 
 	// 3rd type declaration block
 	type (
 		// c is a struct
-		c struct { //@mark(declBlockC, "c"),hover("c", declBlockC)
+		c struct { //@mark(declBlockC, "c"),hoverdef("c", declBlockC)
 			f string
 		}
 
-		d string //@mark(declBlockD, "d"),hover("d", declBlockD)
+		d string //@mark(declBlockD, "d"),hoverdef("d", declBlockD)
 	)
 
 	type (
-		e struct { //@mark(declBlockE, "e"),hover("e", declBlockE)
+		e struct { //@mark(declBlockE, "e"),hoverdef("e", declBlockE)
 			f float64
 		} // e has a comment
 	)
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/godef/a/a.go.golden
index c268293..182928e 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/godef/a/a.go.golden
@@ -1,4 +1,4 @@
--- Lock-hover --
+-- Lock-hoverdef --
 ```go
 func (*sync.Mutex).Lock()
 ```
@@ -6,7 +6,7 @@
 [`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync?utm_source=gopls#Mutex.Lock)
 
 Lock locks m\.
--- Name-hover --
+-- Name-hoverdef --
 ```go
 func (*types.object).Name() string
 ```
@@ -38,7 +38,7 @@
 	"description": "```go\nfunc Random() int\n```\n\n[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random)"
 }
 
--- Random-hover --
+-- Random-hoverdef --
 ```go
 func Random() int
 ```
@@ -68,15 +68,15 @@
 	"description": "```go\nfunc Random2(y int) int\n```\n\n[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)"
 }
 
--- Random2-hover --
+-- Random2-hoverdef --
 ```go
 func Random2(y int) int
 ```
 
 [`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Random2)
--- aPackage-hover --
+-- aPackage-hoverdef --
 Package a is a package for testing go to definition\.
--- declBlockA-hover --
+-- declBlockA-hoverdef --
 ```go
 type a struct {
 	x string
@@ -84,13 +84,13 @@
 ```
 
 1st type declaration block
--- declBlockB-hover --
+-- declBlockB-hoverdef --
 ```go
 type b struct{}
 ```
 
 b has a comment
--- declBlockC-hover --
+-- declBlockC-hoverdef --
 ```go
 type c struct {
 	f string
@@ -98,13 +98,13 @@
 ```
 
 c is a struct
--- declBlockD-hover --
+-- declBlockD-hoverdef --
 ```go
 type d string
 ```
 
 3rd type declaration block
--- declBlockE-hover --
+-- declBlockE-hoverdef --
 ```go
 type e struct {
 	f float64
@@ -125,36 +125,36 @@
 		"start": {
 			"line": 33,
 			"column": 6,
-			"offset": 597
+			"offset": 612
 		},
 		"end": {
 			"line": 33,
 			"column": 9,
-			"offset": 600
+			"offset": 615
 		}
 	},
 	"description": "```go\nvar err error\n```\n\n\\@err"
 }
 
--- err-hover --
+-- err-hoverdef --
 ```go
 var err error
 ```
 
 \@err
--- g-hover --
+-- g-hoverdef --
 ```go
 const g untyped int = 1
 ```
 
 When I hover on g, I should see this comment\.
--- h-hover --
+-- h-hoverdef --
 ```go
 const h untyped int = 2
 ```
 
 Constant block\.
--- make-hover --
+-- make-hoverdef --
 ```go
 func(t Type, size ...IntegerType) Type
 ```
@@ -162,23 +162,23 @@
 [`make` on pkg.go.dev](https://pkg.go.dev/builtin?utm_source=gopls#make)
 
 The make built\-in function allocates and initializes an object of type slice, map, or chan \(only\)\.
--- string-hover --
+-- string-hoverdef --
 ```go
 string
 ```
--- typesImport-hover --
+-- typesImport-hoverdef --
 ```go
 package types ("go/types")
 ```
 
 [`types` on pkg.go.dev](https://pkg.go.dev/go/types?utm_source=gopls)
--- x-hover --
+-- x-hoverdef --
 ```go
 var x string
 ```
 
 x is a variable\.
--- z-hover --
+-- z-hoverdef --
 ```go
 var z string
 ```
diff --git a/internal/lsp/testdata/godef/a/a_test.go.golden b/internal/lsp/testdata/godef/a/a_test.go.golden
index ac50b90..e5cb3d7 100644
--- a/internal/lsp/testdata/godef/a/a_test.go.golden
+++ b/internal/lsp/testdata/godef/a/a_test.go.golden
@@ -20,7 +20,7 @@
 	"description": "```go\nfunc TestA(t *testing.T)\n```"
 }
 
--- TestA-hover --
+-- TestA-hoverdef --
 ```go
 func TestA(t *testing.T)
 ```
diff --git a/internal/lsp/testdata/godef/a/a_x_test.go.golden b/internal/lsp/testdata/godef/a/a_x_test.go.golden
index dd1d740..2e30647 100644
--- a/internal/lsp/testdata/godef/a/a_x_test.go.golden
+++ b/internal/lsp/testdata/godef/a/a_x_test.go.golden
@@ -20,7 +20,7 @@
 	"description": "```go\nfunc TestA2(t *testing.T)\n```"
 }
 
--- TestA2-hover --
+-- TestA2-hoverdef --
 ```go
 func TestA2(t *testing.T)
 ```
diff --git a/internal/lsp/testdata/godef/a/d.go b/internal/lsp/testdata/godef/a/d.go
index d20bdad..2da8d05 100644
--- a/internal/lsp/testdata/godef/a/d.go
+++ b/internal/lsp/testdata/godef/a/d.go
@@ -1,4 +1,4 @@
-package a //@mark(a, "a "),hover("a ", a)
+package a //@mark(a, "a "),hoverdef("a ", a)
 
 import "fmt"
 
diff --git a/internal/lsp/testdata/godef/a/d.go.golden b/internal/lsp/testdata/godef/a/d.go.golden
index d80c14a..23c7da1 100644
--- a/internal/lsp/testdata/godef/a/d.go.golden
+++ b/internal/lsp/testdata/godef/a/d.go.golden
@@ -13,18 +13,18 @@
 		"start": {
 			"line": 6,
 			"column": 2,
-			"offset": 87
+			"offset": 90
 		},
 		"end": {
 			"line": 6,
 			"column": 8,
-			"offset": 93
+			"offset": 96
 		}
 	},
 	"description": "```go\nfield Member string\n```\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)\n\n\\@Member"
 }
 
--- Member-hover --
+-- Member-hoverdef --
 ```go
 field Member string
 ```
@@ -45,18 +45,18 @@
 		"start": {
 			"line": 15,
 			"column": 16,
-			"offset": 216
+			"offset": 219
 		},
 		"end": {
 			"line": 15,
 			"column": 22,
-			"offset": 222
+			"offset": 225
 		}
 	},
 	"description": "```go\nfunc (Thing).Method(i int) string\n```\n\n[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Method)"
 }
 
--- Method-hover --
+-- Method-hoverdef --
 ```go
 func (Thing).Method(i int) string
 ```
@@ -77,18 +77,18 @@
 		"start": {
 			"line": 9,
 			"column": 5,
-			"offset": 118
+			"offset": 121
 		},
 		"end": {
 			"line": 9,
 			"column": 10,
-			"offset": 123
+			"offset": 126
 		}
 	},
 	"description": "```go\nvar Other Thing\n```\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)\n\n\\@Other"
 }
 
--- Other-hover --
+-- Other-hoverdef --
 ```go
 var Other Thing
 ```
@@ -111,18 +111,18 @@
 		"start": {
 			"line": 5,
 			"column": 6,
-			"offset": 62
+			"offset": 65
 		},
 		"end": {
 			"line": 5,
 			"column": 11,
-			"offset": 67
+			"offset": 70
 		}
 	},
 	"description": "```go\ntype Thing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)"
 }
 
--- Thing-hover --
+-- Thing-hoverdef --
 ```go
 type Thing struct {
 	Member string //@Member
@@ -143,22 +143,22 @@
 		"start": {
 			"line": 11,
 			"column": 6,
-			"offset": 145
+			"offset": 148
 		},
 		"end": {
 			"line": 11,
 			"column": 12,
-			"offset": 151
+			"offset": 154
 		}
 	},
 	"description": "```go\nfunc Things(val []string) []Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)"
 }
 
--- Things-hover --
+-- Things-hoverdef --
 ```go
 func Things(val []string) []Thing
 ```
 
 [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- a-hover --
+-- a-hoverdef --
 Package a is a package for testing go to definition\.
diff --git a/internal/lsp/testdata/godef/a/f.go b/internal/lsp/testdata/godef/a/f.go
index 2d3eefc..589c45f 100644
--- a/internal/lsp/testdata/godef/a/f.go
+++ b/internal/lsp/testdata/godef/a/f.go
@@ -7,9 +7,9 @@
 
 	switch y := interface{}(x).(type) { //@mark(switchY, "y"),godef("y", switchY)
 	case int: //@mark(intY, "int")
-		fmt.Printf("%v", y) //@hover("y", intY)
+		fmt.Printf("%v", y) //@hoverdef("y", intY)
 	case string: //@mark(stringY, "string")
-		fmt.Printf("%v", y) //@hover("y", stringY)
+		fmt.Printf("%v", y) //@hoverdef("y", stringY)
 	}
 
 }
diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/godef/a/f.go.golden
index 6c84b4d..a084356 100644
--- a/internal/lsp/testdata/godef/a/f.go.golden
+++ b/internal/lsp/testdata/godef/a/f.go.golden
@@ -1,8 +1,8 @@
--- intY-hover --
+-- intY-hoverdef --
 ```go
 var y int
 ```
--- stringY-hover --
+-- stringY-hoverdef --
 ```go
 var y string
 ```
@@ -28,7 +28,7 @@
 	"description": "```go\nvar y interface{}\n```"
 }
 
--- switchY-hover --
+-- switchY-hoverdef --
 ```go
 var y interface{}
 ```
diff --git a/internal/lsp/testdata/godef/a/g.go b/internal/lsp/testdata/godef/a/g.go
index 4f31857..dfef2fb 100644
--- a/internal/lsp/testdata/godef/a/g.go
+++ b/internal/lsp/testdata/godef/a/g.go
@@ -3,4 +3,4 @@
 import "time"
 
 // dur is a constant of type time.Duration.
-const dur = 15*time.Minute + 10*time.Second + 350*time.Millisecond //@dur,hover("dur", dur)
+const dur = 15*time.Minute + 10*time.Second + 350*time.Millisecond //@dur,hoverdef("dur", dur)
diff --git a/internal/lsp/testdata/godef/a/g.go.golden b/internal/lsp/testdata/godef/a/g.go.golden
index d46ff04..b7ed739 100644
--- a/internal/lsp/testdata/godef/a/g.go.golden
+++ b/internal/lsp/testdata/godef/a/g.go.golden
@@ -1,4 +1,4 @@
--- dur-hover --
+-- dur-hoverdef --
 ```go
 const dur time.Duration = 910350000000 // 15m10.35s
 ```
diff --git a/internal/lsp/testdata/godef/a/h.go b/internal/lsp/testdata/godef/a/h.go
index efe7d4e..5a5dcc6 100644
--- a/internal/lsp/testdata/godef/a/h.go
+++ b/internal/lsp/testdata/godef/a/h.go
@@ -25,9 +25,9 @@
 	}
 
 	var t s
-	_ = t.nested.number  //@hover("number", nestedNumber)
-	_ = t.nested2[0].str //@hover("str", nestedString)
-	_ = t.x.x.x.x.x.m    //@hover("m", nestedMap)
+	_ = t.nested.number  //@hoverdef("number", nestedNumber)
+	_ = t.nested2[0].str //@hoverdef("str", nestedString)
+	_ = t.x.x.x.x.x.m    //@hoverdef("m", nestedMap)
 }
 
 func _() {
@@ -40,9 +40,9 @@
 			c int //@mark(structC, "c")
 		}
 	}
-	_ = s.a   //@hover("a", structA)
-	_ = s.b   //@hover("b", structB)
-	_ = s.b.c //@hover("c", structC)
+	_ = s.a   //@hoverdef("a", structA)
+	_ = s.b   //@hoverdef("b", structB)
+	_ = s.b.c //@hoverdef("c", structC)
 
 	var arr []struct {
 		// d field
@@ -53,9 +53,9 @@
 			f int //@mark(arrF, "f")
 		}
 	}
-	_ = arr[0].d   //@hover("d", arrD)
-	_ = arr[0].e   //@hover("e", arrE)
-	_ = arr[0].e.f //@hover("f", arrF)
+	_ = arr[0].d   //@hoverdef("d", arrD)
+	_ = arr[0].e   //@hoverdef("e", arrE)
+	_ = arr[0].e.f //@hoverdef("f", arrF)
 
 	var complex []struct {
 		c <-chan map[string][]struct {
@@ -68,16 +68,16 @@
 			}
 		}
 	}
-	_ = (<-complex[0].c)["0"][0].h   //@hover("h", complexH)
-	_ = (<-complex[0].c)["0"][0].i   //@hover("i", complexI)
-	_ = (<-complex[0].c)["0"][0].i.j //@hover("j", complexJ)
+	_ = (<-complex[0].c)["0"][0].h   //@hoverdef("h", complexH)
+	_ = (<-complex[0].c)["0"][0].i   //@hoverdef("i", complexI)
+	_ = (<-complex[0].c)["0"][0].i.j //@hoverdef("j", complexJ)
 
 	var mapWithStructKey map[struct {
 		// X key field
 		x []string //@mark(mapStructKeyX, "x")
 	}]int
 	for k := range mapWithStructKey {
-		_ = k.x //@hover("x", mapStructKeyX)
+		_ = k.x //@hoverdef("x", mapStructKeyX)
 	}
 
 	var mapWithStructKeyAndValue map[struct {
@@ -90,15 +90,15 @@
 	for k, v := range mapWithStructKeyAndValue {
 		// TODO: we don't show docs for y field because both map key and value
 		// are structs. And in this case, we parse only map value
-		_ = k.y //@hover("y", mapStructKeyY)
-		_ = v.x //@hover("x", mapStructValueX)
+		_ = k.y //@hoverdef("y", mapStructKeyY)
+		_ = v.x //@hoverdef("x", mapStructValueX)
 	}
 
 	var i []map[string]interface {
 		// open method comment
 		open() error //@mark(openMethod, "open")
 	}
-	i[0]["1"].open() //@hover("open", openMethod)
+	i[0]["1"].open() //@hoverdef("open", openMethod)
 }
 
 func _() {
@@ -106,7 +106,7 @@
 		// test description
 		desc string //@mark(testDescription, "desc")
 	}{}
-	_ = test.desc //@hover("desc", testDescription)
+	_ = test.desc //@hoverdef("desc", testDescription)
 
 	for _, tt := range []struct {
 		// test input
@@ -123,11 +123,11 @@
 			}
 		}
 	}{} {
-		_ = tt.in               //@hover("in", testInput)
-		_ = tt.in["0"][0].key   //@hover("key", testInputKey)
-		_ = tt.in["0"][0].value //@hover("value", testInputValue)
+		_ = tt.in               //@hoverdef("in", testInput)
+		_ = tt.in["0"][0].key   //@hoverdef("key", testInputKey)
+		_ = tt.in["0"][0].value //@hoverdef("value", testInputValue)
 
-		_ = (<-tt.result.v).value //@hover("value", testResultValue)
+		_ = (<-tt.result.v).value //@hoverdef("value", testResultValue)
 	}
 }
 
@@ -142,6 +142,6 @@
 	}
 
 	r := getPoints()
-	r[0].x //@hover("x", returnX)
-	r[0].y //@hover("y", returnY)
+	r[0].x //@hoverdef("x", returnX)
+	r[0].y //@hoverdef("y", returnY)
 }
diff --git a/internal/lsp/testdata/godef/a/h.go.golden b/internal/lsp/testdata/godef/a/h.go.golden
index 3525d4c..4b27211 100644
--- a/internal/lsp/testdata/godef/a/h.go.golden
+++ b/internal/lsp/testdata/godef/a/h.go.golden
@@ -1,134 +1,134 @@
--- arrD-hover --
+-- arrD-hoverdef --
 ```go
 field d int
 ```
 
 d field
--- arrE-hover --
+-- arrE-hoverdef --
 ```go
 field e struct{f int}
 ```
 
 e nested struct
--- arrF-hover --
+-- arrF-hoverdef --
 ```go
 field f int
 ```
 
 f field of nested struct
--- complexH-hover --
+-- complexH-hoverdef --
 ```go
 field h int
 ```
 
 h field
--- complexI-hover --
+-- complexI-hoverdef --
 ```go
 field i struct{j int}
 ```
 
 i nested struct
--- complexJ-hover --
+-- complexJ-hoverdef --
 ```go
 field j int
 ```
 
 j field of nested struct
--- mapStructKeyX-hover --
+-- mapStructKeyX-hoverdef --
 ```go
 field x []string
 ```
 
 X key field
--- mapStructKeyY-hover --
+-- mapStructKeyY-hoverdef --
 ```go
 field y string
 ```
--- mapStructValueX-hover --
+-- mapStructValueX-hoverdef --
 ```go
 field x string
 ```
 
 X value field
--- nestedMap-hover --
+-- nestedMap-hoverdef --
 ```go
 field m map[string]float64
 ```
 
 nested map
--- nestedNumber-hover --
+-- nestedNumber-hoverdef --
 ```go
 field number int64
 ```
 
 nested number
--- nestedString-hover --
+-- nestedString-hoverdef --
 ```go
 field str string
 ```
 
 nested string
--- openMethod-hover --
+-- openMethod-hoverdef --
 ```go
 func (interface).open() error
 ```
 
 open method comment
--- returnX-hover --
+-- returnX-hoverdef --
 ```go
 field x int
 ```
 
 X coord
--- returnY-hover --
+-- returnY-hoverdef --
 ```go
 field y int
 ```
 
 Y coord
--- structA-hover --
+-- structA-hoverdef --
 ```go
 field a int
 ```
 
 a field
--- structB-hover --
+-- structB-hoverdef --
 ```go
 field b struct{c int}
 ```
 
 b nested struct
--- structC-hover --
+-- structC-hoverdef --
 ```go
 field c int
 ```
 
 c field of nested struct
--- testDescription-hover --
+-- testDescription-hoverdef --
 ```go
 field desc string
 ```
 
 test description
--- testInput-hover --
+-- testInput-hoverdef --
 ```go
 field in map[string][]struct{key string; value interface{}}
 ```
 
 test input
--- testInputKey-hover --
+-- testInputKey-hoverdef --
 ```go
 field key string
 ```
 
 test key
--- testInputValue-hover --
+-- testInputValue-hoverdef --
 ```go
 field value interface{}
 ```
 
 test value
--- testResultValue-hover --
+-- testResultValue-hoverdef --
 ```go
 field value int
 ```
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/godef/a/random.go.golden
index 0f99a52..381a11a 100644
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ b/internal/lsp/testdata/godef/a/random.go.golden
@@ -22,7 +22,7 @@
 	"description": "```go\nfunc (*Pos).Sum() int\n```\n\n[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Pos.Sum)"
 }
 
--- PosSum-hover --
+-- PosSum-hoverdef --
 ```go
 func (*Pos).Sum() int
 ```
@@ -52,7 +52,7 @@
 	"description": "```go\nfield x int\n```\n\n\\@mark\\(PosX, \\\"x\\\"\\),mark\\(PosY, \\\"y\\\"\\)"
 }
 
--- PosX-hover --
+-- PosX-hoverdef --
 ```go
 field x int
 ```
@@ -80,7 +80,7 @@
 	"description": "```go\nvar y int\n```"
 }
 
--- RandomParamY-hover --
+-- RandomParamY-hoverdef --
 ```go
 var y int
 ```
@@ -106,7 +106,7 @@
 	"description": "```go\nfield field string\n```"
 }
 
--- TypField-hover --
+-- TypField-hoverdef --
 ```go
 field field string
 ```
diff --git a/internal/lsp/testdata/godef/b/b.go b/internal/lsp/testdata/godef/b/b.go
index 23d908f..f9c1d64 100644
--- a/internal/lsp/testdata/godef/b/b.go
+++ b/internal/lsp/testdata/godef/b/b.go
@@ -13,13 +13,13 @@
 
 func _() {
 	e := Embed{}
-	e.Hi()      //@hover("Hi", AHi)
-	e.B()       //@hover("B", AB)
-	e.Field     //@hover("Field", AField)
-	e.Field2    //@hover("Field2", AField2)
-	e.Hello()   //@hover("Hello", AHello)
-	e.Hey()     //@hover("Hey", AHey)
-	e.Goodbye() //@hover("Goodbye", AGoodbye)
+	e.Hi()      //@hoverdef("Hi", AHi)
+	e.B()       //@hoverdef("B", AB)
+	e.Field     //@hoverdef("Field", AField)
+	e.Field2    //@hoverdef("Field2", AField2)
+	e.Hello()   //@hoverdef("Hello", AHello)
+	e.Hey()     //@hoverdef("Hey", AHey)
+	e.Goodbye() //@hoverdef("Goodbye", AGoodbye)
 }
 
 type aAlias = a.A //@mark(aAlias, "aAlias")
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
index 5537180..7f05a70 100644
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ b/internal/lsp/testdata/godef/b/b.go.golden
@@ -1,4 +1,4 @@
--- AB-hover --
+-- AB-hoverdef --
 ```go
 func (a.I).B()
 ```
@@ -6,7 +6,7 @@
 [`(a.I).B` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#I.B)
 
 \@mark\(AB, \"B\"\)
--- AField-hover --
+-- AField-hoverdef --
 ```go
 field Field int
 ```
@@ -14,7 +14,7 @@
 [`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#S.Field)
 
 \@mark\(AField, \"Field\"\)
--- AField2-hover --
+-- AField2-hoverdef --
 ```go
 field Field2 int
 ```
@@ -22,7 +22,7 @@
 [`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#R.Field2)
 
 \@mark\(AField2, \"Field2\"\)
--- AGoodbye-hover --
+-- AGoodbye-hoverdef --
 ```go
 func (a.H).Goodbye()
 ```
@@ -30,7 +30,7 @@
 [`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#H.Goodbye)
 
 \@mark\(AGoodbye, \"Goodbye\"\)
--- AHello-hover --
+-- AHello-hoverdef --
 ```go
 func (a.J).Hello()
 ```
@@ -38,13 +38,13 @@
 [`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#J.Hello)
 
 \@mark\(AHello, \"Hello\"\)
--- AHey-hover --
+-- AHey-hoverdef --
 ```go
 func (a.R).Hey()
 ```
 
 [`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#R.Hey)
--- AHi-hover --
+-- AHi-hoverdef --
 ```go
 func (a.A).Hi()
 ```
@@ -74,7 +74,7 @@
 	"description": "```go\npackage a (\"golang.org/x/tools/internal/lsp/godef/a\")\n```\n\n[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls)"
 }
 
--- AImport-hover --
+-- AImport-hoverdef --
 ```go
 package a ("golang.org/x/tools/internal/lsp/godef/a")
 ```
@@ -95,18 +95,18 @@
 		"start": {
 			"line": 26,
 			"column": 6,
-			"offset": 452
+			"offset": 467
 		},
 		"end": {
 			"line": 26,
 			"column": 7,
-			"offset": 453
+			"offset": 468
 		}
 	},
 	"description": "```go\ntype A string\n```\n\n[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#A)\n\n\\@mark\\(AString, \\\"A\\\"\\)"
 }
 
--- AString-hover --
+-- AString-hoverdef --
 ```go
 type A string
 ```
@@ -127,18 +127,18 @@
 		"start": {
 			"line": 28,
 			"column": 6,
-			"offset": 489
+			"offset": 504
 		},
 		"end": {
 			"line": 28,
 			"column": 12,
-			"offset": 495
+			"offset": 510
 		}
 	},
 	"description": "```go\nfunc a.AStuff()\n```\n\n[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)"
 }
 
--- AStuff-hover --
+-- AStuff-hoverdef --
 ```go
 func a.AStuff()
 ```
@@ -162,18 +162,18 @@
 		"start": {
 			"line": 27,
 			"column": 6,
-			"offset": 566
+			"offset": 587
 		},
 		"end": {
 			"line": 27,
 			"column": 8,
-			"offset": 568
+			"offset": 589
 		}
 	},
 	"description": "```go\ntype S1 struct {\n\tF1     int //@mark(S1F1, \"F1\")\n\tS2         //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A        //@godef(\"A\", AString)\n\taAlias     //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)"
 }
 
--- S1-hover --
+-- S1-hoverdef --
 ```go
 type S1 struct {
 	F1     int //@mark(S1F1, "F1")
@@ -199,18 +199,18 @@
 		"start": {
 			"line": 28,
 			"column": 2,
-			"offset": 585
+			"offset": 606
 		},
 		"end": {
 			"line": 28,
 			"column": 4,
-			"offset": 587
+			"offset": 608
 		}
 	},
 	"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"
 }
 
--- S1F1-hover --
+-- S1F1-hoverdef --
 ```go
 field F1 int
 ```
@@ -233,18 +233,18 @@
 		"start": {
 			"line": 29,
 			"column": 2,
-			"offset": 617
+			"offset": 638
 		},
 		"end": {
 			"line": 29,
 			"column": 4,
-			"offset": 619
+			"offset": 640
 		}
 	},
 	"description": "```go\nfield S2 S2\n```\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.S2)\n\n\\@godef\\(\\\"S2\\\", S2\\),mark\\(S1S2, \\\"S2\\\"\\)"
 }
 
--- S1S2-hover --
+-- S1S2-hoverdef --
 ```go
 field S2 S2
 ```
@@ -269,18 +269,18 @@
 		"start": {
 			"line": 34,
 			"column": 6,
-			"offset": 741
+			"offset": 762
 		},
 		"end": {
 			"line": 34,
 			"column": 8,
-			"offset": 743
+			"offset": 764
 		}
 	},
 	"description": "```go\ntype S2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", AString),godef(\"a\",AImport)\n}\n```\n\n[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2)"
 }
 
--- S2-hover --
+-- S2-hoverdef --
 ```go
 type S2 struct {
 	F1   string //@mark(S2F1, "F1")
@@ -305,18 +305,18 @@
 		"start": {
 			"line": 35,
 			"column": 2,
-			"offset": 760
+			"offset": 781
 		},
 		"end": {
 			"line": 35,
 			"column": 4,
-			"offset": 762
+			"offset": 783
 		}
 	},
 	"description": "```go\nfield F1 string\n```\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F1)\n\n\\@mark\\(S2F1, \\\"F1\\\"\\)"
 }
 
--- S2F1-hover --
+-- S2F1-hoverdef --
 ```go
 field F1 string
 ```
@@ -339,18 +339,18 @@
 		"start": {
 			"line": 36,
 			"column": 2,
-			"offset": 793
+			"offset": 814
 		},
 		"end": {
 			"line": 36,
 			"column": 4,
-			"offset": 795
+			"offset": 816
 		}
 	},
 	"description": "```go\nfield F2 int\n```\n\n[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S2.F2)\n\n\\@mark\\(S2F2, \\\"F2\\\"\\)"
 }
 
--- S2F2-hover --
+-- S2F2-hoverdef --
 ```go
 field F2 int
 ```
@@ -371,18 +371,18 @@
 		"start": {
 			"line": 25,
 			"column": 6,
-			"offset": 521
+			"offset": 542
 		},
 		"end": {
 			"line": 25,
 			"column": 12,
-			"offset": 527
+			"offset": 548
 		}
 	},
 	"description": "```go\ntype aAlias = a.A\n```\n\n\\@mark\\(aAlias, \\\"aAlias\\\"\\)"
 }
 
--- aAlias-hover --
+-- aAlias-hoverdef --
 ```go
 type aAlias = a.A
 ```
@@ -403,18 +403,18 @@
 		"start": {
 			"line": 57,
 			"column": 7,
-			"offset": 1228
+			"offset": 1249
 		},
 		"end": {
 			"line": 57,
 			"column": 8,
-			"offset": 1229
+			"offset": 1250
 		}
 	},
 	"description": "```go\nconst X untyped int = 0\n```\n\n[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#X)\n\n\\@mark\\(bX, \\\"X\\\"\\),godef\\(\\\"X\\\", bX\\)"
 }
 
--- bX-hover --
+-- bX-hoverdef --
 ```go
 const X untyped int = 0
 ```
@@ -446,7 +446,7 @@
 	"description": "```go\npackage myFoo (\"golang.org/x/tools/internal/lsp/foo\")\n```\n\n[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo?utm_source=gopls)"
 }
 
--- myFoo-hover --
+-- myFoo-hoverdef --
 ```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 9554c0d..3ae3e2d 100644
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ b/internal/lsp/testdata/godef/b/c.go.golden
@@ -16,18 +16,18 @@
 		"start": {
 			"line": 27,
 			"column": 6,
-			"offset": 566
+			"offset": 587
 		},
 		"end": {
 			"line": 27,
 			"column": 8,
-			"offset": 568
+			"offset": 589
 		}
 	},
 	"description": "```go\ntype S1 struct {\n\tF1     int //@mark(S1F1, \"F1\")\n\tS2         //@godef(\"S2\", S2),mark(S1S2, \"S2\")\n\ta.A        //@godef(\"A\", AString)\n\taAlias     //@godef(\"a\", aAlias)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1)"
 }
 
--- S1-hover --
+-- S1-hoverdef --
 ```go
 type S1 struct {
 	F1     int //@mark(S1F1, "F1")
@@ -53,18 +53,18 @@
 		"start": {
 			"line": 28,
 			"column": 2,
-			"offset": 585
+			"offset": 606
 		},
 		"end": {
 			"line": 28,
 			"column": 4,
-			"offset": 587
+			"offset": 608
 		}
 	},
 	"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b?utm_source=gopls#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"
 }
 
--- S1F1-hover --
+-- S1F1-hoverdef --
 ```go
 field F1 int
 ```
diff --git a/internal/lsp/testdata/godef/b/e.go b/internal/lsp/testdata/godef/b/e.go
index 92037ed..7b96cd7 100644
--- a/internal/lsp/testdata/godef/b/e.go
+++ b/internal/lsp/testdata/godef/b/e.go
@@ -22,10 +22,10 @@
 
 func _() {
 	var x interface{}      //@mark(eInterface, "interface{}")
-	switch x := x.(type) { //@hover("x", eInterface)
+	switch x := x.(type) { //@hoverdef("x", eInterface)
 	case string: //@mark(eString, "string")
-		fmt.Println(x) //@hover("x", eString)
+		fmt.Println(x) //@hoverdef("x", eString)
 	case int: //@mark(eInt, "int")
-		fmt.Println(x) //@hover("x", eInt)
+		fmt.Println(x) //@hoverdef("x", eInt)
 	}
 }
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/godef/b/e.go.golden
index 13c2e0e..079ed79 100644
--- a/internal/lsp/testdata/godef/b/e.go.golden
+++ b/internal/lsp/testdata/godef/b/e.go.golden
@@ -13,18 +13,18 @@
 		"start": {
 			"line": 6,
 			"column": 2,
-			"offset": 87
+			"offset": 90
 		},
 		"end": {
 			"line": 6,
 			"column": 8,
-			"offset": 93
+			"offset": 96
 		}
 	},
 	"description": "```go\nfield Member string\n```\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing.Member)\n\n\\@Member"
 }
 
--- Member-hover --
+-- Member-hoverdef --
 ```go
 field Member string
 ```
@@ -47,18 +47,18 @@
 		"start": {
 			"line": 9,
 			"column": 5,
-			"offset": 118
+			"offset": 121
 		},
 		"end": {
 			"line": 9,
 			"column": 10,
-			"offset": 123
+			"offset": 126
 		}
 	},
 	"description": "```go\nvar a.Other a.Thing\n```\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Other)\n\n\\@Other"
 }
 
--- Other-hover --
+-- Other-hoverdef --
 ```go
 var a.Other a.Thing
 ```
@@ -81,18 +81,18 @@
 		"start": {
 			"line": 5,
 			"column": 6,
-			"offset": 62
+			"offset": 65
 		},
 		"end": {
 			"line": 5,
 			"column": 11,
-			"offset": 67
+			"offset": 70
 		}
 	},
 	"description": "```go\ntype Thing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Thing)"
 }
 
--- Thing-hover --
+-- Thing-hoverdef --
 ```go
 type Thing struct {
 	Member string //@Member
@@ -113,32 +113,32 @@
 		"start": {
 			"line": 11,
 			"column": 6,
-			"offset": 145
+			"offset": 148
 		},
 		"end": {
 			"line": 11,
 			"column": 12,
-			"offset": 151
+			"offset": 154
 		}
 	},
 	"description": "```go\nfunc a.Things(val []string) []a.Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)"
 }
 
--- Things-hover --
+-- Things-hoverdef --
 ```go
 func a.Things(val []string) []a.Thing
 ```
 
 [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#Things)
--- eInt-hover --
+-- eInt-hoverdef --
 ```go
 var x int
 ```
--- eInterface-hover --
+-- eInterface-hoverdef --
 ```go
 var x interface{}
 ```
--- eString-hover --
+-- eString-hoverdef --
 ```go
 var x string
 ```
diff --git a/internal/lsp/testdata/godef/b/h.go b/internal/lsp/testdata/godef/b/h.go
index c2776a0..c8cbe85 100644
--- a/internal/lsp/testdata/godef/b/h.go
+++ b/internal/lsp/testdata/godef/b/h.go
@@ -4,7 +4,7 @@
 
 func _() {
 	// variable of type a.A
-	var _ A //@mark(AVariable, "_"),hover("_", AVariable)
+	var _ A //@mark(AVariable, "_"),hoverdef("_", AVariable)
 
-	AStuff() //@hover("AStuff", AStuff)
+	AStuff() //@hoverdef("AStuff", AStuff)
 }
diff --git a/internal/lsp/testdata/godef/b/h.go.golden b/internal/lsp/testdata/godef/b/h.go.golden
index b854dd4..f32f026 100644
--- a/internal/lsp/testdata/godef/b/h.go.golden
+++ b/internal/lsp/testdata/godef/b/h.go.golden
@@ -1,10 +1,10 @@
--- AStuff-hover --
+-- AStuff-hoverdef --
 ```go
 func AStuff()
 ```
 
 [`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a?utm_source=gopls#AStuff)
--- AVariable-hover --
+-- AVariable-hoverdef --
 ```go
 var _ A
 ```
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
index eac0339..5c3329d 100644
--- a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
+++ b/internal/lsp/testdata/godef/broken/unclosedIf.go.golden
@@ -22,7 +22,7 @@
 	"description": "```go\nvar myUnclosedIf string\n```\n\n\\@myUnclosedIf"
 }
 
--- myUnclosedIf-hover --
+-- myUnclosedIf-hoverdef --
 ```go
 var myUnclosedIf string
 ```
diff --git a/internal/lsp/testdata/godef/infer_generics/inferred.go.golden b/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
index 2dd97d9..081ea53 100644
--- a/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
+++ b/internal/lsp/testdata/godef/infer_generics/inferred.go.golden
@@ -1,20 +1,20 @@
--- argInfer-hover --
+-- argInfer-hoverdef --
 ```go
 func app(s []int, e int) []int // func[Sā‚ interface{~[]Eā‚‚}, Eā‚‚ interface{}](s Sā‚, e Eā‚‚) Sā‚
 ```
--- constrInf-hover --
+-- constrInf-hoverdef --
 ```go
 func app(s []int, e int) []int // func[Sā‚ interface{~[]Eā‚‚}, Eā‚‚ interface{}](s Sā‚, e Eā‚‚) Sā‚
 ```
--- constrInfer-hover --
+-- constrInfer-hoverdef --
 ```go
 func app(s []int, e int) []int // func[Sā‚ interface{~[]Eā‚‚}, Eā‚‚ interface{}](s Sā‚, e Eā‚‚) Sā‚
 ```
--- instance-hover --
+-- instance-hoverdef --
 ```go
 func app(s []int, e int) []int // func[Sā‚ interface{~[]Eā‚‚}, Eā‚‚ interface{}](s Sā‚, e Eā‚‚) Sā‚
 ```
--- partialInfer-hover --
+-- partialInfer-hoverdef --
 ```go
 func app(s []int, e int) []int // func[Sā‚ interface{~[]Eā‚‚}, Eā‚‚ interface{}](s Sā‚, e Eā‚‚) Sā‚
 ```
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index d5db454..5d6af9e 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -84,6 +84,7 @@
 type Signatures map[span.Span]*protocol.SignatureHelp
 type Links map[span.URI][]Link
 type AddImport map[span.URI]string
+type Hovers map[span.Span]string
 
 type Data struct {
 	Config                   packages.Config
@@ -119,6 +120,7 @@
 	Signatures               Signatures
 	Links                    Links
 	AddImport                AddImport
+	Hovers                   Hovers
 
 	t         testing.TB
 	fragments map[string]string
@@ -161,6 +163,7 @@
 	SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
 	Link(*testing.T, span.URI, []Link)
 	AddImport(*testing.T, span.URI, string)
+	Hover(*testing.T, span.Span, string)
 }
 
 type Definition struct {
@@ -309,6 +312,7 @@
 		Signatures:               make(Signatures),
 		Links:                    make(Links),
 		AddImport:                make(AddImport),
+		Hovers:                   make(Hovers),
 
 		t:         t,
 		dir:       dir,
@@ -459,7 +463,8 @@
 		"godef":           datum.collectDefinitions,
 		"implementations": datum.collectImplementations,
 		"typdef":          datum.collectTypeDefinitions,
-		"hover":           datum.collectHoverDefinitions,
+		"hoverdef":        datum.collectHoverDefinitions,
+		"hover":           datum.collectHovers,
 		"highlight":       datum.collectHighlights,
 		"refs":            datum.collectReferences,
 		"rename":          datum.collectRenames,
@@ -485,7 +490,7 @@
 	// Collect names for the entries that require golden files.
 	if err := datum.Exported.Expect(map[string]interface{}{
 		"godef":                        datum.collectDefinitionNames,
-		"hover":                        datum.collectDefinitionNames,
+		"hoverdef":                     datum.collectDefinitionNames,
 		"workspacesymbol":              datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
 		"workspacesymbolfuzzy":         datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
 		"workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
@@ -730,6 +735,16 @@
 		}
 	})
 
+	t.Run("Hover", func(t *testing.T) {
+		t.Helper()
+		for pos, info := range data.Hovers {
+			t.Run(SpanName(pos), func(t *testing.T) {
+				t.Helper()
+				tests.Hover(t, pos, info)
+			})
+		}
+	})
+
 	t.Run("References", func(t *testing.T) {
 		t.Helper()
 		for src, itemList := range data.References {
@@ -1222,6 +1237,10 @@
 	}
 }
 
+func (data *Data) collectHovers(src span.Span, expected string) {
+	data.Hovers[src] = expected
+}
+
 func (data *Data) collectTypeDefinitions(src, target span.Span) {
 	data.Definitions[src] = Definition{
 		Src:    src,