Skip to content

Commit

Permalink
Auto-complete function body attributes (#150)
Browse files Browse the repository at this point in the history
Closes #129
Had to implement a more sophisticated word-splitting function because `myfunc(arg1, arg2) has to be a single "word"
  • Loading branch information
julienduchesne authored Aug 7, 2024
1 parent 45e0b27 commit 71857b2
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 3 deletions.
5 changes: 5 additions & 0 deletions pkg/ast/processing/find_field.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
foundDesugaredObjects = FindTopLevelObjectsInFile(vm, start, "")

default:
if strings.Count(start, "(") == 1 && strings.Count(start, ")") == 1 {
// If the index is a function call, we need to find the function definition
// We can ignore the arguments. We'll only consider static attributes from the function's body
start = strings.Split(start, "(")[0]
}
// Get ast.DesugaredObject at variable definition by getting bind then setting ast.DesugaredObject
bind := FindBindByIDViaStack(stack, ast.Identifier(start))
if bind == nil {
Expand Down
39 changes: 38 additions & 1 deletion pkg/server/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func getCompletionLine(fileContent string, position protocol.Position) string {
}

func (s *Server) completionFromStack(line string, stack *nodestack.NodeStack, vm *jsonnet.VM, position protocol.Position) []protocol.CompletionItem {
lineWords := strings.Split(line, " ")
lineWords := splitWords(line)
lastWord := lineWords[len(lineWords)-1]
lastWord = strings.TrimRight(lastWord, ",;") // Ignore trailing commas and semicolons, they can present when someone is modifying an existing line

Expand Down Expand Up @@ -250,6 +250,43 @@ func typeToString(t ast.Node) string {
return "import"
case *ast.Index:
return "object field"
case *ast.Var:
return "variable"
}
return reflect.TypeOf(t).String()
}

// splitWords splits the input string into words, respecting spaces within parentheses.
func splitWords(input string) []string {
var words []string
var currentWord strings.Builder
var insideParentheses bool

for _, char := range input {
switch char {
case ' ':
if insideParentheses {
currentWord.WriteRune(char)
} else if currentWord.Len() > 0 {
words = append(words, currentWord.String())
currentWord.Reset()
}
case '(':
insideParentheses = true
currentWord.WriteRune(char)
case ')':
currentWord.WriteRune(char)
insideParentheses = false
default:
currentWord.WriteRune(char)
}
}

if currentWord.Len() > 0 {
words = append(words, currentWord.String())
} else if strings.HasSuffix(input, " ") {
words = append(words, "")
}

return words
}
29 changes: 29 additions & 0 deletions pkg/server/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,35 @@ func TestCompletion(t *testing.T) {
},
},
},
{
name: "complete attribute from function",
filename: "testdata/goto-functions.libsonnet",
replaceString: "test: myfunc(arg1, arg2)",
replaceByString: "test: myfunc(arg1, arg2).",
expected: protocol.CompletionList{
IsIncomplete: false,
Items: []protocol.CompletionItem{
{
Label: "atb1",
Kind: protocol.FieldCompletion,
Detail: "myfunc(arg1, arg2).atb1",
InsertText: "atb1",
LabelDetails: protocol.CompletionItemLabelDetails{
Description: "variable",
},
},
{
Label: "atb2",
Kind: protocol.FieldCompletion,
Detail: "myfunc(arg1, arg2).atb2",
InsertText: "atb2",
LabelDetails: protocol.CompletionItemLabelDetails{
Description: "variable",
},
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/server/testdata/goto-functions.libsonnet
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
local myfunc(arg1, arg2) = {
arg1: arg1,
arg2: arg2,
atb1: arg1,
atb2: arg2,
};

{
Expand Down

0 comments on commit 71857b2

Please sign in to comment.