Skip to content

Commit 1239d04

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: Hover: use internal pkg doc viewer
This change changes the linksInHover option from bool to a sum of false | true | "gopls". The "gopls" setting causes Hover(SynopsisDocumentation) to generate links to gopls' internal web-based doc viewer. Thanks to Hana for the idea. + Test, release note Fixes golang/go#67949 Change-Id: I384796780436b191a0711c60085d67363d00e5f6 Reviewed-on: https://go-review.googlesource.com/c/tools/+/572037 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Alan Donovan <[email protected]>
1 parent 6ece8fb commit 1239d04

File tree

8 files changed

+106
-18
lines changed

8 files changed

+106
-18
lines changed

gopls/doc/release/v0.16.0.md

+5
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ Editor support:
107107

108108
- TODO: test in vim, neovim, sublime, helix.
109109

110+
The `linksInHover` setting now supports a new value, `"gopls"`,
111+
that causes documentation links in the the Markdown output
112+
of the Hover operation to link to gopls' internal doc viewer.
113+
114+
110115
### Browse free symbols
111116

112117
Gopls offers another web-based code action, "Browse free symbols",

gopls/doc/settings.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -439,9 +439,15 @@ documentation links in hover.
439439
Default: `"pkg.go.dev"`.
440440

441441
<a id='linksInHover'></a>
442-
### `linksInHover` *bool*
442+
### `linksInHover` *any*
443443

444-
linksInHover toggles the presence of links to documentation in hover.
444+
linksInHover controls the presence of documentation links
445+
in hover markdown.
446+
447+
Its legal values are:
448+
- `false`, for no links;
449+
- `true`, for links to the `linkTarget` domain; or
450+
- `"gopls"`, for links to gopls' internal documentation viewer.
445451

446452
Default: `true`.
447453

gopls/internal/cache/errors.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ func typesCodeHref(linkTarget string, code typesinternal.ErrorCode) string {
388388
}
389389

390390
// BuildLink constructs a URL with the given target, path, and anchor.
391-
func BuildLink(target, path, anchor string) string {
391+
func BuildLink(target, path, anchor string) protocol.URI {
392392
link := fmt.Sprintf("https://%s/%s", target, path)
393393
if anchor == "" {
394394
return link

gopls/internal/doc/api.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@
154154
},
155155
{
156156
"Name": "linksInHover",
157-
"Type": "bool",
158-
"Doc": "linksInHover toggles the presence of links to documentation in hover.\n",
157+
"Type": "any",
158+
"Doc": "linksInHover controls the presence of documentation links\nin hover markdown.\n\nIts legal values are:\n- `false`, for no links;\n- `true`, for links to the `linkTarget` domain; or\n- `\"gopls\"`, for links to gopls' internal documentation viewer.\n",
159159
"EnumKeys": {
160160
"ValueType": "",
161161
"Keys": null

gopls/internal/golang/hover.go

+24-9
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ type hoverJSON struct {
9898
}
9999

100100
// Hover implements the "textDocument/hover" RPC for Go files.
101-
func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) {
101+
//
102+
// If pkgURL is non-nil, it should be used to generate doc links.
103+
func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position, pkgURL func(path PackagePath, fragment string) protocol.URI) (*protocol.Hover, error) {
102104
ctx, done := event.Start(ctx, "golang.Hover")
103105
defer done()
104106

@@ -109,7 +111,7 @@ func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, positi
109111
if h == nil {
110112
return nil, nil
111113
}
112-
hover, err := formatHover(h, snapshot.Options())
114+
hover, err := formatHover(h, snapshot.Options(), pkgURL)
113115
if err != nil {
114116
return nil, err
115117
}
@@ -1088,7 +1090,8 @@ func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSe
10881090
return pgf, fullPos, nil
10891091
}
10901092

1091-
func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
1093+
// If pkgURL is non-nil, it should be used to generate doc links.
1094+
func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) {
10921095
maybeMarkdown := func(s string) string {
10931096
if s != "" && options.PreferredContentFormat == protocol.Markdown {
10941097
s = fmt.Sprintf("```go\n%s\n```", strings.Trim(s, "\n"))
@@ -1123,7 +1126,7 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
11231126
formatDoc(h, options),
11241127
maybeMarkdown(h.promotedFields),
11251128
maybeMarkdown(h.methods),
1126-
formatLink(h, options),
1129+
formatLink(h, options, pkgURL),
11271130
}
11281131
if h.typeDecl != "" {
11291132
parts[0] = "" // type: suppress redundant Signature
@@ -1148,18 +1151,30 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
11481151
}
11491152
}
11501153

1151-
func formatLink(h *hoverJSON, options *settings.Options) string {
1152-
if !options.LinksInHover || options.LinkTarget == "" || h.LinkPath == "" {
1154+
// If pkgURL is non-nil, it should be used to generate doc links.
1155+
func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string {
1156+
if options.LinksInHover == false || h.LinkPath == "" {
11531157
return ""
11541158
}
1155-
plainLink := cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
1159+
var url protocol.URI
1160+
var caption string
1161+
if pkgURL != nil { // LinksInHover == "gopls"
1162+
url = pkgURL(PackagePath(h.LinkPath), h.LinkAnchor)
1163+
caption = "in gopls doc viewer"
1164+
} else {
1165+
if options.LinkTarget == "" {
1166+
return ""
1167+
}
1168+
url = cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
1169+
caption = "on " + options.LinkTarget
1170+
}
11561171
switch options.PreferredContentFormat {
11571172
case protocol.Markdown:
1158-
return fmt.Sprintf("[`%s` on %s](%s)", h.SymbolName, options.LinkTarget, plainLink)
1173+
return fmt.Sprintf("[`%s` %s](%s)", h.SymbolName, caption, url)
11591174
case protocol.PlainText:
11601175
return ""
11611176
default:
1162-
return plainLink
1177+
return url
11631178
}
11641179
}
11651180

gopls/internal/server/hover.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,18 @@ func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (_ *pr
3737
case file.Mod:
3838
return mod.Hover(ctx, snapshot, fh, params.Position)
3939
case file.Go:
40-
return golang.Hover(ctx, snapshot, fh, params.Position)
40+
var pkgURL func(path golang.PackagePath, fragment string) protocol.URI
41+
if snapshot.Options().LinksInHover == "gopls" {
42+
web, err := s.getWeb()
43+
if err != nil {
44+
event.Error(ctx, "failed to start web server", err)
45+
} else {
46+
pkgURL = func(path golang.PackagePath, fragment string) protocol.URI {
47+
return web.PkgURL(snapshot.View().ID(), path, fragment)
48+
}
49+
}
50+
}
51+
return golang.Hover(ctx, snapshot, fh, params.Position, pkgURL)
4152
case file.Tmpl:
4253
return template.Hover(ctx, snapshot, fh, params.Position)
4354
case file.Work:

gopls/internal/settings/settings.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,14 @@ type DocumentationOptions struct {
244244
// documentation links in hover.
245245
LinkTarget string
246246

247-
// LinksInHover toggles the presence of links to documentation in hover.
248-
LinksInHover bool
247+
// LinksInHover controls the presence of documentation links
248+
// in hover markdown.
249+
//
250+
// Its legal values are:
251+
// - `false`, for no links;
252+
// - `true`, for links to the `linkTarget` domain; or
253+
// - `"gopls"`, for links to gopls' internal documentation viewer.
254+
LinksInHover any
249255
}
250256

251257
// Note: FormattingOptions must be comparable with reflect.DeepEqual.
@@ -810,7 +816,13 @@ func (o *Options) set(name string, value any, seen map[string]struct{}) error {
810816
return setString(&o.LinkTarget, value)
811817

812818
case "linksInHover":
813-
return setBool(&o.LinksInHover, value)
819+
switch value {
820+
case false, true, "gopls":
821+
o.LinksInHover = value
822+
default:
823+
return fmt.Errorf(`invalid value %s; expect false, true, or "gopls"`,
824+
value)
825+
}
814826

815827
case "importShortcut":
816828
return setEnum(&o.ImportShortcut, value,

gopls/internal/test/integration/misc/hover_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package misc
66

77
import (
88
"fmt"
9+
"regexp"
910
"strings"
1011
"testing"
1112

@@ -514,3 +515,41 @@ func _() {
514515
_, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("p.go", "foo[.]"))
515516
})
516517
}
518+
519+
func TestHoverInternalLinks(t *testing.T) {
520+
const src = `
521+
-- main.go --
522+
package main
523+
524+
import "errors"
525+
526+
func main() {
527+
errors.New("oops")
528+
}
529+
`
530+
for _, test := range []struct {
531+
linksInHover any // JSON configuration value
532+
wantRE string // pattern to match the Hover Markdown output
533+
}{
534+
{
535+
true, // default: use options.LinkTarget domain
536+
regexp.QuoteMeta("[`errors.New` on pkg.go.dev](https://pkg.go.dev/errors#New)"),
537+
},
538+
{
539+
"gopls", // use gopls' internal viewer
540+
"\\[`errors.New` in gopls doc viewer\\]\\(http://127.0.0.1:[0-9]+/gopls/[^/]+/pkg/errors\\?view=[0-9]+#New\\)",
541+
},
542+
} {
543+
WithOptions(
544+
Settings{"linksInHover": test.linksInHover},
545+
).Run(t, src, func(t *testing.T, env *Env) {
546+
env.OpenFile("main.go")
547+
got, _ := env.Hover(env.RegexpSearch("main.go", "New"))
548+
if m, err := regexp.MatchString(test.wantRE, got.Value); err != nil {
549+
t.Fatalf("bad regexp in test: %v", err)
550+
} else if !m {
551+
t.Fatalf("hover output does not match %q; got:\n\n%s", test.wantRE, got.Value)
552+
}
553+
})
554+
}
555+
}

0 commit comments

Comments
 (0)