Skip to content

Commit 4214e85

Browse files
Full completion support (#93)
Closes #5 Simplified the code in the process. Completion was using it's own stack-walking code, I moved it to use the same thing as go-to-definition
1 parent a7565a7 commit 4214e85

File tree

7 files changed

+177
-87
lines changed

7 files changed

+177
-87
lines changed

pkg/ast/processing/find_field.go

+22-17
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
log "github.com/sirupsen/logrus"
1212
)
1313

14-
func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm *jsonnet.VM) ([]ObjectRange, error) {
14+
func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm *jsonnet.VM, partialMatchFields bool) ([]ObjectRange, error) {
1515
var foundDesugaredObjects []*ast.DesugaredObject
1616
// First element will be super, self, or var name
1717
start, indexList := indexList[0], indexList[1:]
@@ -45,7 +45,7 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
4545
// Get ast.DesugaredObject at variable definition by getting bind then setting ast.DesugaredObject
4646
bind := FindBindByIDViaStack(stack, ast.Identifier(start))
4747
if bind == nil {
48-
param := FindParameterByIDViaStack(stack, ast.Identifier(start))
48+
param := FindParameterByIDViaStack(stack, ast.Identifier(start), partialMatchFields)
4949
if param != nil {
5050
return []ObjectRange{
5151
{
@@ -69,7 +69,7 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
6969
case *ast.Index, *ast.Apply:
7070
tempStack := nodestack.NewNodeStack(bodyNode)
7171
indexList = append(tempStack.BuildIndexList(), indexList...)
72-
return FindRangesFromIndexList(stack, indexList, vm)
72+
return FindRangesFromIndexList(stack, indexList, vm, partialMatchFields)
7373
case *ast.Function:
7474
// If the function's body is an object, it means we can look for indexes within the function
7575
if funcBody := findChildDesugaredObject(bodyNode.Body); funcBody != nil {
@@ -80,15 +80,15 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
8080
}
8181
}
8282

83-
return extractObjectRangesFromDesugaredObjs(stack, vm, foundDesugaredObjects, sameFileOnly, indexList)
83+
return extractObjectRangesFromDesugaredObjs(stack, vm, foundDesugaredObjects, sameFileOnly, indexList, partialMatchFields)
8484
}
8585

86-
func extractObjectRangesFromDesugaredObjs(stack *nodestack.NodeStack, vm *jsonnet.VM, desugaredObjs []*ast.DesugaredObject, sameFileOnly bool, indexList []string) ([]ObjectRange, error) {
86+
func extractObjectRangesFromDesugaredObjs(stack *nodestack.NodeStack, vm *jsonnet.VM, desugaredObjs []*ast.DesugaredObject, sameFileOnly bool, indexList []string, partialMatchFields bool) ([]ObjectRange, error) {
8787
var ranges []ObjectRange
8888
for len(indexList) > 0 {
8989
index := indexList[0]
9090
indexList = indexList[1:]
91-
foundFields := findObjectFieldsInObjects(desugaredObjs, index)
91+
foundFields := findObjectFieldsInObjects(desugaredObjs, index, partialMatchFields)
9292
desugaredObjs = nil
9393
if len(foundFields) == 0 {
9494
return nil, fmt.Errorf("field %s was not found in ast.DesugaredObject", index)
@@ -98,7 +98,8 @@ func extractObjectRangesFromDesugaredObjs(stack *nodestack.NodeStack, vm *jsonne
9898
ranges = append(ranges, FieldToRange(*found))
9999

100100
// If the field is not PlusSuper (field+: value), we stop there. Other previous values are not relevant
101-
if !found.PlusSuper {
101+
// If partialMatchFields is true, we can continue to look for other fields
102+
if !found.PlusSuper && !partialMatchFields {
102103
break
103104
}
104105
}
@@ -134,7 +135,7 @@ func extractObjectRangesFromDesugaredObjs(stack *nodestack.NodeStack, vm *jsonne
134135
desugaredObjs = append(desugaredObjs, fieldNode)
135136
case *ast.Index:
136137
additionalIndexList := append(nodestack.NewNodeStack(fieldNode).BuildIndexList(), indexList...)
137-
result, err := FindRangesFromIndexList(stack, additionalIndexList, vm)
138+
result, err := FindRangesFromIndexList(stack, additionalIndexList, vm, partialMatchFields)
138139
if len(result) > 0 {
139140
if !sameFileOnly || result[0].Filename == stack.From.Loc().FileName {
140141
return result, err
@@ -186,32 +187,36 @@ func unpackFieldNodes(vm *jsonnet.VM, fields []*ast.DesugaredObjectField) ([]ast
186187
return fieldNodes, nil
187188
}
188189

189-
func findObjectFieldsInObjects(objectNodes []*ast.DesugaredObject, index string) []*ast.DesugaredObjectField {
190+
func findObjectFieldsInObjects(objectNodes []*ast.DesugaredObject, index string, partialMatchFields bool) []*ast.DesugaredObjectField {
190191
var matchingFields []*ast.DesugaredObjectField
191192
for _, object := range objectNodes {
192-
field := findObjectFieldInObject(object, index)
193-
if field != nil {
194-
matchingFields = append(matchingFields, field)
195-
}
193+
fields := findObjectFieldsInObject(object, index, partialMatchFields)
194+
matchingFields = append(matchingFields, fields...)
196195
}
197196
return matchingFields
198197
}
199198

200-
func findObjectFieldInObject(objectNode *ast.DesugaredObject, index string) *ast.DesugaredObjectField {
199+
func findObjectFieldsInObject(objectNode *ast.DesugaredObject, index string, partialMatchFields bool) []*ast.DesugaredObjectField {
201200
if objectNode == nil {
202201
return nil
203202
}
203+
204+
var matchingFields []*ast.DesugaredObjectField
204205
for _, field := range objectNode.Fields {
206+
field := field
205207
literalString, isString := field.Name.(*ast.LiteralString)
206208
if !isString {
207209
continue
208210
}
209211
log.Debugf("Checking index name %s against field name %s", index, literalString.Value)
210-
if index == literalString.Value {
211-
return &field
212+
if index == literalString.Value || (partialMatchFields && strings.HasPrefix(literalString.Value, index)) {
213+
matchingFields = append(matchingFields, &field)
214+
if !partialMatchFields {
215+
break
216+
}
212217
}
213218
}
214-
return nil
219+
return matchingFields
215220
}
216221

217222
func findChildDesugaredObject(node ast.Node) *ast.DesugaredObject {

pkg/ast/processing/find_param.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package processing
22

33
import (
4+
"strings"
5+
46
"github.com/google/go-jsonnet/ast"
57
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
68
)
79

8-
func FindParameterByIDViaStack(stack *nodestack.NodeStack, id ast.Identifier) *ast.Parameter {
10+
func FindParameterByIDViaStack(stack *nodestack.NodeStack, id ast.Identifier, partialMatchFields bool) *ast.Parameter {
911
for _, node := range stack.Stack {
1012
if f, ok := node.(*ast.Function); ok {
1113
for _, param := range f.Parameters {
12-
if param.Name == id {
14+
if param.Name == id || (partialMatchFields && strings.HasPrefix(string(param.Name), string(id))) {
1315
return &param
1416
}
1517
}

pkg/ast/processing/object_range.go

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ type ObjectRange struct {
1111
Filename string
1212
SelectionRange ast.LocationRange
1313
FullRange ast.LocationRange
14+
FieldName string
15+
Node ast.Node
1416
}
1517

1618
func FieldToRange(field ast.DesugaredObjectField) ObjectRange {
@@ -28,6 +30,8 @@ func FieldToRange(field ast.DesugaredObjectField) ObjectRange {
2830
Filename: field.LocRange.FileName,
2931
SelectionRange: selectionRange,
3032
FullRange: field.LocRange,
33+
FieldName: FieldNameToString(field.Name),
34+
Node: field.Body,
3135
}
3236
}
3337

pkg/ast/processing/top_level_objects.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.Desu
4343
if !indexIsString {
4444
continue
4545
}
46-
obj := findObjectFieldInObject(containerObj, indexValue.Value)
47-
if obj != nil {
48-
stack.Push(obj.Body)
46+
objs := findObjectFieldsInObject(containerObj, indexValue.Value, false)
47+
if len(objs) > 0 {
48+
stack.Push(objs[0].Body)
4949
}
5050
}
5151
case *ast.Var:

pkg/server/completion.go

+48-63
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package server
22

33
import (
44
"context"
5+
"reflect"
56
"sort"
67
"strings"
78

89
"github.com/google/go-jsonnet"
910
"github.com/google/go-jsonnet/ast"
10-
"github.com/google/go-jsonnet/toolutils"
1111
"github.com/grafana/jsonnet-language-server/pkg/ast/processing"
1212
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
1313
position "github.com/grafana/jsonnet-language-server/pkg/position_conversion"
@@ -63,17 +63,16 @@ func (s *Server) completionFromStack(line string, stack *nodestack.NodeStack, vm
6363
lastWord = strings.TrimRight(lastWord, ",;") // Ignore trailing commas and semicolons, they can present when someone is modifying an existing line
6464

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

68-
if len(indexes) == 0 {
67+
if len(indexes) == 1 {
6968
var items []protocol.CompletionItem
7069
// firstIndex is a variable (local) completion
7170
for !stack.IsEmpty() {
7271
if curr, ok := stack.Pop().(*ast.Local); ok {
7372
for _, bind := range curr.Binds {
7473
label := string(bind.Variable)
7574

76-
if !strings.HasPrefix(label, firstIndex) {
75+
if !strings.HasPrefix(label, indexes[0]) {
7776
continue
7877
}
7978

@@ -84,46 +83,14 @@ func (s *Server) completionFromStack(line string, stack *nodestack.NodeStack, vm
8483
return items
8584
}
8685

87-
if len(indexes) > 1 {
88-
// TODO: Support multiple indexes, the objects to search through will be the reference in the last index
86+
ranges, err := processing.FindRangesFromIndexList(stack, indexes, vm, true)
87+
if err != nil {
88+
log.Errorf("Completion: error finding ranges: %v", err)
8989
return nil
9090
}
9191

92-
var (
93-
objectsToSearch []*ast.DesugaredObject
94-
)
95-
96-
if firstIndex == "self" {
97-
// Search through the current stack
98-
objectsToSearch = processing.FindTopLevelObjects(stack, vm)
99-
} else {
100-
// If the index is something other than 'self', find what it refers to (Var reference) and find objects in that
101-
for !stack.IsEmpty() {
102-
curr := stack.Pop()
103-
104-
if targetVar, ok := curr.(*ast.Var); ok && string(targetVar.Id) == firstIndex {
105-
ref, _ := processing.FindVarReference(targetVar, vm)
106-
107-
switch ref := ref.(type) {
108-
case *ast.Self: // This case catches `$` references (it's set as a self reference on the root object)
109-
objectsToSearch = processing.FindTopLevelObjects(nodestack.NewNodeStack(stack.From), vm)
110-
case *ast.DesugaredObject:
111-
objectsToSearch = []*ast.DesugaredObject{ref}
112-
case *ast.Import:
113-
filename := ref.File.Value
114-
objectsToSearch = processing.FindTopLevelObjectsInFile(vm, filename, string(curr.Loc().File.DiagnosticFileName))
115-
}
116-
break
117-
}
118-
119-
for _, node := range toolutils.Children(curr) {
120-
stack.Push(node)
121-
}
122-
}
123-
}
124-
125-
fieldPrefix := indexes[0]
126-
return createCompletionItemsFromObjects(objectsToSearch, firstIndex, fieldPrefix, line)
92+
completionPrefix := strings.Join(indexes[:len(indexes)-1], ".")
93+
return createCompletionItemsFromRanges(ranges, completionPrefix, line)
12794
}
12895

12996
func (s *Server) completionStdLib(line string) []protocol.CompletionItem {
@@ -165,31 +132,24 @@ func (s *Server) completionStdLib(line string) []protocol.CompletionItem {
165132
return items
166133
}
167134

168-
func createCompletionItemsFromObjects(objects []*ast.DesugaredObject, firstIndex, fieldPrefix, currentLine string) []protocol.CompletionItem {
135+
func createCompletionItemsFromRanges(ranges []processing.ObjectRange, completionPrefix, currentLine string) []protocol.CompletionItem {
169136
var items []protocol.CompletionItem
170137
labels := make(map[string]bool)
171138

172-
for _, obj := range objects {
173-
for _, field := range obj.Fields {
174-
label := processing.FieldNameToString(field.Name)
175-
176-
if labels[label] {
177-
continue
178-
}
179-
180-
// Ignore fields that don't match the prefix
181-
if !strings.HasPrefix(label, fieldPrefix) {
182-
continue
183-
}
139+
for _, field := range ranges {
140+
label := field.FieldName
184141

185-
// Ignore the current field
186-
if strings.Contains(currentLine, label+":") {
187-
continue
188-
}
142+
if labels[label] {
143+
continue
144+
}
189145

190-
items = append(items, createCompletionItem(label, firstIndex+"."+label, protocol.FieldCompletion, field.Body))
191-
labels[label] = true
146+
// Ignore the current field
147+
if strings.Contains(currentLine, label+":") && completionPrefix == "self" {
148+
continue
192149
}
150+
151+
items = append(items, createCompletionItem(label, completionPrefix+"."+label, protocol.FieldCompletion, field.Node))
152+
labels[label] = true
193153
}
194154

195155
sort.Slice(items, func(i, j int) bool {
@@ -213,9 +173,34 @@ func createCompletionItem(label, detail string, kind protocol.CompletionItemKind
213173
}
214174

215175
return protocol.CompletionItem{
216-
Label: label,
217-
Detail: detail,
218-
Kind: kind,
176+
Label: label,
177+
Detail: detail,
178+
Kind: kind,
179+
LabelDetails: protocol.CompletionItemLabelDetails{
180+
Description: typeToString(body),
181+
},
219182
InsertText: insertText,
220183
}
221184
}
185+
186+
func typeToString(t ast.Node) string {
187+
switch t.(type) {
188+
case *ast.Array:
189+
return "array"
190+
case *ast.LiteralBoolean:
191+
return "boolean"
192+
case *ast.Function:
193+
return "function"
194+
case *ast.LiteralNull:
195+
return "null"
196+
case *ast.LiteralNumber:
197+
return "number"
198+
case *ast.Object, *ast.DesugaredObject:
199+
return "object"
200+
case *ast.LiteralString:
201+
return "string"
202+
case *ast.Import, *ast.ImportStr:
203+
return "import"
204+
}
205+
return reflect.TypeOf(t).String()
206+
}

0 commit comments

Comments
 (0)