From 7d35cce8813cedb362f2c032dc154ee39727cdc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:38:22 +0000 Subject: [PATCH 1/4] Initial plan From 36e4f91e313a41ea9ec2740b0acf603001200ebf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:45:24 +0000 Subject: [PATCH 2/4] Add sidebar with example cases to playground Co-authored-by: Sander-Toonen <5106372+Sander-Toonen@users.noreply.github.com> --- samples/language-service-sample/app.js | 142 +++++++++++++++++++++ samples/language-service-sample/index.html | 15 ++- samples/language-service-sample/styles.css | 109 ++++++++++++++++ 3 files changed, 265 insertions(+), 1 deletion(-) diff --git a/samples/language-service-sample/app.js b/samples/language-service-sample/app.js index 09d0b17..1bb9e89 100644 --- a/samples/language-service-sample/app.js +++ b/samples/language-service-sample/app.js @@ -43,6 +43,148 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) initTheme(); +// Examples data +const exampleCases = [ + { + id: 'math', + title: 'Mathematical Expression', + description: 'Basic math operations with variables', + expression: '(x + y) * multiplier + sqrt(16)', + context: { + x: 10, + y: 5, + multiplier: 3 + } + }, + { + id: 'arrays', + title: 'Working with Arrays', + description: 'Array functions like sum, min, max', + expression: 'sum(numbers) + max(numbers) - min(numbers)', + context: { + numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + values: [15, 25, 35] + } + }, + { + id: 'objects', + title: 'Object Manipulation', + description: 'Access nested object properties', + expression: 'user.profile.score * level.multiplier + bonus.points', + context: { + user: { + name: "Alice", + profile: { + score: 85, + rank: "Gold" + } + }, + level: { + current: 5, + multiplier: 1.5 + }, + bonus: { + points: 100, + active: true + } + } + }, + { + id: 'map-filter', + title: 'Map and Filter Functions', + description: 'Transform and filter data with callbacks', + expression: 'sum(map(filter(items, item => item > 3), x => x * 2))', + context: { + items: [1, 2, 3, 4, 5, 6, 7, 8], + threshold: 3 + } + }, + { + id: 'complex', + title: 'Complex Objects', + description: 'Work with deeply nested data structures', + expression: 'company.departments[0].employees.length * company.settings.bonusRate + sum(map(company.departments, d => d.budget))', + context: { + company: { + name: "TechCorp", + departments: [ + { + name: "Engineering", + budget: 500000, + employees: ["John", "Jane", "Bob"] + }, + { + name: "Marketing", + budget: 200000, + employees: ["Alice", "Carol"] + } + ], + settings: { + bonusRate: 0.15, + fiscalYear: 2024 + } + } + } + } +]; + +// Render examples sidebar +function renderExamplesSidebar() { + const examplesList = document.getElementById('examplesList'); + if (!examplesList) return; + + examplesList.innerHTML = exampleCases.map(example => ` + + `).join(''); + + // Add click handlers + examplesList.querySelectorAll('.example-item').forEach(button => { + button.addEventListener('click', () => { + const exampleId = button.dataset.exampleId; + const example = exampleCases.find(e => e.id === exampleId); + if (example) { + loadExample(example); + } + }); + }); +} + +// Load example into editors +function loadExample(example) { + if (typeof expressionEditor !== 'undefined' && expressionEditor) { + expressionEditor.getModel().setValue(example.expression); + } + if (typeof contextEditor !== 'undefined' && contextEditor) { + contextEditor.getModel().setValue(JSON.stringify(example.context, null, 2)); + } +} + +// Initialize sidebar +renderExamplesSidebar(); + // Split pane resizing (function() { const resizer = document.getElementById('resizer'); diff --git a/samples/language-service-sample/index.html b/samples/language-service-sample/index.html index 3599fa1..64a550b 100644 --- a/samples/language-service-sample/index.html +++ b/samples/language-service-sample/index.html @@ -53,8 +53,21 @@

+ + + -
+
Expression Enter your expr-eval expression diff --git a/samples/language-service-sample/styles.css b/samples/language-service-sample/styles.css index e6afdcf..79883f2 100644 --- a/samples/language-service-sample/styles.css +++ b/samples/language-service-sample/styles.css @@ -1,4 +1,8 @@ /* Base styles */ +:root { + --sidebar-width: 256px; +} + html, body { height: 100%; margin: 0; @@ -99,3 +103,108 @@ html, body { .error-shake { animation: shake 0.3s ease-in-out; } + +/* Examples sidebar styles */ +#examplesSidebar { + min-width: 200px; + width: var(--sidebar-width); + display: flex; + flex-direction: column; + flex-shrink: 0; + background-color: #f9fafb; + border-right: 1px solid #e5e7eb; +} + +.dark #examplesSidebar { + background-color: #252526; + border-right-color: #3c3c3c; +} + +#examplesSidebar > div:first-child { + height: 40px; + background-color: #f3f4f6; + border-bottom: 1px solid #e5e7eb; + display: flex; + align-items: center; + padding: 0 16px; + flex-shrink: 0; +} + +.dark #examplesSidebar > div:first-child { + background-color: #333333; + border-bottom-color: #3c3c3c; +} + +#examplesList { + flex: 1; + overflow-y: auto; + padding: 8px; +} + +.example-item { + width: 100%; + text-align: left; + padding: 12px; + border-radius: 8px; + border: 1px solid transparent; + background: transparent; + cursor: pointer; + margin-bottom: 4px; + transition: all 0.2s ease; +} + +.example-item:hover { + background-color: #ffffff; + border-color: #e0e7ff; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.dark .example-item:hover { + background-color: #2d2d2d; + border-color: #3c3c3c; +} + +.example-item:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); +} + +.example-item:active { + transform: scale(0.98); +} + +.example-item p:first-child { + font-size: 14px; + font-weight: 500; + color: #1f2937; + margin: 0; +} + +.dark .example-item p:first-child { + color: #cccccc; +} + +.example-item:hover p:first-child { + color: #4f46e5; +} + +.dark .example-item:hover p:first-child { + color: #569cd6; +} + +.example-item p:last-child { + font-size: 12px; + color: #6b7280; + margin: 4px 0 0 0; +} + +.dark .example-item p:last-child { + color: #808080; +} + +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} From 36913fe689f2a2f5c170d8dec63e7802b73e8cfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:00:34 +0000 Subject: [PATCH 3/4] Add data transformation example and URL query parameter support Co-authored-by: Sander-Toonen <5106372+Sander-Toonen@users.noreply.github.com> --- samples/language-service-sample/app.js | 35 +++++++++++++++++++ .../language-service-sample/serve-sample.cjs | 6 ++++ 2 files changed, 41 insertions(+) diff --git a/samples/language-service-sample/app.js b/samples/language-service-sample/app.js index 1bb9e89..4e7fc04 100644 --- a/samples/language-service-sample/app.js +++ b/samples/language-service-sample/app.js @@ -125,6 +125,19 @@ const exampleCases = [ } } } + }, + { + id: 'data-transform', + title: 'Data Transformation', + description: 'Flatten nested objects and transform rows', + expression: "map(f(row) = {_id: row.rowId} + flatten(row.data, ''), $event)", + context: { + "$event": [ + {"rowId": 1, "state": "saved", "data": { "InventoryId": 1256, "Description": "Bal", "Weight": { "Unit": "g", "Amount": 120 } }}, + {"rowId": 2, "state": "new", "data": { "InventoryId": 2344, "Description": "Basket", "Weight": { "Unit": "g", "Amount": 300 } }}, + {"rowId": 3, "state": "unchanged", "data": { "InventoryId": 9362, "Description": "Wood", "Weight": { "Unit": "kg", "Amount": 18 } }} + ] + } } ]; @@ -185,6 +198,25 @@ function loadExample(example) { // Initialize sidebar renderExamplesSidebar(); +// Get example ID from URL query parameter +function getExampleFromUrl() { + const params = new URLSearchParams(window.location.search); + return params.get('example'); +} + +// Load example from URL if present (called after Monaco initializes) +function loadExampleFromUrl() { + const exampleId = getExampleFromUrl(); + if (exampleId) { + const example = exampleCases.find(e => e.id === exampleId); + if (example) { + loadExample(example); + return true; + } + } + return false; +} + // Split pane resizing (function() { const resizer = document.getElementById('resizer'); @@ -585,6 +617,9 @@ require(['vs/editor/editor.main'], function () { applyHighlighting(); evaluate(); + // Load example from URL query parameter if present + loadExampleFromUrl(); + // Event listeners for changes expressionModel.onDidChangeContent(() => { applyHighlighting(); diff --git a/samples/language-service-sample/serve-sample.cjs b/samples/language-service-sample/serve-sample.cjs index d894327..be52d7e 100644 --- a/samples/language-service-sample/serve-sample.cjs +++ b/samples/language-service-sample/serve-sample.cjs @@ -23,6 +23,12 @@ function send(res, status, body, headers = {}) { const server = http.createServer((req, res) => { let urlPath = decodeURIComponent(req.url || '/'); + + // Strip query string + const queryIndex = urlPath.indexOf('?'); + if (queryIndex !== -1) { + urlPath = urlPath.substring(0, queryIndex); + } if (urlPath === '/' || urlPath === '/index.html') { urlPath = 'samples/language-service-sample/index.html'; From 1e52c2dfc3949a4235e5ac558b1767d4d02a552c Mon Sep 17 00:00:00 2001 From: Sander Toonen Date: Tue, 20 Jan 2026 17:08:35 +0100 Subject: [PATCH 4/4] Fix highlighting --- samples/language-service-sample/app.js | 22 ++++++++++------------ samples/language-service-sample/styles.css | 2 ++ src/functions/string/operations.ts | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/language-service-sample/app.js b/samples/language-service-sample/app.js index 4e7fc04..e119bd8 100644 --- a/samples/language-service-sample/app.js +++ b/samples/language-service-sample/app.js @@ -455,24 +455,22 @@ require(['vs/editor/editor.main'], function () { }); // Syntax highlighting + let highlightDecorations = []; + function applyHighlighting() { const doc = makeTextDocument(expressionModel); const tokens = ls.getHighlighting(doc); - const rangesByClass = new Map(); - for (const t of tokens) { + const decorations = tokens.map(t => { const start = expressionModel.getPositionAt(t.start); const end = expressionModel.getPositionAt(t.end); - const range = new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column); - const cls = 'tok-' + t.type; - if (!rangesByClass.has(cls)) rangesByClass.set(cls, []); - rangesByClass.get(cls).push({range, options: {inlineClassName: cls}}); - } + return { + range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column), + options: { inlineClassName: 'tok-' + t.type } + }; + }); - window.__exprEvalDecos = window.__exprEvalDecos || {}; - for (const [cls, decos] of rangesByClass.entries()) { - const prev = window.__exprEvalDecos[cls] || []; - window.__exprEvalDecos[cls] = expressionEditor.deltaDecorations(prev, decos); - } + // deltaDecorations replaces old decorations with new ones atomically + highlightDecorations = expressionEditor.deltaDecorations(highlightDecorations, decorations); } // Result display functions diff --git a/samples/language-service-sample/styles.css b/samples/language-service-sample/styles.css index 79883f2..ff35d92 100644 --- a/samples/language-service-sample/styles.css +++ b/samples/language-service-sample/styles.css @@ -83,6 +83,7 @@ html, body { .tok-function { color: #795e26 !important; } .tok-punctuation { color: #383a42 !important; } .tok-name { color: #001080 !important; } +.tok-constant { color: #0070c1 !important; } /* Dark theme token colors */ .dark .tok-number { color: #b5cea8 !important; } @@ -92,6 +93,7 @@ html, body { .dark .tok-function { color: #dcdcaa !important; } .dark .tok-punctuation { color: #d4d4d4 !important; } .dark .tok-name { color: #9cdcfe !important; } +.dark .tok-constant { color: #4fc1ff !important; } /* Error styling animation */ @keyframes shake { diff --git a/src/functions/string/operations.ts b/src/functions/string/operations.ts index 59b9a26..9a98ded 100644 --- a/src/functions/string/operations.ts +++ b/src/functions/string/operations.ts @@ -22,14 +22,14 @@ export function stringLength(str: string | undefined): number | undefined { export function isEmpty(str: string | null | undefined): boolean | undefined { if (str === undefined) { return undefined; - } + } if (str === null) { return true; } if (typeof str !== 'string') { throw new Error('Argument to isEmpty must be a string'); } - + return str.length === 0; }