Skip to content

Commit

Permalink
Support hover on all tokens (#152)
Browse files Browse the repository at this point in the history
* Support hover on all tokens
Closes #8
Closes #9
Closes #10

Next step will be to support docsonnet: #109 to get some proper descriptions

* switch case
  • Loading branch information
julienduchesne authored Aug 7, 2024
1 parent 8cf3395 commit 4c756e3
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 25 deletions.
50 changes: 48 additions & 2 deletions pkg/server/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package server
import (
"errors"
"fmt"
"os"
"strings"
"sync"

"github.com/google/go-jsonnet/ast"
Expand Down Expand Up @@ -43,8 +45,6 @@ type cache struct {
}

// put adds or replaces a document in the cache.
// Documents are only replaced if the new document version is greater than the currently
// cached version.
func (c *cache) put(new *document) error {
c.mu.Lock()
defer c.mu.Unlock()
Expand Down Expand Up @@ -72,3 +72,49 @@ func (c *cache) get(uri protocol.DocumentURI) (*document, error) {

return doc, nil
}

func (c *cache) getContents(uri protocol.DocumentURI, position protocol.Range) (string, error) {
text := ""
doc, err := c.get(uri)
if err == nil {
text = doc.item.Text
} else {
// Read the file from disk (TODO: cache this)
bytes, err := os.ReadFile(uri.SpanURI().Filename())
if err != nil {
return "", err
}
text = string(bytes)
}

lines := strings.Split(text, "\n")
if int(position.Start.Line) >= len(lines) {
return "", fmt.Errorf("line %d out of range", position.Start.Line)
}
if int(position.Start.Character) >= len(lines[position.Start.Line]) {
return "", fmt.Errorf("character %d out of range", position.Start.Character)
}
if int(position.End.Line) >= len(lines) {
return "", fmt.Errorf("line %d out of range", position.End.Line)
}
if int(position.End.Character) >= len(lines[position.End.Line]) {
return "", fmt.Errorf("character %d out of range", position.End.Character)
}

contentBuilder := strings.Builder{}
for i := position.Start.Line; i <= position.End.Line; i++ {
switch i {
case position.Start.Line:
contentBuilder.WriteString(lines[i][position.Start.Character:])
case position.End.Line:
contentBuilder.WriteString(lines[i][:position.End.Character])
default:
contentBuilder.WriteString(lines[i])
}
if i != position.End.Line {
contentBuilder.WriteRune('\n')
}
}

return contentBuilder.String(), nil
}
75 changes: 53 additions & 22 deletions pkg/server/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,7 @@ func (s *Server) Hover(_ context.Context, params *protocol.HoverParams) (*protoc
return nil, nil
}

node := stack.Pop()

// // DEBUG
// var node2 ast.Node
// if !stack.IsEmpty() {
// _, node2 = stack.Pop()
// }
// r := protocol.Range{
// Start: protocol.Position{
// Line: uint32(node.Loc().Begin.Line) - 1,
// Character: uint32(node.Loc().Begin.Column) - 1,
// },
// End: protocol.Position{
// Line: uint32(node.Loc().End.Line) - 1,
// Character: uint32(node.Loc().End.Column) - 1,
// },
// }
// return &protocol.Hover{Range: r,
// Contents: protocol.MarkupContent{Kind: protocol.PlainText,
// Value: fmt.Sprintf("%v: %+v\n\n%v: %+v", reflect.TypeOf(node), node, reflect.TypeOf(node2), node2)},
// }, nil
node := stack.Peek()

_, isIndex := node.(*ast.Index)
_, isVar := node.(*ast.Var)
Expand Down Expand Up @@ -84,5 +64,56 @@ func (s *Server) Hover(_ context.Context, params *protocol.HoverParams) (*protoc
}
}

return nil, nil
definitionParams := &protocol.DefinitionParams{
TextDocumentPositionParams: params.TextDocumentPositionParams,
}
definitions, err := findDefinition(doc.ast, definitionParams, s.getVM(doc.item.URI.SpanURI().Filename()))
if err != nil {
log.Debugf("Hover: error finding definition: %s", err)
return nil, nil
}

if len(definitions) == 0 {
return nil, nil
}

// Show the contents at the target range
// If there are multiple definitions, show the filenames+line numbers
contentBuilder := strings.Builder{}
for _, def := range definitions {
if len(definitions) > 1 {
header := fmt.Sprintf("%s:%d", def.TargetURI, def.TargetRange.Start.Line+1)
if def.TargetRange.Start.Line != def.TargetRange.End.Line {
header += fmt.Sprintf("-%d", def.TargetRange.End.Line+1)
}
contentBuilder.WriteString(fmt.Sprintf("## `%s`\n", header))
}

targetContent, err := s.cache.getContents(def.TargetURI, def.TargetRange)
if err != nil {
log.Debugf("Hover: error reading target content: %s", err)
return nil, nil
}
// Limit the content to 5 lines
if strings.Count(targetContent, "\n") > 5 {
targetContent = strings.Join(strings.Split(targetContent, "\n")[:5], "\n") + "\n..."
}
contentBuilder.WriteString(fmt.Sprintf("```jsonnet\n%s\n```\n", targetContent))

if len(definitions) > 1 {
contentBuilder.WriteString("\n")
}
}

result := &protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: contentBuilder.String(),
},
}
if loc := node.Loc(); loc != nil {
result.Range = position.RangeASTToProtocol(*loc)
}

return result, nil
}
94 changes: 93 additions & 1 deletion pkg/server/hover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"
"os"
"path/filepath"
"testing"

"github.com/grafana/jsonnet-language-server/pkg/stdlib"
Expand Down Expand Up @@ -66,7 +67,7 @@ var (
}
)

func TestHover(t *testing.T) {
func TestHoverOnStdLib(t *testing.T) {
logrus.SetOutput(io.Discard)

var testCases = []struct {
Expand Down Expand Up @@ -241,3 +242,94 @@ func TestHover(t *testing.T) {
})
}
}

func TestHover(t *testing.T) {
logrus.SetOutput(io.Discard)

testCases := []struct {
name string
filename string
position protocol.Position
expectedContent protocol.Hover
}{
{
name: "hover on nested attribute",
filename: "testdata/goto-indexes.jsonnet",
position: protocol.Position{Line: 9, Character: 16},
expectedContent: protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: "```jsonnet\nbar: 'innerfoo',\n```\n",
},
Range: protocol.Range{
Start: protocol.Position{Line: 9, Character: 5},
End: protocol.Position{Line: 9, Character: 18},
},
},
},
{
name: "hover on multi-line string",
filename: "testdata/goto-indexes.jsonnet",
position: protocol.Position{Line: 8, Character: 9},
expectedContent: protocol.Hover{
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: "```jsonnet\nobj = {\n foo: {\n bar: 'innerfoo',\n },\n bar: 'foo',\n}\n```\n",
},
Range: protocol.Range{
Start: protocol.Position{Line: 8, Character: 8},
End: protocol.Position{Line: 8, Character: 11},
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
params := &protocol.HoverParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.URIFromPath(tc.filename),
},
Position: tc.position,
},
}

server := NewServer("any", "test version", nil, Configuration{
JPaths: []string{"testdata", filepath.Join(filepath.Dir(tc.filename), "vendor")},
})
serverOpenTestFile(t, server, tc.filename)
response, err := server.Hover(context.Background(), params)

require.NoError(t, err)
assert.Equal(t, &tc.expectedContent, response)
})
}
}

func TestHoverGoToDefinitionTests(t *testing.T) {
logrus.SetOutput(io.Discard)

for _, tc := range definitionTestCases {
t.Run(tc.name, func(t *testing.T) {
params := &protocol.HoverParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.URIFromPath(tc.filename),
},
Position: tc.position,
},
}

server := NewServer("any", "test version", nil, Configuration{
JPaths: []string{"testdata", filepath.Join(filepath.Dir(tc.filename), "vendor")},
})
serverOpenTestFile(t, server, tc.filename)
response, err := server.Hover(context.Background(), params)

// We only want to check that it found something. In combination with other tests, we can assume the content is OK.
require.NoError(t, err)
require.NotNil(t, response)
})
}
}

0 comments on commit 4c756e3

Please sign in to comment.