From 55ca313b73949ccf961a0c42b0c3141672fdc68a Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Mon, 2 Feb 2026 14:37:03 +0100 Subject: [PATCH] added suggestion for variables --- internal/lsp/server.go | 83 ++++++++++++++++++++++++++++++++----- test/lsp_completion_test.go | 64 +++++++++++++++++++++++++++- 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 24e9389..f0c2f60 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -265,7 +265,7 @@ func HandleMessage(msg *JsonRpcMessage) { "documentFormattingProvider": true, "renameProvider": true, "completionProvider": map[string]any{ - "triggerCharacters": []string{"=", " "}, + "triggerCharacters": []string{"=", " ", "@"}, }, }, }) @@ -675,6 +675,20 @@ func HandleCompletion(params CompletionParams) *CompletionList { prefix := lineStr[:col] + // Case 3: Variable completion + varRegex := regexp.MustCompile(`([@$])([a-zA-Z0-9_]*)$`) + if matches := varRegex.FindStringSubmatch(prefix); matches != nil { + container := Tree.GetNodeContaining(path, parser.Position{Line: params.Position.Line + 1, Column: col + 1}) + if container == nil { + if iso, ok := Tree.IsolatedFiles[path]; ok { + container = iso + } else { + container = Tree.Root + } + } + return suggestVariables(container) + } + // Case 1: Assigning a value (Ends with "=" or "= ") if strings.Contains(prefix, "=") { lastIdx := strings.LastIndex(prefix, "=") @@ -907,20 +921,41 @@ func suggestFieldValues(container *index.ProjectNode, field string, path string) root = Tree.Root } + var items []CompletionItem + if field == "DataSource" { - return suggestObjects(root, "DataSource") - } - if field == "Functions" { - return suggestObjects(root, "GAM") - } - if field == "Type" { - return suggestSignalTypes() + if list := suggestObjects(root, "DataSource"); list != nil { + items = append(items, list.Items...) + } + } else if field == "Functions" { + if list := suggestObjects(root, "GAM"); list != nil { + items = append(items, list.Items...) + } + } else if field == "Type" { + if list := suggestSignalTypes(); list != nil { + items = append(items, list.Items...) + } + } else { + if list := suggestCUEEnums(container, field); list != nil { + items = append(items, list.Items...) + } } - if list := suggestCUEEnums(container, field); list != nil { - return list + // Add variables + vars := suggestVariables(container) + if vars != nil { + for _, item := range vars.Items { + // Create copy to modify label + newItem := item + newItem.Label = "@" + newItem.Label + newItem.InsertText = "@" + item.Label + items = append(items, newItem) + } } + if len(items) > 0 { + return &CompletionList{Items: items} + } return nil } @@ -1518,3 +1553,31 @@ func send(msg any) { body, _ := json.Marshal(msg) fmt.Fprintf(Output, "Content-Length: %d\r\n\r\n%s", len(body), body) } + +func suggestVariables(container *index.ProjectNode) *CompletionList { + items := []CompletionItem{} + seen := make(map[string]bool) + + curr := container + for curr != nil { + for name, info := range curr.Variables { + if !seen[name] { + seen[name] = true + + doc := "" + if info.Def.DefaultValue != nil { + doc = fmt.Sprintf("Default: %s", valueToString(info.Def.DefaultValue)) + } + + items = append(items, CompletionItem{ + Label: name, + Kind: 6, // Variable + Detail: fmt.Sprintf("Variable (%s)", info.Def.TypeExpr), + Documentation: doc, + }) + } + } + curr = curr.Parent + } + return &CompletionList{Items: items} +} diff --git a/test/lsp_completion_test.go b/test/lsp_completion_test.go index 07822d3..c290ad4 100644 --- a/test/lsp_completion_test.go +++ b/test/lsp_completion_test.go @@ -163,7 +163,7 @@ $App = { } }) - t.Run("Scope-aware suggestions", func(t *testing.T) { + t.Run("Scope-aware suggestions", func(t *testing.T) { setup() // Define a project DataSource in one file cfg1, _ := parser.NewParser("#package MYPROJ.Data\n+ProjectDS = { Class = FileReader +Signals = { S1 = { Type = int32 } } }").Parse() @@ -317,4 +317,66 @@ package schema } } }) + + t.Run("Suggest Variables", func(t *testing.T) { + setup() + content := ` +#var MyVar: uint = 10 ++App = { + Field = +} +` + lsp.Documents[uri] = content + p := parser.NewParser(content) + cfg, _ := p.Parse() + lsp.Tree.AddFile(path, cfg) + + // 1. Triggered by = + params := lsp.CompletionParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 3, Character: 12}, // After "Field = " + } + list := lsp.HandleCompletion(params) + if list == nil { + t.Fatal("Expected suggestions") + } + + found := false + for _, item := range list.Items { + if item.Label == "@MyVar" { + found = true + break + } + } + if !found { + t.Error("Expected @MyVar in suggestions for =") + } + + // 2. Triggered by $ + // "Field = $" + lsp.Documents[uri] = ` +#var MyVar: uint = 10 ++App = { + Field = $ +} +` + params2 := lsp.CompletionParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 3, Character: 13}, // After "Field = $" + } + list2 := lsp.HandleCompletion(params2) + if list2 == nil { + t.Fatal("Expected suggestions for $") + } + found = false + for _, item := range list2.Items { + if item.Label == "MyVar" { // suggestVariables returns "MyVar" + found = true + break + } + } + if !found { + t.Error("Expected MyVar in suggestions for $") + } + }) }