/* 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 = `

${escapeHtml(problem.entry_point)}

${escapeHtml(problem.source)}
Task ID: ${escapeHtml(problem.task_id)}
Index: ${problem.idx}
Dataset: ${escapeHtml(datasetName)}
${problem.inputs.length > 0 ? `
Test Inputs: ${problem.inputs.length}
` : ''}
`; // --- 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 += ` `; 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 += ` `; 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 += `
`; } html += `
`; problem.lang_solutions.forEach((sol, i) => { const label = sol.language_label || sol.language; html += ``; }); html += `
`; problem.lang_solutions.forEach((sol, i) => { html += `
${sol.highlighted_code}
${sol.buggy_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 += ` `; 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 += ``; } // 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

`; } // Hints if (problem.hints) { html += `

Hints

${escapeHtml(problem.hints)}
`; } html += ` `; 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

${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 += ` `; 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 += ` `; 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 += ``; } // 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 += ` `; 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 += ` `; }); html += `
`; // Navigation hint html += ` `; 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 = `

${escapeHtml(problem.entry_point)}

${escapeHtml(problem.source)}
Task ID: ${escapeHtml(problem.task_id)}
Index: ${problem.idx}
Dataset: ${escapeHtml(datasetName)}
Test Inputs: ${problem.inputs.length}

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 += ` `; }); html += `
`; // Navigation hint html += ` `; 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):

`; } // 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 += `
${escapeHtml(file.path)} ${statsHtml}
${renderDiff(diffChunk)}
`; }); 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(`${escapeHtml(line)}`); return; } if (line.startsWith('---') || line.startsWith('+++')) { rows.push(`${escapeHtml(line)}`); 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 `${rows.join('')}
`; } /** * 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 `${rows.join('')}
`; } function escapeHtml(text) { if (text === null || text === undefined) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } loadProblem();