Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 188 additions & 13 deletions samples/language-service-sample/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,180 @@ 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]
Comment on lines +65 to +66
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'values' array in the context is not used in the expression. The expression only references 'numbers'. Consider removing 'values' from the context or updating the expression to demonstrate its usage.

Suggested change
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
values: [15, 25, 35]
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Copilot uses AI. Check for mistakes.
}
},
{
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
}
Comment on lines +99 to +100
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'threshold' variable in the context is not used in the expression. The expression hardcodes the value '3' in the filter predicate. Consider either removing 'threshold' or updating the expression to use it, such as 'sum(map(filter(items, item => item > threshold), x => x * 2))'.

Copilot uses AI. Check for mistakes.
},
{
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
}
}
}
},
{
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 } }}
]
}
}
];

// Render examples sidebar
function renderExamplesSidebar() {
const examplesList = document.getElementById('examplesList');
if (!examplesList) return;

examplesList.innerHTML = exampleCases.map(example => `
<button
class="example-item w-full text-left p-3 rounded-lg transition-all duration-200
hover:bg-white dark:hover:bg-[#2d2d2d]
hover:shadow-sm hover:border-indigo-200 dark:hover:border-[#3c3c3c]
border border-transparent
group"
data-example-id="${example.id}"
>
<div class="flex items-start gap-2">
<div class="flex-shrink-0 w-6 h-6 rounded bg-indigo-100 dark:bg-[#3c3c3c] flex items-center justify-center mt-0.5">
<svg class="w-3.5 h-3.5 text-indigo-600 dark:text-[#569cd6]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-gray-800 dark:text-[#cccccc] truncate group-hover:text-indigo-600 dark:group-hover:text-[#569cd6]">
${example.title}
</p>
<p class="text-xs text-gray-500 dark:text-[#808080] mt-0.5 line-clamp-2">
${example.description}
</p>
</div>
</div>
</button>
`).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();

// 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');
Expand Down Expand Up @@ -281,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}});
}

window.__exprEvalDecos = window.__exprEvalDecos || {};
for (const [cls, decos] of rangesByClass.entries()) {
const prev = window.__exprEvalDecos[cls] || [];
window.__exprEvalDecos[cls] = expressionEditor.deltaDecorations(prev, decos);
}
return {
range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
options: { inlineClassName: 'tok-' + t.type }
};
});

// deltaDecorations replaces old decorations with new ones atomically
highlightDecorations = expressionEditor.deltaDecorations(highlightDecorations, decorations);
}

// Result display functions
Expand Down Expand Up @@ -443,6 +615,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();
Expand Down
15 changes: 14 additions & 1 deletion samples/language-service-sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,21 @@ <h1 class="text-xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 dark

<!-- Main Content -->
<main id="mainContent">
<!-- Examples Sidebar -->
<aside id="examplesSidebar" class="w-64 bg-gray-50 dark:bg-[#252526] border-r border-gray-200 dark:border-[#3c3c3c] flex-shrink-0 flex flex-col">
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sidebar width is specified in two places: the Tailwind class 'w-64' (which equals 256px/16rem) in the HTML and 'width: var(--sidebar-width)' in the CSS. While both currently resolve to 256px, having two sources of truth can lead to inconsistencies if one is changed. Consider removing the 'w-64' class and relying solely on the CSS variable for width control.

Copilot uses AI. Check for mistakes.
<div class="h-10 bg-gray-100 dark:bg-[#333333] border-b border-gray-200 dark:border-[#3c3c3c] flex items-center px-4">
<svg class="w-4 h-4 text-indigo-500 dark:text-[#569cd6] mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<span class="text-sm font-medium text-gray-700 dark:text-[#cccccc]">Examples</span>
</div>
<div id="examplesList" class="flex-1 overflow-y-auto p-2 space-y-1">
<!-- Examples will be populated by JavaScript -->
</div>
</aside>

<!-- Expression Editor Pane -->
<div id="leftPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: 60%;">
<div id="leftPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: calc(60% - var(--sidebar-width));">
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leftPane width calculation is incorrect. Using "calc(60% - var(--sidebar-width))" treats percentages as relative to the parent container width. If the container is 1000px wide and --sidebar-width is 256px, this would calculate to 600px - 256px = 344px, but the sidebar already takes up 256px, leaving only 744px for both panes and the resizer. The correct calculation should subtract the sidebar width proportionally or use a different approach, such as "calc((100% - var(--sidebar-width)) * 0.6)" to get 60% of the remaining space after the sidebar.

Suggested change
<div id="leftPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: calc(60% - var(--sidebar-width));">
<div id="leftPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: calc((100% - var(--sidebar-width)) * 0.6);">

Copilot uses AI. Check for mistakes.
<div class="pane-header h-10 bg-gray-100 dark:bg-[#252526] border-b border-gray-200 dark:border-[#3c3c3c] flex items-center px-4">
<span class="text-sm font-medium text-gray-600 dark:text-[#cccccc]">Expression</span>
<span class="ml-2 text-xs text-gray-400 dark:text-[#808080]">Enter your expr-eval expression</span>
Expand Down
6 changes: 6 additions & 0 deletions samples/language-service-sample/serve-sample.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
111 changes: 111 additions & 0 deletions samples/language-service-sample/styles.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* Base styles */
:root {
--sidebar-width: 256px;
}

html, body {
height: 100%;
margin: 0;
Expand Down Expand Up @@ -79,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; }
Expand All @@ -88,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 {
Expand All @@ -99,3 +105,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;
}
Loading
Loading