Skip to content

Commit b2f7418

Browse files
Add basic completion for self fields and locals (#72)
* Fix benchstat Run sequentially to get good comparison * Add basic completion Supports `self` fields and locals Closes #3 * Oops, install benchstat
1 parent 2961df8 commit b2f7418

File tree

5 files changed

+289
-43
lines changed

5 files changed

+289
-43
lines changed

.github/workflows/benchstat-pr.yml

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
1-
name: Benchstat
1+
name: benchstat
22

33
on: [pull_request]
44

55
jobs:
6-
incoming:
6+
benchstat:
77
runs-on: ubuntu-latest
88
steps:
99
- name: Install Go
1010
uses: actions/setup-go@v3
1111
with:
1212
go-version: '1.19'
13+
14+
# Generate benchmark report for main branch
1315
- name: Checkout
1416
uses: actions/checkout@v3
17+
with:
18+
ref: main
1519
- name: Benchmark
1620
run: go test ./... -count=5 -run=Benchmark -bench=. | tee -a bench.txt
1721
- name: Upload Benchmark
1822
uses: actions/upload-artifact@v3
1923
with:
20-
name: bench-incoming
24+
name: bench-current
2125
path: bench.txt
22-
current:
23-
runs-on: ubuntu-latest
24-
steps:
25-
- name: Install Go
26-
uses: actions/setup-go@v3
27-
with:
28-
go-version: '1.19'
26+
27+
# Generate benchmark report for the PR
2928
- name: Checkout
3029
uses: actions/checkout@v3
31-
with:
32-
ref: main
3330
- name: Benchmark
3431
run: go test ./... -count=5 -run=Benchmark -bench=. | tee -a bench.txt
3532
- name: Upload Benchmark
3633
uses: actions/upload-artifact@v3
3734
with:
38-
name: bench-current
35+
name: bench-incoming
3936
path: bench.txt
40-
benchstat:
41-
needs: [incoming, current]
42-
runs-on: ubuntu-latest
43-
steps:
44-
- name: Install Go
45-
uses: actions/setup-go@v3
46-
with:
47-
go-version: '1.19'
37+
38+
# Compare the two reports
39+
- name: Checkout
40+
uses: actions/checkout@v3
4841
- name: Install benchstat
4942
run: go install golang.org/x/perf/cmd/benchstat@latest
5043
- name: Download Incoming
@@ -79,4 +72,4 @@ jobs:
7972
${{ steps.benchstat_content.outputs.content }}
8073
```
8174
comment_includes: 'Benchstat (compared to main):'
82-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

pkg/server/completion.go

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import (
44
"context"
55
"strings"
66

7+
"github.com/google/go-jsonnet/ast"
8+
"github.com/grafana/jsonnet-language-server/pkg/ast/processing"
9+
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
10+
position "github.com/grafana/jsonnet-language-server/pkg/position_conversion"
711
"github.com/grafana/jsonnet-language-server/pkg/utils"
812
"github.com/jdbaldry/go-language-server-protocol/lsp/protocol"
13+
log "github.com/sirupsen/logrus"
914
)
1015

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

17-
items := []protocol.CompletionItem{}
22+
line := getCompletionLine(doc.item.Text, params.Position)
23+
24+
// Short-circuit if it's a stdlib completion
25+
if items := s.completionStdLib(line); len(items) > 0 {
26+
return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
27+
}
28+
29+
// Otherwise, parse the AST and search for completions
30+
if doc.ast == nil {
31+
log.Errorf("Completion: document was never successfully parsed, can't autocomplete")
32+
return nil, nil
33+
}
34+
35+
searchStack, err := processing.FindNodeByPosition(doc.ast, position.ProtocolToAST(params.Position))
36+
if err != nil {
37+
log.Errorf("Completion: error computing node: %v", err)
38+
return nil, nil
39+
}
40+
41+
items := s.completionFromStack(line, searchStack)
42+
return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
43+
}
1844

19-
line := strings.Split(doc.item.Text, "\n")[params.Position.Line]
20-
charIndex := int(params.Position.Character)
45+
func getCompletionLine(fileContent string, position protocol.Position) string {
46+
line := strings.Split(fileContent, "\n")[position.Line]
47+
charIndex := int(position.Character)
2148
if charIndex > len(line) {
2249
charIndex = len(line)
2350
}
2451
line = line[:charIndex]
52+
return line
53+
}
54+
55+
func (s *Server) completionFromStack(line string, stack *nodestack.NodeStack) []protocol.CompletionItem {
56+
var items []protocol.CompletionItem
57+
58+
lineWords := strings.Split(line, " ")
59+
lastWord := lineWords[len(lineWords)-1]
60+
61+
indexes := strings.Split(lastWord, ".")
62+
firstIndex, indexes := indexes[0], indexes[1:]
63+
64+
if firstIndex == "self" && len(indexes) > 0 {
65+
fieldPrefix := indexes[0]
66+
67+
for !stack.IsEmpty() {
68+
curr := stack.Pop()
69+
70+
switch curr := curr.(type) {
71+
case *ast.Binary:
72+
stack.Push(curr.Left)
73+
stack.Push(curr.Right)
74+
case *ast.DesugaredObject:
75+
for _, field := range curr.Fields {
76+
label := processing.FieldNameToString(field.Name)
77+
// Ignore fields that don't match the prefix
78+
if !strings.HasPrefix(label, fieldPrefix) {
79+
continue
80+
}
81+
82+
// Ignore the current field
83+
if strings.Contains(line, label+":") {
84+
continue
85+
}
86+
87+
items = append(items, createCompletionItem(label, "self."+label, protocol.FieldCompletion, field.Body))
88+
}
89+
}
90+
}
91+
} else if len(indexes) == 0 {
92+
// firstIndex is a variable (local) completion
93+
for !stack.IsEmpty() {
94+
if curr, ok := stack.Pop().(*ast.Local); ok {
95+
for _, bind := range curr.Binds {
96+
label := string(bind.Variable)
97+
98+
if !strings.HasPrefix(label, firstIndex) {
99+
continue
100+
}
101+
102+
items = append(items, createCompletionItem(label, label, protocol.VariableCompletion, bind.Body))
103+
}
104+
}
105+
}
106+
}
107+
108+
return items
109+
}
110+
111+
func (s *Server) completionStdLib(line string) []protocol.CompletionItem {
112+
items := []protocol.CompletionItem{}
113+
25114
stdIndex := strings.LastIndex(line, "std.")
26115
if stdIndex != -1 {
27116
userInput := line[stdIndex+4:]
@@ -55,5 +144,26 @@ func (s *Server) Completion(ctx context.Context, params *protocol.CompletionPara
55144
items = append(items, funcContains...)
56145
}
57146

58-
return &protocol.CompletionList{IsIncomplete: false, Items: items}, nil
147+
return items
148+
}
149+
150+
func createCompletionItem(label, detail string, kind protocol.CompletionItemKind, body ast.Node) protocol.CompletionItem {
151+
insertText := label
152+
if asFunc, ok := body.(*ast.Function); ok {
153+
kind = protocol.FunctionCompletion
154+
params := []string{}
155+
for _, param := range asFunc.Parameters {
156+
params = append(params, string(param.Name))
157+
}
158+
paramsString := "(" + strings.Join(params, ", ") + ")"
159+
detail += paramsString
160+
insertText += paramsString
161+
}
162+
163+
return protocol.CompletionItem{
164+
Label: label,
165+
Detail: detail,
166+
Kind: kind,
167+
InsertText: insertText,
168+
}
59169
}

0 commit comments

Comments
 (0)