diff --git a/pkg/ast/processing/find_field.go b/pkg/ast/processing/find_field.go index edf26f1..f2497fd 100644 --- a/pkg/ast/processing/find_field.go +++ b/pkg/ast/processing/find_field.go @@ -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 { diff --git a/pkg/server/completion.go b/pkg/server/completion.go index bb78e00..e6b53f8 100644 --- a/pkg/server/completion.go +++ b/pkg/server/completion.go @@ -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 @@ -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 +} diff --git a/pkg/server/completion_test.go b/pkg/server/completion_test.go index fa85eb9..e7bb0cf 100644 --- a/pkg/server/completion_test.go +++ b/pkg/server/completion_test.go @@ -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) { diff --git a/pkg/server/testdata/goto-functions.libsonnet b/pkg/server/testdata/goto-functions.libsonnet index 8c5c3de..f554567 100644 --- a/pkg/server/testdata/goto-functions.libsonnet +++ b/pkg/server/testdata/goto-functions.libsonnet @@ -1,6 +1,6 @@ local myfunc(arg1, arg2) = { - arg1: arg1, - arg2: arg2, + atb1: arg1, + atb2: arg2, }; {