Skip to content

Commit 777f155

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/golang: show package attributes on hover
When hovering over a package name in a package declaration, show language version and GODEBUG values that differ from the toolchain default. Also, for consistency with other hover content, show the package declaration as the 'signature' of both a package name and an import. Finally, to help differentiate this information, introduce hlines in the hover output between logical sections. This is similar to what is done by other LSP servers such as tsserver. Fixes golang/go#68900 Change-Id: I5013bb9fb4086c71cc3565fd67993764cad69237 Reviewed-on: https://go-review.googlesource.com/c/tools/+/626276 Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 8a0e08f commit 777f155

20 files changed

+480
-55
lines changed

gopls/doc/release/v0.17.0.md

+9-4
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ which allows editors to request diagnostics directly from gopls using a
3939
`textDocument/publishDiagnostics` notification. This feature is off by default
4040
until the performance of pull diagnostics is comparable to push diagnostics.
4141

42-
## Standard library version information in Hover
42+
## Hover improvements
4343

44-
Hovering over a standard library symbol now displays information about the first
45-
Go release containing the symbol. For example, hovering over `errors.As` shows
46-
"Added in go1.13".
44+
The `textDocument/hover` response has slightly tweaked markdown rendering, and
45+
includes the following additional information:
46+
47+
- Hovering over a standard library symbol now displays information about the
48+
first Go release containing the symbol. For example, hovering over
49+
`errors.As` shows "Added in go1.13".
50+
- Hovering over the package name in a package declaration includes additional
51+
package metadata.
4752

4853
## Semantic token modifiers of top-level constructor of types
4954

gopls/internal/golang/hover.go

+103-50
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"go/format"
1616
"go/token"
1717
"go/types"
18+
"go/version"
1819
"io/fs"
1920
"path/filepath"
2021
"sort"
@@ -80,10 +81,6 @@ type hoverJSON struct {
8081
// For example, the "Node" part of "pkg.go.dev/go/ast#Node".
8182
LinkAnchor string `json:"linkAnchor"`
8283

83-
// stdVersion is the Go release version at which this symbol became available.
84-
// It is nil for non-std library.
85-
stdVersion *stdlib.Version
86-
8784
// New fields go below, and are unexported. The existing
8885
// exported fields are underspecified and have already
8986
// constrained our movements too much. A detailed JSON
@@ -103,6 +100,10 @@ type hoverJSON struct {
103100
// fields of a (struct) type that were promoted through an
104101
// embedded field.
105102
promotedFields string
103+
104+
// footer is additional content to insert at the bottom of the hover
105+
// documentation, before the pkgdoc link.
106+
footer string
106107
}
107108

108109
// Hover implements the "textDocument/hover" RPC for Go files.
@@ -602,9 +603,9 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
602603
linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1)
603604
}
604605

605-
var version *stdlib.Version
606-
if symbol := StdSymbolOf(obj); symbol != nil {
607-
version = &symbol.Version
606+
var footer string
607+
if sym := StdSymbolOf(obj); sym != nil && sym.Version > 0 {
608+
footer = fmt.Sprintf("Added in %v", sym.Version)
608609
}
609610

610611
return *hoverRange, &hoverJSON{
@@ -618,7 +619,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
618619
typeDecl: typeDecl,
619620
methods: methods,
620621
promotedFields: fields,
621-
stdVersion: version,
622+
footer: footer,
622623
}, nil
623624
}
624625

@@ -733,6 +734,7 @@ func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Packa
733734

734735
docText := comment.Text()
735736
return rng, &hoverJSON{
737+
Signature: "package " + string(impMetadata.Name),
736738
Synopsis: doc.Synopsis(docText),
737739
FullDocumentation: docText,
738740
}, nil
@@ -753,11 +755,47 @@ func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *h
753755
return protocol.Range{}, nil, err
754756
}
755757
docText := comment.Text()
758+
759+
// List some package attributes at the bottom of the documentation, if
760+
// applicable.
761+
type attr struct{ title, value string }
762+
var attrs []attr
763+
764+
if !metadata.IsCommandLineArguments(pkg.Metadata().ID) {
765+
attrs = append(attrs, attr{"Package path", string(pkg.Metadata().PkgPath)})
766+
}
767+
768+
if pkg.Metadata().Module != nil {
769+
attrs = append(attrs, attr{"Module", pkg.Metadata().Module.Path})
770+
}
771+
772+
// Show the effective language version for this package.
773+
if v := pkg.TypesInfo().FileVersions[pgf.File]; v != "" {
774+
attr := attr{value: version.Lang(v)}
775+
if v == pkg.Types().GoVersion() {
776+
attr.title = "Language version"
777+
} else {
778+
attr.title = "Language version (current file)"
779+
}
780+
attrs = append(attrs, attr)
781+
}
782+
783+
// TODO(rfindley): consider exec'ing go here to compute DefaultGODEBUG, or
784+
// propose adding GODEBUG info to go/packages.
785+
786+
var footer string
787+
for i, attr := range attrs {
788+
if i > 0 {
789+
footer += "\n"
790+
}
791+
footer += fmt.Sprintf(" - %s: %s", attr.title, attr.value)
792+
}
793+
756794
return rng, &hoverJSON{
795+
Signature: "package " + string(pkg.Metadata().Name),
757796
Synopsis: doc.Synopsis(docText),
758797
FullDocumentation: docText,
759-
// Note: including a signature is redundant, since the cursor is already on the
760-
// package name.
798+
footer: footer,
761799
}, nil
762800
}
763801

@@ -1149,8 +1187,9 @@ func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSe
11491187

11501188
// If pkgURL is non-nil, it should be used to generate doc links.
11511189
func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) {
1152-
maybeMarkdown := func(s string) string {
1153-
if s != "" && options.PreferredContentFormat == protocol.Markdown {
1190+
markdown := options.PreferredContentFormat == protocol.Markdown
1191+
maybeFenced := func(s string) string {
1192+
if s != "" && markdown {
11541193
s = fmt.Sprintf("```go\n%s\n```", strings.Trim(s, "\n"))
11551194
}
11561195
return s
@@ -1161,7 +1200,7 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa
11611200
return h.SingleLine, nil
11621201

11631202
case settings.NoDocumentation:
1164-
return maybeMarkdown(h.Signature), nil
1203+
return maybeFenced(h.Signature), nil
11651204

11661205
case settings.Structured:
11671206
b, err := json.Marshal(h)
@@ -1170,42 +1209,70 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa
11701209
}
11711210
return string(b), nil
11721211

1173-
case settings.SynopsisDocumentation,
1174-
settings.FullDocumentation:
1212+
case settings.SynopsisDocumentation, settings.FullDocumentation:
1213+
var sections [][]string // assembled below
1214+
1215+
// Signature section.
1216+
//
11751217
// For types, we display TypeDecl and Methods,
11761218
// but not Signature, which is redundant (= TypeDecl + "\n" + Methods).
11771219
// For all other symbols, we display Signature;
11781220
// TypeDecl and Methods are empty.
11791221
// (This awkwardness is to preserve JSON compatibility.)
1180-
parts := []string{
1181-
maybeMarkdown(h.Signature),
1182-
maybeMarkdown(h.typeDecl),
1183-
formatDoc(h, options),
1184-
maybeMarkdown(h.promotedFields),
1185-
maybeMarkdown(h.methods),
1186-
fmt.Sprintf("Added in %v", h.stdVersion),
1187-
formatLink(h, options, pkgURL),
1188-
}
11891222
if h.typeDecl != "" {
1190-
parts[0] = "" // type: suppress redundant Signature
1223+
sections = append(sections, []string{maybeFenced(h.typeDecl)})
1224+
} else {
1225+
sections = append(sections, []string{maybeFenced(h.Signature)})
1226+
}
1227+
1228+
// Doc section.
1229+
var doc string
1230+
switch options.HoverKind {
1231+
case settings.SynopsisDocumentation:
1232+
doc = h.Synopsis
1233+
case settings.FullDocumentation:
1234+
doc = h.FullDocumentation
11911235
}
1192-
if h.stdVersion == nil || *h.stdVersion == stdlib.Version(0) {
1193-
parts[5] = "" // suppress stdlib version if not applicable or initial version 1.0
1236+
if options.PreferredContentFormat == protocol.Markdown {
1237+
doc = DocCommentToMarkdown(doc, options)
11941238
}
1239+
sections = append(sections, []string{
1240+
doc,
1241+
maybeFenced(h.promotedFields),
1242+
maybeFenced(h.methods),
1243+
})
1244+
1245+
// Footer section.
1246+
sections = append(sections, []string{
1247+
h.footer,
1248+
formatLink(h, options, pkgURL),
1249+
})
11951250

11961251
var b strings.Builder
1197-
for _, part := range parts {
1198-
if part == "" {
1199-
continue
1252+
newline := func() {
1253+
if options.PreferredContentFormat == protocol.Markdown {
1254+
b.WriteString("\n\n")
1255+
} else {
1256+
b.WriteByte('\n')
12001257
}
1201-
if b.Len() > 0 {
1202-
if options.PreferredContentFormat == protocol.Markdown {
1203-
b.WriteString("\n\n")
1204-
} else {
1205-
b.WriteByte('\n')
1258+
}
1259+
for _, section := range sections {
1260+
start := b.Len()
1261+
for _, part := range section {
1262+
if part == "" {
1263+
continue
1264+
}
1265+
// When markdown is a available, insert an hline before the start of
1266+
// the section, if there is content above.
1267+
if markdown && b.Len() == start && start > 0 {
1268+
newline()
1269+
b.WriteString("---")
12061270
}
1271+
if b.Len() > 0 {
1272+
newline()
1273+
}
1274+
b.WriteString(part)
12071275
}
1208-
b.WriteString(part)
12091276
}
12101277
return b.String(), nil
12111278

@@ -1313,20 +1380,6 @@ func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path Packag
13131380
}
13141381
}
13151382

1316-
func formatDoc(h *hoverJSON, options *settings.Options) string {
1317-
var doc string
1318-
switch options.HoverKind {
1319-
case settings.SynopsisDocumentation:
1320-
doc = h.Synopsis
1321-
case settings.FullDocumentation:
1322-
doc = h.FullDocumentation
1323-
}
1324-
if options.PreferredContentFormat == protocol.Markdown {
1325-
return DocCommentToMarkdown(doc, options)
1326-
}
1327-
return doc
1328-
}
1329-
13301383
// findDeclInfo returns the syntax nodes involved in the declaration of the
13311384
// types.Object with position pos, searching the given list of file syntax
13321385
// trees.

gopls/internal/test/marker/testdata/definition/cgo.txt

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ func _() {
4343
func Example()
4444
```
4545

46+
---
47+
4648
[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/cgo.test/cgo#Example)
4749
-- usecgo/usecgo.go --
4850
package cgoimport
@@ -59,4 +61,6 @@ func _() {
5961
func cgo.Example()
6062
```
6163

64+
---
65+
6266
[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/cgo.test/cgo#Example)

0 commit comments

Comments
 (0)