Skip to content

Commit

Permalink
gopls/internal/golang/hover: added hover support for pkg idents
Browse files Browse the repository at this point in the history
  • Loading branch information
GSmithApps committed Jan 20, 2025
1 parent 1261a24 commit a7be2b0
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 14 deletions.
79 changes: 65 additions & 14 deletions gopls/internal/golang/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,18 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
// The general case: compute hover information for the object referenced by
// the identifier at pos.
ident, obj, selectedType := referencedObject(pkg, pgf, pos)

if pkgName, ok := obj.(*types.PkgName); ok {
rng, hoverRes, err := hoverPackageIdent(ctx, snapshot, pkg, pgf, ident, pkgName.Imported().Path())
if err != nil {
return protocol.Range{}, nil, err
}
if hoverRange == nil {
hoverRange = &rng
}
return *hoverRange, hoverRes, nil // (hoverRes may be nil)
}

if obj == nil || ident == nil {
return protocol.Range{}, nil, nil // no object to hover
}
Expand Down Expand Up @@ -691,27 +703,22 @@ func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Objec
}, nil
}

// hoverImport computes hover information when hovering over the import path of
// imp in the file pgf of pkg.
// hoverPackageRef computes hover information when hovering over the import path or ident of
// imp in the file pgf of pkg or over the identifier for an imported pkg.
//
// If we do not have metadata for the hovered import, it returns _
func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, imp *ast.ImportSpec) (protocol.Range, *hoverResult, error) {
rng, err := pgf.NodeRange(imp.Path)
if err != nil {
return protocol.Range{}, nil, err
}

func hoverPackageRef(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, imp *ast.ImportSpec) (*hoverResult, error) {
importPath := metadata.UnquoteImportPath(imp)
if importPath == "" {
return protocol.Range{}, nil, fmt.Errorf("invalid import path")
return nil, fmt.Errorf("invalid import path")
}
impID := pkg.Metadata().DepsByImpPath[importPath]
if impID == "" {
return protocol.Range{}, nil, fmt.Errorf("no package data for import %q", importPath)
return nil, fmt.Errorf("no package data for import %q", importPath)
}
impMetadata := snapshot.Metadata(impID)
if impMetadata == nil {
return protocol.Range{}, nil, bug.Errorf("failed to resolve import ID %q", impID)
return nil, bug.Errorf("failed to resolve import ID %q", impID)
}

// Find the first file with a package doc comment.
Expand All @@ -720,14 +727,14 @@ func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Packa
fh, err := snapshot.ReadFile(ctx, f)
if err != nil {
if ctx.Err() != nil {
return protocol.Range{}, nil, ctx.Err()
return nil, ctx.Err()
}
continue
}
pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header)
if err != nil {
if ctx.Err() != nil {
return protocol.Range{}, nil, ctx.Err()
return nil, ctx.Err()
}
continue
}
Expand All @@ -738,13 +745,57 @@ func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Packa
}

docText := comment.Text()
return rng, &hoverResult{
return &hoverResult{
signature: "package " + string(impMetadata.Name),
synopsis: doc.Synopsis(docText),
fullDocumentation: docText,
}, nil
}

// hoverImport computes hover information when hovering over the import path of
// imp in the file pgf of pkg.
//
// If we do not have metadata for the hovered import, it returns _
func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, imp *ast.ImportSpec) (protocol.Range, *hoverResult, error) {
rng, err := pgf.NodeRange(imp.Path)
if err != nil {
return protocol.Range{}, nil, err
}
hoverRes, err := hoverPackageRef(ctx, snapshot, pkg, imp)
if err != nil {
return protocol.Range{}, nil, err
}
return rng, hoverRes, err
}

// hoverPackageIdent computes hover information when hovering over the identifier
// of an imported pkg.
//
// If we do not have metadata for the hovered import, it returns _
func hoverPackageIdent(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, ident *ast.Ident, path string) (protocol.Range, *hoverResult, error) {

for _, spec := range pgf.File.Imports {
importPathString, err := strconv.Unquote(spec.Path.Value)
if err != nil {
return protocol.Range{}, nil, err
}
if importPathString != path {
continue
}
rng, err := pgf.NodeRange(ident)
if err != nil {
return protocol.Range{}, nil, err
}
hoverRes, err := hoverPackageRef(ctx, snapshot, pkg, spec)
if err != nil {
return protocol.Range{}, nil, err
}
return rng, hoverRes, nil // (hoverRes may be nil)
}

return protocol.Range{}, nil, fmt.Errorf("invalid import path")
}

// hoverPackageName computes hover information for the package name of the file
// pgf in pkg.
func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *hoverResult, error) {
Expand Down
88 changes: 88 additions & 0 deletions gopls/internal/test/integration/misc/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,94 @@ func main() {
})
}

func TestHoverPackageIdent(t *testing.T) {
const packageDoc1 = "Package lib1 hover documentation"
const packageDoc2 = "Package lib2 hover documentation"
tests := []struct {
hoverIdent string
want string
wantError bool
}{
{
"lib1",
packageDoc1,
false,
},
{
"lib2",
packageDoc2,
false,
},
{
"lib3",
"",
false,
},
{
"lib4",
"",
true,
},
}
source := fmt.Sprintf(`
-- go.mod --
module mod.com
go 1.12
-- lib1/a.go --
// %s
package lib1
const C = 1
-- lib1/b.go --
package lib1
const D = 1
-- lib2/a.go --
// %s
package lib2
const E = 1
-- lib3/a.go --
package lib3
const F = 1
-- main.go --
package main
import (
"mod.com/lib1"
"mod.com/lib2"
"mod.com/lib3"
"mod.com/lib4"
)
func main() {
println(lib1.C)
println(lib2.E)
println(lib3.F)
println(lib4.Z)
}
`, packageDoc1, packageDoc2)
Run(t, source, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
for _, test := range tests {
got, _, err := env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "("+test.hoverIdent+")\\."))
if test.wantError {
if err == nil {
t.Errorf("Hover(%q) succeeded unexpectedly", test.hoverIdent)
}
} else if !strings.Contains(got.Value, test.want) {
t.Errorf("Hover(%q): got:\n%q\nwant:\n%q", test.hoverIdent, got.Value, test.want)
}
}
})
}

// for x/tools/gopls: unhandled named anchor on the hover #57048
func TestHoverTags(t *testing.T) {
const source = `
Expand Down

0 comments on commit a7be2b0

Please sign in to comment.