From 2fd6d3d0968636180eadea24d9a6bd086e42353d Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Thu, 29 Jan 2026 15:55:28 +0100 Subject: [PATCH] added hover doc to variable --- internal/lsp/server.go | 36 +++++++++++++++++- test/lsp_hover_variable_test.go | 67 +++++++++++++++++++++++++++++++++ test/lsp_variable_refs_test.go | 62 ++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 test/lsp_hover_variable_test.go create mode 100644 test/lsp_variable_refs_test.go diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 12cdc95..98468b8 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -593,6 +593,9 @@ func HandleHover(params HoverParams) *Hover { content = fmt.Sprintf("**Field**: `%s`", res.Field.Name) } else if res.Variable != nil { content = fmt.Sprintf("**Variable**: `%s`\nType: `%s`", res.Variable.Name, res.Variable.TypeExpr) + if res.Variable.DefaultValue != nil { + content += fmt.Sprintf("\nDefault: `%s`", valueToString(res.Variable.DefaultValue)) + } } else if res.Reference != nil { targetName := "Unresolved" fullInfo := "" @@ -606,12 +609,15 @@ func HandleHover(params HoverParams) *Hover { v := res.Reference.TargetVariable targetName = v.Name fullInfo = fmt.Sprintf("**Variable**: `%s`\nType: `%s`", v.Name, v.TypeExpr) + if v.DefaultValue != nil { + fullInfo += fmt.Sprintf("\nDefault: `%s`", valueToString(v.DefaultValue)) + } } content = fmt.Sprintf("**Reference**: `%s` -> `%s`", res.Reference.Name, targetName) if fullInfo != "" { content += fmt.Sprintf("\n\n---\n%s", fullInfo) - } else if targetDoc != "" { // Fallback if formatNodeInfo returned empty (unlikely) + } else if targetDoc != "" { content += fmt.Sprintf("\n\n%s", targetDoc) } } @@ -628,6 +634,34 @@ func HandleHover(params HoverParams) *Hover { } } +func valueToString(val parser.Value) string { + switch v := val.(type) { + case *parser.StringValue: + if v.Quoted { + return fmt.Sprintf("\"%s\"", v.Value) + } + return v.Value + case *parser.IntValue: + return v.Raw + case *parser.FloatValue: + return v.Raw + case *parser.BoolValue: + return fmt.Sprintf("%v", v.Value) + case *parser.ReferenceValue: + return v.Value + case *parser.VariableReferenceValue: + return v.Name + case *parser.ArrayValue: + elements := []string{} + for _, e := range v.Elements { + elements = append(elements, valueToString(e)) + } + return fmt.Sprintf("{ %s }", strings.Join(elements, " ")) + default: + return "" + } +} + func HandleCompletion(params CompletionParams) *CompletionList { uri := params.TextDocument.URI path := uriToPath(uri) diff --git a/test/lsp_hover_variable_test.go b/test/lsp_hover_variable_test.go new file mode 100644 index 0000000..1b35646 --- /dev/null +++ b/test/lsp_hover_variable_test.go @@ -0,0 +1,67 @@ +package integration + +import ( + "strings" + "testing" + + "github.com/marte-community/marte-dev-tools/internal/index" + "github.com/marte-community/marte-dev-tools/internal/lsp" + "github.com/marte-community/marte-dev-tools/internal/parser" +) + +func TestLSPHoverVariable(t *testing.T) { + lsp.Tree = index.NewProjectTree() + lsp.Documents = make(map[string]string) + + content := ` +#var MyInt: int = 123 ++Obj = { + Field = $MyInt +} +` + uri := "file://hover_var.marte" + lsp.Documents[uri] = content + p := parser.NewParser(content) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + lsp.Tree.AddFile("hover_var.marte", cfg) + lsp.Tree.ResolveReferences() + + // 1. Hover on Definition (#var MyInt) + // Line 2 (index 1). # is at 0. Name "MyInt" is at 5. + paramsDef := lsp.HoverParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 1, Character: 5}, + } + resDef := lsp.HandleHover(paramsDef) + if resDef == nil { + t.Fatal("Expected hover for definition") + } + contentDef := resDef.Contents.(lsp.MarkupContent).Value + if !strings.Contains(contentDef, "Type: `int`") { + t.Errorf("Hover def missing type. Got: %s", contentDef) + } + if !strings.Contains(contentDef, "Default: `123`") { + t.Errorf("Hover def missing default value. Got: %s", contentDef) + } + + // 2. Hover on Reference ($MyInt) + // Line 4 (index 3). $MyInt is at col 12. + paramsRef := lsp.HoverParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 3, Character: 12}, + } + resRef := lsp.HandleHover(paramsRef) + if resRef == nil { + t.Fatal("Expected hover for reference") + } + contentRef := resRef.Contents.(lsp.MarkupContent).Value + if !strings.Contains(contentRef, "Type: `int`") { + t.Errorf("Hover ref missing type. Got: %s", contentRef) + } + if !strings.Contains(contentRef, "Default: `123`") { + t.Errorf("Hover ref missing default value. Got: %s", contentRef) + } +} diff --git a/test/lsp_variable_refs_test.go b/test/lsp_variable_refs_test.go new file mode 100644 index 0000000..fa44975 --- /dev/null +++ b/test/lsp_variable_refs_test.go @@ -0,0 +1,62 @@ +package integration + +import ( + "testing" + + "github.com/marte-community/marte-dev-tools/internal/index" + "github.com/marte-community/marte-dev-tools/internal/lsp" + "github.com/marte-community/marte-dev-tools/internal/parser" +) + +func TestLSPVariableRefs(t *testing.T) { + lsp.Tree = index.NewProjectTree() + lsp.Documents = make(map[string]string) + + content := ` +#var MyVar: int = 1 ++Obj = { + Field = $MyVar +} +` + uri := "file://vars.marte" + lsp.Documents[uri] = content + p := parser.NewParser(content) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + lsp.Tree.AddFile("vars.marte", cfg) + lsp.Tree.ResolveReferences() + + // 1. Definition from Usage + // Line 4: " Field = $MyVar" + // $ is at col 12 (0-based) ? + // " Field = " is 4 + 6 + 3 = 13 chars? + // 4 spaces. Field (5). " = " (3). 4+5+3 = 12. + // So $ is at 12. + paramsDef := lsp.DefinitionParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 3, Character: 12}, + } + resDef := lsp.HandleDefinition(paramsDef) + locs, ok := resDef.([]lsp.Location) + if !ok || len(locs) != 1 { + t.Fatalf("Expected 1 definition location, got %v", resDef) + } + // Line 2 in file is index 1. + if locs[0].Range.Start.Line != 1 { + t.Errorf("Expected definition at line 1, got %d", locs[0].Range.Start.Line) + } + + // 2. References from Definition + // #var at line 2 (index 1). Col 0. + paramsRef := lsp.ReferenceParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: 1, Character: 1}, + Context: lsp.ReferenceContext{IncludeDeclaration: true}, + } + resRef := lsp.HandleReferences(paramsRef) + if len(resRef) != 2 { // Decl + Usage + t.Errorf("Expected 2 references, got %d", len(resRef)) + } +}