Skip to content

Commit

Permalink
gopls: added method docs to hover info
Browse files Browse the repository at this point in the history
The change also includes a jump to definition feature for all methods.The
change is quite fail-safe, such that if it fails in any way, it will still
display the hover info without the method docs(as before). The change is
localised to the the gopls/internal/golang/hover.go file, in function
hover and formatHover.
I should also mention that it does not alter the method displayed or it
order, it just add the method docs (if any) and a jump to page source
url to the hover info.

Fixes golang/go#66721
Fixes golang/go#66721

Signed-off-by: tobigiwa <[email protected]>
  • Loading branch information
tobigiwa committed Apr 8, 2024
1 parent c7b6b8d commit 27d26b0
Showing 1 changed file with 74 additions and 3 deletions.
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
})
}

0 comments on commit 27d26b0

Please sign in to comment.