/* global problemIdx, datasetSlug, datasetName, hasGroundTruth, hasTasks */
function badgeClass(source) {
return 'badge-' + source.toLowerCase().replace(/[^a-z0-9]/g, '');
}
async function loadProblem() {
try {
const response = await fetch(`/api/${datasetSlug}/problem/${problemIdx}`);
const problem = await response.json();
if (problem.error) {
document.getElementById('problem-content').innerHTML =
'
Error: ' + problem.error + '
';
return;
}
renderProblem(problem);
} catch (error) {
document.getElementById('problem-content').innerHTML =
'Error loading problem: ' + error.message + '
';
}
}
function renderProblem(problem) {
const container = document.getElementById('problem-content');
// Main problem info card (shared by all datasets)
let html = `
`;
// --- BigOBench view (problem description + per-solution code & complexity) ---
if (problem.solutions && problem.solutions.length > 0) {
// Problem description
if (problem.description) {
html += `
Problem Statement
${escapeHtml(problem.description)}
`;
}
// Each solution: code + complexity/language badges
problem.solutions.forEach((sol, i) => {
html += `
Solution ${i + 1} ${escapeHtml(sol.solution_id)}
`;
if (sol.language) {
html += `
Language
${escapeHtml(sol.language)}
`;
}
if (sol.time_complexity) {
html += `
Time
${escapeHtml(sol.time_complexity)}
`;
}
if (sol.space_complexity) {
html += `
Space
${escapeHtml(sol.space_complexity)}
`;
}
html += `
${sol.highlighted_code}
`;
});
// Navigation hint
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- DebugBench before/after view (buggy → fixed) ---
if (problem.buggy_code !== undefined && problem.fixed_code !== undefined) {
// Problem description
if (problem.description) {
html += `
Problem Statement
${escapeHtml(problem.description)}
`;
}
// Bug info
html += `
Bug Information
${escapeHtml(problem.bug_category || '')}
${escapeHtml(problem.bug_subtype || '')}
${escapeHtml(problem.bug_explanation || '')}
`;
if (problem.difficulty) {
html += `
Difficulty: ${escapeHtml(problem.difficulty)}
`;
}
html += `
`;
// Unified diff view of buggy → fixed
const unifiedDiff = computeUnifiedDiff(problem.buggy_code, problem.fixed_code);
html += `
Changes
${renderComputedDiff(unifiedDiff)}
`;
// Side-by-side buggy/fixed code
html += `
Full Code Comparison
Before
${problem.buggy_highlighted_code}
After
${problem.fixed_highlighted_code}
`;
// Examples
if (problem.examples && problem.examples.length > 0) {
html += `Examples `;
problem.examples.forEach((ex, i) => {
html += `
${escapeHtml(ex)} `;
});
html += `
`;
}
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- HumanEval-X / HumanEvalPack multi-language view ---
if (problem.lang_solutions && problem.lang_solutions.length > 0) {
// Check if this is HumanEvalPack (has buggy_code in solutions)
const hasBuggy = problem.lang_solutions.some(sol => sol.buggy_code);
// Bug info (HumanEvalPack only)
if (hasBuggy && (problem.bug_type || problem.failure_symptoms)) {
html += `
Bug Information
${problem.bug_type ? `${escapeHtml(problem.bug_type)} ` : ''}
${problem.failure_symptoms ? `${escapeHtml(problem.failure_symptoms)} ` : ''}
`;
}
// Language tabs with code panels
html += `
Source Code
`;
// Buggy/Canonical toggle for HumanEvalPack
if (hasBuggy) {
html += `
Canonical
Buggy
`;
}
html += `
`;
problem.lang_solutions.forEach((sol, i) => {
const label = sol.language_label || sol.language;
html += `${escapeHtml(label)} `;
});
html += `
`;
problem.lang_solutions.forEach((sol, i) => {
html += `
${sol.highlighted_code}
${sol.buggy_code ? `
${sol.buggy_highlighted_code}
` : ''}
`;
});
html += `
`;
// Test suite for current language
html += ``;
if (problem.lang_solutions[0].test) {
html += `
Test Suite ${escapeHtml(problem.lang_solutions[0].test)} `;
}
html += `
`;
// Description
if (problem.description) {
html += `
Problem Description
${escapeHtml(problem.description)}
`;
}
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
window._currentCodeMode = 'canonical';
return;
}
// --- SWE-bench / CommitBench diff view (unified diff patch) ---
if (problem.patch !== undefined) {
// GitHub links bar (SWE-bench variants)
const ghLinks = [];
if (problem.repo_url) ghLinks.push(`Repository `);
if (problem.issue_url) ghLinks.push(`Issue `);
if (problem.commit_url) ghLinks.push(`Base Commit `);
if (ghLinks.length > 0) {
html += `${ghLinks.join('')}
`;
}
// Metadata badges (version, date)
const metaBadges = [];
if (problem.version) metaBadges.push(`v${escapeHtml(problem.version)} `);
if (problem.created_at) metaBadges.push(`${escapeHtml(problem.created_at.split('T')[0])} `);
if (problem.commit_hash) metaBadges.push(`${escapeHtml(problem.commit_hash.substring(0, 12))} `);
if (problem.diff_languages) metaBadges.push(`${escapeHtml(problem.diff_languages)} `);
if (metaBadges.length > 0) {
html += `${metaBadges.join(' ')}
`;
}
// Problem statement (issue text / commit message)
if (problem.description) {
html += `
${problem.issue_url ? 'Issue Description' : 'Description'}
${escapeHtml(problem.description)}
`;
}
// Render unified diff with per-file sections
html += renderDiffFiles(problem.patch, 'Solution Patch');
// Test patch if available
if (problem.test_patch) {
html += renderDiffFiles(problem.test_patch, 'Test Patch');
}
// Failing tests
if (problem.fail_to_pass && problem.fail_to_pass.length > 0) {
html += `Tests: Fail → Pass `;
problem.fail_to_pass.forEach(t => {
html += `${escapeHtml(t)} `;
});
html += ` `;
}
// Hints
if (problem.hints) {
html += `
Hints
${escapeHtml(problem.hints)}
`;
}
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- SAFIM Fill-in-the-Middle view ---
if (problem.fim_ground_truth !== undefined) {
// Tab bar: "With Gap" | "Completed" | "Completion Only"
html += `
Fill-in-the-Middle
With Gap
Completed
Completion Only
${problem.highlighted_code}
Inserted completion (lines ${problem.fim_gt_start_line}–${problem.fim_gt_end_line})
${problem.fim_merged_highlighted}
${problem.fim_ground_truth_highlighted || escapeHtml(problem.fim_ground_truth)}
`;
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- Vulnerability view (BigVul, DiverseVul, PrimeVul) ---
if (problem.vulnerable_code !== undefined || problem.is_vulnerable !== undefined) {
// Vulnerability status and CWE info
const isVuln = problem.is_vulnerable;
html += `
Vulnerability Information
${isVuln ? 'Vulnerable' : 'Patched'}
${problem.cwe_id ? `${escapeHtml(problem.cwe_id)} ` : ''}
${problem.cve_id ? `${escapeHtml(problem.cve_id)} ` : ''}
${problem.project ? `${escapeHtml(problem.project)} ` : ''}
${problem.description ? `
${escapeHtml(problem.description).substring(0, 500)}
` : ''}
`;
// Show code with vuln/patched side-by-side if both available
if (problem.vulnerable_code && problem.patched_code) {
const vulnDiff = computeUnifiedDiff(problem.vulnerable_code, problem.patched_code);
html += `
Changes
${renderComputedDiff(vulnDiff)}
`;
html += `
Full Code Comparison
Vulnerable
${problem.vulnerable_highlighted_code}
Patched
${problem.patched_highlighted_code}
`;
} else {
// Single code view
html += `
Source Code
${problem.highlighted_code}
`;
}
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// Source Code card
html += `
Source Code
${problem.highlighted_code}
`;
// --- Non-DREval (simple) view ---
if (!hasTasks) {
// GitHub / external links (e.g. LCA adapters)
const simpleLinks = [];
if (problem.repo_url) simpleLinks.push(`Repository `);
if (problem.issue_url) simpleLinks.push(`Issue `);
if (problem.commit_url) simpleLinks.push(`Commit `);
if (simpleLinks.length > 0) {
html += `${simpleLinks.join('')}
`;
}
// Show description if available (e.g. LiveCodeBench, MBPP+, ClassEval)
if (problem.description) {
html += `
Problem Description
${escapeHtml(problem.description)}
`;
}
// Show difficulty, contest date, tags, rating if available
if (problem.difficulty || problem.contest_date || problem.tags || problem.cf_rating) {
let metaHtml = '';
if (problem.difficulty) {
metaHtml += `Difficulty: ${escapeHtml(problem.difficulty)} `;
}
if (problem.cf_rating) {
metaHtml += `Rating: ${problem.cf_rating} `;
}
if (problem.contest_date) {
metaHtml += `Date: ${escapeHtml(problem.contest_date.split('T')[0])} `;
}
if (problem.tags && problem.tags.length > 0) {
problem.tags.forEach(tag => {
metaHtml += `${escapeHtml(tag)} `;
});
}
html += `${metaHtml}
`;
}
// Show inputs/outputs if available
if (problem.inputs && problem.inputs.length > 0) {
html += `Inputs & Outputs `;
problem.inputs.forEach((inp, i) => {
const out = (problem.outputs && problem.outputs[i]) || '';
html += `
Input ${i + 1}
${escapeHtml(inp)}
Output
${escapeHtml(out)}
`;
});
html += `
`;
}
// Show test suite if available
if (problem.test) {
html += `
Test Suite
${escapeHtml(problem.test)}
`;
}
// Navigation hint
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
return;
}
// --- CRUXEval task view (tasks have given/predict fields, no task_items) ---
if (problem.tasks.length > 0 && problem.tasks[0].given !== undefined) {
// Task selector
html += `
Tasks
`;
problem.tasks.forEach((task, idx) => {
html += `
${escapeHtml(task.name)}
`;
});
html += `
`;
// Navigation hint
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
window.currentProblem = problem;
showCruxTask(0);
return;
}
// --- DREval (full) view with tasks, coverage, arrows ---
// Rebuild html cleanly with coverage legend and SVG overlay
html = `
Source Code
Coverage:
Executed
Not executed
${problem.highlighted_code}
`;
// Task selector
html += `
Test Cases & Tasks
Select a test input to view associated reasoning tasks:
`;
problem.tasks.forEach((task, idx) => {
html += `
Input ${task.input_idx + 1}
`;
});
html += `
`;
// Navigation hint
html += `
Tip: Use the Previous/Next buttons at the top to browse through problems,
or return to the list view to filter by dataset source or search by name.
`;
container.innerHTML = html;
// Store problem data globally
window.currentProblem = problem;
// Show first task by default
showTask(0);
}
function injectTaskMarkers(taskItems) {
const codePre = document.querySelector('.source .code pre');
// Save the pristine original innerHTML once, before any modification.
if (codePre && !window._codePreOriginalHtml) {
window._codePreOriginalHtml = codePre.innerHTML;
}
// Invalidate span cache (rebuilt lazily on next arrow draw)
window._linenoSpanCache = null;
// Store current task items so applyCoverage can re-add markers after wrapping.
window._currentTaskItems = taskItems || [];
// Reset code pre to original, then add markers from scratch.
if (codePre && window._codePreOriginalHtml) {
codePre.innerHTML = window._codePreOriginalHtml;
}
if (!taskItems || taskItems.length === 0) {
return;
}
// Group tasks by line number
const tasksByLine = {};
taskItems.forEach(item => {
if (!tasksByLine[item.lineno]) tasksByLine[item.lineno] = [];
tasksByLine[item.lineno].push(item.var);
});
// Inject task marker badges into the code pre
if (!codePre) return;
const codeLines = codePre.innerHTML.split('\n');
codePre.innerHTML = codeLines.map((line, idx) => {
const lineNum = idx + 1;
if (tasksByLine[lineNum] && line.trim() !== '') {
const vars = tasksByLine[lineNum];
return line + `${escapeHtml(vars.join(', '))} `;
}
return line;
}).join('\n');
}
function applyCoverage(coverageSet, totalLines) {
// Remove previous coverage classes from lineno spans.
document.querySelectorAll('td.linenos .normal').forEach(el => {
el.classList.remove('line-executed', 'line-not-executed');
});
if (!coverageSet) {
const legend = document.getElementById('coverage-legend');
if (legend) legend.style.display = 'none';
return;
}
const legend = document.getElementById('coverage-legend');
if (legend) legend.style.display = 'block';
// Color lineno spans only.
document.querySelectorAll('td.linenos .normal').forEach(span => {
const lineNum = parseInt(span.textContent.trim());
if (!isNaN(lineNum) && lineNum <= totalLines) {
span.classList.add(coverageSet.has(lineNum) ? 'line-executed' : 'line-not-executed');
}
});
}
// Global map: lineno -> list of next line numbers (1-indexed; -1 = end of trace)
window._nextLinesMap = {};
async function loadAndApplyGroundTruth(problemIdx, inputIdx, taskItems) {
// Show "loading" placeholders on all task items
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = '…'; el.className = 'gt-answer loading'; }
});
// Clear next-lines data from previous input
window._nextLinesMap = {};
try {
const resp = await fetch(`/api/${datasetSlug}/problem/${problemIdx}/ground_truth/${inputIdx}`);
const gt = await resp.json();
if (gt.status !== 'ok') {
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = gt.status === 'error' ? '(exec error)' : '(unavailable)'; el.className = 'gt-answer'; }
});
applyCoverage(null, 0);
return;
}
// Apply coverage highlighting
const coverageSet = new Set(gt.coverage);
applyCoverage(coverageSet, gt.total_lines);
// Fill in variable answers
const answerMap = {};
gt.variable_answers.forEach(a => {
answerMap[`${a.lineno}-${a.var}`] = a.answer_str;
});
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) {
const answer = answerMap[`${item.lineno}-${item.var}`] || '(not available)';
el.textContent = answer;
el.className = 'gt-answer';
}
});
// Store next-lines data for arrow visualization
if (gt.next_lines_answers) {
gt.next_lines_answers.forEach(a => {
window._nextLinesMap[a.lineno] = a.next_lines;
});
}
// Attach hover handlers to task-marker spans now that we have next-lines data
attachArrowHoverHandlers();
} catch (e) {
taskItems.forEach(item => {
const el = document.getElementById(`gt-${item.lineno}-${item.var}`);
if (el) { el.textContent = '(error)'; el.className = 'gt-answer'; }
});
}
}
// Cache of lineNum → DOM span, rebuilt whenever injectTaskMarkers runs.
window._linenoSpanCache = null;
function buildLinenoSpanCache(container) {
const cache = {};
container.querySelectorAll('td.linenos .normal').forEach(span => {
const n = parseInt(span.textContent.trim());
if (!isNaN(n)) cache[n] = span;
});
window._linenoSpanCache = cache;
}
/**
* Get the bounding rect of the lineno span for a given 1-indexed line number,
* relative to the code container element. Uses a cached span map.
*/
function getLinenoSpanRect(lineNum, container) {
if (!window._linenoSpanCache) buildLinenoSpanCache(container);
const span = window._linenoSpanCache[lineNum];
if (!span) return null;
const spanRect = span.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
return {
top: spanRect.top - containerRect.top + container.scrollTop,
bottom: spanRect.bottom - containerRect.top + container.scrollTop,
left: spanRect.left - containerRect.left,
right: spanRect.right - containerRect.left,
width: spanRect.width,
height: spanRect.height,
midY: (spanRect.top + spanRect.bottom) / 2 - containerRect.top + container.scrollTop,
};
}
/**
* Draw arrows from sourceLine to each of the targetLines in the SVG overlay.
* Lines are 1-indexed. -1 means "end of execution" (no arrow drawn).
*/
function drawArrows(sourceLineNum, targetLineNums) {
const container = document.getElementById('code-container');
const svg = document.getElementById('arrow-overlay');
if (!container || !svg) return;
// Remove previous arrows (but keep defs)
svg.querySelectorAll('.arrow-path').forEach(el => el.remove());
const srcRect = getLinenoSpanRect(sourceLineNum, container);
if (!srcRect) return;
// Update SVG height to match container
svg.setAttribute('height', container.scrollHeight);
targetLineNums.forEach(targetLineNum => {
if (targetLineNum === -1) return; // end of trace — no arrow
const dstRect = getLinenoSpanRect(targetLineNum, container);
if (!dstRect) return;
// Start point: right edge of source lineno span, vertically centered
const x1 = srcRect.right + 2;
const y1 = srcRect.midY;
// End point: right edge of target lineno span, vertically centered
const x2 = dstRect.right + 2;
const y2 = dstRect.midY;
// Horizontal offset for the bezier control points — curves to the right
const curveOffset = Math.max(30, Math.abs(y2 - y1) * 0.4);
// Cubic bezier: both control points extend to the right of the lineno column
const cx1 = x1 + curveOffset;
const cy1 = y1;
const cx2 = x2 + curveOffset;
const cy2 = y2;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', `M ${x1} ${y1} C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x2} ${y2}`);
path.setAttribute('class', 'exec-arrow arrow-path');
path.setAttribute('marker-end', 'url(#arrowhead)');
svg.appendChild(path);
});
}
/**
* Clear all arrows from the SVG overlay.
*/
function clearArrows() {
const svg = document.getElementById('arrow-overlay');
if (svg) {
svg.querySelectorAll('.arrow-path').forEach(el => el.remove());
}
}
// AbortController for the current set of marker hover listeners.
let _markerListenersAbort = null;
/**
* Attach mouseenter/mouseleave handlers to all .task-marker spans so that
* hovering shows execution-flow arrows to next lines.
*/
function attachArrowHoverHandlers() {
// Cancel any previously attached listeners without touching the DOM.
if (_markerListenersAbort) _markerListenersAbort.abort();
_markerListenersAbort = new AbortController();
const { signal } = _markerListenersAbort;
document.querySelectorAll('.task-marker').forEach(marker => {
marker.addEventListener('mouseenter', () => {
const lineNum = parseInt(marker.dataset.lineno);
if (!lineNum) return;
const nextLines = window._nextLinesMap[lineNum];
if (nextLines && nextLines.length > 0) {
drawArrows(lineNum, nextLines);
}
}, { signal });
marker.addEventListener('mouseleave', () => {
clearArrows();
}, { signal });
});
}
function showCruxTask(taskIdx) {
const problem = window.currentProblem;
const task = problem.tasks[taskIdx];
// Update active button
document.querySelectorAll('.task-btn').forEach((btn, idx) => {
btn.classList.toggle('active', idx === taskIdx);
});
const givenLabel = task.given === 'input' ? 'Input (given)' : 'Output (given)';
const predictLabel = task.predict === 'output' ? 'Output (predict)' : 'Input (predict)';
const givenValue = task.given === 'input' ? task.input : task.output;
const predictValue = task.predict === 'output' ? task.output : task.input;
const html = `
${escapeHtml(task.description)}
${escapeHtml(givenLabel)}
${escapeHtml(givenValue)}
${escapeHtml(predictLabel)}
${escapeHtml(predictValue)}
`;
document.getElementById('task-content').innerHTML = html;
}
function showTask(taskIdx) {
const problem = window.currentProblem;
const task = problem.tasks[taskIdx];
// Update active button
const buttons = document.querySelectorAll('.task-btn');
buttons.forEach((btn, idx) => {
if (idx === taskIdx) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
// Inject task markers into the code
injectTaskMarkers(task.task_items);
// Clear previous coverage while new one loads
applyCoverage(null, 0);
// Render task content
const ioSection = task.test_class_code
? `
Input
${escapeHtml(task.input)}
Test Class — ${escapeHtml(task.test_class_name)}
${escapeHtml(task.test_class_code)}
`
: `
Input
${escapeHtml(task.input)}
Expected Output
${escapeHtml(task.output)}
`;
let html = `
${ioSection}
`;
// Show task items with ground truth answer placeholders
if (task.task_items && task.task_items.length > 0) {
html += `
Reasoning Tasks
Variable state at each execution point (correct answer shown in
teal ):
`;
task.task_items.forEach(item => {
html += `
Line ${item.lineno}
${escapeHtml(item.var)}
…
`;
});
html += `
`;
}
// Show output prediction task if exists
if (task.output_pred) {
html += `
Output Completion Task
The model needs to complete this test assertion:
${escapeHtml(task.output_pred)}
`;
}
html += `
`;
document.getElementById('task-content').innerHTML = html;
// Fetch and apply ground truth (coverage + variable answers)
if (hasGroundTruth && task.task_items) {
loadAndApplyGroundTruth(problem.idx, task.input_idx, task.task_items);
}
}
function showLangTab(idx) {
// Only toggle tabs within the #lang-tabs container (not code-mode tabs)
const langTabsContainer = document.getElementById('lang-tabs');
if (langTabsContainer) {
langTabsContainer.querySelectorAll('.lang-tab').forEach((tab, i) => {
tab.classList.toggle('active', i === idx);
});
}
document.querySelectorAll('.lang-code-panel').forEach((panel, i) => {
panel.classList.toggle('active', i === idx);
});
// Re-apply the current code mode (canonical/buggy) to the newly visible panel
if (window._currentCodeMode) {
const problem = window.currentProblem;
if (problem && problem.lang_solutions) {
const sol = problem.lang_solutions[idx];
if (sol) {
const canonical = document.getElementById('lang-code-canonical-' + idx);
const buggy = document.getElementById('lang-code-buggy-' + idx);
if (canonical) canonical.style.display = window._currentCodeMode === 'canonical' ? '' : 'none';
if (buggy) buggy.style.display = window._currentCodeMode === 'buggy' ? '' : 'none';
// If buggy mode is selected but this language has no buggy code, fall back to canonical
if (window._currentCodeMode === 'buggy' && !buggy && canonical) {
canonical.style.display = '';
}
}
}
}
// Update test section
const problem = window.currentProblem;
if (problem && problem.lang_solutions) {
const sol = problem.lang_solutions[idx];
const testContainer = document.getElementById('lang-test-container');
if (testContainer && sol && sol.test) {
testContainer.innerHTML = `Test Suite ${escapeHtml(sol.test)} `;
} else if (testContainer) {
testContainer.innerHTML = '';
}
}
}
function toggleCodeMode(mode) {
window._currentCodeMode = mode;
const problem = window.currentProblem;
if (!problem || !problem.lang_solutions) return;
// Update mode tabs
const modeTabs = document.querySelectorAll('#code-mode-tabs .lang-tab');
modeTabs.forEach(tab => {
tab.classList.toggle('active', tab.textContent.trim().toLowerCase() === mode);
});
// Toggle visibility of canonical/buggy code in all panels
problem.lang_solutions.forEach((sol, i) => {
const canonical = document.getElementById('lang-code-canonical-' + i);
const buggy = document.getElementById('lang-code-buggy-' + i);
if (canonical) canonical.style.display = mode === 'canonical' ? '' : 'none';
if (buggy) buggy.style.display = mode === 'buggy' ? '' : 'none';
});
}
function showFimTab(idx) {
const tabs = document.querySelectorAll('#fim-tabs .lang-tab');
tabs.forEach((tab, i) => tab.classList.toggle('active', i === idx));
for (let i = 0; i < 3; i++) {
const panel = document.getElementById('fim-panel-' + i);
if (panel) panel.classList.toggle('active', i === idx);
}
}
/**
* Split a unified diff into per-file sections and render each with a GitHub-style
* file header bar. Returns an HTML string with one card per file.
*/
function renderDiffFiles(diffText, title) {
if (!diffText) return '';
// Split into per-file chunks by "diff --git" boundaries
const files = [];
let current = null;
diffText.split('\n').forEach(line => {
if (line.startsWith('diff --git')) {
if (current) files.push(current);
// Extract file path from "diff --git a/path b/path"
const m = line.match(/^diff --git a\/(.+?) b\/(.+)/);
const filePath = m ? m[2] : line;
current = { path: filePath, lines: [line] };
} else if (current) {
current.lines.push(line);
} else {
// Lines before any diff header — create a default section
if (!current) current = { path: '', lines: [] };
current.lines.push(line);
}
});
if (current) files.push(current);
if (files.length === 0) return '';
let html = '';
if (files.length === 1 && !files[0].path) {
// Single unnamed diff — render as before
html += `${escapeHtml(title)} ${renderDiff(diffText)}
`;
} else {
html += `${escapeHtml(title)} `;
files.forEach(file => {
const diffChunk = file.lines.join('\n');
// Count additions/deletions
let adds = 0, dels = 0;
file.lines.forEach(l => {
if (l.startsWith('+') && !l.startsWith('+++')) adds++;
if (l.startsWith('-') && !l.startsWith('---')) dels++;
});
const statsHtml = `
+${adds} -${dels} `;
html += `
`;
});
html += `
`;
}
return html;
}
/**
* Render a unified diff with line numbers and file headers (GitHub-style).
*/
function renderDiff(diffText) {
if (!diffText) return '';
const lines = diffText.split('\n');
let oldLine = 0, newLine = 0;
const rows = [];
lines.forEach(line => {
if (line.startsWith('diff ')) {
rows.push(``);
return;
}
if (line.startsWith('---') || line.startsWith('+++')) {
rows.push(``);
return;
}
if (line.startsWith('@@')) {
// Parse hunk header: @@ -oldStart,oldCount +newStart,newCount @@
const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
if (m) {
oldLine = parseInt(m[1]);
newLine = parseInt(m[2]);
}
rows.push(`${escapeHtml(line)} `);
return;
}
if (line.startsWith('+')) {
rows.push(`${newLine} ${escapeHtml(line.substring(1))} `);
newLine++;
} else if (line.startsWith('-')) {
rows.push(`${oldLine} ${escapeHtml(line.substring(1))} `);
oldLine++;
} else if (line.startsWith(' ')) {
rows.push(`${oldLine} ${newLine} ${escapeHtml(line.substring(1))} `);
oldLine++;
newLine++;
} else if (line.trim() === '') {
// Empty trailing line
} else {
rows.push(`${oldLine} ${newLine} ${escapeHtml(line)} `);
oldLine++;
newLine++;
}
});
return ``;
}
/**
* Simple line-by-line diff (LCS-based) between two code strings.
* Returns an array of {type: 'context'|'add'|'del', line: string}.
*/
function computeUnifiedDiff(oldText, newText) {
const oldLines = (oldText || '').split('\n');
const newLines = (newText || '').split('\n');
// LCS for line sequences
const m = oldLines.length, n = newLines.length;
// For very large files, just show both in full instead of computing LCS
if (m * n > 500000) {
const result = [];
oldLines.forEach(l => result.push({type: 'del', line: l}));
newLines.forEach(l => result.push({type: 'add', line: l}));
return result;
}
const dp = Array.from({length: m + 1}, () => new Uint16Array(n + 1));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (oldLines[i - 1] === newLines[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// Backtrack to build diff
const result = [];
let i = m, j = n;
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
result.push({type: 'context', line: oldLines[i - 1]});
i--; j--;
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
result.push({type: 'add', line: newLines[j - 1]});
j--;
} else {
result.push({type: 'del', line: oldLines[i - 1]});
i--;
}
}
result.reverse();
// Compact: only show hunks with context (3 lines around changes)
const contextSize = 3;
const hasChange = result.map(r => r.type !== 'context');
const show = new Uint8Array(result.length);
for (let k = 0; k < result.length; k++) {
if (hasChange[k]) {
for (let c = Math.max(0, k - contextSize); c <= Math.min(result.length - 1, k + contextSize); c++) {
show[c] = 1;
}
}
}
const compacted = [];
let lastShown = -1;
for (let k = 0; k < result.length; k++) {
if (show[k]) {
if (lastShown >= 0 && k - lastShown > 1) {
compacted.push({type: 'separator', line: '...'});
}
compacted.push(result[k]);
lastShown = k;
}
}
return compacted.length > 0 ? compacted : result;
}
/**
* Render the output of computeUnifiedDiff into diff HTML with line numbers.
*/
function renderComputedDiff(diffEntries) {
let oldLine = 1, newLine = 1;
const rows = diffEntries.map(entry => {
if (entry.type === 'separator') {
return `${escapeHtml(entry.line)} `;
}
if (entry.type === 'del') {
const row = `${oldLine} ${escapeHtml(entry.line)} `;
oldLine++;
return row;
}
if (entry.type === 'add') {
const row = `${newLine} ${escapeHtml(entry.line)} `;
newLine++;
return row;
}
// context
const row = `${oldLine} ${newLine} ${escapeHtml(entry.line)} `;
oldLine++;
newLine++;
return row;
});
return ``;
}
function escapeHtml(text) {
if (text === null || text === undefined) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
loadProblem();