Skip to content

Commit

Permalink
Hover support for package instances in SelectorExpr
Browse files Browse the repository at this point in the history
Hovering over a package name in SelectorExpr, such as `pkg.Func()`,
now provides additional information about the package.

Allowing users to get more context on the package while navigating
the codebase.

Implement better hovering logic, resulting in an improved and
more consistent user experience.
  • Loading branch information
harry-hov committed Jan 9, 2024
1 parent 7213e04 commit d7bccbc
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 52 deletions.
178 changes: 127 additions & 51 deletions internal/lsp/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"go/ast"
"log/slog"
"strings"

Expand All @@ -30,68 +32,142 @@ func (s *server) Hover(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2
}

offset := file.PositionToOffset(params.Position)
line := params.Position.Line
// tokedf := pgf.FileSet.AddFile(doc.Path, -1, len(doc.Content))
// target := tokedf.Pos(offset)

slog.Info("hover", "offset", offset)
slog.Info("hover", "line", line, "offset", offset)
pgf, err := file.ParseGno(ctx)
if err != nil {
return reply(ctx, nil, errors.New("cannot parse gno file"))
}
for _, spec := range pgf.File.Imports {
slog.Info("hover", "spec", spec.Path.Value, "pos", spec.Path.Pos(), "end", spec.Path.End())
if int(spec.Path.Pos()) <= offset && offset <= int(spec.Path.End()) {
// TODO: handle hover for imports
slog.Info("hover", "import", spec.Path.Value)

var expr ast.Expr
ast.Inspect(pgf.File, func(n ast.Node) bool {
if e, ok := n.(ast.Expr); ok && pgf.Fset.Position(e.Pos()).Line == int(line+1) {
expr = e
return false
}
return true
})

// TODO: Remove duplicate code
switch e := expr.(type) {
case *ast.CallExpr:
slog.Info("hover - CALL_EXPR")
switch v := e.Fun.(type) {
case *ast.Ident:
// TODO: is a func? handle.
case *ast.SelectorExpr:
// case pkg.Func
i, ok := v.X.(*ast.Ident)
if !ok {
return reply(ctx, nil, nil)
}
if offset >= int(i.Pos())-1 && offset < int(i.End())-1 { // pkg
for _, spec := range pgf.File.Imports {
// remove leading and trailing `"`
path := spec.Path.Value[1 : len(spec.Path.Value)-1]
parts := strings.Split(path, "/")
last := parts[len(parts)-1]
if last == i.Name {
header := fmt.Sprintf("```gno\npackage %s (%s)\n```\n\n", last, spec.Path.Value)
body := func() string {
if strings.HasPrefix(path, "gno.land/") {
return fmt.Sprintf("[```%s``` on gno.land](https://%s)", last, path)
}
return fmt.Sprintf("[```%s``` on gno.land](https://gno.land)", last)
}()
return reply(ctx, protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: header + body,
},
Range: posToRange(
int(params.Position.Line),
[]int{int(i.Pos()), int(i.End())},
),
}, nil)
}
}
} else if offset >= int(e.Pos())-1 && offset < int(e.End())-1 { // Func
symbol := s.completionStore.lookupSymbol(i.Name, v.Sel.Name)
if symbol != nil {
return reply(ctx, protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: symbol.String(),
},
Range: posToRange(
int(params.Position.Line),
[]int{int(e.Pos()), int(e.End())},
),
}, nil)
}
}
default:
return reply(ctx, nil, nil)
}
}

token, err := file.TokenAt(params.Position)
if err != nil {
return reply(ctx, protocol.Hover{}, err)
}
text := strings.TrimSpace(token.Text)

// FIXME: Use the AST package to do this + get type of token.
//
// This is just a quick PoC to get something working.

// strings.Split(p.Body,
text = strings.Split(text, "(")[0]

text = strings.TrimSuffix(text, ",")
text = strings.TrimSuffix(text, ")")

// *mux.Request
text = strings.TrimPrefix(text, "*")

slog.Info("hover", "pkg", len(s.completionStore.pkgs))

parts := strings.Split(text, ".")
if len(parts) == 2 {
pkg := parts[0]
sym := parts[1]

slog.Info("hover", "pkg", pkg, "sym", sym)
found := s.completionStore.lookupSymbol(pkg, sym)
if found == nil && pgf.File != nil {
found = s.completionStore.lookupSymbolByImports(sym, pgf.File.Imports)
return reply(ctx, nil, nil)
case *ast.SelectorExpr:
slog.Info("hover - SELECTOR_EXPR")
// we have a format X.A
i, ok := e.X.(*ast.Ident)
if !ok {
return reply(ctx, nil, nil)
}

if found != nil {
return reply(ctx, protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: found.String(),
},
Range: posToRange(
int(params.Position.Line),
[]int{token.Start, token.End},
),
}, nil)
if offset >= int(i.Pos())-1 && offset < int(i.End())-1 { // X
for _, spec := range pgf.File.Imports {
// remove leading and trailing `"`
path := spec.Path.Value[1 : len(spec.Path.Value)-1]
parts := strings.Split(path, "/")
last := parts[len(parts)-1]
if last == i.Name {
header := fmt.Sprintf("```gno\npackage %s (%s)\n```\n\n", last, spec.Path.Value)
body := func() string {
if strings.HasPrefix(path, "gno.land/") {
return fmt.Sprintf("[```%s``` on gno.land](https://%s)", last, path)
}
return fmt.Sprintf("[```%s``` on gno.land](https://gno.land)", last)
}()
return reply(ctx, protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: header + body,
},
Range: posToRange(
int(params.Position.Line),
[]int{int(i.Pos()), int(i.End())},
),
}, nil)
}
}
} else if offset >= int(e.Pos())-1 && offset < int(e.End())-1 { // A
symbol := s.completionStore.lookupSymbol(i.Name, e.Sel.Name)
if symbol != nil {
return reply(ctx, protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: symbol.String(),
},
Range: posToRange(
int(params.Position.Line),
[]int{int(e.Pos()), int(e.End())},
),
}, nil)
}
}
// slog.Info("SELECTOR_EXPR", "name", e.Sel.Name, "obj", e.Sel.String())
default:
slog.Info("hover - NOT HANDLED")
return reply(ctx, nil, nil)
}

return reply(ctx, nil, err)
return reply(ctx, nil, nil)
}

// handleSelectorExpr returns jsonrpc2.Replier for Hover
// on SelectorExpr
// TODO: Move duplicate logic here
// func handleSelectorExpr(expr *ast.SelectorExpr) jsonrpc2.Replier {
// }
4 changes: 3 additions & 1 deletion internal/lsp/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type GnoFile struct {
type ParsedGnoFile struct {
URI protocol.DocumentURI
File *ast.File
Fset *token.FileSet

Src []byte
}
Expand All @@ -56,6 +57,7 @@ func (f *GnoFile) ParseGno(ctx context.Context) (*ParsedGnoFile, error) {
URI: f.URI,

File: ast,
Fset: fset,
Src: f.Src,
}

Expand Down Expand Up @@ -95,7 +97,7 @@ func (f *GnoFile) TokenAt(pos protocol.Position) (*HoveredToken, error) {
}

end := index
slog.Info(fmt.Sprintf("end: %d", end))
slog.Info(fmt.Sprintf("curser at: %d", end))
for end < lineLen && line[end] != ' ' {
end++
}
Expand Down

0 comments on commit d7bccbc

Please sign in to comment.