Skip to content

Commit

Permalink
Add hover and definition support for package names and prefixes
Browse files Browse the repository at this point in the history
  • Loading branch information
kralicky committed Feb 13, 2024
1 parent a1388e7 commit 94c1849
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 1 deletion.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ Features in progress:
- [x] Imports
- [x] Options, extensions, and field references
- [x] Inlay Hints
- [x] Package names and prefixes
- [x] Hover
- [x] Types and enums
- [x] Options, extensions, and field references
- [x] Inlay Hints
- [x] Package names and prefixes
- [ ] CEL tokens
- [ ] Code Actions & Refactors
- [x] Identify and remove unused imports
Expand Down
3 changes: 2 additions & 1 deletion pkg/lsp/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ func (c *Cache) ComputeHover(params protocol.TextDocumentPositionParams) (*proto
if err != nil {
return nil, err
} else if desc == nil {
return nil, nil
return c.tryHoverPackageNode(params), nil
}

location, err := c.FindDefinitionForTypeDescriptor(desc)
if err != nil {
return nil, err
Expand Down
215 changes: 215 additions & 0 deletions pkg/lsp/packages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package lsp

import (
"fmt"
"strings"

"github.com/kralicky/protocompile/ast"
"github.com/kralicky/protocompile/linker"
"github.com/kralicky/protols/pkg/format"
"github.com/kralicky/tools-lite/gopls/pkg/lsp/protocol"
"google.golang.org/protobuf/reflect/protoreflect"
)

func (c *Cache) TryFindPackageReferences(params protocol.TextDocumentPositionParams) []protocol.Location {
parseRes, err := c.FindParseResultByURI(params.TextDocument.URI)
if err != nil {
return nil
}

mapper, err := c.GetMapper(params.TextDocument.URI)
if err != nil {
return nil
}

offset, err := mapper.PositionOffset(params.Position)
if err != nil {
return nil
}

fileNode := parseRes.AST()

tokenAtOffset, comment := fileNode.ItemAtOffset(offset)
if tokenAtOffset == ast.TokenError && comment.IsValid() {
return nil
}

LOOP:
for _, decl := range fileNode.Decls {
switch decl := decl.(type) {
case *ast.PackageNode:
if decl.Name != nil {
if tokenAtOffset >= decl.Name.Start() && tokenAtOffset <= decl.Name.End() {
switch name := decl.Name.(type) {
case *ast.IdentNode:
return c.FindPackageNameRefs(protoreflect.FullName(name.Val), false)
case *ast.CompoundIdentNode:
for i, ident := range name.Components {
if tokenAtOffset >= ident.Start() && tokenAtOffset <= ident.End() {
var parts []string
for j := 0; j <= i; j++ {
parts = append(parts, name.Components[j].Val)
}
return c.FindPackageNameRefs(protoreflect.FullName(strings.Join(parts, ".")), i < len(name.Components)-1)
}
}
}
}
}
break LOOP
}
}
return nil
}

func (c *Cache) FindPackageNameRefs(name protoreflect.FullName, prefixMatch bool) []protocol.Location {
c.resultsMu.RLock()
defer c.resultsMu.RUnlock()

var locations []protocol.Location
searchFunc := c.results.RangeFilesByPackage
if prefixMatch {
searchFunc = c.results.RangeFilesByPackagePrefix
}
searchFunc(name, func(f linker.File) bool {
if f.IsPlaceholder() {
return true
}
uri, err := c.resolver.PathToURI(f.Path())
if err != nil {
return true
}
res := f.(linker.Result)
resFileNode := res.AST()
for _, decl := range resFileNode.Decls {
pkgNode, ok := decl.(*ast.PackageNode)
if !ok {
continue
}

var rng protocol.Range
switch node := pkgNode.Name.(type) {
case *ast.IdentNode:
if info := resFileNode.NodeInfo(node); info.IsValid() {
rng = toRange(info)
}
case *ast.CompoundIdentNode:
if !prefixMatch {
if info := resFileNode.NodeInfo(node); info.IsValid() {
rng = toRange(info)
}
} else {
parts := make([]string, 0, len(node.Components))
for i, part := range node.Components {
parts = append(parts, part.Val)
if strings.Join(parts, ".") == string(name) {
start := resFileNode.NodeInfo(node.Components[0])
end := resFileNode.NodeInfo(node.Components[i])
if start.IsValid() && end.IsValid() {
rng = protocol.Range{
Start: toPosition(start.Start()),
End: toPosition(end.End()),
}
}
break
}
}
}
}
if rng == (protocol.Range{}) {
return true
}
locations = append(locations, protocol.Location{
URI: uri,
Range: rng,
})
}
return true
})
return locations
}

func (c *Cache) tryHoverPackageNode(params protocol.TextDocumentPositionParams) *protocol.Hover {
parseRes, err := c.FindParseResultByURI(params.TextDocument.URI)
if err != nil {
return nil
}

mapper, err := c.GetMapper(params.TextDocument.URI)
if err != nil {
return nil
}

offset, err := mapper.PositionOffset(params.Position)
if err != nil {
return nil
}

fileNode := parseRes.AST()

tokenAtOffset, comment := fileNode.ItemAtOffset(offset)
if tokenAtOffset == ast.TokenError && comment.IsValid() {
return nil
}

LOOP:
for _, decl := range fileNode.Decls {
switch decl := decl.(type) {
case *ast.PackageNode:
if decl.Name == nil {
break
}

makeStandardHover := func() *protocol.Hover {
info := fileNode.NodeInfo(decl.Name)
text, err := format.PrintNode(fileNode, decl)
if err != nil {
return nil
}
return &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: fmt.Sprintf("```protobuf\n%s\n```\n", text),
},
Range: toRange(info),
}
}

switch name := decl.Name.(type) {
case *ast.IdentNode:
if tokenAtOffset >= decl.Name.Start() && tokenAtOffset <= decl.Name.End() {
return makeStandardHover()
}
case *ast.CompoundIdentNode:
parts := []string{}
for i, part := range name.Components {
parts = append(parts, part.Val)
if tokenAtOffset >= part.Start() && tokenAtOffset <= part.End() {
if i == len(name.Components)-1 {
return makeStandardHover()
}

start := fileNode.NodeInfo(name.Components[0])
end := fileNode.NodeInfo(name.Components[i])
if start.IsValid() && end.IsValid() {
partialName := protoreflect.FullName(strings.Join(parts, "."))
return &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: fmt.Sprintf("```protobuf\npackage %s.*\n```\n", partialName),
},
Range: protocol.Range{
Start: toPosition(start.Start()),
End: toPosition(end.End()),
},
}
}
}
}
}

break LOOP
}
}
return nil
}
9 changes: 9 additions & 0 deletions pkg/lsp/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lsp

import (
"context"
"slices"

"github.com/kralicky/protocompile/ast"
"github.com/kralicky/tools-lite/gopls/pkg/lsp/protocol"
Expand Down Expand Up @@ -42,6 +43,14 @@ func (c *Cache) FindReferences(ctx context.Context, params protocol.TextDocument
return nil, err
}
if desc == nil {
if locations := c.TryFindPackageReferences(params); locations != nil {
if !refCtx.IncludeDeclaration {
locations = slices.DeleteFunc(locations, func(loc protocol.Location) bool {
return loc.URI == params.TextDocument.URI
})
}
return locations, nil
}
return nil, nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ func (s *Server) Definition(ctx context.Context, params *protocol.DefinitionPara
if err != nil {
return nil, err
} else if desc == nil {
if locations := c.TryFindPackageReferences(params.TextDocumentPositionParams); locations != nil {
return locations, nil
}
return nil, nil
}
loc, err := c.FindDefinitionForTypeDescriptor(desc)
Expand Down

0 comments on commit 94c1849

Please sign in to comment.