Skip to content

Commit 0d0b520

Browse files
committed
fix(bloblang): Fix autocomplete for functions and methods
- Fix type mismatch in getCompletions() for functionSpecWithHTML/methodSpecWithHTML - Improve autocompletion suggestions for Bloblang keywords - Add tests to verify completion types and structure Signed-off-by: Ramtin Mesgari <26694963+iamramtin@users.noreply.github.com>
1 parent 69b952e commit 0d0b520

File tree

8 files changed

+156
-79
lines changed

8 files changed

+156
-79
lines changed

internal/cli/blobl/core.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,12 +319,9 @@ func generateAutocompletion(env *bloblang.Environment, req AutocompletionRequest
319319

320320
// Determine context: method vs function/keyword context
321321
isMethodContext := regexp.MustCompile(`\.\w*$`).MatchString(req.BeforeCursor)
322-
323322
if isMethodContext {
324-
// Add method completions
325323
completions = append(completions, getCompletions(syntaxData.Methods)...)
326324
} else {
327-
// Add function and keyword completions
328325
completions = append(completions, getCompletions(syntaxData.Functions)...)
329326
completions = append(completions, getKeywordCompletions()...)
330327
}

internal/cli/blobl/core_test.go

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -221,45 +221,76 @@ func TestGenerateAutocompletion(t *testing.T) {
221221
env := bloblang.GlobalEnvironment()
222222

223223
tests := []struct {
224-
name string
225-
request AutocompletionRequest
226-
expectError bool
224+
name string
225+
request AutocompletionRequest
226+
expectError bool
227+
expectCompletions bool
228+
expectKeywords bool
229+
expectFunctions bool
230+
expectMethods bool
231+
minCompletions int
227232
}{
228233
{
229-
name: "function context",
234+
name: "function context - should return functions and keywords",
230235
request: AutocompletionRequest{
231236
Line: "root = ",
232237
Column: 7,
233238
BeforeCursor: "root = ",
234239
},
235-
expectError: false,
240+
expectError: false,
241+
expectCompletions: true,
242+
expectKeywords: false, // Keywords only show when typing keyword prefix
243+
expectFunctions: true,
244+
expectMethods: false,
245+
minCompletions: 10, // Should have many function completions
236246
},
237247
{
238-
name: "method context with dot",
248+
name: "method context - should return methods only",
239249
request: AutocompletionRequest{
240250
Line: "root = this.u",
241251
Column: 13,
242252
BeforeCursor: "root = this.u",
243253
},
244-
expectError: false,
254+
expectError: false,
255+
expectCompletions: true,
256+
expectKeywords: false,
257+
expectFunctions: false,
258+
expectMethods: true,
259+
minCompletions: 5, // Should have method completions (uppercase, etc.)
245260
},
246261
{
247-
name: "invalid column",
262+
name: "keyword context - should return keywords",
263+
request: AutocompletionRequest{
264+
Line: "ro",
265+
Column: 2,
266+
BeforeCursor: "ro",
267+
},
268+
expectError: false,
269+
expectCompletions: true,
270+
expectKeywords: true,
271+
expectFunctions: true,
272+
expectMethods: false,
273+
minCompletions: 1,
274+
},
275+
{
276+
name: "invalid column - should fail",
248277
request: AutocompletionRequest{
249278
Line: "root = this",
250279
Column: -1,
251280
BeforeCursor: "",
252281
},
253-
expectError: true,
282+
expectError: true,
283+
expectCompletions: false,
254284
},
255285
{
256-
name: "inside comment",
286+
name: "inside comment - should return empty",
257287
request: AutocompletionRequest{
258288
Line: "# comment",
259289
Column: 5,
260290
BeforeCursor: "# com",
261291
},
262-
expectError: false,
292+
expectError: false,
293+
expectCompletions: false,
263294
},
264295
}
265296

@@ -271,12 +302,59 @@ func TestGenerateAutocompletion(t *testing.T) {
271302
t.Errorf("generateAutocompletion() success = %v, expectError = %v", result.Success, tt.expectError)
272303
}
273304

274-
if !tt.expectError && len(result.Completions) > 0 {
275-
// Check that completions have DocHTML when present
305+
if tt.expectCompletions {
306+
if len(result.Completions) < tt.minCompletions {
307+
t.Errorf("Expected at least %d completions, got %d", tt.minCompletions, len(result.Completions))
308+
}
309+
310+
// Verify completion structure
276311
for _, comp := range result.Completions {
312+
if comp.Caption == "" {
313+
t.Error("Completion missing Caption")
314+
}
315+
if comp.Type == "" {
316+
t.Error("Completion missing Type")
317+
}
277318
if comp.DocHTML == "" {
278-
t.Error("Completion missing DocHTML field")
319+
t.Error("Completion missing DocHTML")
320+
}
321+
if comp.Meta == "" {
322+
t.Error("Completion missing Meta")
279323
}
324+
// Either Value or Snippet should be set
325+
if comp.Value == "" && comp.Snippet == "" {
326+
t.Error("Completion missing both Value and Snippet")
327+
}
328+
}
329+
330+
// Verify expected types are present
331+
hasKeyword := false
332+
hasFunction := false
333+
hasMethod := false
334+
335+
for _, comp := range result.Completions {
336+
switch comp.Type {
337+
case "keyword":
338+
hasKeyword = true
339+
case "function":
340+
hasFunction = true
341+
case "method":
342+
hasMethod = true
343+
}
344+
}
345+
346+
if tt.expectKeywords && !hasKeyword {
347+
t.Error("Expected keyword completions but found none")
348+
}
349+
if tt.expectFunctions && !hasFunction {
350+
t.Error("Expected function completions but found none")
351+
}
352+
if tt.expectMethods && !hasMethod {
353+
t.Error("Expected method completions but found none")
354+
}
355+
} else {
356+
if len(result.Completions) > 0 {
357+
t.Errorf("Expected no completions, got %d", len(result.Completions))
280358
}
281359
}
282360
})

internal/cli/blobl/editor.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,17 @@ func getKeywordCompletions() []CompletionItem {
5959
name string
6060
description string
6161
score int
62-
category string
6362
}{
64-
{"root", "The root of the output document", 950, "core"},
65-
{"this", "The current context value", 950, "core"},
66-
{"if", "Conditional expression", 900, "control"},
67-
{"match", "Pattern matching expression", 900, "control"},
68-
{"let", "Variable assignment", 880, "variable"},
69-
{"map", "Create named mapping", 870, "mapping"},
70-
{"else", "Alternative branch", 850, "control"},
71-
{"import", "Import external mapping", 800, "mapping"},
72-
{"meta", "Access message metadata", 820, "metadata"},
73-
{"deleted", "Delete the current field", 750, "mutation"},
63+
{"root", "The root of the output document", 950},
64+
{"this", "The current context value", 950},
65+
{"if", "Conditional expression", 900},
66+
{"match", "Pattern matching expression", 900},
67+
{"let", "Variable assignment", 880},
68+
{"map", "Create named mapping", 870},
69+
{"else", "Alternative branch", 850},
70+
{"import", "Import external mapping", 800},
71+
{"meta", "Access message metadata", 820},
72+
{"deleted", "Delete the current field", 750},
7473
}
7574

7675
var completions []CompletionItem
@@ -81,16 +80,15 @@ func getKeywordCompletions() []CompletionItem {
8180
<div class="ace-doc-header">
8281
<div class="ace-doc-signature">
8382
<strong>%s</strong>
84-
<span class="ace-keyword-category">%s</span>
8583
</div>
8684
</div>
8785
<div class="ace-doc-description">%s</div>
88-
</div>`, keyword.name, keyword.category, keyword.description)
86+
</div>`, keyword.name, keyword.description)
8987

9088
completions = append(completions, CompletionItem{
9189
Caption: keyword.name,
9290
Value: keyword.name,
93-
Meta: keyword.category,
91+
Meta: "keyword",
9492
Type: "keyword",
9593
Score: keyword.score,
9694
Description: keyword.description,
@@ -101,32 +99,31 @@ func getKeywordCompletions() []CompletionItem {
10199
return completions
102100
}
103101

104-
// getCompletions converts function/method specs into autocompletion items.
105-
// Uses type switch to handle both FunctionSpec and MethodSpec maps.
102+
// getCompletions converts function/method specs with HTML into autocompletion items.
106103
func getCompletions(specs any) []CompletionItem {
107104
var completions []CompletionItem
108105

109106
switch s := specs.(type) {
110-
case map[string]query.FunctionSpec:
107+
case map[string]functionSpecWithHTML:
111108
for name, spec := range s {
112109
completions = append(completions, buildCompletionItem(
113110
name,
114-
FunctionSpecWrapper{spec},
111+
FunctionSpecWrapper{spec.FunctionSpec},
115112
spec.Category,
116113
"function",
117114
false, // isMethod
118115
))
119116
}
120117

121-
case map[string]query.MethodSpec:
118+
case map[string]methodSpecWithHTML:
122119
for name, spec := range s {
123120
category := "general"
124121
if len(spec.Categories) > 0 {
125122
category = spec.Categories[0].Category
126123
}
127124
completions = append(completions, buildCompletionItem(
128125
name,
129-
MethodSpecWrapper{spec},
126+
MethodSpecWrapper{spec.MethodSpec},
130127
category,
131128
"method",
132129
true, // isMethod
@@ -139,6 +136,11 @@ func getCompletions(specs any) []CompletionItem {
139136

140137
// buildCompletionItem creates a CompletionItem from a spec wrapper.
141138
func buildCompletionItem(name string, spec Spec, category, itemType string, isMethod bool) CompletionItem {
139+
// Use "general" as default if no category provided
140+
if category == "" {
141+
category = "general"
142+
}
143+
142144
item := CompletionItem{
143145
Caption: name,
144146
Meta: category,

internal/cli/blobl/playground/assets/css/components.css

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
padding: 6px 12px;
1616
font-size: 13px;
1717
}
18-
18+
1919
.panel {
2020
border-radius: 4px;
2121
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
@@ -109,7 +109,8 @@ body.embedded .status-badge {
109109
font-family: var(--bento-font-family);
110110
box-shadow: var(--bento-shadow-3d);
111111
transform: translateY(0);
112-
transition: transform var(--bento-transition-fast), box-shadow var(--bento-transition-fast),
112+
transition: transform var(--bento-transition-fast),
113+
box-shadow var(--bento-transition-fast),
113114
background-color var(--bento-transition-fast);
114115
margin-left: 4px;
115116
}
@@ -581,18 +582,15 @@ body.embedded .status-badge {
581582
}
582583
}
583584

584-
/* ===================================================================
585-
Notifications
586-
=================================================================== */
587-
585+
/* Notifications */
588586
.notification {
589587
position: fixed;
590588
top: 20px;
591589
right: 20px;
592590
padding: 12px 16px;
593591
border-radius: 6px;
594592
border: 1px solid;
595-
font-family: 'IBM Plex Sans', sans-serif;
593+
font-family: "IBM Plex Sans", sans-serif;
596594
font-size: 13px;
597595
font-weight: 500;
598596
z-index: 1000;
@@ -608,25 +606,25 @@ body.embedded .status-badge {
608606
}
609607

610608
.notification-success {
611-
background: #E8F5E8;
612-
color: #2E7D32;
613-
border-color: #2E7D32;
609+
background: #e8f5e8;
610+
color: #2e7d32;
611+
border-color: #2e7d32;
614612
}
615613

616614
.notification-error {
617-
background: #FFEBEE;
618-
color: #D32F2F;
619-
border-color: #D32F2F;
615+
background: #ffebee;
616+
color: #d32f2f;
617+
border-color: #d32f2f;
620618
}
621619

622620
.notification-warning {
623-
background: #FFF3E0;
624-
color: #F57C00;
625-
border-color: #F57C00;
621+
background: #fff3e0;
622+
color: #f57c00;
623+
border-color: #f57c00;
626624
}
627625

628626
.notification-info {
629-
background: #FDE5D8;
627+
background: #fde5d8;
630628
color: #553630;
631-
border-color: #EB8788;
629+
border-color: #eb8788;
632630
}

internal/cli/blobl/playground/assets/css/editor.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
.ace-bento .ace_cursor {
5151
margin-left: 3px;
5252
}
53-
53+
5454
.ace-bento .ace_marker-layer .ace_bracket {
5555
margin-left: 2.5px;
5656
}
@@ -62,11 +62,11 @@
6262
font-size: 12px !important;
6363
line-height: 1.4 !important;
6464
}
65-
65+
6666
.editor {
6767
min-height: 80px !important;
6868
}
69-
69+
7070
.formatter-section {
7171
padding: 4px 8px;
7272
font-size: 11px;

0 commit comments

Comments
 (0)