Skip to content

Commit

Permalink
Add basic completion for self fields and locals (#72)
Browse files Browse the repository at this point in the history
* Fix benchstat
Run sequentially to get good comparison

* Add basic completion
Supports `self` fields and locals
Closes #3

* Oops, install benchstat
  • Loading branch information
julienduchesne authored Sep 8, 2022
1 parent 2961df8 commit b2f7418
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 43 deletions.
37 changes: 15 additions & 22 deletions .github/workflows/benchstat-pr.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,43 @@
name: Benchstat
name: benchstat

on: [pull_request]

jobs:
incoming:
benchstat:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'

# Generate benchmark report for main branch
- name: Checkout
uses: actions/checkout@v3
with:
ref: main
- name: Benchmark
run: go test ./... -count=5 -run=Benchmark -bench=. | tee -a bench.txt
- name: Upload Benchmark
uses: actions/upload-artifact@v3
with:
name: bench-incoming
name: bench-current
path: bench.txt
current:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'

# Generate benchmark report for the PR
- name: Checkout
uses: actions/checkout@v3
with:
ref: main
- name: Benchmark
run: go test ./... -count=5 -run=Benchmark -bench=. | tee -a bench.txt
- name: Upload Benchmark
uses: actions/upload-artifact@v3
with:
name: bench-current
name: bench-incoming
path: bench.txt
benchstat:
needs: [incoming, current]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'

# Compare the two reports
- name: Checkout
uses: actions/checkout@v3
- name: Install benchstat
run: go install golang.org/x/perf/cmd/benchstat@latest
- name: Download Incoming
Expand Down Expand Up @@ -79,4 +72,4 @@ jobs:
${{ steps.benchstat_content.outputs.content }}
```
comment_includes: 'Benchstat (compared to main):'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118 changes: 114 additions & 4 deletions pkg/server/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import (
"context"
"strings"

"github.com/google/go-jsonnet/ast"
"github.com/grafana/jsonnet-language-server/pkg/ast/processing"
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
position "github.com/grafana/jsonnet-language-server/pkg/position_conversion"
"github.com/grafana/jsonnet-language-server/pkg/utils"
"github.com/jdbaldry/go-language-server-protocol/lsp/protocol"
log "github.com/sirupsen/logrus"
)

func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
Expand All @@ -14,14 +19,98 @@ func (s *Server) Completion(ctx context.Context, params *protocol.CompletionPara
return nil, utils.LogErrorf("Completion: %s: %w", errorRetrievingDocument, err)
}

items := []protocol.CompletionItem{}
line := getCompletionLine(doc.item.Text, params.Position)

// Short-circuit if it's a stdlib completion
if items := s.completionStdLib(line); len(items) > 0 {
return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
}

// Otherwise, parse the AST and search for completions
if doc.ast == nil {
log.Errorf("Completion: document was never successfully parsed, can't autocomplete")
return nil, nil
}

searchStack, err := processing.FindNodeByPosition(doc.ast, position.ProtocolToAST(params.Position))
if err != nil {
log.Errorf("Completion: error computing node: %v", err)
return nil, nil
}

items := s.completionFromStack(line, searchStack)
return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
}

line := strings.Split(doc.item.Text, "\n")[params.Position.Line]
charIndex := int(params.Position.Character)
func getCompletionLine(fileContent string, position protocol.Position) string {
line := strings.Split(fileContent, "\n")[position.Line]
charIndex := int(position.Character)
if charIndex > len(line) {
charIndex = len(line)
}
line = line[:charIndex]
return line
}

func (s *Server) completionFromStack(line string, stack *nodestack.NodeStack) []protocol.CompletionItem {
var items []protocol.CompletionItem

lineWords := strings.Split(line, " ")
lastWord := lineWords[len(lineWords)-1]

indexes := strings.Split(lastWord, ".")
firstIndex, indexes := indexes[0], indexes[1:]

if firstIndex == "self" && len(indexes) > 0 {
fieldPrefix := indexes[0]

for !stack.IsEmpty() {
curr := stack.Pop()

switch curr := curr.(type) {
case *ast.Binary:
stack.Push(curr.Left)
stack.Push(curr.Right)
case *ast.DesugaredObject:
for _, field := range curr.Fields {
label := processing.FieldNameToString(field.Name)
// Ignore fields that don't match the prefix
if !strings.HasPrefix(label, fieldPrefix) {
continue
}

// Ignore the current field
if strings.Contains(line, label+":") {
continue
}

items = append(items, createCompletionItem(label, "self."+label, protocol.FieldCompletion, field.Body))
}
}
}
} else if len(indexes) == 0 {
// firstIndex is a variable (local) completion
for !stack.IsEmpty() {
if curr, ok := stack.Pop().(*ast.Local); ok {
for _, bind := range curr.Binds {
label := string(bind.Variable)

if !strings.HasPrefix(label, firstIndex) {
continue
}

items = append(items, createCompletionItem(label, label, protocol.VariableCompletion, bind.Body))
}
}
}
}

return items
}

func (s *Server) completionStdLib(line string) []protocol.CompletionItem {
items := []protocol.CompletionItem{}

stdIndex := strings.LastIndex(line, "std.")
if stdIndex != -1 {
userInput := line[stdIndex+4:]
Expand Down Expand Up @@ -55,5 +144,26 @@ func (s *Server) Completion(ctx context.Context, params *protocol.CompletionPara
items = append(items, funcContains...)
}

return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
return items
}

func createCompletionItem(label, detail string, kind protocol.CompletionItemKind, body ast.Node) protocol.CompletionItem {
insertText := label
if asFunc, ok := body.(*ast.Function); ok {
kind = protocol.FunctionCompletion
params := []string{}
for _, param := range asFunc.Parameters {
params = append(params, string(param.Name))
}
paramsString := "(" + strings.Join(params, ", ") + ")"
detail += paramsString
insertText += paramsString
}

return protocol.CompletionItem{
Label: label,
Detail: detail,
Kind: kind,
InsertText: insertText,
}
}
Loading

0 comments on commit b2f7418

Please sign in to comment.