From a9f97401e1b5b56b9fa606f5f0f337dad468c5eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 07:29:29 +0000 Subject: [PATCH 01/10] Initial plan From 9c049007db02db4694b8b9a1435047c5af94ba89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 07:37:14 +0000 Subject: [PATCH 02/10] fix: escape in webview content and add structured logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch files can contain (e.g. when patching HTML files). JSON.stringify does not escape this, so the HTML parser would close the " payloads cannot abort the viewer. Tests now validate the shipped script parses and that content round-trips exactly. Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com> --- CHANGELOG.md | 10 +- media/patchViewer.css | 458 ++++++++++++++++++++ media/patchViewer.js | 375 ++++++++++++++++ src/patchEditorProvider.ts | 847 ++----------------------------------- src/test/extension.test.ts | 75 +++- 5 files changed, 925 insertions(+), 840 deletions(-) create mode 100644 media/patchViewer.css create mode 100644 media/patchViewer.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e3796e8..e5e16c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ ## [Unreleased] +### Changed +- Refactored the webview so the diff renderer runs from standalone static assets (`media/patchViewer.js` and `media/patchViewer.css`) loaded via `webview.asWebviewUri(...)`, instead of embedding the ~350‑line client script and CSS inside a TypeScript template literal. The browser now receives the script verbatim, eliminating the class of escaping bugs (`\n`, ``, ...) that repeatedly broke the inline script. + ### Fixed -- Fixed a blank Visual view and unresponsive tabs caused by invalid JavaScript in the webview: `\n` escape sequences inside the client script were consumed by the surrounding template literal, producing a syntax error that aborted the entire inline script -- Added a regression test that verifies the generated webview script is syntactically valid JavaScript +- Fixed a blank Visual view and an unresponsive Content tab caused by the inline client script failing to run when the surrounding template literal mangled its escape sequences. The diff now renders and the tabs respond regardless of the patch contents. +- The initial patch content is passed to the webview through a non-executable JSON data block (with every `<` escaped as `\u003c`), so patches that contain `` or HTML comments can no longer abort the viewer. + +### Tests +- Replaced the inline-script validity test with checks that the shipped `media/patchViewer.js` parses as JavaScript and that the embedded initial content round-trips exactly (including `` payloads). ## [1.0.2] - 2026-07-03 diff --git a/media/patchViewer.css b/media/patchViewer.css new file mode 100644 index 0000000..06ea37f --- /dev/null +++ b/media/patchViewer.css @@ -0,0 +1,458 @@ +/* Patch Reader webview styles. + * Loaded into the custom editor webview via webview.asWebviewUri(). + * Kept as a standalone file so the browser receives it verbatim (no template-literal escaping). */ +:root { + --bg-primary: var(--vscode-editor-background); + --bg-secondary: var(--vscode-sideBar-background, var(--vscode-editor-background)); + --text-primary: var(--vscode-editor-foreground); + --text-muted: var(--vscode-descriptionForeground); + --border-color: var(--vscode-panel-border, var(--vscode-editorGroup-border)); + --btn-bg: var(--vscode-button-secondaryBackground); + --btn-bg-hover: var(--vscode-button-secondaryHoverBackground); + --btn-text: var(--vscode-button-secondaryForeground); + --btn-primary-bg: var(--vscode-button-background); + --btn-primary-bg-hover: var(--vscode-button-hoverBackground); + --btn-primary-text: var(--vscode-button-foreground); + --tab-active-bg: var(--vscode-tab-activeBackground); + --tab-inactive-bg: var(--vscode-tab-inactiveBackground); + --tab-active-fg: var(--vscode-tab-activeForeground); + --tab-inactive-fg: var(--vscode-tab-inactiveForeground); + --tab-border: var(--vscode-tab-border); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + background-color: var(--bg-primary); + color: var(--text-primary); + height: 100vh; + overflow: hidden; +} + +.container { + display: flex; + flex-direction: column; + height: 100vh; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; + background-color: var(--bg-secondary); + border-top: 1px solid var(--border-color); + order: 1; +} + +.tabs { + display: flex; + gap: 0; +} + +.tab { + padding: 8px 16px; + border: none; + background-color: var(--tab-inactive-bg); + color: var(--tab-inactive-fg); + cursor: pointer; + font-size: 13px; + border-top: 2px solid transparent; + transition: all 0.2s ease; +} + +.tab:hover { + background-color: var(--tab-active-bg); +} + +.tab.active { + background-color: var(--tab-active-bg); + color: var(--tab-active-fg); + border-top-color: var(--btn-primary-bg); +} + +.view-toggle { + display: flex; + gap: 4px; +} + +.view-btn { + padding: 6px 12px; + border: 1px solid var(--border-color); + background-color: var(--btn-bg); + color: var(--btn-text); + cursor: pointer; + font-size: 12px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.view-btn:hover { + background-color: var(--btn-bg-hover); +} + +.view-btn.active { + background-color: var(--btn-primary-bg); + color: var(--btn-primary-text); + border-color: var(--btn-primary-bg); +} + +.content { + flex: 1; + overflow: auto; + padding: 16px; + min-height: 0; +} + +.tab-content { + display: none; + height: 100%; + overflow: hidden; +} + +.tab-content.active { + display: block; +} + +#diff-output { + overflow: auto; + height: 100%; +} + +#content-output { + width: 100%; + height: 100%; + overflow: auto; + padding: 8px 12px; + margin: 0; + font-family: var(--vscode-editor-font-family, 'Consolas', 'Courier New', monospace); + font-size: var(--vscode-editor-font-size, 14px); + line-height: var(--vscode-editor-line-height, 1.5); + background-color: var(--bg-primary); + color: var(--text-primary); + border: none; + white-space: pre; + tab-size: 4; + resize: none; + outline: none; +} + +/* Diff syntax highlighting - matching VS Code built-in editor */ +.diff-line-header { + color: var(--vscode-diffEditor-unchangedRegionForeground, #608b4e); +} +.diff-line-add { + color: var(--vscode-gitDecoration-addedResourceForeground, #89d185); + background-color: var(--vscode-diffEditor-insertedLineBackground, rgba(155, 185, 85, 0.2)); +} +.diff-line-delete { + color: var(--vscode-gitDecoration-deletedResourceForeground, #f14c4c); + background-color: var(--vscode-diffEditor-removedLineBackground, rgba(255, 0, 0, 0.2)); +} +.diff-line-meta { + color: var(--vscode-symbolIcon-functionForeground, #569cd6); +} +.diff-line-range { + color: var(--vscode-editorLineNumber-activeForeground, #c586c0); +} +.diff-line-index { + color: var(--vscode-textPreformat-foreground, #9cdcfe); +} + +/* Light theme overrides */ +body.vscode-light .diff-line-header { + color: #22863a; +} +body.vscode-light .diff-line-add { + color: #22863a; + background-color: rgba(46, 160, 67, 0.15); +} +body.vscode-light .diff-line-delete { + color: #cb2431; + background-color: rgba(248, 81, 73, 0.15); +} +body.vscode-light .diff-line-meta { + color: #0550ae; +} +body.vscode-light .diff-line-range { + color: #6f42c1; +} +body.vscode-light .diff-line-index { + color: #953800; +} + +.placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-muted); +} + +/* diff2html theme overrides for VS Code integration */ +.d2h-wrapper { + font-family: var(--vscode-editor-font-family, monospace); +} + +.d2h-file-header { + background-color: var(--bg-secondary); + border-color: var(--border-color); +} + +.d2h-file-name { + color: var(--text-primary); +} + +.d2h-file-wrapper { + border-color: var(--border-color); +} + +/* Light theme diff colors */ +body.vscode-light .d2h-del { + background-color: #ffeef0; +} + +body.vscode-light .d2h-ins { + background-color: #e6ffec; +} + +body.vscode-light .d2h-info { + background-color: #f1f8ff; + color: #0366d6; +} + +body.vscode-light .d2h-code-line-ctn, +body.vscode-light .d2h-code-side-line { + background-color: var(--bg-primary); + color: var(--text-primary); +} + +body.vscode-light .d2h-code-linenumber, +body.vscode-light .d2h-code-side-linenumber { + background-color: var(--bg-secondary); + color: var(--text-muted); + border-color: var(--border-color); +} + +body.vscode-light .d2h-del .d2h-code-line-ctn, +body.vscode-light .d2h-del .d2h-code-side-line { + background-color: #ffeef0; +} + +body.vscode-light .d2h-ins .d2h-code-line-ctn, +body.vscode-light .d2h-ins .d2h-code-side-line { + background-color: #e6ffec; +} + +/* Dark theme diff colors */ +body.vscode-dark .d2h-del, +body.vscode-high-contrast .d2h-del { + background-color: #3d1d26; +} + +body.vscode-dark .d2h-ins, +body.vscode-high-contrast .d2h-ins { + background-color: #1f3d2a; +} + +body.vscode-dark .d2h-info, +body.vscode-high-contrast .d2h-info { + background-color: #1f2937; + color: #93c5fd; + border-color: var(--border-color); +} + +body.vscode-dark .d2h-code-line-ctn, +body.vscode-dark .d2h-code-side-line, +body.vscode-high-contrast .d2h-code-line-ctn, +body.vscode-high-contrast .d2h-code-side-line { + background-color: var(--bg-primary); + color: var(--text-primary); +} + +body.vscode-dark .d2h-code-linenumber, +body.vscode-dark .d2h-code-side-linenumber, +body.vscode-high-contrast .d2h-code-linenumber, +body.vscode-high-contrast .d2h-code-side-linenumber { + background-color: var(--bg-secondary); + color: var(--text-muted); + border-color: var(--border-color); +} + +body.vscode-dark .d2h-del .d2h-code-line-ctn, +body.vscode-dark .d2h-del .d2h-code-side-line, +body.vscode-high-contrast .d2h-del .d2h-code-line-ctn, +body.vscode-high-contrast .d2h-del .d2h-code-side-line { + background-color: #3d1d26; +} + +body.vscode-dark .d2h-ins .d2h-code-line-ctn, +body.vscode-dark .d2h-ins .d2h-code-side-line, +body.vscode-high-contrast .d2h-ins .d2h-code-line-ctn, +body.vscode-high-contrast .d2h-ins .d2h-code-side-line { + background-color: #1f3d2a; +} + +body.vscode-dark .d2h-emptyplaceholder, +body.vscode-dark .d2h-code-side-emptyplaceholder, +body.vscode-high-contrast .d2h-emptyplaceholder, +body.vscode-high-contrast .d2h-code-side-emptyplaceholder { + background-color: var(--bg-secondary); + border-color: var(--border-color); +} + +body.vscode-dark .d2h-file-list-wrapper, +body.vscode-high-contrast .d2h-file-list-wrapper { + background-color: var(--bg-secondary); + border-color: var(--border-color); +} + +body.vscode-dark .d2h-file-list-header, +body.vscode-high-contrast .d2h-file-list-header { + background-color: var(--bg-secondary); +} + +body.vscode-dark .d2h-file-list-title, +body.vscode-high-contrast .d2h-file-list-title { + color: var(--text-primary); +} + +body.vscode-dark .d2h-file-list li, +body.vscode-high-contrast .d2h-file-list li { + border-color: var(--border-color); +} + +/* Unified (line-by-line) view specific styles */ +body.vscode-dark .d2h-file-diff .d2h-del, +body.vscode-high-contrast .d2h-file-diff .d2h-del { + background-color: rgba(248, 81, 73, 0.25); +} + +body.vscode-dark .d2h-file-diff .d2h-ins, +body.vscode-high-contrast .d2h-file-diff .d2h-ins { + background-color: rgba(63, 185, 80, 0.25); +} + +body.vscode-dark .d2h-file-diff .d2h-del .d2h-code-line-ctn, +body.vscode-high-contrast .d2h-file-diff .d2h-del .d2h-code-line-ctn { + background-color: rgba(248, 81, 73, 0.25); +} + +body.vscode-dark .d2h-file-diff .d2h-ins .d2h-code-line-ctn, +body.vscode-high-contrast .d2h-file-diff .d2h-ins .d2h-code-line-ctn { + background-color: rgba(63, 185, 80, 0.25); +} + +body.vscode-light .d2h-file-diff .d2h-del { + background-color: #ffebe9; +} + +body.vscode-light .d2h-file-diff .d2h-ins { + background-color: #e6ffec; +} + +body.vscode-light .d2h-file-diff .d2h-del .d2h-code-line-ctn { + background-color: #ffebe9; +} + +body.vscode-light .d2h-file-diff .d2h-ins .d2h-code-line-ctn { + background-color: #e6ffec; +} + +/* Deletion marker color */ +body.vscode-dark .d2h-del .d2h-code-line-prefix, +body.vscode-high-contrast .d2h-del .d2h-code-line-prefix { + color: #f85149; +} + +/* Insertion marker color */ +body.vscode-dark .d2h-ins .d2h-code-line-prefix, +body.vscode-high-contrast .d2h-ins .d2h-code-line-prefix { + color: #3fb950; +} + +body.vscode-light .d2h-del .d2h-code-line-prefix { + color: #cf222e; +} + +body.vscode-light .d2h-ins .d2h-code-line-prefix { + color: #1a7f37; +} + +/* Viewed checkbox styles */ +.d2h-viewed-checkbox { + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + padding-right: 8px; + cursor: pointer; + font-size: 12px; + color: var(--text-muted); +} + +.d2h-viewed-checkbox input[type="checkbox"] { + cursor: pointer; +} + +.d2h-file-wrapper.viewed .d2h-file-diff, +.d2h-file-wrapper.viewed .d2h-files-diff { + display: none; +} + +.d2h-file-name-wrapper { + display: flex; + align-items: center; + width: 100%; +} + +/* Fix: Make line numbers sticky on the left when scrolling horizontally */ +/* Fix: Remove the gap between line numbers and content */ +.d2h-code-linenumber, +.d2h-code-side-linenumber { + position: sticky; + left: 0; + z-index: 1; + border-right: none; + padding-right: 0; + padding-left: 0; +} + +/* Fix: Adjust content padding to match line number width exactly */ +.d2h-code-linenumber { + width: 5em; +} + +.d2h-code-side-linenumber { + width: 2.5em; +} + +.d2h-code-line { + padding-left: 5em; + padding-right: 0; + width: calc(100% - 5em); +} + +.d2h-code-side-line { + padding-left: 2.5em; + padding-right: 0; + width: calc(100% - 2.5em); +} + +/* Fix: Remove padding from code line prefix to eliminate gap */ +.d2h-code-line-prefix { + padding: 0; + margin: 0; +} + +/* Fix: Ensure header tabs appear above sticky line numbers */ +.header { + position: relative; + z-index: 10; +} diff --git a/media/patchViewer.js b/media/patchViewer.js new file mode 100644 index 0000000..6652984 --- /dev/null +++ b/media/patchViewer.js @@ -0,0 +1,375 @@ +// Patch Reader webview client script. +// +// Loaded into the custom editor webview via webview.asWebviewUri(). Keeping this +// as a standalone static file (rather than inlining it in a TypeScript template +// literal) means the browser receives it verbatim, so escape sequences such as +// "\n" and "" can never be mangled by the surrounding literal. The +// diff2html library is loaded before this script and exposes a global Diff2Html. +// +// The initial patch content is read from a non-executable JSON data block +// (#patch-initial-content) that the extension injects into the page. +(function() { + const vscode = acquireVsCodeApi(); + + // Logging helpers — forward messages to the VS Code output channel + function logInfo(message) { + vscode.postMessage({ type: 'log', message: message }); + } + function logWarn(message) { + vscode.postMessage({ type: 'warn', message: message }); + } + function logError(message, error) { + vscode.postMessage({ type: 'error', message: message, error: error instanceof Error ? error.message : String(error || '') }); + } + + // Read the initial patch content that the extension embedded in a + // non-executable JSON data block. Using a data block (instead of inlining + // the content into executable JS) means malformed or ""-containing + // patches can never break the script. + function getInitialContent() { + const dataEl = document.getElementById('patch-initial-content'); + if (!dataEl) { + return ''; + } + try { + return JSON.parse(dataEl.textContent || '""'); + } catch (error) { + logError('Failed to parse initial patch content', error); + return ''; + } + } + + // DOM elements + const diffOutput = document.getElementById('diff-output'); + const contentOutput = document.getElementById('content-output'); + const tabContents = document.querySelectorAll('.tab-content'); + const tabs = document.querySelectorAll('.tab'); + const viewBtns = document.querySelectorAll('.view-btn'); + + if (!diffOutput) { + logError('Failed to find #diff-output element in DOM'); + } + if (!contentOutput) { + logError('Failed to find #content-output element in DOM'); + } + + // State + let currentContent = getInitialContent(); + let currentViewMode = 'side-by-side'; + let renderDebounceTimer = null; + + // Debounce helper + function debounce(fn, delay) { + return function(...args) { + if (renderDebounceTimer) { + clearTimeout(renderDebounceTimer); + } + renderDebounceTimer = setTimeout(() => fn.apply(this, args), delay); + }; + } + + // Debounced render for content changes + const debouncedRenderDiff = debounce(renderDiff, 300); + + // Initialize + function init() { + logInfo('Initializing patch viewer'); + bindEvents(); + renderDiff(); + updateContentTab(); + } + + // Bind events + function bindEvents() { + // Tab switching + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const targetTab = tab.dataset.tab; + switchTab(targetTab); + }); + }); + + // View mode switching + viewBtns.forEach(btn => { + btn.addEventListener('click', () => { + const viewMode = btn.dataset.view; + setViewMode(viewMode); + }); + }); + + // Content editing - send changes to VS Code + if (contentOutput) { + contentOutput.addEventListener('input', () => { + const newContent = contentOutput.value; + if (newContent !== currentContent) { + currentContent = newContent; + vscode.postMessage({ + type: 'contentChanged', + content: newContent + }); + debouncedRenderDiff(); + } + }); + } + + // Handle messages from extension + window.addEventListener('message', event => { + const message = event.data; + switch (message.type) { + case 'update': + logInfo('Document updated, re-rendering diff'); + currentContent = message.content; + renderDiff(); + updateContentTab(); + break; + case 'themeChanged': + logInfo('Theme changed to kind: ' + message.kind); + applyTheme(message.kind); + renderDiff(); + break; + case 'setViewMode': + logInfo('View mode set to: ' + message.viewMode); + setViewMode(message.viewMode, false); + break; + } + }); + } + + // Set view mode + function setViewMode(viewMode, notify = true) { + currentViewMode = viewMode; + viewBtns.forEach(btn => { + const isActive = btn.dataset.view === viewMode; + btn.classList.toggle('active', isActive); + btn.setAttribute('aria-pressed', isActive ? 'true' : 'false'); + }); + renderDiff(); + + if (notify) { + vscode.postMessage({ + type: 'viewModeChanged', + viewMode: viewMode + }); + } + } + + // Switch between tabs + function switchTab(targetTab) { + // Update tab buttons + tabs.forEach(tab => { + const isActive = tab.dataset.tab === targetTab; + tab.classList.toggle('active', isActive); + tab.setAttribute('aria-selected', isActive ? 'true' : 'false'); + }); + + // Update tab content visibility + tabContents.forEach(content => { + const isActive = content.id === targetTab + '-tab'; + content.classList.toggle('active', isActive); + }); + } + + // Update content tab with raw content + function updateContentTab() { + if (contentOutput) { + contentOutput.value = currentContent || ''; + } + } + + // Apply VS Code theme + function applyTheme(themeKind) { + // Remove existing theme classes + document.body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); + + // Add appropriate class based on theme kind + // ThemeKind: 1 = Light, 2 = Dark, 3 = HighContrast (Dark), 4 = HighContrast (Light) + switch (themeKind) { + case 1: + document.body.classList.add('vscode-light'); + break; + case 2: + document.body.classList.add('vscode-dark'); + break; + case 3: + document.body.classList.add('vscode-high-contrast'); + break; + case 4: + document.body.classList.add('vscode-light'); + break; + } + } + + // Setup synchronized horizontal scrolling for side-by-side view + function setupSynchronizedScroll() { + // Find all file wrappers with side-by-side diff + const fileWrappers = diffOutput.querySelectorAll('.d2h-file-wrapper'); + + fileWrappers.forEach(wrapper => { + const sidePanels = wrapper.querySelectorAll('.d2h-file-side-diff'); + if (sidePanels.length !== 2) return; + + const leftPanel = sidePanels[0]; + const rightPanel = sidePanels[1]; + let scrollSource = null; + + function syncScroll(source, target) { + if (scrollSource && scrollSource !== source) return; + scrollSource = source; + + requestAnimationFrame(() => { + target.scrollLeft = source.scrollLeft; + scrollSource = null; + }); + } + + leftPanel.addEventListener('scroll', function() { + syncScroll(this, rightPanel); + }); + + rightPanel.addEventListener('scroll', function() { + syncScroll(this, leftPanel); + }); + }); + } + + // Setup viewed checkbox functionality for each file + function setupViewedCheckboxes() { + const fileWrappers = diffOutput.querySelectorAll('.d2h-file-wrapper'); + fileWrappers.forEach((wrapper, index) => { + const header = wrapper.querySelector('.d2h-file-header'); + if (!header) return; + + // Check if checkbox already exists + if (header.querySelector('.d2h-viewed-checkbox')) return; + + // Get file name for accessibility + const fileNameEl = header.querySelector('.d2h-file-name'); + const fileName = fileNameEl ? fileNameEl.textContent : 'File ' + (index + 1); + const checkboxId = 'd2h-viewed-' + index; + + // Create viewed checkbox container + const checkboxContainer = document.createElement('label'); + checkboxContainer.className = 'd2h-viewed-checkbox'; + checkboxContainer.setAttribute('for', checkboxId); + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = checkboxId; + checkbox.setAttribute('aria-label', 'Mark ' + fileName + ' as viewed'); + checkbox.addEventListener('change', function() { + wrapper.classList.toggle('viewed', this.checked); + }); + + const labelText = document.createElement('span'); + labelText.textContent = 'Viewed'; + + checkboxContainer.appendChild(checkbox); + checkboxContainer.appendChild(labelText); + + // Insert checkbox into header + const fileNameWrapper = header.querySelector('.d2h-file-name-wrapper'); + if (fileNameWrapper) { + fileNameWrapper.appendChild(checkboxContainer); + } + }); + } + + // Strip git format-patch footer (a trailing "-- " line followed by a git version such as 2.43.0). + // This footer is added by git format-patch and should not be parsed as diff content. + function stripGitPatchFooter(content) { + if (!content) return content; + // Match the git email signature footer: a "-- " line on its own, + // immediately followed by a git version number (such as 2.43.0). + return content.replace(/\n-- \n\d+\.\d+[^\n]*\n?$/, '\n'); + } + + // Render diff using diff2html + function renderDiff() { + if (!diffOutput) { + logError('Cannot render diff: #diff-output element not found'); + return; + } + if (!currentContent || !currentContent.trim()) { + logInfo('No diff content to display'); + diffOutput.innerHTML = '
No diff content to display
'; + return; + } + + // Check if Diff2Html is available + const Diff2HtmlLib = typeof Diff2Html !== 'undefined' ? Diff2Html : window.Diff2Html; + if (!Diff2HtmlLib) { + const errorMsg = 'Diff2Html library is not loaded. Please reload the editor.'; + logError('Failed to render diff', 'Diff2Html is not defined — check that the extension assets are correctly installed'); + diffOutput.innerHTML = '
' + errorMsg + '
'; + return; + } + + try { + const outputFormat = currentViewMode === 'side-by-side' ? 'side-by-side' : 'line-by-line'; + + // Strip git format-patch footer before parsing + const contentToParse = stripGitPatchFooter(currentContent); + + // Parse the diff content + const diffJson = Diff2HtmlLib.parse(contentToParse, { + inputFormat: 'diff' + }); + + // Check if parsing produced any results + if (!diffJson || diffJson.length === 0) { + const errorMsg = 'Unable to parse diff content. Please check if the content is a valid diff/patch format.'; + logWarn('Diff2Html.parse returned an empty result for the provided content'); + diffOutput.innerHTML = '
' + errorMsg + '
'; + return; + } + + logInfo('Rendering diff: ' + diffJson.length + ' file(s), format=' + outputFormat); + + // Generate HTML from parsed diff + const html = Diff2HtmlLib.html(diffJson, { + inputFormat: 'json', + outputFormat: outputFormat, + showFiles: true, + matching: 'lines', + matchWordsThreshold: 0.25, + maxLineLengthHighlight: 10000, + renderNothingWhenEmpty: false, + fileListToggle: true, + fileListStartVisible: true, + fileContentToggle: true, + stickyFileHeaders: true + }); + + // Check if HTML output is empty + if (!html || html.trim() === '') { + logWarn('Diff2Html.html returned empty output for ' + diffJson.length + ' parsed file(s)'); + diffOutput.innerHTML = '
Failed to generate diff view. The content could not be rendered.
'; + return; + } + + diffOutput.innerHTML = html; + + // Setup synchronized scrolling for side-by-side view + if (currentViewMode === 'side-by-side') { + setupSynchronizedScroll(); + } + + // Add viewed checkbox functionality + setupViewedCheckboxes(); + } catch (error) { + const errorMessage = 'An error occurred while rendering the diff. Please check if the content is a valid diff/patch format.'; + logError('Failed to render diff', error); + diffOutput.innerHTML = '
' + errorMessage + '
'; + } + } + + // Initialize + try { + init(); + } catch (error) { + logError('Fatal error during initialization', error); + if (diffOutput) { + diffOutput.innerHTML = '
Failed to initialize the viewer. Please close and reopen the file.
'; + } + } +})(); diff --git a/src/patchEditorProvider.ts b/src/patchEditorProvider.ts index 46d93d9..619cd94 100644 --- a/src/patchEditorProvider.ts +++ b/src/patchEditorProvider.ts @@ -119,10 +119,12 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { } const diff2htmlAssetRoot = vscode.Uri.file(diff2htmlAssetDirectory); + const mediaRoot = vscode.Uri.joinPath(this.context.extensionUri, 'media'); webviewPanel.webview.options = { enableScripts: true, localResourceRoots: [ + mediaRoot, diff2htmlAssetRoot ] }; @@ -135,12 +137,23 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { vscode.Uri.joinPath(diff2htmlAssetRoot, 'js', 'diff2html.min.js') ); + // Get URIs for the extension's own webview assets (kept as standalone + // static files so the browser receives them verbatim). + const patchViewerCssUri = webviewPanel.webview.asWebviewUri( + vscode.Uri.joinPath(mediaRoot, 'patchViewer.css') + ); + const patchViewerJsUri = webviewPanel.webview.asWebviewUri( + vscode.Uri.joinPath(mediaRoot, 'patchViewer.js') + ); + // Set initial HTML content webviewPanel.webview.html = this.getHtmlForWebview( webviewPanel.webview, document.getText(), diff2htmlCssUri, - diff2htmlJsUri + diff2htmlJsUri, + patchViewerCssUri, + patchViewerJsUri ); // Handle messages from the webview @@ -224,21 +237,17 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { webview: vscode.Webview, content: string, diff2htmlCssUri: vscode.Uri, - diff2htmlJsUri: vscode.Uri + diff2htmlJsUri: vscode.Uri, + patchViewerCssUri: vscode.Uri, + patchViewerJsUri: vscode.Uri ): string { const nonce = getNonce(); - // Safely embed content as a JS string literal inside a " - // the HTML parser will close the ", so the HTML parser cannot + // close the data block early — regardless of what the patch contains. + const initialContentJson = JSON.stringify(content).replace(/ @@ -248,464 +257,8 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { + Patch Reader -
@@ -729,359 +282,9 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider {
+ - + `; diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 5fd8bbc..78b61c0 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -73,8 +73,29 @@ suite('Extension Test Suite', () => { } }); - test('Generated webview script should be valid JavaScript', () => { - const provider = new PatchEditorProvider({ subscriptions: [] } as any); + test('Webview client script file should be valid JavaScript', () => { + // The webview client script is now a standalone static file loaded via + // asWebviewUri(), so it is delivered to the browser verbatim. Validate + // that the shipped file parses as JavaScript. + const extension = vscode.extensions.getExtension('unknowIfGuestInDream.tlcsdm-patch-reader'); + assert.ok(extension, 'Extension should be present'); + const scriptPath = path.join(extension.extensionPath, 'media', 'patchViewer.js'); + assert.ok(fs.existsSync(scriptPath), 'media/patchViewer.js should exist'); + const scriptBody = fs.readFileSync(scriptPath, 'utf8'); + + // new Function only parses (does not execute) the body, so browser globals + // are not required. A malformed script would throw a SyntaxError here. + assert.doesNotThrow( + () => new Function(scriptBody), + 'Webview client script must be syntactically valid JavaScript' + ); + }); + + test('Initial patch content should be embedded safely and round-trip', () => { + const provider = new PatchEditorProvider({ + extensionUri: vscode.Uri.file('/ext'), + subscriptions: [] + } as any); const panel = vscode.window.createWebviewPanel( 'tlcsdm.patchReader.test', 'Patch Reader Test', @@ -84,31 +105,53 @@ suite('Extension Test Suite', () => { try { const cssUri = panel.webview.asWebviewUri(vscode.Uri.file('/assets/diff2html.min.css')); const jsUri = panel.webview.asWebviewUri(vscode.Uri.file('/assets/diff2html.min.js')); - // Content that would break naive inline embedding, to also exercise the escaping - // (a diffed ', - '+', + '+', '-- ', '2.43.0', '' ].join('\n'); - const html = (provider as any).getHtmlForWebview(panel.webview, trickyContent, cssUri, jsUri); + const html = (provider as any).getHtmlForWebview( + panel.webview, + trickyContent, + cssUri, + jsUri, + viewerCssUri, + viewerJsUri + ); + + // The client script must be referenced as an external file, not inlined. + assert.ok( + /]*src="[^"]*patchViewer\.js"[^>]*><\/script>/.test(html), + 'Webview HTML should load the external patchViewer.js script' + ); - // Extract the inline application script (the nonce " from + // the patch content, otherwise the HTML parser would close it early. + const dataMatch = html.match( + / sequence' + ); - // new Function only parses (does not execute) the body, so browser globals are not required. - // A malformed script — e.g. a comment or string literal broken by an unescaped newline — - // would throw a SyntaxError here. - assert.doesNotThrow( - () => new Function(scriptBody), - 'Inline webview script must be syntactically valid JavaScript' + // The data block must be valid JSON that round-trips to the original content. + assert.strictEqual( + JSON.parse(dataBlock), + trickyContent, + 'Embedded content must round-trip back to the original patch text' ); } finally { panel.dispose(); From 5dc1bda9351029140ba88287f0cbcc86cfe05ac3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:02:35 +0000 Subject: [PATCH 07/10] refactor(webview): drop misleading nonce on JSON data block; clarify test Remove the nonce from the non-executable `type="application/json"` initial-content block (CSP script-src does not apply to data blocks) and document why. Clarify the test comment explaining why the "<" -> "\u003c" escaping makes the extraction regex safe against "" payloads. Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com> --- src/patchEditorProvider.ts | 4 +++- src/test/extension.test.ts | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/patchEditorProvider.ts b/src/patchEditorProvider.ts index 619cd94..37f4b46 100644 --- a/src/patchEditorProvider.ts +++ b/src/patchEditorProvider.ts @@ -244,6 +244,8 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { const nonce = getNonce(); // Embed the initial patch content in a non-executable JSON data block. + // A `type="application/json"` block is never executed, so the CSP + // `script-src` directive does not apply to it and it needs no nonce. // Escaping every "<" as "\u003c" keeps the JSON valid while guaranteeing // the raw text can never contain "", so the HTML parser cannot // close the data block early — regardless of what the patch contains. @@ -282,7 +284,7 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { - + diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 78b61c0..6ccabe2 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -135,8 +135,10 @@ suite('Extension Test Suite', () => { 'Webview HTML should load the external patchViewer.js script' ); - // The embedded JSON data block must not contain a raw "" from - // the patch content, otherwise the HTML parser would close it early. + // Because getHtmlForWebview escapes every "<" as "\u003c", the patch's + // own "" becomes "\u003c/script>" in the data block. The lazy + // regex terminator below is therefore only matched by the block's real + // closing tag, and the data block itself contains no raw "". const dataMatch = html.match( / sequence' ); - // The data block must be valid JSON that round-trips to the original content. + // The data block must be valid JSON that round-trips to the original + // content exactly, proving the escaping preserves the patch verbatim. assert.strictEqual( JSON.parse(dataBlock), trickyContent, From 2497d57585eb586dfbcc4e3d76f7464e61b55948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=A6=E9=87=8C=E4=B8=8D=E7=9F=A5=E8=BA=AB=E6=98=AF?= =?UTF-8?q?=E5=AE=A2?= Date: Sat, 8 Aug 2026 17:05:07 +0800 Subject: [PATCH 08/10] Potential fix for pull request finding 'CodeQL / Missing origin verification in `postMessage` handler' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: 梦里不知身是客 --- media/patchViewer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/media/patchViewer.js b/media/patchViewer.js index 6652984..a5e140d 100644 --- a/media/patchViewer.js +++ b/media/patchViewer.js @@ -114,6 +114,12 @@ // Handle messages from extension window.addEventListener('message', event => { + const trustedOrigin = window.location.origin; + if (event.origin !== trustedOrigin) { + logWarn('Ignored message from untrusted origin: ' + event.origin); + return; + } + const message = event.data; switch (message.type) { case 'update': From 23a38576fd89d3f3eaf58daf46a0ecf410623de0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:25:43 +0000 Subject: [PATCH 09/10] fix(webview): vendor diff2html assets so the viewer always renders Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com> --- .gitignore | 1 - media/diff2html/css/diff2html.min.css | 1 + media/diff2html/js/diff2html.min.js | 1 + package-lock.json | 570 ++++++++++++++++++++++++++ package.json | 4 +- src/test/extension.test.ts | 94 +++++ 6 files changed, 669 insertions(+), 2 deletions(-) create mode 100644 media/diff2html/css/diff2html.min.css create mode 100644 media/diff2html/js/diff2html.min.js diff --git a/.gitignore b/.gitignore index 177824c..8b654ef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ node_modules/ out/ dist/ *.vsix -media/diff2html/ # IDE .vscode-test/ diff --git a/media/diff2html/css/diff2html.min.css b/media/diff2html/css/diff2html.min.css new file mode 100644 index 0000000..8d572e0 --- /dev/null +++ b/media/diff2html/css/diff2html.min.css @@ -0,0 +1 @@ +:host,:root{--d2h-bg-color:#fff;--d2h-border-color:#ddd;--d2h-dim-color:rgba(0,0,0,.3);--d2h-line-border-color:#eee;--d2h-file-header-bg-color:#f7f7f7;--d2h-file-header-border-color:#d8d8d8;--d2h-empty-placeholder-bg-color:#f1f1f1;--d2h-empty-placeholder-border-color:#e1e1e1;--d2h-selected-color:#c8e1ff;--d2h-ins-bg-color:#dfd;--d2h-ins-border-color:#b4e2b4;--d2h-ins-highlight-bg-color:#97f295;--d2h-ins-label-color:#399839;--d2h-del-bg-color:#fee8e9;--d2h-del-border-color:#e9aeae;--d2h-del-highlight-bg-color:#ffb6ba;--d2h-del-label-color:#c33;--d2h-change-del-color:#fdf2d0;--d2h-change-ins-color:#ded;--d2h-info-bg-color:#f8fafd;--d2h-info-border-color:#d5e4f2;--d2h-change-label-color:#d0b44c;--d2h-moved-label-color:#3572b0;--d2h-dark-color:#e6edf3;--d2h-dark-bg-color:#0d1117;--d2h-dark-border-color:#30363d;--d2h-dark-dim-color:#6e7681;--d2h-dark-line-border-color:#21262d;--d2h-dark-file-header-bg-color:#161b22;--d2h-dark-file-header-border-color:#30363d;--d2h-dark-empty-placeholder-bg-color:hsla(215,8%,47%,.1);--d2h-dark-empty-placeholder-border-color:#30363d;--d2h-dark-selected-color:rgba(56,139,253,.1);--d2h-dark-ins-bg-color:rgba(46,160,67,.15);--d2h-dark-ins-border-color:rgba(46,160,67,.4);--d2h-dark-ins-highlight-bg-color:rgba(46,160,67,.4);--d2h-dark-ins-label-color:#3fb950;--d2h-dark-del-bg-color:rgba(248,81,73,.1);--d2h-dark-del-border-color:rgba(248,81,73,.4);--d2h-dark-del-highlight-bg-color:rgba(248,81,73,.4);--d2h-dark-del-label-color:#f85149;--d2h-dark-change-del-color:rgba(210,153,34,.2);--d2h-dark-change-ins-color:rgba(46,160,67,.25);--d2h-dark-info-bg-color:rgba(56,139,253,.1);--d2h-dark-info-border-color:rgba(56,139,253,.4);--d2h-dark-change-label-color:#d29922;--d2h-dark-moved-label-color:#3572b0}.d2h-wrapper{text-align:left}.d2h-file-header{background-color:#f7f7f7;background-color:var(--d2h-file-header-bg-color);border-bottom:1px solid #d8d8d8;border-bottom:1px solid var(--d2h-file-header-border-color);display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;height:35px;padding:5px 10px}.d2h-file-header.d2h-sticky-header{position:sticky;top:0;z-index:1}.d2h-file-stats{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:14px;margin-left:auto}.d2h-lines-added{border:1px solid #b4e2b4;border:1px solid var(--d2h-ins-border-color);border-radius:5px 0 0 5px;color:#399839;color:var(--d2h-ins-label-color);padding:2px;text-align:right;vertical-align:middle}.d2h-lines-deleted{border:1px solid #e9aeae;border:1px solid var(--d2h-del-border-color);border-radius:0 5px 5px 0;color:#c33;color:var(--d2h-del-label-color);margin-left:1px;padding:2px;text-align:left;vertical-align:middle}.d2h-file-name-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px;width:100%}.d2h-file-name{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.d2h-file-wrapper{border:1px solid #ddd;border:1px solid var(--d2h-border-color);border-radius:3px;margin-bottom:1em}.d2h-file-collapse{-webkit-box-pack:end;-ms-flex-pack:end;cursor:pointer;display:none;font-size:12px;justify-content:flex-end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;border:1px solid var(--d2h-border-color);border-radius:3px;padding:4px 8px}.d2h-file-collapse.d2h-selected{background-color:#c8e1ff;background-color:var(--d2h-selected-color)}.d2h-file-collapse-input{margin:0 4px 0 0}.d2h-diff-table{border-collapse:collapse;font-family:Menlo,Consolas,monospace;font-size:13px;width:100%}.d2h-files-diff{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.d2h-file-diff{overflow-y:hidden}.d2h-file-diff.d2h-d-none,.d2h-files-diff.d2h-d-none{display:none}.d2h-file-side-diff{display:inline-block;overflow-x:scroll;overflow-y:hidden;width:50%}.d2h-code-line{padding:0 8em;width:calc(100% - 16em)}.d2h-code-line,.d2h-code-side-line{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}.d2h-code-side-line{padding:0 4.5em;width:calc(100% - 9em)}.d2h-code-line-ctn{background:none;display:inline-block;padding:0;word-wrap:normal;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;vertical-align:middle;white-space:pre;width:100%}.d2h-code-line del,.d2h-code-side-line del{background-color:#ffb6ba;background-color:var(--d2h-del-highlight-bg-color)}.d2h-code-line del,.d2h-code-line ins,.d2h-code-side-line del,.d2h-code-side-line ins{border-radius:.2em;display:inline-block;margin-top:-1px;-webkit-text-decoration:none;text-decoration:none}.d2h-code-line ins,.d2h-code-side-line ins{background-color:#97f295;background-color:var(--d2h-ins-highlight-bg-color);text-align:left}.d2h-code-line-prefix{background:none;display:inline;padding:0;word-wrap:normal;white-space:pre}.line-num1{float:left}.line-num1,.line-num2{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding:0 .5em;text-overflow:ellipsis;width:3.5em}.line-num2{float:right}.d2h-code-linenumber{background-color:#fff;background-color:var(--d2h-bg-color);border:solid #eee;border:solid var(--d2h-line-border-color);border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);color:var(--d2h-dim-color);cursor:pointer;display:inline-block;position:absolute;text-align:right;width:7.5em}.d2h-code-linenumber:after{content:"\200b"}.d2h-code-side-linenumber{background-color:#fff;background-color:var(--d2h-bg-color);border:solid #eee;border:solid var(--d2h-line-border-color);border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);color:var(--d2h-dim-color);cursor:pointer;display:inline-block;overflow:hidden;padding:0 .5em;position:absolute;text-align:right;text-overflow:ellipsis;width:4em}.d2h-code-side-linenumber:after{content:"\200b"}.d2h-code-side-emptyplaceholder,.d2h-emptyplaceholder{background-color:#f1f1f1;background-color:var(--d2h-empty-placeholder-bg-color);border-color:#e1e1e1;border-color:var(--d2h-empty-placeholder-border-color)}.d2h-code-line-prefix,.d2h-code-linenumber,.d2h-code-side-linenumber,.d2h-emptyplaceholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.d2h-code-linenumber,.d2h-code-side-linenumber{direction:rtl}.d2h-del{background-color:#fee8e9;background-color:var(--d2h-del-bg-color);border-color:#e9aeae;border-color:var(--d2h-del-border-color)}.d2h-ins{background-color:#dfd;background-color:var(--d2h-ins-bg-color);border-color:#b4e2b4;border-color:var(--d2h-ins-border-color)}.d2h-info{background-color:#f8fafd;background-color:var(--d2h-info-bg-color);border-color:#d5e4f2;border-color:var(--d2h-info-border-color);color:rgba(0,0,0,.3);color:var(--d2h-dim-color)}.d2h-file-diff .d2h-del.d2h-change{background-color:#fdf2d0;background-color:var(--d2h-change-del-color)}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded;background-color:var(--d2h-change-ins-color)}.d2h-file-list-wrapper{margin-bottom:10px}.d2h-file-list-wrapper a{-webkit-text-decoration:none;text-decoration:none}.d2h-file-list-wrapper a,.d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-moved-label-color)}.d2h-file-list-header{text-align:left}.d2h-file-list-title{font-weight:700}.d2h-file-list-line{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:left}.d2h-file-list{display:block;list-style:none;margin:0;padding:0}.d2h-file-list>li{border-bottom:1px solid #ddd;border-bottom:1px solid var(--d2h-border-color);margin:0;padding:5px 10px}.d2h-file-list>li:last-child{border-bottom:none}.d2h-file-switch{cursor:pointer;display:none;font-size:10px}.d2h-icon{fill:currentColor;margin-right:10px;vertical-align:middle}.d2h-deleted{color:#c33;color:var(--d2h-del-label-color)}.d2h-added{color:#399839;color:var(--d2h-ins-label-color)}.d2h-changed{color:#d0b44c;color:var(--d2h-change-label-color)}.d2h-moved{color:#3572b0;color:var(--d2h-moved-label-color)}.d2h-tag{background-color:#fff;background-color:var(--d2h-bg-color);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:10px;margin-left:5px;padding:0 2px}.d2h-deleted-tag{border:1px solid #c33;border:1px solid var(--d2h-del-label-color)}.d2h-added-tag{border:1px solid #399839;border:1px solid var(--d2h-ins-label-color)}.d2h-changed-tag{border:1px solid #d0b44c;border:1px solid var(--d2h-change-label-color)}.d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-moved-label-color)}.d2h-dark-color-scheme{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);color:#e6edf3;color:var(--d2h-dark-color)}.d2h-dark-color-scheme .d2h-file-header{background-color:#161b22;background-color:var(--d2h-dark-file-header-bg-color);border-bottom:#30363d;border-bottom:var(--d2h-dark-file-header-border-color)}.d2h-dark-color-scheme .d2h-lines-added{border:1px solid rgba(46,160,67,.4);border:1px solid var(--d2h-dark-ins-border-color);color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-lines-deleted{border:1px solid rgba(248,81,73,.4);border:1px solid var(--d2h-dark-del-border-color);color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-code-line del,.d2h-dark-color-scheme .d2h-code-side-line del{background-color:rgba(248,81,73,.4);background-color:var(--d2h-dark-del-highlight-bg-color)}.d2h-dark-color-scheme .d2h-code-line ins,.d2h-dark-color-scheme .d2h-code-side-line ins{background-color:rgba(46,160,67,.4);background-color:var(--d2h-dark-ins-highlight-bg-color)}.d2h-dark-color-scheme .d2h-diff-tbody{border-color:#30363d;border-color:var(--d2h-dark-border-color)}.d2h-dark-color-scheme .d2h-code-side-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,.d2h-dark-color-scheme .d2h-files-diff .d2h-emptyplaceholder{background-color:hsla(215,8%,47%,.1);background-color:var(--d2h-dark-empty-placeholder-bg-color);border-color:#30363d;border-color:var(--d2h-dark-empty-placeholder-border-color)}.d2h-dark-color-scheme .d2h-code-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-del{background-color:rgba(248,81,73,.1);background-color:var(--d2h-dark-del-bg-color);border-color:rgba(248,81,73,.4);border-color:var(--d2h-dark-del-border-color)}.d2h-dark-color-scheme .d2h-ins{background-color:rgba(46,160,67,.15);background-color:var(--d2h-dark-ins-bg-color);border-color:rgba(46,160,67,.4);border-color:var(--d2h-dark-ins-border-color)}.d2h-dark-color-scheme .d2h-info{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-info-bg-color);border-color:rgba(56,139,253,.4);border-color:var(--d2h-dark-info-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-file-diff .d2h-del.d2h-change{background-color:rgba(210,153,34,.2);background-color:var(--d2h-dark-change-del-color)}.d2h-dark-color-scheme .d2h-file-diff .d2h-ins.d2h-change{background-color:rgba(46,160,67,.25);background-color:var(--d2h-dark-change-ins-color)}.d2h-dark-color-scheme .d2h-file-wrapper{border:1px solid #30363d;border:1px solid var(--d2h-dark-border-color)}.d2h-dark-color-scheme .d2h-file-collapse{border:1px solid #0d1117;border:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-file-collapse.d2h-selected{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-selected-color)}.d2h-dark-color-scheme .d2h-file-list-wrapper a,.d2h-dark-color-scheme .d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-dark-color-scheme .d2h-file-list>li{border-bottom:1px solid #0d1117;border-bottom:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted{color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-added{color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-changed{color:#d29922;color:var(--d2h-dark-change-label-color)}.d2h-dark-color-scheme .d2h-moved{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-dark-color-scheme .d2h-tag{background-color:#0d1117;background-color:var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted-tag{border:1px solid #f85149;border:1px solid var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-added-tag{border:1px solid #3fb950;border:1px solid var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-changed-tag{border:1px solid #d29922;border:1px solid var(--d2h-dark-change-label-color)}.d2h-dark-color-scheme .d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-dark-moved-label-color)}@media (prefers-color-scheme:dark){.d2h-auto-color-scheme{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);color:#e6edf3;color:var(--d2h-dark-color)}.d2h-auto-color-scheme .d2h-file-header{background-color:#161b22;background-color:var(--d2h-dark-file-header-bg-color);border-bottom:#30363d;border-bottom:var(--d2h-dark-file-header-border-color)}.d2h-auto-color-scheme .d2h-lines-added{border:1px solid rgba(46,160,67,.4);border:1px solid var(--d2h-dark-ins-border-color);color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-lines-deleted{border:1px solid rgba(248,81,73,.4);border:1px solid var(--d2h-dark-del-border-color);color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-code-line del,.d2h-auto-color-scheme .d2h-code-side-line del{background-color:rgba(248,81,73,.4);background-color:var(--d2h-dark-del-highlight-bg-color)}.d2h-auto-color-scheme .d2h-code-line ins,.d2h-auto-color-scheme .d2h-code-side-line ins{background-color:rgba(46,160,67,.4);background-color:var(--d2h-dark-ins-highlight-bg-color)}.d2h-auto-color-scheme .d2h-diff-tbody{border-color:#30363d;border-color:var(--d2h-dark-border-color)}.d2h-auto-color-scheme .d2h-code-side-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,.d2h-auto-color-scheme .d2h-files-diff .d2h-emptyplaceholder{background-color:hsla(215,8%,47%,.1);background-color:var(--d2h-dark-empty-placeholder-bg-color);border-color:#30363d;border-color:var(--d2h-dark-empty-placeholder-border-color)}.d2h-auto-color-scheme .d2h-code-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-del{background-color:rgba(248,81,73,.1);background-color:var(--d2h-dark-del-bg-color);border-color:rgba(248,81,73,.4);border-color:var(--d2h-dark-del-border-color)}.d2h-auto-color-scheme .d2h-ins{background-color:rgba(46,160,67,.15);background-color:var(--d2h-dark-ins-bg-color);border-color:rgba(46,160,67,.4);border-color:var(--d2h-dark-ins-border-color)}.d2h-auto-color-scheme .d2h-info{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-info-bg-color);border-color:rgba(56,139,253,.4);border-color:var(--d2h-dark-info-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-file-diff .d2h-del.d2h-change{background-color:rgba(210,153,34,.2);background-color:var(--d2h-dark-change-del-color)}.d2h-auto-color-scheme .d2h-file-diff .d2h-ins.d2h-change{background-color:rgba(46,160,67,.25);background-color:var(--d2h-dark-change-ins-color)}.d2h-auto-color-scheme .d2h-file-wrapper{border:1px solid #30363d;border:1px solid var(--d2h-dark-border-color)}.d2h-auto-color-scheme .d2h-file-collapse{border:1px solid #0d1117;border:1px solid var(--d2h-dark-bg-color)}.d2h-auto-color-scheme .d2h-file-collapse.d2h-selected{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-selected-color)}.d2h-auto-color-scheme .d2h-file-list-wrapper a,.d2h-auto-color-scheme .d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-auto-color-scheme .d2h-file-list>li{border-bottom:1px solid #0d1117;border-bottom:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted{color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-added{color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-changed{color:#d29922;color:var(--d2h-dark-change-label-color)}.d2h-auto-color-scheme .d2h-moved{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-auto-color-scheme .d2h-tag{background-color:#0d1117;background-color:var(--d2h-dark-bg-color)}.d2h-auto-color-scheme .d2h-deleted-tag{border:1px solid #f85149;border:1px solid var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-added-tag{border:1px solid #3fb950;border:1px solid var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-changed-tag{border:1px solid #d29922;border:1px solid var(--d2h-dark-change-label-color)}.d2h-auto-color-scheme .d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-dark-moved-label-color)}} \ No newline at end of file diff --git a/media/diff2html/js/diff2html.min.js b/media/diff2html/js/diff2html.min.js new file mode 100644 index 0000000..1085451 --- /dev/null +++ b/media/diff2html/js/diff2html.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Diff2Html",[],t):"object"==typeof exports?exports.Diff2Html=t():e.Diff2Html=t()}(this,(()=>{return e={25(e,t,n){"use strict";var r,i=this&&this.__extends||(r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0}),t.wordsWithSpaceDiff=t.wordDiff=void 0,t.diffWords=function(e,n,r){return null==(null==r?void 0:r.ignoreWhitespace)||r.ignoreWhitespace?t.wordDiff.diff(e,n,r):d(e,n,r)},t.diffWordsWithSpace=d;var o=n(188),s=n(665),a="a-zA-Z0-9_\\u{AD}\\u{C0}-\\u{D6}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}",l=new RegExp("[".concat(a,"]+|\\s+|[^").concat(a,"]"),"ug"),c=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return i(t,e),t.prototype.equals=function(e,t,n){return n.ignoreCase&&(e=e.toLowerCase(),t=t.toLowerCase()),e.trim()===t.trim()},t.prototype.tokenize=function(e,t){var n;if(void 0===t&&(t={}),t.intlSegmenter){var r=t.intlSegmenter;if("word"!=r.resolvedOptions().granularity)throw new Error('The segmenter passed must have a granularity of "word"');n=[];for(var i=0,o=Array.from(r.segment(e));i{let t;return t=e.blocks.length?this.generateFileHtml(e):this.generateEmptyDiff(),this.makeFileDiffHtml(e,t)})).join("\n");return this.hoganUtils.render(f,"wrapper",{colorScheme:l.colorSchemeToCss(this.config.colorScheme),content:t})}makeFileDiffHtml(e,t){if(this.config.renderNothingWhenEmpty&&Array.isArray(e.blocks)&&0===e.blocks.length)return"";const n=this.hoganUtils.template("side-by-side","file-diff"),r=this.hoganUtils.template(f,"file-path"),i=this.hoganUtils.template("icon","file"),o=this.hoganUtils.template("tag",l.getFileIcon(e));return n.render({file:e,fileHtmlId:l.getHtmlId(e),diffs:t,filePath:r.render({fileDiffName:l.filenameDiff(e)},{fileIcon:i,fileTag:o})})}generateEmptyDiff(){return{right:"",left:this.hoganUtils.render(f,"empty-diff",{contentClass:"d2h-code-side-line",CSSLineClass:l.CSSLineClass})}}generateFileHtml(e){const t=a.newMatcherFn(a.newDistanceFn((t=>l.deconstructLine(t.content,e.isCombined).content)));return e.blocks.map((n=>{const r={left:this.makeHeaderHtml(n.header,e),right:this.makeHeaderHtml("")};return this.applyLineGroupping(n).forEach((([n,i,o])=>{if(i.length&&o.length&&!n.length)this.applyRematchMatching(i,o,t).map((([t,n])=>{const{left:i,right:o}=this.processChangedLines(e.isCombined,t,n);r.left+=i,r.right+=o}));else if(n.length)n.forEach((t=>{const{prefix:n,content:i}=l.deconstructLine(t.content,e.isCombined),{left:o,right:s}=this.generateLineHtml({type:l.CSSLineClass.CONTEXT,prefix:n,content:i,number:t.oldNumber},{type:l.CSSLineClass.CONTEXT,prefix:n,content:i,number:t.newNumber});r.left+=o,r.right+=s}));else if(i.length||o.length){const{left:t,right:n}=this.processChangedLines(e.isCombined,i,o);r.left+=t,r.right+=n}else console.error("Unknown state reached while processing groups of lines",n,i,o)})),r})).reduce(((e,t)=>({left:e.left+t.left,right:e.right+t.right})),{left:"",right:""})}applyLineGroupping(e){const t=[];let n=[],r=[];for(let i=0;i0)&&(t.push([[],n,r]),n=[],r=[]),o.type===c.LineType.CONTEXT?t.push([[o],[],[]]):o.type===c.LineType.INSERT&&0===n.length?t.push([[],[],[o]]):o.type===c.LineType.INSERT&&n.length>0?r.push(o):o.type===c.LineType.DELETE&&n.push(o)}return(n.length||r.length)&&(t.push([[],n,r]),n=[],r=[]),t}applyRematchMatching(e,t,n){const r=e.length*t.length,i=(0,u.max)(e.concat(t).map((e=>e.content.length)));return r{const r=a.compile(n,{asString:!1});return Object.assign(Object.assign({},e),{[t]:r})}),{});this.preCompiledTemplates=Object.assign(Object.assign(Object.assign({},l.defaultTemplates),e),n)}static compile(e){return a.compile(e,{asString:!1})}render(e,t,n,r,i){const o=this.templateKey(e,t);try{return this.preCompiledTemplates[o].render(n,r,i)}catch(e){throw new Error(`Could not find template to render '${o}'`)}}template(e,t){return this.preCompiledTemplates[this.templateKey(e,t)]}templateKey(e,t){return`${e}-${t}`}}},185(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.escapeForRegExp=function(e){return e.replace(n,"\\$&")},t.unifyPath=function(e){return e?e.replace(/\\/g,"/"):e},t.hashCode=function(e){let t,n,r,i=0;for(t=0,r=e.length;t=l&&p+1>=a)return s(this.buildValues(h[0].lastComponent,t,e));var b=-1/0,g=1/0,v=function(){for(var r=Math.max(b,-c);r<=Math.min(g,c);r+=2){var i=void 0,u=h[r-1],f=h[r+1];u&&(h[r-1]=void 0);var d=!1;if(f){var v=f.oldPos-r;d=f&&0<=v&&v=l&&p+1>=a)return s(o.buildValues(i.lastComponent,t,e))||!0;h[r]=i,i.oldPos+1>=l&&(g=Math.min(g,r-1)),p+1>=a&&(b=Math.max(b,r+1))}else h[r]=void 0}c++};if(r)!function e(){setTimeout((function(){if(c>u||Date.now()>d)return r(void 0);v()||e()}),0)}();else for(;c<=u&&Date.now()<=d;){var m=v();if(m)return m}},e.prototype.addToPath=function(e,t,n,r,i){var o=e.lastComponent;return o&&!i.oneChangePerToken&&o.added===t&&o.removed===n?{oldPos:e.oldPos+r,lastComponent:{count:o.count+1,added:t,removed:n,previousComponent:o.previousComponent}}:{oldPos:e.oldPos+r,lastComponent:{count:1,added:t,removed:n,previousComponent:o}}},e.prototype.extractCommon=function(e,t,n,r,i){for(var o=t.length,s=n.length,a=e.oldPos,l=a-r,c=0;l+1e.length?r:e})),c.value=this.join(u)}else c.value=this.join(t.slice(a,a+c.count));a+=c.count,c.added||(l+=c.count)}}return i},e}();t.default=n},302(e,t,n){"use strict";var r,i=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),s=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),s=0;ss||f.content.length>s)return{oldLine:{prefix:u.prefix,content:g(u.content)},newLine:{prefix:f.prefix,content:g(f.content)}};const p="char"===c?a.diffChars(u.content,f.content):a.diffWordsWithSpace(u.content,f.content),m=[];if("word"===c&&"words"===o){const e=p.filter((e=>e.removed)),t=p.filter((e=>e.added));h(t,e).forEach((e=>{1===e[0].length&&1===e[1].length&&d(e[0][0],e[1][0]){const n=t.added?"ins":t.removed?"del":null,r=m.indexOf(t)>-1?' class="d2h-change"':"",i=g(t.value);return null!==n?`${e}<${n}${r}>${i}`:`${e}${i}`}),"");return{oldLine:{prefix:u.prefix,content:(w=y,w.replace(/(]*>((.|\n)*?)<\/ins>)/g,""))},newLine:{prefix:f.prefix,content:b(y)}};var w};const a=s(n(801)),l=n(185),c=s(n(598)),u=n(613);t.CSSLineClass={INSERTS:"d2h-ins",DELETES:"d2h-del",CONTEXT:"d2h-cntx",INFO:"d2h-info",INSERT_CHANGES:"d2h-ins d2h-change",DELETE_CHANGES:"d2h-del d2h-change"},t.defaultRenderConfig={matching:u.LineMatchingType.NONE,matchWordsThreshold:.25,maxLineLengthHighlight:1e4,diffStyle:u.DiffStyleType.WORD,colorScheme:u.ColorSchemeType.LIGHT};const f="/",d=c.newDistanceFn((e=>e.value)),h=c.newMatcherFn(d);function p(e){return-1!==e.indexOf("dev/null")}function b(e){return e.replace(/(]*>((.|\n)*?)<\/del>)/g,"")}function g(e){return e.slice(0).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")}function v(e,t,n=!0){const r=function(e){return e?2:1}(t);return{prefix:e.substring(0,r),content:n?g(e.substring(r)):e.substring(r)}}function m(e){const t=(0,l.unifyPath)(e.oldName),n=(0,l.unifyPath)(e.newName);if(t===n||p(t)||p(n))return p(n)?t:n;{const e=[],r=[],i=t.split(f),o=n.split(f);let s=0,a=i.length-1,l=o.length-1;for(;ss&&l>s&&i[a]===o[l];)r.unshift(o[l]),a-=1,l-=1;const c=e.join(f),u=r.join(f),d=i.slice(s,a+1).join(f),h=o.slice(s,l+1).join(f);return c.length&&u.length?c+f+"{"+d+" → "+h+"}"+f+u:c.length?c+f+"{"+d+" → "+h+"}":u.length?"{"+d+" → "+h+"}"+f+u:t+" → "+n}}},363(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.parsePatch=function(e){var t=e.split(/\n/),n=[],r=0;function i(){var e={};for(n.push(e);r":7,"=":8,_v:9,"{":10,"&":11,_t:12},e.scan=function(n,r){var i,o=n.length,s=0,a=null,u=null,f="",d=[],h=!1,p=0,b=0,g="{{",v="}}";function m(){f.length>0&&(d.push({tag:"_t",text:new String(f)}),f="")}function y(n,r){if(m(),n&&function(){for(var n=!0,r=b;r"==i.tag&&(i.indent=d[o].text.toString()),d.splice(o,1));else r||d.push({tag:"\n"});h=!1,b=d.length}function w(e,t){var n="="+v,r=e.indexOf(n,t),i=l(e.substring(e.indexOf("=",t)+1,r)).split(" ");return g=i[0],v=i[i.length-1],r+n.length-1}for(r&&(r=r.split(" "),g=r[0],v=r[1]),p=0;p0;){if(l=t.shift(),o&&"<"==o.tag&&!(l.tag in u))throw new Error("Illegal content in < super tag.");if(e.tags[l.tag]<=e.tags.$||d(l,i))r.push(l),l.nodes=f(t,l.tag,r,i);else{if("/"==l.tag){if(0===r.length)throw new Error("Closing tag without opener: /"+l.n);if(a=r.pop(),l.n!=a.n&&!h(l.n,a.n,i))throw new Error("Nesting error: "+a.n+" vs. "+l.n);return a.end=l.i,s}"\n"==l.tag&&(l.last=0==t.length||"\n"==t[0].tag)}s.push(l)}if(r.length>0)throw new Error("missing closing tag: "+r.pop().n);return s}function d(e,t){for(var n=0,r=t.length;n":m,"<":function(t,n){var r={partials:{},code:"",subs:{},inPartial:!0};e.walk(t.nodes,r);var i=n.partials[m(t,n)];i.subs=r.subs,i.partials=r.partials},$:function(t,n){var r={subs:{},code:"",partials:n.partials,prefix:t.n};e.walk(t.nodes,r),n.subs[t.n]=r.code,n.inPartial||(n.code+='t.sub("'+g(t.n)+'",c,p,i);')},"\n":function(e,t){t.code+=w('"\\n"'+(e.last?"":" + i"))},_v:function(e,t){t.code+="t.b(t.v(t."+v(e.n)+'("'+g(e.n)+'",c,p,0)));'},_t:function(e,t){t.code+=w('"'+g(e.text)+'"')},"{":y,"&":y},e.walk=function(t,n){for(var r,i=0,o=t.length;i'),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(r.rp("'),r.b(r.v(r.f("fileName",e,t,0))),r.b(""),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(' '),r.b(r.v(r.f("addedLines",e,t,0))),r.b(""),r.b("\n"+n),r.b(' '),r.b(r.v(r.f("deletedLines",e,t,0))),r.b(""),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(""),r.fl()},partials:{"'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(' Files changed ('),r.b(r.v(r.f("filesNumber",e,t,0))),r.b(")"),r.b("\n"+n),r.b(' hide'),r.b("\n"+n),r.b(' show'),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b('
    '),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("files",e,t,0))),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b(""),r.fl()},partials:{},subs:{}}),t.defaultTemplates["generic-block-header"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b(""),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b('
'),r.s(r.f("blockHeader",e,t,1),e,t,0,156,173,"{{ }}")&&(r.rs(e,t,(function(e,t,n){n.b(n.t(n.f("blockHeader",e,t,0)))})),e.pop()),r.s(r.f("blockHeader",e,t,1),e,t,1,0,0,"")||r.b(" "),r.b("
"),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(""),r.fl()},partials:{},subs:{}}),t.defaultTemplates["generic-empty-diff"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b(""),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(" File without changes"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(""),r.fl()},partials:{},subs:{}}),t.defaultTemplates["generic-file-path"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b(''),r.b("\n"+n),r.b(r.rp("'),r.b(r.v(r.f("fileDiffName",e,t,0))),r.b(""),r.b("\n"+n),r.b(r.rp(""),r.b("\n"+n),r.b('"),r.fl()},partials:{""),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("lineNumber",e,t,0))),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.s(r.f("prefix",e,t,1),e,t,0,162,238,"{{ }}")&&(r.rs(e,t,(function(e,t,r){r.b(' '),r.b(r.t(r.f("prefix",e,t,0))),r.b(""),r.b("\n"+n)})),e.pop()),r.s(r.f("prefix",e,t,1),e,t,1,0,0,"")||(r.b('  '),r.b("\n"+n)),r.s(r.f("content",e,t,1),e,t,0,371,445,"{{ }}")&&(r.rs(e,t,(function(e,t,r){r.b(' '),r.b(r.t(r.f("content",e,t,0))),r.b(""),r.b("\n"+n)})),e.pop()),r.s(r.f("content",e,t,1),e,t,1,0,0,"")||(r.b('
'),r.b("\n"+n)),r.b("
"),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b(""),r.fl()},partials:{},subs:{}}),t.defaultTemplates["generic-wrapper"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('
'),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("content",e,t,0))),r.b("\n"+n),r.b("
"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["icon-file-added"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["icon-file-changed"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["icon-file-deleted"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["icon-file-renamed"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["icon-file"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["line-by-line-file-diff"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("filePath",e,t,0))),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("diffs",e,t,0))),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["line-by-line-numbers"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('
'),r.b(r.v(r.f("oldNumber",e,t,0))),r.b("
"),r.b("\n"+n),r.b('
'),r.b(r.v(r.f("newNumber",e,t,0))),r.b("
"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["side-by-side-file-diff"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(" "),r.b(r.t(r.f("filePath",e,t,0))),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(" "),r.b(r.t(r.d("diffs.left",e,t,0))),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b('
'),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(' '),r.b("\n"+n),r.b(" "),r.b(r.t(r.d("diffs.right",e,t,0))),r.b("\n"+n),r.b(" "),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.b("\n"+n),r.b("
"),r.fl()},partials:{},subs:{}}),t.defaultTemplates["tag-file-added"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('ADDED'),r.fl()},partials:{},subs:{}}),t.defaultTemplates["tag-file-changed"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('CHANGED'),r.fl()},partials:{},subs:{}}),t.defaultTemplates["tag-file-deleted"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('DELETED'),r.fl()},partials:{},subs:{}}),t.defaultTemplates["tag-file-renamed"]=new a.Template({code:function(e,t,n){var r=this;return r.b(n=n||""),r.b('RENAMED'),r.fl()},partials:{},subs:{}})},501(e,t,n){"use strict";var r,i=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),s=this&&this.__importStar||(r=function(e){return r=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},r(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n=r(e),s=0;sthis.hoganUtils.render(l,"line",{fileHtmlId:a.getHtmlId(e),oldName:e.oldName,newName:e.newName,fileName:a.filenameDiff(e),deletedLines:"-"+e.deletedLines,addedLines:"+"+e.addedLines},{fileIcon:this.hoganUtils.template("icon",a.getFileIcon(e))}))).join("\n");return this.hoganUtils.render(l,"wrapper",{colorScheme:a.colorSchemeToCss(this.config.colorScheme),filesNumber:e.length,files:t})}}},598(e,t){"use strict";function n(e,t){if(0===e.length)return t.length;if(0===t.length)return e.length;const n=[];let r,i;for(r=0;r<=t.length;r++)n[r]=[r];for(i=0;i<=e.length;i++)n[0][i]=i;for(r=1;r<=t.length;r++)for(i=1;i<=e.length;i++)t.charAt(r-1)===e.charAt(i-1)?n[r][i]=n[r-1][i-1]:n[r][i]=Math.min(n[r-1][i-1]+1,Math.min(n[r][i-1]+1,n[r-1][i]+1));return n[t.length][e.length]}Object.defineProperty(t,"__esModule",{value:!0}),t.levenshtein=n,t.newDistanceFn=function(e){return(t,r)=>{const i=e(t).trim(),o=e(r).trim();return n(i,o)/(i.length+o.length)}},t.newMatcherFn=function(e){return function t(n,r,i=0,o=new Map){const s=function(t,n,r=new Map){let i,o=1/0;for(let s=0;s0||s.indexB>0)&&(m=b.concat(m)),(n.length>f||r.length>d)&&(m=m.concat(v)),m}}},613(e,t){"use strict";var n,r;Object.defineProperty(t,"__esModule",{value:!0}),t.ColorSchemeType=t.DiffStyleType=t.LineMatchingType=t.OutputFormatType=t.LineType=void 0,function(e){e.INSERT="insert",e.DELETE="delete",e.CONTEXT="context"}(n||(t.LineType=n={})),t.OutputFormatType={LINE_BY_LINE:"line-by-line",SIDE_BY_SIDE:"side-by-side"},t.LineMatchingType={LINES:"lines",WORDS:"words",NONE:"none"},t.DiffStyleType={WORD:"word",CHAR:"char"},function(e){e.AUTO="auto",e.DARK="dark",e.LIGHT="light"}(r||(t.ColorSchemeType=r={}))},622(e,t,n){"use strict";var r,i=this&&this.__extends||(r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0}),t.characterDiff=void 0,t.diffChars=function(e,n,r){return t.characterDiff.diff(e,n,r)};var o=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return i(t,e),t}(n(188).default);t.characterDiff=new o},660(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.convertChangesToXML=function(e){for(var t=[],n=0;n"):r.removed&&t.push(""),t.push(r.value.replace(/&/g,"&").replace(//g,">").replace(/"/g,""")),r.added?t.push(""):r.removed&&t.push("")}return t.join("")}},665(e,t){"use strict";function n(e,t,n){if(e.slice(0,t.length)!=t)throw Error("string ".concat(JSON.stringify(e)," doesn't start with prefix ").concat(JSON.stringify(t),"; this is a bug"));return n+e.slice(t.length)}function r(e,t,n){if(!t)return e+n;if(e.slice(-t.length)!=t)throw Error("string ".concat(JSON.stringify(e)," doesn't end with suffix ").concat(JSON.stringify(t),"; this is a bug"));return e.slice(0,-t.length)+n}Object.defineProperty(t,"__esModule",{value:!0}),t.longestCommonPrefix=function(e,t){var n;for(n=0;nt.length&&(n=e.length-t.length);var r=t.length;e.length0&&t[s]!=t[o];)o=i[o];t[s]==t[o]&&o++}o=0;for(var a=n;a0&&e[a]!=t[o];)o=i[o];e[a]==t[o]&&o++}return o}(e,t))},t.hasOnlyWinLineEndings=function(e){return e.includes("\r\n")&&!e.startsWith("\n")&&!e.match(/[^\r]\n/)},t.hasOnlyUnixLineEndings=function(e){return!e.includes("\r\n")&&e.includes("\n")},t.trailingWs=function(e){var t;for(t=e.length-1;t>=0&&e[t].match(/\s/);t--);return e.substring(t+1)},t.leadingWs=function(e){var t=e.match(/^\s*/);return t?t[0]:""}},725(e,t){"use strict";var n=this&&this.__assign||function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=0;l--)if(void 0!==(o=t(e,n[l],a))){s=!0;break}return s?(i||"function"!=typeof o||(o=this.mv(o,n,r)),o):!i&&""},ls:function(e,t,n,r,i,o){var s=this.options.delimiters;return this.options.delimiters=o,this.b(this.ct(l(e.call(t,i,n)),t,r)),this.options.delimiters=s,!1},ct:function(e,t,n){if(this.options.disableLambda)throw new Error("Lambda features disabled.");return this.c.compile(e,this.options).render(t,n)},b:function(e){this.buf+=e},fl:function(){var e=this.buf;return this.buf="",e},ms:function(e,t,n,r,i,o,s){var a,l=t[t.length-1],c=e.call(l);return"function"==typeof c?!!r||(a=this.activeSub&&this.subsText&&this.subsText[this.activeSub]?this.subsText[this.activeSub]:this.text,this.ls(c,l,t,n,a.substring(i,o),s)):c},mv:function(e,t,n){var r=t[t.length-1],i=e.call(r);return"function"==typeof i?this.ct(l(i.call(r)),r,n):i},sub:function(e,t,n,r){var i=this.subs[e];i&&(this.activeSub=e,i(t,n,this,r),this.activeSub=!1)}};var n=/&/g,r=//g,o=/\'/g,s=/\"/g,a=/[&<>\"\']/;function l(e){return String(null==e?"":e)}var c=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}}(t)},801(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.canonicalize=t.convertChangesToXML=t.convertChangesToDMP=t.reversePatch=t.parsePatch=t.applyPatches=t.applyPatch=t.OMIT_HEADERS=t.FILE_HEADERS_ONLY=t.INCLUDE_HEADERS=t.formatPatch=t.createPatch=t.createTwoFilesPatch=t.structuredPatch=t.arrayDiff=t.diffArrays=t.jsonDiff=t.diffJson=t.cssDiff=t.diffCss=t.sentenceDiff=t.diffSentences=t.diffTrimmedLines=t.lineDiff=t.diffLines=t.wordsWithSpaceDiff=t.diffWordsWithSpace=t.wordDiff=t.diffWords=t.characterDiff=t.diffChars=t.Diff=void 0;var r=n(188);t.Diff=r.default;var i=n(622);Object.defineProperty(t,"diffChars",{enumerable:!0,get:function(){return i.diffChars}}),Object.defineProperty(t,"characterDiff",{enumerable:!0,get:function(){return i.characterDiff}});var o=n(25);Object.defineProperty(t,"diffWords",{enumerable:!0,get:function(){return o.diffWords}}),Object.defineProperty(t,"diffWordsWithSpace",{enumerable:!0,get:function(){return o.diffWordsWithSpace}}),Object.defineProperty(t,"wordDiff",{enumerable:!0,get:function(){return o.wordDiff}}),Object.defineProperty(t,"wordsWithSpaceDiff",{enumerable:!0,get:function(){return o.wordsWithSpaceDiff}});var s=n(839);Object.defineProperty(t,"diffLines",{enumerable:!0,get:function(){return s.diffLines}}),Object.defineProperty(t,"diffTrimmedLines",{enumerable:!0,get:function(){return s.diffTrimmedLines}}),Object.defineProperty(t,"lineDiff",{enumerable:!0,get:function(){return s.lineDiff}});var a=n(484);Object.defineProperty(t,"diffSentences",{enumerable:!0,get:function(){return a.diffSentences}}),Object.defineProperty(t,"sentenceDiff",{enumerable:!0,get:function(){return a.sentenceDiff}});var l=n(42);Object.defineProperty(t,"diffCss",{enumerable:!0,get:function(){return l.diffCss}}),Object.defineProperty(t,"cssDiff",{enumerable:!0,get:function(){return l.cssDiff}});var c=n(843);Object.defineProperty(t,"diffJson",{enumerable:!0,get:function(){return c.diffJson}}),Object.defineProperty(t,"canonicalize",{enumerable:!0,get:function(){return c.canonicalize}}),Object.defineProperty(t,"jsonDiff",{enumerable:!0,get:function(){return c.jsonDiff}});var u=n(968);Object.defineProperty(t,"diffArrays",{enumerable:!0,get:function(){return u.diffArrays}}),Object.defineProperty(t,"arrayDiff",{enumerable:!0,get:function(){return u.arrayDiff}});var f=n(904);Object.defineProperty(t,"applyPatch",{enumerable:!0,get:function(){return f.applyPatch}}),Object.defineProperty(t,"applyPatches",{enumerable:!0,get:function(){return f.applyPatches}});var d=n(363);Object.defineProperty(t,"parsePatch",{enumerable:!0,get:function(){return d.parsePatch}});var h=n(406);Object.defineProperty(t,"reversePatch",{enumerable:!0,get:function(){return h.reversePatch}});var p=n(822);Object.defineProperty(t,"structuredPatch",{enumerable:!0,get:function(){return p.structuredPatch}}),Object.defineProperty(t,"createTwoFilesPatch",{enumerable:!0,get:function(){return p.createTwoFilesPatch}}),Object.defineProperty(t,"createPatch",{enumerable:!0,get:function(){return p.createPatch}}),Object.defineProperty(t,"formatPatch",{enumerable:!0,get:function(){return p.formatPatch}}),Object.defineProperty(t,"INCLUDE_HEADERS",{enumerable:!0,get:function(){return p.INCLUDE_HEADERS}}),Object.defineProperty(t,"FILE_HEADERS_ONLY",{enumerable:!0,get:function(){return p.FILE_HEADERS_ONLY}}),Object.defineProperty(t,"OMIT_HEADERS",{enumerable:!0,get:function(){return p.OMIT_HEADERS}});var b=n(868);Object.defineProperty(t,"convertChangesToDMP",{enumerable:!0,get:function(){return b.convertChangesToDMP}});var g=n(660);Object.defineProperty(t,"convertChangesToXML",{enumerable:!0,get:function(){return g.convertChangesToXML}})},822(e,t,n){"use strict";var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0?E(b.lines.slice(-u)):[]).length,o-=l.length)}for(var g=0,v=p;g1&&!n.includeFileHeaders)throw new Error("Cannot omit file headers on a multi-file patch. (The result would be unparseable; how would a tool trying to apply the patch know which changes are to which file?)");return e.map((function(e){return s(e,n)})).join("\n")}var r=[];n.includeIndex&&e.oldFileName==e.newFileName&&r.push("Index: "+e.oldFileName),n.includeUnderline&&r.push("==================================================================="),n.includeFileHeaders&&(r.push("--- "+e.oldFileName+(void 0===e.oldHeader?"":"\t"+e.oldHeader)),r.push("+++ "+e.newFileName+(void 0===e.newHeader?"":"\t"+e.newHeader)));for(var i=0;i{let t;return t=e.blocks.length?this.generateFileHtml(e):this.generateEmptyDiff(),this.makeFileDiffHtml(e,t)})).join("\n");return this.hoganUtils.render(f,"wrapper",{colorScheme:l.colorSchemeToCss(this.config.colorScheme),content:t})}makeFileDiffHtml(e,t){if(this.config.renderNothingWhenEmpty&&Array.isArray(e.blocks)&&0===e.blocks.length)return"";const n=this.hoganUtils.template(d,"file-diff"),r=this.hoganUtils.template(f,"file-path"),i=this.hoganUtils.template("icon","file"),o=this.hoganUtils.template("tag",l.getFileIcon(e));return n.render({file:e,fileHtmlId:l.getHtmlId(e),diffs:t,filePath:r.render({fileDiffName:l.filenameDiff(e)},{fileIcon:i,fileTag:o})})}generateEmptyDiff(){return this.hoganUtils.render(f,"empty-diff",{contentClass:"d2h-code-line",CSSLineClass:l.CSSLineClass})}generateFileHtml(e){const t=a.newMatcherFn(a.newDistanceFn((t=>l.deconstructLine(t.content,e.isCombined).content)));return e.blocks.map((n=>{let r=this.hoganUtils.render(f,"block-header",{CSSLineClass:l.CSSLineClass,blockHeader:e.isTooBig?n.header:l.escapeForHtml(n.header),lineClass:"d2h-code-linenumber",contentClass:"d2h-code-line"});return this.applyLineGroupping(n).forEach((([n,i,o])=>{if(i.length&&o.length&&!n.length)this.applyRematchMatching(i,o,t).map((([t,n])=>{const{left:i,right:o}=this.processChangedLines(e,e.isCombined,t,n);r+=i,r+=o}));else if(n.length)n.forEach((t=>{const{prefix:n,content:i}=l.deconstructLine(t.content,e.isCombined);r+=this.generateSingleLineHtml(e,{type:l.CSSLineClass.CONTEXT,prefix:n,content:i,oldNumber:t.oldNumber,newNumber:t.newNumber})}));else if(i.length||o.length){const{left:t,right:n}=this.processChangedLines(e,e.isCombined,i,o);r+=t,r+=n}else console.error("Unknown state reached while processing groups of lines",n,i,o)})),r})).join("\n")}applyLineGroupping(e){const t=[];let n=[],r=[];for(let i=0;i0)&&(t.push([[],n,r]),n=[],r=[]),o.type===c.LineType.CONTEXT?t.push([[o],[],[]]):o.type===c.LineType.INSERT&&0===n.length?t.push([[],[],[o]]):o.type===c.LineType.INSERT&&n.length>0?r.push(o):o.type===c.LineType.DELETE&&n.push(o)}return(n.length||r.length)&&(t.push([[],n,r]),n=[],r=[]),t}applyRematchMatching(e,t,n){const r=e.length*t.length,i=(0,u.max)(e.concat(t).map((e=>e.content.length)));return r1)throw new Error("applyPatch only works with a single input.");return function(e,t,n){void 0===n&&(n={}),(n.autoConvertLineEndings||null==n.autoConvertLineEndings)&&((0,r.hasOnlyWinLineEndings)(e)&&(0,i.isUnix)(t)?t=(0,i.unixToWin)(t):(0,r.hasOnlyUnixLineEndings)(e)&&(0,i.isWin)(t)&&(t=(0,i.winToUnix)(t)));var o=e.split("\n"),a=t.hunks,l=n.compareLine||function(e,t,n,r){return t===r},c=n.fuzzFactor||0,u=0;if(c<0||!Number.isInteger(c))throw new Error("fuzzFactor must be a non-negative integer");if(!a.length)return e;for(var f="",d=!1,h=!1,p=0;p0?f[0]:" ",h=f.length>0?f.substr(1):f;if("-"===d){if(!l(t+1,o[t],d,h))return n&&null!=o[t]?(s[a]=o[t],b(e,t+1,n-1,r,!1,s,a+1)):null;t++,c=0}if("+"===d){if(!i)return null;s[a]=h,a++,c=0,u=!0}if(" "===d){if(c++,s[a]=o[t],!l(t+1,o[t],d,h))return u||!n?null:o[t]&&(b(e,t+1,n-1,r+1,!1,s,a+1)||b(e,t+1,n-1,r,!1,s,a+1))||b(e,t,n-1,r+1,!1,s,a);a++,i=!0,u=!1,t++}}return a-=c,t-=c,s.length=a,{patchedLines:s,oldLineLastI:t-1}}var g=[],v=0;for(p=0;p{if(!e||e.startsWith("*"))return;let M;const W=H[u-1],A=H[u+1],R=H[u+2];if(e.startsWith("diff --git")||e.startsWith("diff --combined")){if(I(),(M=/^diff --git "?([a-ciow]\/.+)"? "?([a-ciow]\/.+)"?/.exec(e))&&(d=l(M[1],void 0,t.dstPrefix),h=l(M[2],void 0,t.srcPrefix)),null===i)throw new Error("Where is my file !!!");return void(i.isGitDiff=!0)}if(e.startsWith("Binary files")&&!(null==i?void 0:i.isGitDiff)){if(I(),(M=/^Binary files "?([a-ciow]\/.+)"? and "?([a-ciow]\/.+)"? differ/.exec(e))&&(d=l(M[1],void 0,t.dstPrefix),h=l(M[2],void 0,t.srcPrefix)),null===i)throw new Error("Where is my file !!!");return void(i.isBinary=!0)}if((!i||!i.isGitDiff&&i&&e.startsWith(p)&&A.startsWith(b)&&R.startsWith(g))&&I(),null==i?void 0:i.isTooBig)return;if(i&&("number"==typeof t.diffMaxChanges&&i.addedLines+i.deletedLines>t.diffMaxChanges||"number"==typeof t.diffMaxLineLength&&e.length>t.diffMaxLineLength))return i.isTooBig=!0,i.addedLines=0,i.deletedLines=0,i.blocks=[],a=null,void F("function"==typeof t.diffTooBigMessage?t.diffTooBigMessage(n.length):"Diff too big to be displayed");if(e.startsWith(p)&&A.startsWith(b)||e.startsWith(b)&&W.startsWith(p)){if(i&&!i.oldName&&e.startsWith("--- ")&&(M=function(e,t){return l(e,"---",t)}(e,t.srcPrefix)))return i.oldName=M,void(i.language=o(i.oldName,i.language));if(i&&!i.newName&&e.startsWith("+++ ")&&(M=function(e,t){return l(e,"+++",t)}(e,t.dstPrefix)))return i.newName=M,void(i.language=o(i.newName,i.language))}if(i&&(e.startsWith(g)||i.isGitDiff&&i.oldName&&i.newName&&!a))return void F(e);if(a&&(e.startsWith("+")||e.startsWith("-")||e.startsWith(" ")))return void function(e){if(null===i||null===a||null===c||null===f)return;const t={content:e},n=i.isCombined?["+ "," +","++"]:["+"],o=i.isCombined?["- "," -","--"]:["-"];s(e,n)?(i.addedLines++,t.type=r.LineType.INSERT,t.oldNumber=void 0,t.newNumber=f++):s(e,o)?(i.deletedLines++,t.type=r.LineType.DELETE,t.oldNumber=c++,t.newNumber=void 0):(t.type=r.LineType.CONTEXT,t.oldNumber=c++,t.newNumber=f++),a.lines.push(t)}(e);const z=!function(e,t){let n=t;for(;n1?n[n.length-1]:t}function s(e,t){return t.reduce(((t,n)=>t||e.startsWith(n)),!1)}const a=["a/","b/","i/","w/","c/","o/"];function l(e,t,n){const r=void 0!==n?[...a,n]:a,o=t?new RegExp(`^${(0,i.escapeForRegExp)(t)} "?(.+?)"?$`):new RegExp('^"?(.+?)"?$'),[,s=""]=o.exec(e)||[],l=r.find((e=>0===s.indexOf(e)));return(l?s.slice(l.length):s).replace(/\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? [+-]\d{4}.*$/,"")}},968(e,t,n){"use strict";var r,i=this&&this.__extends||(r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},r(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0}),t.arrayDiff=void 0,t.diffArrays=function(e,n,r){return t.arrayDiff.diff(e,n,r)};var o=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return i(t,e),t.prototype.tokenize=function(e){return e.slice()},t.prototype.join=function(e){return e},t.prototype.removeEmpty=function(e){return e},t}(n(188).default);t.arrayDiff=new o}},t={},function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(166);var e,t})); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ded1fa..bfe61c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@babel/eslint-parser": "^8.0.1", "@babel/plugin-syntax-typescript": "^8.0.3", "@eslint/js": "^10.0.1", + "@types/jsdom": "^27.0.0", "@types/mocha": "^10.0.10", "@types/node": "26.x", "@types/vscode": "^1.125.0", @@ -23,12 +24,66 @@ "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.9.2", "eslint": "^10.8.0", + "jsdom": "^30.0.1", "typescript": "^7.0.2" }, "engines": { "vscode": "^1.125.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@azu/format-text": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", @@ -595,6 +650,159 @@ "node": ">=18" } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -723,6 +931,24 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1258,6 +1484,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/jsesc": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", @@ -1303,6 +1541,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.125.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", @@ -2079,6 +2324,16 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binaryextensions": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", @@ -2704,6 +2959,20 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -2717,6 +2986,45 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2748,6 +3056,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -3807,6 +4122,19 @@ "node": ">=10" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4087,6 +4415,13 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -4246,6 +4581,103 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -4612,6 +5044,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -5940,6 +6379,19 @@ "node": ">=11.0.0" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/secretlint": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", @@ -6184,6 +6636,16 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -6433,6 +6895,13 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -6590,6 +7059,26 @@ "url": "https://bevry.me/fund" } }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -6613,6 +7102,32 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6865,6 +7380,29 @@ "url": "https://bevry.me/fund" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -6889,6 +7427,21 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7044,6 +7597,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", @@ -7068,6 +7631,13 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index b460b58..3ff636e 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "compile": "tsc -p ./", "copy-assets": "node scripts/copy-assets.js", "watch": "tsc -watch -p ./", - "pretest": "npm run compile && npm run lint", + "pretest": "npm run compile && npm run copy-assets && npm run lint", "lint": "eslint src", "test": "vscode-test --config ./.vscode-test.json" }, @@ -86,6 +86,7 @@ "@babel/eslint-parser": "^8.0.1", "@babel/plugin-syntax-typescript": "^8.0.3", "@eslint/js": "^10.0.1", + "@types/jsdom": "^27.0.0", "@types/mocha": "^10.0.10", "@types/node": "26.x", "@types/vscode": "^1.125.0", @@ -93,6 +94,7 @@ "@vscode/test-electron": "^3.1.0", "@vscode/vsce": "^3.9.2", "eslint": "^10.8.0", + "jsdom": "^30.0.1", "typescript": "^7.0.2" }, "dependencies": { diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 6ccabe2..5cd4861 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -91,6 +91,100 @@ suite('Extension Test Suite', () => { ); }); + test('Webview client script and diff2html should render a diff end-to-end', async () => { + // End-to-end validation of the diff2html usage: load the shipped + // diff2html bundle and the webview client script into a DOM and assert + // that a patch is actually rendered into #diff-output. This guards + // against the "blank Visual tab" class of regressions. + const { JSDOM } = await import('jsdom'); + + const extension = vscode.extensions.getExtension('unknowIfGuestInDream.tlcsdm-patch-reader'); + assert.ok(extension, 'Extension should be present'); + const assetDir = resolveDiff2HtmlAssetDirectory(extension.extensionPath); + const diff2htmlJsPath = path.join(assetDir, 'js', 'diff2html.min.js'); + assert.ok(fs.existsSync(diff2htmlJsPath), 'diff2html.min.js should be available for the webview'); + const diff2htmlJs = fs.readFileSync(diff2htmlJsPath, 'utf8'); + const patchViewerJs = fs.readFileSync( + path.join(extension.extensionPath, 'media', 'patchViewer.js'), + 'utf8' + ); + + const patch = [ + 'diff --git a/file.txt b/file.txt', + 'index 0000000..1111111 100644', + '--- a/file.txt', + '+++ b/file.txt', + '@@ -1 +1 @@', + '-old line', + '+new line', + '' + ].join('\n'); + const initialContentJson = JSON.stringify(patch).replace(/ +
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+ +`; + + const dom = new JSDOM(html, { runScripts: 'dangerously', pretendToBeVisual: true }); + const { window } = dom; + try { + (window as any).acquireVsCodeApi = () => ({ + postMessage: () => { /* no-op */ } + }); + + const bundleScript = window.document.createElement('script'); + bundleScript.textContent = diff2htmlJs; + window.document.body.appendChild(bundleScript); + assert.ok( + (window as any).Diff2Html, + 'diff2html bundle should expose a global Diff2Html' + ); + + const viewerScript = window.document.createElement('script'); + viewerScript.textContent = patchViewerJs; + window.document.body.appendChild(viewerScript); + + const diffOutput = window.document.getElementById('diff-output'); + assert.ok(diffOutput, '#diff-output should exist'); + assert.ok( + diffOutput!.querySelector('.d2h-file-wrapper'), + 'diff2html should render at least one file into #diff-output' + ); + + // Content tab must be switchable: clicking the Content tab activates it. + const contentTabBtn = window.document.querySelector('.tab[data-tab="content"]') as any; + assert.ok(contentTabBtn, 'Content tab button should exist'); + contentTabBtn!.click(); + assert.ok( + window.document.getElementById('content-tab')!.classList.contains('active'), + 'Clicking the Content tab should activate the content panel' + ); + assert.strictEqual( + (window.document.getElementById('content-output') as any).value, + patch, + 'Content tab should display the raw patch text' + ); + } finally { + window.close(); + } + }); + test('Initial patch content should be embedded safely and round-trip', () => { const provider = new PatchEditorProvider({ extensionUri: vscode.Uri.file('/ext'), From a40d253c448c77ff6a2484be85ebac56a9dfebc0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:26:04 +0000 Subject: [PATCH 10/10] docs(changelog): note vendored diff2html assets and render test Co-authored-by: unknowIfGuestInDream <57802425+unknowIfGuestInDream@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e16c1..964853e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,11 @@ ### Fixed - Fixed a blank Visual view and an unresponsive Content tab caused by the inline client script failing to run when the surrounding template literal mangled its escape sequences. The diff now renders and the tabs respond regardless of the patch contents. - The initial patch content is passed to the webview through a non-executable JSON data block (with every `<` escaped as `\u003c`), so patches that contain `` or HTML comments can no longer abort the viewer. +- Vendored the `diff2html` webview assets (`media/diff2html/`) into the repository so they are always present when the extension runs from source (Extension Development Host), during tests, and when packaged. Previously these files were git-ignored and only generated at publish time, which left the Visual view blank whenever the assets had not been copied. ### Tests - Replaced the inline-script validity test with checks that the shipped `media/patchViewer.js` parses as JavaScript and that the embedded initial content round-trips exactly (including `` payloads). +- Added an end-to-end test that loads the shipped `diff2html` bundle and `media/patchViewer.js` into a DOM and asserts that a patch actually renders into the Visual view and that the Content tab switches, guarding against the "blank Visual tab" class of regressions. ## [1.0.2] - 2026-07-03