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
73 changes: 57 additions & 16 deletions samples/language-service-sample/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ function loadExampleFromUrl() {
return false;
}

// Split pane resizing
// Vertical resizing for bottom split (context/results)
(function() {
const resizer = document.getElementById('resizer');
const leftPane = document.getElementById('leftPane');
const rightPane = document.getElementById('rightPane');
const mainContent = document.getElementById('mainContent');
const resizer = document.getElementById('verticalResizer');
const contextPane = document.getElementById('contextPane');
const resultsPane = document.getElementById('resultsPane');

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

Unused variable resultsPane.

Suggested change
const resultsPane = document.getElementById('resultsPane');

Copilot uses AI. Check for mistakes.
const bottomArea = document.getElementById('bottomArea');
let isResizing = false;

resizer.addEventListener('mousedown', (e) => {
Expand All @@ -180,15 +180,15 @@ function loadExampleFromUrl() {
if (!isResizing) return;
e.preventDefault();

const containerRect = mainContent.getBoundingClientRect();
const containerRect = bottomArea.getBoundingClientRect();
const containerWidth = containerRect.width;
const resizerWidth = 6;

let newLeftWidth = e.clientX - containerRect.left;
newLeftWidth = Math.max(containerWidth * 0.15, Math.min(containerWidth * 0.85, newLeftWidth));
newLeftWidth = Math.max(containerWidth * 0.2, Math.min(containerWidth * 0.8, newLeftWidth));

leftPane.style.width = newLeftWidth + 'px';
rightPane.style.width = (containerWidth - newLeftWidth - resizerWidth) + 'px';
const percentage = (newLeftWidth / containerWidth) * 100;
contextPane.style.width = percentage + '%';
});

document.addEventListener('mouseup', () => {
Expand Down Expand Up @@ -417,6 +417,24 @@ require(['vs/editor/editor.main'], function () {
highlightDecorations = expressionEditor.deltaDecorations(highlightDecorations, decorations);
}

// Syntax highlight JSON
function syntaxHighlightJson(json) {
if (typeof json !== 'string') {
json = JSON.stringify(json, null, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("+[^"]*"+)(:)?/g, function(match, p1, p2) {
let cls = 'json-key';
if (!p2) {
cls = 'json-string';
}
return '<span class="' + cls + '">' + p1 + '</span>' + (p2 || '');
})
.replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
.replace(/: (null)/g, ': <span class="json-null">$1</span>')
.replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>');
}

// Result display functions
function showResult(result) {
const resultSuccess = document.getElementById('resultSuccess');
Expand All @@ -432,21 +450,42 @@ require(['vs/editor/editor.main'], function () {
// Format the result
let displayValue;
let typeInfo;
let isJson = false;

if (result === null) {
displayValue = 'null';
displayValue = '<span class="json-null">null</span>';
typeInfo = 'Type: null';
isJson = true;
} else if (result === undefined) {
displayValue = 'undefined';
displayValue = '<span class="json-null">undefined</span>';
typeInfo = 'Type: undefined';
isJson = true;
} else if (typeof result === 'object') {
displayValue = JSON.stringify(result, null, 2);
displayValue = syntaxHighlightJson(result);
typeInfo = Array.isArray(result) ? `Type: array (${result.length} items)` : 'Type: object';
isJson = true;
} else if (typeof result === 'boolean') {
displayValue = `<span class="json-boolean">${result}</span>`;
typeInfo = 'Type: boolean';
isJson = true;
} else if (typeof result === 'number') {
displayValue = `<span class="json-number">${result}</span>`;
typeInfo = 'Type: number';
isJson = true;
} else if (typeof result === 'string') {
displayValue = `<span class="json-string">"${result}"</span>`;

Copilot AI Jan 23, 2026

Copy link

Choose a reason for hiding this comment

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

XSS vulnerability: String results are not HTML-escaped before being inserted into the DOM via innerHTML. If an expression evaluates to a string containing HTML special characters like <script>alert('xss')</script>, it will be executed as HTML. The string should be HTML-escaped before wrapping it in the span tag, similar to how it's done in the syntaxHighlightJson function on line 425.

Copilot uses AI. Check for mistakes.
typeInfo = 'Type: string';
isJson = true;
} else {
displayValue = String(result);
typeInfo = `Type: ${typeof result}`;
}

resultValue.textContent = displayValue;
if (isJson) {
resultValue.innerHTML = displayValue;
} else {
resultValue.textContent = displayValue;
}
resultType.textContent = typeInfo;
}

Expand All @@ -456,15 +495,17 @@ require(['vs/editor/editor.main'], function () {
const resultEmpty = document.getElementById('resultEmpty');
const errorMessage = document.getElementById('errorMessage');
const errorDetails = document.getElementById('errorDetails');
const footer = document.getElementById('footer');
const resultsPane = document.getElementById('resultsPane');

resultSuccess.classList.add('hidden');
resultError.classList.remove('hidden');
resultEmpty.classList.add('hidden');

// Shake animation
footer.classList.add('error-shake');
setTimeout(() => footer.classList.remove('error-shake'), 300);
if (resultsPane) {
resultsPane.classList.add('error-shake');
setTimeout(() => resultsPane.classList.remove('error-shake'), 300);
}

errorMessage.textContent = error.message;

Expand Down
116 changes: 60 additions & 56 deletions samples/language-service-sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,73 +66,77 @@ <h1 class="text-xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 dark
</div>
</aside>

<!-- Expression Editor Pane -->
<div id="leftPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: calc(60% - var(--sidebar-width));">
<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>
</div>
<div class="editor-wrapper">
<div id="expressionEditor"></div>
<!-- Main Work Area -->
<div id="workArea" class="flex-1 flex flex-col">
<!-- Expression Editor -->
<div id="expressionPane" class="pane bg-white dark:bg-[#1e1e1e]">
<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>
</div>
<div class="editor-wrapper">
<div id="expressionEditor"></div>
</div>
</div>
</div>

<!-- Resizer -->
<div id="resizer" class="bg-gray-200 dark:bg-[#3c3c3c]"></div>
<!-- Bottom Split Area -->
<div id="bottomArea" class="flex-1 flex">
<!-- Context Editor Pane -->
<div id="contextPane" class="pane bg-white dark:bg-[#1e1e1e] border-t border-gray-200 dark:border-[#3c3c3c]">
<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]">Context (JSON)</span>
<span class="ml-2 text-xs text-gray-400 dark:text-[#808080]">Variables available in expressions</span>
</div>
<div class="editor-wrapper">
<div id="contextEditor"></div>
</div>
</div>

<!-- Context Editor Pane -->
<div id="rightPane" class="pane bg-white dark:bg-[#1e1e1e]" style="width: calc(40% - 6px);">
<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]">Context (JSON)</span>
<span class="ml-2 text-xs text-gray-400 dark:text-[#808080]">Variables available in expressions</span>
</div>
<div class="editor-wrapper">
<div id="contextEditor"></div>
</div>
</div>
</main>
<!-- Vertical Resizer -->
<div id="verticalResizer" class="bg-gray-200 dark:bg-[#3c3c3c]"></div>

<!-- Footer / Results -->
<footer id="footer" class="bg-white dark:bg-[#1e1e1e] border-t border-gray-200 dark:border-[#3c3c3c]">
<div class="h-full flex flex-col">
<div class="h-8 bg-gray-100 dark:bg-[#252526] flex items-center px-4 border-b border-gray-200 dark:border-[#3c3c3c] flex-shrink-0">
<span class="text-sm font-medium text-gray-600 dark:text-[#cccccc]">Result</span>
</div>
<div id="resultContainer" class="flex-1 p-4 overflow-auto">
<div id="resultSuccess" class="hidden">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 dark:bg-[#2d4a3e] flex items-center justify-center">
<svg class="w-5 h-5 text-green-600 dark:text-[#4ec9b0]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<div class="flex-1">
<p class="text-sm text-gray-500 dark:text-[#808080]">Evaluation Result</p>
<p id="resultValue" class="text-2xl font-mono font-bold text-gray-900 dark:text-[#d4d4d4] mt-1"></p>
<p id="resultType" class="text-xs text-gray-400 dark:text-[#808080] mt-1"></p>
</div>
<!-- Results Pane -->
<div id="resultsPane" class="pane bg-white dark:bg-[#1e1e1e] border-t border-gray-200 dark:border-[#3c3c3c]">
<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]">Result</span>
</div>
</div>
<div id="resultError" class="hidden">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 dark:bg-[#4a2d2d] flex items-center justify-center">
<svg class="w-5 h-5 text-red-600 dark:text-[#f48771]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div id="resultContainer" class="flex-1 p-4 overflow-auto">
<div id="resultSuccess" class="hidden">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 dark:bg-[#2d4a3e] flex items-center justify-center">
<svg class="w-5 h-5 text-green-600 dark:text-[#4ec9b0]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<div class="flex-1">
<p class="text-sm text-gray-500 dark:text-[#808080]">Evaluation Result</p>
<p id="resultValue" class="text-2xl font-mono font-bold text-gray-900 dark:text-[#d4d4d4] mt-1"></p>
<p id="resultType" class="text-xs text-gray-400 dark:text-[#808080] mt-1"></p>
</div>
</div>
</div>
<div id="resultError" class="hidden">
<div class="flex items-start gap-3">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 dark:bg-[#4a2d2d] flex items-center justify-center">
<svg class="w-5 h-5 text-red-600 dark:text-[#f48771]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-red-600 dark:text-[#f48771]">Error</p>
<p id="errorMessage" class="text-base font-mono text-red-700 dark:text-[#f48771] mt-1 bg-red-50 dark:bg-[#3a2222] p-3 rounded-lg border border-red-200 dark:border-[#5a3c3c]"></p>
<div id="errorDetails" class="mt-3 text-sm text-gray-600 dark:text-[#cccccc] space-y-2"></div>
</div>
</div>
</div>
<div class="flex-1">
<p class="text-sm font-medium text-red-600 dark:text-[#f48771]">Error</p>
<p id="errorMessage" class="text-base font-mono text-red-700 dark:text-[#f48771] mt-1 bg-red-50 dark:bg-[#3a2222] p-3 rounded-lg border border-red-200 dark:border-[#5a3c3c]"></p>
<div id="errorDetails" class="mt-3 text-sm text-gray-600 dark:text-[#cccccc] space-y-2"></div>
<div id="resultEmpty" class="flex items-center justify-center h-full text-gray-400 dark:text-[#808080]">
<p>Enter an expression to see results</p>
</div>
</div>
</div>
<div id="resultEmpty" class="flex items-center justify-center h-full text-gray-400 dark:text-[#808080]">
<p>Enter an expression to see results</p>
</div>
</div>
</div>
</footer>
</main>
</div>

<!-- Save Toast Notification -->
Expand Down
91 changes: 85 additions & 6 deletions samples/language-service-sample/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ html, body {
display: flex;
}

/* Work area layout */
#workArea {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}

#expressionPane {
height: 300px;
flex-shrink: 0;
}

#bottomArea {
flex: 1;
min-height: 0;
display: flex;
}

#contextPane {
width: 50%;
flex-shrink: 0;
}

#resultsPane {
flex: 1;
min-width: 0;
}

/* Split pane styles */
.pane {
display: flex;
Expand All @@ -47,18 +76,19 @@ html, body {
bottom: 0;
}

#resizer {
/* Vertical resizer */
#verticalResizer {
width: 6px;
cursor: col-resize;
flex-shrink: 0;
position: relative;
}

#resizer:hover {
#verticalResizer:hover {
background-color: #6366f1 !important;
}

#resizer::after {
#verticalResizer::after {
content: '';
position: absolute;
top: 50%;
Expand All @@ -70,9 +100,58 @@ html, body {
border-radius: 2px;
}

#footer {
flex-shrink: 0;
height: 300px;
#resultContainer {
flex: 1;
overflow-y: auto;
}

/* JSON syntax highlighting */
#resultValue {
white-space: pre-wrap;
word-break: break-word;
}

.json-key {
color: #0451a5;
font-weight: 500;
}

.json-string {
color: #a31515;
}

.json-number {
color: #098658;
}

.json-boolean {
color: #0000ff;
font-weight: 600;
}

.json-null {
color: #0000ff;
font-weight: 600;
}

.dark .json-key {
color: #9cdcfe;
}

.dark .json-string {
color: #ce9178;
}

.dark .json-number {
color: #b5cea8;
}

.dark .json-boolean {
color: #569cd6;
}

.dark .json-null {
color: #569cd6;
}

/* Light theme token colors */
Expand Down
Loading