Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gopls: added method docs to hover info #487

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions gopls/internal/golang/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"go/constant"
"go/doc"
"go/format"
"go/parser"
"go/token"
"go/types"
"io/fs"
Expand Down Expand Up @@ -398,8 +399,15 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
// including those that require a pointer receiver,
// and those promoted from embedded struct fields or
// embedded interfaces.
var b strings.Builder
for _, m := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
var (
b strings.Builder
// methodDocs is a map of the method name and it associated documentation.
methodDocs = make(map[string]string)
)
methodSetOfHoveredType := typeutil.IntuitiveMethodSet(obj.Type(), nil)
lenOfMethodSet := len(methodSetOfHoveredType)
methodNameArray := make([]string, 0, lenOfMethodSet)
for _, m := range methodSetOfHoveredType {
if !accessibleTo(m.Obj(), pkg.Types()) {
continue // inaccessible
}
Expand All @@ -412,10 +420,38 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro

// Use objectString for its prettier rendering of method receivers.
b.WriteString(objectString(m.Obj(), qf, token.NoPos, nil, nil))
methodNameArray = append(methodNameArray, m.Obj().Name())
methodDocs[m.Obj().Name()] = ""
}
methods = b.String()

signature = typeDecl + "\n" + methods

// -- method doc --
{
if len(methodNameArray) > 0 { // if we have methods at all from methodSet
fs := token.NewFileSet()
if allPkgsInDir, err := parser.ParseDir(fs, declPGF.URI.Dir().Path(), nil, parser.ParseComments); err == nil {
if files, ok := allPkgsInDir[obj.Pkg().Name()]; ok {
for _, file := range files.Files {
getMethodDoc(file, fs, ident, methodDocs)
}

methodArray := strings.Split(methods, "\n")
if m := len(methodArray); m > 0 && m == len(methodNameArray) {
var d strings.Builder
for i := 0; i < m; i++ {
if doc, ok := methodDocs[methodNameArray[i]]; ok {
d.WriteString(fmt.Sprintf("```go\n%s\n```\n%s\n", methodArray[i], doc))
}
}
methods = d.String()
}
}
}
}
}

} else {
// Non-types
if sizeOffset != "" {
Expand Down Expand Up @@ -1063,6 +1099,13 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
return s
}

fallback := func(s string) string {
if !strings.Contains(s, "```go") {
return maybeMarkdown(s)
}
return s
}

switch options.HoverKind {
case settings.SingleLine:
return h.SingleLine, nil
Expand All @@ -1089,7 +1132,7 @@ func formatHover(h *hoverJSON, options *settings.Options) (string, error) {
maybeMarkdown(h.typeDecl),
formatDoc(h, options),
maybeMarkdown(h.promotedFields),
maybeMarkdown(h.methods),
fallback(h.methods),
formatLink(h, options),
}
if h.typeDecl != "" {
Expand Down Expand Up @@ -1409,3 +1452,31 @@ func computeSizeOffsetInfo(pkg *cache.Package, path []ast.Node, obj types.Object

return
}

// getMethodDoc populates the map type parameter(methodDocs) with the respective docs,
// if the key (method name) is present in the map.
func getMethodDoc(file *ast.File, fs *token.FileSet, hoveredType *ast.Ident, methodDocs map[string]string) {
ast.Inspect(file, func(n ast.Node) bool {
if fn, ok := n.(*ast.FuncDecl); ok {
if recv := fn.Recv; recv != nil { // if it is a method
if recvList := recv.List; len(recvList) == 1 { // as it should always be
methodType := types.ExprString(recvList[0].Type)
rFmethodType, _ := strings.CutPrefix(methodType, "*") // if it a pointer

if rFmethodType == hoveredType.Name {
fnPos := fn.Pos()
f := fs.File(fnPos)
filname, lineNumber := f.Name(), f.Line(fnPos)
pageSourceURL := fmt.Sprintf("[jump to page source](%s#L%d)", filname, lineNumber)

methodDocs[fn.Name.Name] = strings.TrimSpace(fn.Doc.Text()) + "\n\n" + pageSourceURL
return false
}
}
return true
}
return true
}
return true
})
}