@openclaw/diffs 2026.10.2 → 2026.10.5-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{browser.runtime-B8JeyUXz.mjs → browser.runtime-BfvyJ76b.mjs} +7 -11
- package/dist/assets/viewer-runtime.js +97 -1316
- package/dist/index.js +19 -42
- package/node_modules/@pierre/diffs/dist/components/CodeView.d.ts +2 -4
- package/node_modules/@pierre/diffs/dist/components/CodeView.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/CodeView.js +23 -12
- package/node_modules/@pierre/diffs/dist/components/CodeView.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/File.d.ts +2 -0
- package/node_modules/@pierre/diffs/dist/components/File.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/File.js +6 -0
- package/node_modules/@pierre/diffs/dist/components/File.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/FileDiff.d.ts +18 -1
- package/node_modules/@pierre/diffs/dist/components/FileDiff.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/FileDiff.js +47 -15
- package/node_modules/@pierre/diffs/dist/components/FileDiff.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFile.js +11 -11
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFile.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFileDiff.js +18 -17
- package/node_modules/@pierre/diffs/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/constants.d.ts +1 -3
- package/node_modules/@pierre/diffs/dist/constants.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/constants.js +1 -3
- package/node_modules/@pierre/diffs/dist/constants.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/editor/editor.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/editor/editor.js +16 -3
- package/node_modules/@pierre/diffs/dist/editor/editor.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/editor/editor2.js +1 -1
- package/node_modules/@pierre/diffs/dist/index.d.ts +2 -2
- package/node_modules/@pierre/diffs/dist/index.js +2 -2
- package/node_modules/@pierre/diffs/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/react/utils/useFileDiffInstance.js +47 -11
- package/node_modules/@pierre/diffs/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/renderers/DiffHunksRenderer.js +20 -13
- package/node_modules/@pierre/diffs/dist/renderers/DiffHunksRenderer.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/renderers/FileRenderer.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/renderers/FileRenderer.js +30 -11
- package/node_modules/@pierre/diffs/dist/renderers/FileRenderer.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/sprite.d.ts +2 -2
- package/node_modules/@pierre/diffs/dist/sprite.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/sprite.js +57 -0
- package/node_modules/@pierre/diffs/dist/sprite.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/style.js +1 -1
- package/node_modules/@pierre/diffs/dist/types.d.ts +1 -1
- package/node_modules/@pierre/diffs/dist/types.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/editSessionHunks.d.ts +5 -4
- package/node_modules/@pierre/diffs/dist/utils/editSessionHunks.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/editSessionHunks.js +32 -9
- package/node_modules/@pierre/diffs/dist/utils/editSessionHunks.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js +21 -13
- package/node_modules/@pierre/diffs/dist/utils/parsePatchFiles.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/parseQuotedDiffFileName.d.ts +8 -0
- package/node_modules/@pierre/diffs/dist/utils/parseQuotedDiffFileName.d.ts.map +1 -0
- package/node_modules/@pierre/diffs/dist/utils/parseQuotedDiffFileName.js +54 -0
- package/node_modules/@pierre/diffs/dist/utils/parseQuotedDiffFileName.js.map +1 -0
- package/node_modules/@pierre/diffs/dist/utils/realignChangeContent.d.ts +13 -10
- package/node_modules/@pierre/diffs/dist/utils/realignChangeContent.d.ts.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/realignChangeContent.js +23 -19
- package/node_modules/@pierre/diffs/dist/utils/realignChangeContent.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/utils/renderDiffWithHighlighter.js +39 -0
- package/node_modules/@pierre/diffs/dist/utils/renderDiffWithHighlighter.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/worker/worker-portable.js +39 -0
- package/node_modules/@pierre/diffs/dist/worker/worker-portable.js.map +1 -1
- package/node_modules/@pierre/diffs/dist/worker/worker.js +39 -0
- package/node_modules/@pierre/diffs/dist/worker/worker.js.map +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/core/dist/index.d.mts +28 -28
- package/node_modules/@pierre/diffs/node_modules/@shikijs/core/dist/index.mjs +15 -12
- package/node_modules/@pierre/diffs/node_modules/@shikijs/core/package.json +4 -4
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/{engine-compile-Bdn9ihA2.d.mts → engine-compile-CtqiSkRk.d.mts} +5 -0
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/engine-compile.d.mts +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/engine-compile.mjs +2 -4
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/engine-raw.d.mts +2 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/engine-raw.mjs +2 -4
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/index.d.mts +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/index.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/string-TLTPtCPs.mjs +115 -0
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-oniguruma/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/primitive/dist/index.d.mts +31 -32
- package/node_modules/@pierre/diffs/node_modules/@shikijs/primitive/dist/index.mjs +2 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/primitive/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/dist/monokai.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/dist/one-dark-pro.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/dist/red.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/dist/solarized-dark.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/dist/solarized-light.mjs +1 -1
- package/node_modules/@pierre/diffs/node_modules/@shikijs/themes/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/transformers/dist/index.d.mts +58 -35
- package/node_modules/@pierre/diffs/node_modules/@shikijs/transformers/dist/index.mjs +35 -4
- package/node_modules/@pierre/diffs/node_modules/@shikijs/transformers/package.json +3 -3
- package/node_modules/@pierre/diffs/node_modules/@shikijs/types/dist/index.d.mts +76 -76
- package/node_modules/@pierre/diffs/node_modules/@shikijs/types/package.json +1 -1
- package/node_modules/@pierre/diffs/node_modules/micromark-util-types/index.d.ts +6 -3
- package/node_modules/@pierre/diffs/node_modules/micromark-util-types/package.json +2 -36
- package/node_modules/@pierre/diffs/node_modules/shiki/dist/bundle-full.d.mts +4 -4
- package/node_modules/@pierre/diffs/node_modules/shiki/dist/bundle-web.d.mts +9 -9
- package/node_modules/@pierre/diffs/node_modules/shiki/dist/types.d.mts +3 -3
- package/node_modules/@pierre/diffs/node_modules/shiki/package.json +10 -10
- package/node_modules/@pierre/diffs/package.json +1 -1
- package/node_modules/@shikijs/langs/dist/ada.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/ahk.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/beancount.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/coq.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/csharp.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/css.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/dart.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/dotenv.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/emacs-lisp.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/gn.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/handlebars.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/julia.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/latex.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/luau.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/marko.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/moonbit.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/nextflow-groovy.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/nextflow.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/nushell.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/php.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/powershell.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/ruby.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/rust.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/sql.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/svelte.mjs +1 -1
- package/node_modules/@shikijs/langs/dist/twig.mjs +3 -3
- package/node_modules/@shikijs/langs/dist/typespec.mjs +1 -1
- package/node_modules/@shikijs/langs/node_modules/@shikijs/types/dist/index.d.mts +76 -76
- package/node_modules/@shikijs/langs/node_modules/@shikijs/types/package.json +1 -1
- package/node_modules/@shikijs/langs/package.json +3 -3
- package/package.json +6 -6
- package/node_modules/@pierre/diffs/node_modules/@shikijs/engine-javascript/dist/scanner-DX8LRFGE.mjs +0 -77
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parsePatchFiles.js","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":["import {\n ALTERNATE_FILE_NAMES_GIT,\n COMMIT_METADATA_SPLIT,\n FILENAME_HEADER_REGEX,\n FILENAME_HEADER_REGEX_GIT,\n GIT_DIFF_FILE_BREAK_REGEX,\n INDEX_LINE_METADATA,\n} from '../constants';\nimport type {\n ChangeContent,\n ContextContent,\n FileContents,\n FileDiffMetadata,\n Hunk,\n HunkLineType,\n ParsedPatch,\n} from '../types';\nimport { cleanLastNewline } from './cleanLastNewline';\nimport { composeCacheKey } from './composeCacheKey';\nimport { detachString, releaseStringDetachBuffer } from './detachString';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\nimport { realignChangeContentBySimilarity } from './realignChangeContent';\n\ninterface ParsedHunkHeader {\n additionCount: number;\n additionStart: number;\n deletionCount: number;\n deletionStart: number;\n hunkContext?: string;\n}\n\nexport function processPatch(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError?: boolean\n): ParsedPatch {\n try {\n return _processPatch(data, cacheKeyPrefix, throwOnError);\n } finally {\n releaseStringDetachBuffer();\n }\n}\n\nfunction _processPatch(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError = false,\n patchIndex?: number\n): ParsedPatch {\n const isGitDiff = isGitDiffPatch(data);\n const rawFiles = isGitDiff\n ? splitGitDiffFiles(data)\n : splitUnifiedDiffFiles(data);\n let patchMetadata: string | undefined;\n const files: FileDiffMetadata[] = [];\n for (const fileOrPatchMetadata of rawFiles) {\n if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {\n if (patchMetadata == null) {\n patchMetadata = detachString(fileOrPatchMetadata);\n } else {\n if (throwOnError) {\n throw Error('parsePatchContent: unknown file blob');\n } else {\n console.error(\n 'parsePatchContent: unknown file blob:',\n fileOrPatchMetadata\n );\n }\n }\n // If we get in here, it's most likely the introductory metadata from the\n // patch, or something is fucked with the diff format\n continue;\n } else if (\n !isGitDiff &&\n !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)\n ) {\n if (patchMetadata == null) {\n patchMetadata = detachString(fileOrPatchMetadata);\n } else {\n if (throwOnError) {\n throw Error('parsePatchContent: unknown file blob');\n } else {\n console.error(\n 'parsePatchContent: unknown file blob:',\n fileOrPatchMetadata\n );\n }\n }\n continue;\n }\n const currentFile = _processFile(fileOrPatchMetadata, {\n cacheKey:\n cacheKeyPrefix != null\n ? patchIndex == null\n ? composeCacheKey(\n 'patch-file',\n cacheKeyPrefix,\n String(files.length)\n )\n : composeCacheKey(\n 'patch-file',\n cacheKeyPrefix,\n String(patchIndex),\n String(files.length)\n )\n : undefined,\n isGitDiff,\n throwOnError,\n });\n if (currentFile != null) {\n files.push(currentFile);\n }\n }\n return { patchMetadata, files };\n}\n\ninterface ProcessFileOptions {\n cacheKey?: string;\n isGitDiff?: boolean;\n oldFile?: FileContents;\n newFile?: FileContents;\n throwOnError?: boolean;\n}\n\nexport function processFile(\n fileDiffString: string,\n options?: ProcessFileOptions\n): FileDiffMetadata | undefined {\n try {\n return _processFile(fileDiffString, options);\n } finally {\n releaseStringDetachBuffer();\n }\n}\n\nfunction _processFile(\n fileDiffString: string,\n {\n cacheKey,\n isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString),\n oldFile,\n newFile,\n throwOnError = false,\n }: ProcessFileOptions = {}\n): FileDiffMetadata | undefined {\n let lastHunkEnd = 0;\n const hunks = splitAtLinePrefix(fileDiffString, '@@ ');\n let currentFile: FileDiffMetadata | undefined;\n const isPartial = oldFile == null || newFile == null;\n let deletionLineIndex = 0;\n let additionLineIndex = 0;\n for (const hunk of hunks) {\n const lines = splitWithNewlines(hunk);\n const firstLine = lines[0];\n if (firstLine == null) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid hunk');\n } else {\n console.error('parsePatchContent: invalid hunk', hunk);\n }\n continue;\n }\n const fileHeader = parseHunkHeader(firstLine);\n let additionLines = 0;\n let deletionLines = 0;\n // Setup currentFile, this should be the first iteration of our hunks, and\n // technically not a hunk\n if (fileHeader == null || currentFile == null) {\n if (currentFile != null) {\n if (throwOnError) {\n throw Error('parsePatchContent: Invalid hunk');\n } else {\n console.error('parsePatchContent: Invalid hunk', hunk);\n }\n continue;\n }\n currentFile = {\n name: '',\n type: 'change',\n hunks: [],\n splitLineCount: 0,\n unifiedLineCount: 0,\n isPartial,\n additionLines:\n !isPartial && oldFile != null && newFile != null\n ? splitFileContents(newFile.contents)\n : [],\n deletionLines:\n !isPartial && oldFile != null && newFile != null\n ? splitFileContents(oldFile.contents)\n : [],\n cacheKey: maybeDetachOptionalString(cacheKey),\n };\n // If either file is technically empty, then we should empty the\n // arrays respectively\n if (currentFile.additionLines.length === 1 && newFile?.contents === '') {\n currentFile.additionLines.length = 0;\n }\n if (currentFile.deletionLines.length === 1 && oldFile?.contents === '') {\n currentFile.deletionLines.length = 0;\n }\n\n for (const line of lines) {\n if (line.startsWith('diff --git')) {\n const filenameMatch = line.trim().match(ALTERNATE_FILE_NAMES_GIT);\n const prevName = filenameMatch?.[1] ?? filenameMatch?.[2];\n const name = filenameMatch?.[3] ?? filenameMatch?.[4];\n if (prevName == null || name == null) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid git diff header');\n } else {\n console.error('parsePatchContent: invalid git diff header', line);\n }\n continue;\n }\n currentFile.name = detachString(name.trim());\n if (prevName !== name) {\n currentFile.prevName = detachString(prevName.trim());\n }\n continue;\n }\n\n const filenameMatch =\n line.startsWith('---') || line.startsWith('+++')\n ? line.match(\n isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX\n )\n : null;\n if (filenameMatch != null) {\n const [, type, fileName] = filenameMatch;\n if (type === '---' && fileName !== '/dev/null') {\n const detachedFileName = detachString(fileName.trim());\n currentFile.prevName = detachedFileName;\n currentFile.name = detachedFileName;\n } else if (type === '+++' && fileName !== '/dev/null') {\n currentFile.name = detachString(fileName.trim());\n }\n }\n // Git diffs have a bunch of additional metadata we can pull from\n else if (isGitDiff) {\n if (line.startsWith('new mode ')) {\n currentFile.mode = detachString(\n line.slice('new mode'.length).trim()\n );\n }\n if (line.startsWith('old mode ')) {\n currentFile.prevMode = detachString(\n line.slice('old mode'.length).trim()\n );\n }\n if (line.startsWith('new file mode')) {\n currentFile.type = 'new';\n currentFile.mode = detachString(\n line.slice('new file mode'.length).trim()\n );\n }\n if (line.startsWith('deleted file mode')) {\n currentFile.type = 'deleted';\n currentFile.mode = detachString(\n line.slice('deleted file mode'.length).trim()\n );\n }\n if (line.startsWith('similarity index')) {\n if (line.startsWith('similarity index 100%')) {\n currentFile.type = 'rename-pure';\n } else {\n currentFile.type = 'rename-changed';\n }\n }\n if (line.startsWith('index ')) {\n const [, prevObjectId, newObjectId, mode] =\n line.trim().match(INDEX_LINE_METADATA) ?? [];\n if (prevObjectId != null) {\n currentFile.prevObjectId = detachString(prevObjectId);\n }\n if (newObjectId != null) {\n currentFile.newObjectId = detachString(newObjectId);\n }\n if (mode != null) {\n currentFile.mode = detachString(mode);\n }\n }\n // We have to handle these for pure renames because there won't be\n // --- and +++ lines\n if (line.startsWith('rename from ')) {\n currentFile.prevName = detachString(\n line.slice('rename from '.length).trim()\n );\n }\n if (line.startsWith('rename to ')) {\n currentFile.name = detachString(\n line.slice('rename to '.length).trim()\n );\n }\n }\n }\n continue;\n }\n\n // Otherwise, time to start parsing out the hunk\n let currentContent: ContextContent | ChangeContent | undefined;\n let lastLineType: 'context' | 'addition' | 'deletion' | undefined;\n\n // Strip trailing bare newlines (format-patch separators between commits)\n // if needed\n while (\n lines.length > 0 &&\n (lines[lines.length - 1] === '\\n' ||\n lines[lines.length - 1] === '\\r' ||\n lines[lines.length - 1] === '\\r\\n' ||\n lines[lines.length - 1] === '')\n ) {\n lines.pop();\n }\n\n const { additionStart, deletionStart } = fileHeader;\n deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;\n additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;\n\n const hunkData: Hunk = {\n collapsedBefore: 0,\n\n splitLineCount: 0,\n splitLineStart: 0,\n\n unifiedLineCount: 0,\n unifiedLineStart: 0,\n\n additionCount: fileHeader.additionCount,\n additionStart,\n additionLines,\n\n deletionCount: fileHeader.deletionCount,\n deletionStart,\n deletionLines,\n\n deletionLineIndex,\n additionLineIndex,\n\n hunkContent: [],\n hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),\n hunkSpecs: detachString(firstLine),\n\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n\n // Now we process each line of the hunk\n let parsedAdditionLines = 0;\n let parsedDeletionLines = 0;\n for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {\n const rawLine = lines[lineIndex];\n if (\n parsedAdditionLines >= hunkData.additionCount &&\n parsedDeletionLines >= hunkData.deletionCount &&\n !rawLine.startsWith('\\\\')\n ) {\n const isUnexpectedBodyLine =\n isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine);\n if (!isUnexpectedBodyLine) {\n break;\n }\n if (throwOnError) {\n throw Error('parsePatchContent: hunk has more lines than expected');\n }\n }\n\n const firstChar = rawLine[0];\n // If we can't properly process the line, well, lets just try to salvage\n // things and continue... It's possible an AI generated diff might have\n // some stray blank lines or something in there\n if (\n firstChar !== '+' &&\n firstChar !== '-' &&\n firstChar !== ' ' &&\n firstChar !== '\\\\'\n ) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid hunk line');\n }\n console.error(\n `parseLineType: Invalid firstChar: \"${firstChar}\", full line: \"${rawLine}\"`\n );\n console.error('processFile: invalid rawLine:', rawLine);\n continue;\n }\n\n const type = parseRawLineType(firstChar);\n if (type === 'addition') {\n if (throwOnError && parsedAdditionLines >= hunkData.additionCount) {\n throw Error('parsePatchContent: hunk has too many addition lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup(\n 'change',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n additionLineIndex++;\n parsedAdditionLines++;\n if (isPartial) {\n currentFile.additionLines.push(line);\n }\n currentContent.additions++;\n additionLines++;\n lastLineType = 'addition';\n } else if (type === 'deletion') {\n if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) {\n throw Error('parsePatchContent: hunk has too many deletion lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup(\n 'change',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n deletionLineIndex++;\n parsedDeletionLines++;\n if (isPartial) {\n currentFile.deletionLines.push(line);\n }\n currentContent.deletions++;\n deletionLines++;\n lastLineType = 'deletion';\n } else if (type === 'context') {\n if (\n throwOnError &&\n (parsedDeletionLines >= hunkData.deletionCount ||\n parsedAdditionLines >= hunkData.additionCount)\n ) {\n throw Error('parsePatchContent: hunk has too many context lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'context') {\n currentContent = createContentGroup(\n 'context',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n additionLineIndex++;\n deletionLineIndex++;\n parsedAdditionLines++;\n parsedDeletionLines++;\n if (isPartial) {\n currentFile.deletionLines.push(line);\n currentFile.additionLines.push(line);\n }\n currentContent.lines++;\n lastLineType = 'context';\n } else if (type === 'metadata' && currentContent != null) {\n if (currentContent.type === 'context') {\n hunkData.noEOFCRAdditions = true;\n hunkData.noEOFCRDeletions = true;\n } else if (lastLineType === 'deletion') {\n hunkData.noEOFCRDeletions = true;\n } else if (lastLineType === 'addition') {\n hunkData.noEOFCRAdditions = true;\n }\n // If we're dealing with partial content from a diff, we need to strip\n // newlines manually from the content\n if (\n isPartial &&\n (lastLineType === 'addition' || lastLineType === 'context')\n ) {\n const lastIndex = currentFile.additionLines.length - 1;\n if (lastIndex >= 0) {\n currentFile.additionLines[lastIndex] = cleanLastNewline(\n currentFile.additionLines[lastIndex]\n );\n }\n }\n if (\n isPartial &&\n (lastLineType === 'deletion' || lastLineType === 'context')\n ) {\n const lastIndex = currentFile.deletionLines.length - 1;\n if (lastIndex >= 0) {\n currentFile.deletionLines[lastIndex] = cleanLastNewline(\n currentFile.deletionLines[lastIndex]\n );\n }\n }\n }\n }\n\n if (\n parsedAdditionLines !== hunkData.additionCount ||\n parsedDeletionLines !== hunkData.deletionCount\n ) {\n if (throwOnError) {\n throw Error('parsePatchContent: hunk line count mismatch');\n }\n console.error(\n `parsePatchContent: hunk line count mismatch: \"${firstLine.trimEnd()}\", declared old/new ${hunkData.deletionCount}/${hunkData.additionCount}, parsed old/new ${parsedDeletionLines}/${parsedAdditionLines}`\n );\n // Re-encode each original boundary using the repaired count. Zero-count\n // ranges use the boundary itself as their start; positive ranges use +1.\n const repairedAdditionStart =\n getHunkSideStartBoundary(\n hunkData.additionStart,\n hunkData.additionCount\n ) + (parsedAdditionLines === 0 ? 0 : 1);\n const repairedDeletionStart =\n getHunkSideStartBoundary(\n hunkData.deletionStart,\n hunkData.deletionCount\n ) + (parsedDeletionLines === 0 ? 0 : 1);\n\n // Hydrated hunks index into full-file line arrays, so their indexes must\n // move with repaired starts. Partial hunks index patch-built arrays.\n if (!isPartial) {\n const additionStartDelta =\n repairedAdditionStart - hunkData.additionStart;\n const deletionStartDelta =\n repairedDeletionStart - hunkData.deletionStart;\n hunkData.additionLineIndex += additionStartDelta;\n hunkData.deletionLineIndex += deletionStartDelta;\n for (const content of hunkData.hunkContent) {\n content.additionLineIndex += additionStartDelta;\n content.deletionLineIndex += deletionStartDelta;\n }\n }\n\n hunkData.additionStart = repairedAdditionStart;\n hunkData.deletionStart = repairedDeletionStart;\n hunkData.additionCount = parsedAdditionLines;\n hunkData.deletionCount = parsedDeletionLines;\n }\n\n hunkData.additionLines = additionLines;\n hunkData.deletionLines = deletionLines;\n\n hunkData.collapsedBefore = Math.max(\n getHunkSideStartBoundary(hunkData.additionStart, hunkData.additionCount) -\n lastHunkEnd,\n 0\n );\n currentFile.hunks.push(hunkData);\n lastHunkEnd = getHunkSideEndBoundary(\n hunkData.additionStart,\n hunkData.additionCount\n );\n for (const content of hunkData.hunkContent) {\n if (content.type === 'context') {\n hunkData.splitLineCount += content.lines;\n hunkData.unifiedLineCount += content.lines;\n } else {\n hunkData.splitLineCount += Math.max(\n content.additions,\n content.deletions\n );\n hunkData.unifiedLineCount += content.deletions + content.additions;\n }\n }\n hunkData.splitLineStart =\n currentFile.splitLineCount + hunkData.collapsedBefore;\n hunkData.unifiedLineStart =\n currentFile.unifiedLineCount + hunkData.collapsedBefore;\n\n currentFile.splitLineCount +=\n hunkData.collapsedBefore + hunkData.splitLineCount;\n currentFile.unifiedLineCount +=\n hunkData.collapsedBefore + hunkData.unifiedLineCount;\n }\n if (currentFile == null) {\n return undefined;\n }\n if (\n throwOnError &&\n isPartial &&\n !isGitDiff &&\n currentFile.hunks.length === 0\n ) {\n throw Error('parsePatchContent: unified file has no hunks');\n }\n\n // Account for collapsed lines after the final hunk and increment the\n // split/unified counts properly\n if (\n currentFile.hunks.length > 0 &&\n !isPartial &&\n currentFile.additionLines.length > 0 &&\n currentFile.deletionLines.length > 0\n ) {\n const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];\n const lastHunkEnd = getHunkSideEndBoundary(\n lastHunk.additionStart,\n lastHunk.additionCount\n );\n const totalFileLines = currentFile.additionLines.length;\n const collapsedAfter = Math.max(totalFileLines - lastHunkEnd, 0);\n currentFile.splitLineCount += collapsedAfter;\n currentFile.unifiedLineCount += collapsedAfter;\n }\n\n // If this isn't a git diff style patch, then we'll need to sus out some\n // additional metadata manually\n if (!isGitDiff) {\n if (\n currentFile.prevName != null &&\n currentFile.name !== currentFile.prevName\n ) {\n if (currentFile.hunks.length > 0) {\n currentFile.type = 'rename-changed';\n } else {\n currentFile.type = 'rename-pure';\n }\n }\n // Sort of a hack for detecting deleted/added files...\n else if (\n (oldFile == null || oldFile.contents === '') &&\n newFile != null &&\n newFile.contents !== ''\n ) {\n currentFile.type = 'new';\n } else if (\n oldFile != null &&\n oldFile.contents !== '' &&\n (newFile == null || newFile.contents === '')\n ) {\n currentFile.type = 'deleted';\n }\n }\n if (\n currentFile.type !== 'rename-pure' &&\n currentFile.type !== 'rename-changed'\n ) {\n currentFile.prevName = undefined;\n }\n // Pair change-block lines by similarity instead of the patch's positional\n // ordering. Partial diffs work too: their hunk line indexes point into the\n // patch-built line arrays, which hold every line a block references.\n realignChangeContentBySimilarity(currentFile);\n return currentFile;\n}\n\n/**\n * Parses a patch file string into an array of parsed patches.\n *\n * @param data - The raw patch file content (supports multi-commit patches)\n * @param cacheKeyPrefix - Optional prefix for collision-safe cache keys derived\n * from the prefix, patch index, and file index. This enables caching of\n * rendered diff results in the worker pool.\n * @param throwOnError - When true, invalid data throws. When false, invalid data\n * is reported with `console.error` and the parser attempts to recover when\n * possible. Recovery is best-effort and does not guarantee valid output.\n */\nexport function parsePatchFiles(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError = false\n): ParsedPatch[] {\n // NOTE(amadeus): This function is pretty forgiving in that it can accept a\n // patch file that includes commit metdata, multiple commits, or not\n const patches: ParsedPatch[] = [];\n const rawPatches = hasCommitMetadataBoundary(data)\n ? data.split(COMMIT_METADATA_SPLIT)\n : [data];\n for (const patch of rawPatches) {\n try {\n patches.push(\n _processPatch(\n patch,\n cacheKeyPrefix,\n throwOnError,\n cacheKeyPrefix != null ? patches.length : undefined\n )\n );\n } catch (error) {\n if (throwOnError) {\n throw error;\n } else {\n console.error(error);\n }\n } finally {\n releaseStringDetachBuffer();\n }\n }\n return patches;\n}\n\nfunction hasCommitMetadataBoundary(data: string): boolean {\n return data.startsWith('From ') || data.includes('\\nFrom ');\n}\n\nfunction splitFileContents(contents: string): string[] {\n const lines = splitWithNewlines(contents);\n for (let index = 0; index < lines.length; index++) {\n lines[index] = detachString(lines[index]);\n }\n return lines;\n}\n\nfunction splitWithNewlines(contents: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const lines: string[] = [];\n let startIndex = 0;\n for (;;) {\n const newlineIndex = contents.indexOf('\\n', startIndex);\n if (newlineIndex === -1) {\n break;\n }\n\n lines.push(contents.slice(startIndex, newlineIndex + 1));\n startIndex = newlineIndex + 1;\n }\n\n if (startIndex < contents.length) {\n lines.push(contents.slice(startIndex));\n }\n return lines;\n}\n\nfunction splitGitDiffFiles(contents: string): string[] {\n return splitAtLinePrefix(contents, 'diff --git');\n}\n\nfunction splitUnifiedDiffFiles(contents: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const parts: string[] = [];\n let partStartIndex = 0;\n let lineStartIndex = 0;\n let remainingDeletionLines = 0;\n let remainingAdditionLines = 0;\n let hasOpenedUnifiedFile = false;\n\n while (lineStartIndex < contents.length) {\n const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);\n if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {\n if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {\n if (lineStartIndex > partStartIndex) {\n parts.push(contents.slice(partStartIndex, lineStartIndex));\n }\n partStartIndex = lineStartIndex;\n hasOpenedUnifiedFile = true;\n lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);\n continue;\n }\n\n if (hasOpenedUnifiedFile && contents.startsWith('@@ -', lineStartIndex)) {\n const fileHeader = parseHunkHeader(\n contents.slice(lineStartIndex, nextLineStartIndex)\n );\n if (fileHeader != null) {\n remainingDeletionLines = fileHeader.deletionCount;\n remainingAdditionLines = fileHeader.additionCount;\n }\n }\n lineStartIndex = nextLineStartIndex;\n continue;\n }\n\n const firstChar = contents[lineStartIndex];\n if (firstChar === '\\\\') {\n lineStartIndex = nextLineStartIndex;\n continue;\n }\n\n if (firstChar === ' ') {\n remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);\n remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);\n } else if (firstChar === '-') {\n remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);\n } else if (firstChar === '+') {\n remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);\n }\n lineStartIndex = nextLineStartIndex;\n }\n\n parts.push(contents.slice(partStartIndex));\n return parts;\n}\n\nfunction startsWithUnifiedDiffFileHeader(contents: string): boolean {\n return isUnifiedDiffFileHeaderAt(contents, 0);\n}\n\nfunction isUnifiedDiffFileHeaderAt(contents: string, lineStartIndex: number) {\n const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);\n return (\n isUnifiedDiffHeaderLineAt(contents, lineStartIndex, '---') &&\n isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, '+++')\n );\n}\n\nfunction isUnifiedDiffHeaderLineAt(\n contents: string,\n lineStartIndex: number,\n prefix: '---' | '+++'\n): boolean {\n if (!contents.startsWith(prefix, lineStartIndex)) {\n return false;\n }\n\n const separator = contents[lineStartIndex + prefix.length];\n if (separator !== ' ' && separator !== '\\t') {\n return false;\n }\n\n for (\n let index = lineStartIndex + prefix.length + 1;\n index < contents.length;\n index++\n ) {\n const char = contents[index];\n if (char === '\\n' || char === '\\r') {\n break;\n }\n if (char !== ' ' && char !== '\\t') {\n return true;\n }\n }\n return false;\n}\n\nfunction getNextLineStartIndex(\n contents: string,\n lineStartIndex: number\n): number {\n const newlineIndex = contents.indexOf('\\n', lineStartIndex);\n return newlineIndex === -1 ? contents.length : newlineIndex + 1;\n}\n\nfunction isHunkBodyLine(line: string): boolean {\n const firstChar = line[0];\n return firstChar === '+' || firstChar === '-' || firstChar === ' ';\n}\n\nfunction isFormatPatchVersionSeparator(line: string): boolean {\n if (!line.startsWith('--')) {\n return false;\n }\n\n for (let index = 2; index < line.length; index++) {\n const char = line[index];\n if (char !== ' ' && char !== '\\t' && char !== '\\n' && char !== '\\r') {\n return false;\n }\n }\n return true;\n}\n\nfunction parseHunkHeader(line: string): ParsedHunkHeader | undefined {\n if (!line.startsWith('@@ -')) {\n return undefined;\n }\n\n let index = 4;\n const deletionStartResult = readPositiveInteger(line, index);\n if (deletionStartResult == null) {\n return undefined;\n }\n const deletionStart = deletionStartResult.value;\n index = deletionStartResult.endIndex;\n\n let deletionCount = 1;\n if (line[index] === ',') {\n const deletionCountResult = readPositiveInteger(line, index + 1);\n if (deletionCountResult == null) {\n return undefined;\n }\n deletionCount = deletionCountResult.value;\n index = deletionCountResult.endIndex;\n }\n\n if (line[index] !== ' ' || line[index + 1] !== '+') {\n return undefined;\n }\n index += 2;\n\n const additionStartResult = readPositiveInteger(line, index);\n if (additionStartResult == null) {\n return undefined;\n }\n const additionStart = additionStartResult.value;\n index = additionStartResult.endIndex;\n\n let additionCount = 1;\n if (line[index] === ',') {\n const additionCountResult = readPositiveInteger(line, index + 1);\n if (additionCountResult == null) {\n return undefined;\n }\n additionCount = additionCountResult.value;\n index = additionCountResult.endIndex;\n }\n\n if (\n line[index] !== ' ' ||\n line[index + 1] !== '@' ||\n line[index + 2] !== '@'\n ) {\n return undefined;\n }\n\n let hunkContext: string | undefined;\n const contextStartIndex = index + 3;\n if (line[contextStartIndex] === ' ') {\n hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));\n }\n\n return {\n additionCount,\n additionStart,\n deletionCount,\n deletionStart,\n hunkContext,\n };\n}\n\nfunction readPositiveInteger(\n value: string,\n startIndex: number\n): { value: number; endIndex: number } | undefined {\n let index = startIndex;\n let parsedValue = 0;\n for (; index < value.length; index++) {\n const digit = value.charCodeAt(index) - 48;\n if (digit < 0 || digit > 9) {\n break;\n }\n parsedValue = parsedValue * 10 + digit;\n }\n\n if (index === startIndex) {\n return undefined;\n }\n return { value: parsedValue, endIndex: index };\n}\n\nfunction trimLineEnd(value: string): string {\n if (value.endsWith('\\r\\n')) {\n return value.slice(0, -2);\n }\n if (value.endsWith('\\n')) {\n return value.slice(0, -1);\n }\n return value;\n}\n\nfunction isGitDiffPatch(data: string): boolean {\n return data.startsWith('diff --git') || data.includes('\\ndiff --git');\n}\n\nfunction splitAtLinePrefix(contents: string, prefix: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const newlinePrefix = `\\n${prefix}`;\n const firstBoundaryIndex = contents.startsWith(prefix)\n ? 0\n : findLinePrefixIndex(contents, newlinePrefix, 0);\n if (firstBoundaryIndex === -1) {\n return [contents];\n }\n\n const parts: string[] = [];\n if (firstBoundaryIndex > 0) {\n parts.push(contents.slice(0, firstBoundaryIndex));\n }\n\n let startIndex = firstBoundaryIndex;\n for (;;) {\n const nextBoundaryIndex = findLinePrefixIndex(\n contents,\n newlinePrefix,\n startIndex + 1\n );\n if (nextBoundaryIndex === -1) {\n break;\n }\n\n parts.push(contents.slice(startIndex, nextBoundaryIndex));\n startIndex = nextBoundaryIndex;\n }\n parts.push(contents.slice(startIndex));\n return parts;\n}\n\nfunction findLinePrefixIndex(\n contents: string,\n newlinePrefix: string,\n fromIndex: number\n): number {\n const index = contents.indexOf(newlinePrefix, fromIndex);\n return index === -1 ? -1 : index + 1;\n}\n\nfunction maybeDetachOptionalString<T extends string | undefined>(value: T): T {\n return (value == null ? value : detachString(value)) as T;\n}\n\nfunction parseRawLineType(\n firstChar: string | undefined\n): Exclude<HunkLineType, 'expanded'> {\n return firstChar === ' '\n ? 'context'\n : firstChar === '\\\\'\n ? 'metadata'\n : firstChar === '+'\n ? 'addition'\n : 'deletion';\n}\n\nfunction getParsedLineContent(rawLine: string): string {\n const processedLine = rawLine.slice(1);\n return detachString(processedLine === '' ? '\\n' : processedLine);\n}\n\nfunction createContentGroup(\n type: 'change',\n deletionLineIndex: number,\n additionLineIndex: number\n): ChangeContent;\nfunction createContentGroup(\n type: 'context',\n deletionLineIndex: number,\n additionLineIndex: number\n): ContextContent;\nfunction createContentGroup(\n type: 'change' | 'context',\n deletionLineIndex: number,\n additionLineIndex: number\n): ChangeContent | ContextContent {\n if (type === 'change') {\n return {\n type: 'change',\n additions: 0,\n deletions: 0,\n additionLineIndex,\n deletionLineIndex,\n };\n }\n return {\n type: 'context',\n lines: 0,\n additionLineIndex,\n deletionLineIndex,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parsePatchFiles.js","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":["import {\n COMMIT_METADATA_SPLIT,\n FILENAME_HEADER_REGEX,\n GIT_DIFF_FILE_BREAK_REGEX,\n INDEX_LINE_METADATA,\n} from '../constants';\nimport type {\n ChangeContent,\n ContextContent,\n FileContents,\n FileDiffMetadata,\n Hunk,\n HunkLineType,\n ParsedPatch,\n} from '../types';\nimport { cleanLastNewline } from './cleanLastNewline';\nimport { composeCacheKey } from './composeCacheKey';\nimport { detachString, releaseStringDetachBuffer } from './detachString';\nimport {\n getHunkSideEndBoundary,\n getHunkSideStartBoundary,\n} from './getHunkSideBoundaries';\nimport { parseQuotedDiffFileName } from './parseQuotedDiffFileName';\nimport { realignChangeContentBySimilarity } from './realignChangeContent';\n\n// Keep quotes and transport prefixes until decoding, while allowing spaces in\n// unquoted paths and escaped quotes inside quoted paths.\nconst GIT_DIFF_HEADER_FILENAMES =\n /^diff --git (\"a\\/(?:[^\"\\\\]|\\\\.)*\"|a\\/.+?) (\"b\\/(?:[^\"\\\\]|\\\\.)*\"|b\\/.+?)$/;\n\ninterface ParsedHunkHeader {\n additionCount: number;\n additionStart: number;\n deletionCount: number;\n deletionStart: number;\n hunkContext?: string;\n}\n\nexport function processPatch(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError?: boolean\n): ParsedPatch {\n try {\n return _processPatch(data, cacheKeyPrefix, throwOnError);\n } finally {\n releaseStringDetachBuffer();\n }\n}\n\nfunction _processPatch(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError = false,\n patchIndex?: number\n): ParsedPatch {\n const isGitDiff = isGitDiffPatch(data);\n const rawFiles = isGitDiff\n ? splitGitDiffFiles(data)\n : splitUnifiedDiffFiles(data);\n let patchMetadata: string | undefined;\n const files: FileDiffMetadata[] = [];\n for (const fileOrPatchMetadata of rawFiles) {\n if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(fileOrPatchMetadata)) {\n if (patchMetadata == null) {\n patchMetadata = detachString(fileOrPatchMetadata);\n } else {\n if (throwOnError) {\n throw Error('parsePatchContent: unknown file blob');\n } else {\n console.error(\n 'parsePatchContent: unknown file blob:',\n fileOrPatchMetadata\n );\n }\n }\n // If we get in here, it's most likely the introductory metadata from the\n // patch, or something is fucked with the diff format\n continue;\n } else if (\n !isGitDiff &&\n !startsWithUnifiedDiffFileHeader(fileOrPatchMetadata)\n ) {\n if (patchMetadata == null) {\n patchMetadata = detachString(fileOrPatchMetadata);\n } else {\n if (throwOnError) {\n throw Error('parsePatchContent: unknown file blob');\n } else {\n console.error(\n 'parsePatchContent: unknown file blob:',\n fileOrPatchMetadata\n );\n }\n }\n continue;\n }\n const currentFile = _processFile(fileOrPatchMetadata, {\n cacheKey:\n cacheKeyPrefix != null\n ? patchIndex == null\n ? composeCacheKey(\n 'patch-file',\n cacheKeyPrefix,\n String(files.length)\n )\n : composeCacheKey(\n 'patch-file',\n cacheKeyPrefix,\n String(patchIndex),\n String(files.length)\n )\n : undefined,\n isGitDiff,\n throwOnError,\n });\n if (currentFile != null) {\n files.push(currentFile);\n }\n }\n return { patchMetadata, files };\n}\n\ninterface ProcessFileOptions {\n cacheKey?: string;\n isGitDiff?: boolean;\n oldFile?: FileContents;\n newFile?: FileContents;\n throwOnError?: boolean;\n}\n\nexport function processFile(\n fileDiffString: string,\n options?: ProcessFileOptions\n): FileDiffMetadata | undefined {\n try {\n return _processFile(fileDiffString, options);\n } finally {\n releaseStringDetachBuffer();\n }\n}\n\nfunction _processFile(\n fileDiffString: string,\n {\n cacheKey,\n isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(fileDiffString),\n oldFile,\n newFile,\n throwOnError = false,\n }: ProcessFileOptions = {}\n): FileDiffMetadata | undefined {\n let lastHunkEnd = 0;\n const hunks = splitAtLinePrefix(fileDiffString, '@@ ');\n let currentFile: FileDiffMetadata | undefined;\n const isPartial = oldFile == null || newFile == null;\n let deletionLineIndex = 0;\n let additionLineIndex = 0;\n for (const hunk of hunks) {\n const lines = splitWithNewlines(hunk);\n const firstLine = lines[0];\n if (firstLine == null) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid hunk');\n } else {\n console.error('parsePatchContent: invalid hunk', hunk);\n }\n continue;\n }\n const fileHeader = parseHunkHeader(firstLine);\n let additionLines = 0;\n let deletionLines = 0;\n // Setup currentFile, this should be the first iteration of our hunks, and\n // technically not a hunk\n if (fileHeader == null || currentFile == null) {\n if (currentFile != null) {\n if (throwOnError) {\n throw Error('parsePatchContent: Invalid hunk');\n } else {\n console.error('parsePatchContent: Invalid hunk', hunk);\n }\n continue;\n }\n currentFile = {\n name: '',\n type: 'change',\n hunks: [],\n splitLineCount: 0,\n unifiedLineCount: 0,\n isPartial,\n additionLines:\n !isPartial && oldFile != null && newFile != null\n ? splitFileContents(newFile.contents)\n : [],\n deletionLines:\n !isPartial && oldFile != null && newFile != null\n ? splitFileContents(oldFile.contents)\n : [],\n cacheKey: maybeDetachOptionalString(cacheKey),\n };\n // If either file is technically empty, then we should empty the\n // arrays respectively\n if (currentFile.additionLines.length === 1 && newFile?.contents === '') {\n currentFile.additionLines.length = 0;\n }\n if (currentFile.deletionLines.length === 1 && oldFile?.contents === '') {\n currentFile.deletionLines.length = 0;\n }\n\n for (const line of lines) {\n if (line.startsWith('diff --git')) {\n const filenameMatch = line.trim().match(GIT_DIFF_HEADER_FILENAMES);\n if (filenameMatch == null) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid git diff header');\n } else {\n console.error('parsePatchContent: invalid git diff header', line);\n }\n continue;\n }\n const prevName = decodeDiffFileName(filenameMatch[1], true);\n const name = decodeDiffFileName(filenameMatch[2], true);\n currentFile.name = detachString(name);\n if (prevName !== name) {\n currentFile.prevName = detachString(prevName);\n }\n continue;\n }\n\n const filenameMatch =\n line.startsWith('---') || line.startsWith('+++')\n ? line.match(FILENAME_HEADER_REGEX)\n : null;\n if (filenameMatch != null) {\n const [, type, rawFileName] = filenameMatch;\n const fileName = decodeDiffFileName(rawFileName, isGitDiff);\n if (type === '---' && fileName !== '/dev/null') {\n const detachedFileName = detachString(fileName);\n currentFile.prevName = detachedFileName;\n currentFile.name = detachedFileName;\n } else if (type === '+++' && fileName !== '/dev/null') {\n currentFile.name = detachString(fileName);\n }\n }\n // Git diffs have a bunch of additional metadata we can pull from\n else if (isGitDiff) {\n if (line.startsWith('new mode ')) {\n currentFile.mode = detachString(\n line.slice('new mode'.length).trim()\n );\n }\n if (line.startsWith('old mode ')) {\n currentFile.prevMode = detachString(\n line.slice('old mode'.length).trim()\n );\n }\n if (line.startsWith('new file mode')) {\n currentFile.type = 'new';\n currentFile.mode = detachString(\n line.slice('new file mode'.length).trim()\n );\n }\n if (line.startsWith('deleted file mode')) {\n currentFile.type = 'deleted';\n currentFile.mode = detachString(\n line.slice('deleted file mode'.length).trim()\n );\n }\n if (line.startsWith('similarity index')) {\n if (line.startsWith('similarity index 100%')) {\n currentFile.type = 'rename-pure';\n } else {\n currentFile.type = 'rename-changed';\n }\n }\n if (line.startsWith('index ')) {\n const [, prevObjectId, newObjectId, mode] =\n line.trim().match(INDEX_LINE_METADATA) ?? [];\n if (prevObjectId != null) {\n currentFile.prevObjectId = detachString(prevObjectId);\n }\n if (newObjectId != null) {\n currentFile.newObjectId = detachString(newObjectId);\n }\n if (mode != null) {\n currentFile.mode = detachString(mode);\n }\n }\n // Pure renames and copies have no --- / +++ headers. These paths\n // are already relative to the repository, without transport prefixes.\n if (\n line.startsWith('rename from ') ||\n line.startsWith('copy from ')\n ) {\n currentFile.prevName = detachString(\n decodeDiffFileName(\n line.slice(\n line.startsWith('rename')\n ? 'rename from '.length\n : 'copy from '.length\n )\n )\n );\n }\n if (line.startsWith('rename to ') || line.startsWith('copy to ')) {\n currentFile.name = detachString(\n decodeDiffFileName(\n line.slice(\n line.startsWith('rename')\n ? 'rename to '.length\n : 'copy to '.length\n )\n )\n );\n }\n }\n }\n continue;\n }\n\n // Otherwise, time to start parsing out the hunk\n let currentContent: ContextContent | ChangeContent | undefined;\n let lastLineType: 'context' | 'addition' | 'deletion' | undefined;\n\n // Strip trailing bare newlines (format-patch separators between commits)\n // if needed\n while (\n lines.length > 0 &&\n (lines[lines.length - 1] === '\\n' ||\n lines[lines.length - 1] === '\\r' ||\n lines[lines.length - 1] === '\\r\\n' ||\n lines[lines.length - 1] === '')\n ) {\n lines.pop();\n }\n\n const { additionStart, deletionStart } = fileHeader;\n deletionLineIndex = isPartial ? deletionLineIndex : deletionStart - 1;\n additionLineIndex = isPartial ? additionLineIndex : additionStart - 1;\n\n const hunkData: Hunk = {\n collapsedBefore: 0,\n\n splitLineCount: 0,\n splitLineStart: 0,\n\n unifiedLineCount: 0,\n unifiedLineStart: 0,\n\n additionCount: fileHeader.additionCount,\n additionStart,\n additionLines,\n\n deletionCount: fileHeader.deletionCount,\n deletionStart,\n deletionLines,\n\n deletionLineIndex,\n additionLineIndex,\n\n hunkContent: [],\n hunkContext: maybeDetachOptionalString(fileHeader.hunkContext),\n hunkSpecs: detachString(firstLine),\n\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n\n // Now we process each line of the hunk\n let parsedAdditionLines = 0;\n let parsedDeletionLines = 0;\n for (let lineIndex = 1; lineIndex < lines.length; lineIndex++) {\n const rawLine = lines[lineIndex];\n if (\n parsedAdditionLines >= hunkData.additionCount &&\n parsedDeletionLines >= hunkData.deletionCount &&\n !rawLine.startsWith('\\\\')\n ) {\n const isUnexpectedBodyLine =\n isHunkBodyLine(rawLine) && !isFormatPatchVersionSeparator(rawLine);\n if (!isUnexpectedBodyLine) {\n break;\n }\n if (throwOnError) {\n throw Error('parsePatchContent: hunk has more lines than expected');\n }\n }\n\n const firstChar = rawLine[0];\n // If we can't properly process the line, well, lets just try to salvage\n // things and continue... It's possible an AI generated diff might have\n // some stray blank lines or something in there\n if (\n firstChar !== '+' &&\n firstChar !== '-' &&\n firstChar !== ' ' &&\n firstChar !== '\\\\'\n ) {\n if (throwOnError) {\n throw Error('parsePatchContent: invalid hunk line');\n }\n console.error(\n `parseLineType: Invalid firstChar: \"${firstChar}\", full line: \"${rawLine}\"`\n );\n console.error('processFile: invalid rawLine:', rawLine);\n continue;\n }\n\n const type = parseRawLineType(firstChar);\n if (type === 'addition') {\n if (throwOnError && parsedAdditionLines >= hunkData.additionCount) {\n throw Error('parsePatchContent: hunk has too many addition lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup(\n 'change',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n additionLineIndex++;\n parsedAdditionLines++;\n if (isPartial) {\n currentFile.additionLines.push(line);\n }\n currentContent.additions++;\n additionLines++;\n lastLineType = 'addition';\n } else if (type === 'deletion') {\n if (throwOnError && parsedDeletionLines >= hunkData.deletionCount) {\n throw Error('parsePatchContent: hunk has too many deletion lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup(\n 'change',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n deletionLineIndex++;\n parsedDeletionLines++;\n if (isPartial) {\n currentFile.deletionLines.push(line);\n }\n currentContent.deletions++;\n deletionLines++;\n lastLineType = 'deletion';\n } else if (type === 'context') {\n if (\n throwOnError &&\n (parsedDeletionLines >= hunkData.deletionCount ||\n parsedAdditionLines >= hunkData.additionCount)\n ) {\n throw Error('parsePatchContent: hunk has too many context lines');\n }\n const line = getParsedLineContent(rawLine);\n if (currentContent == null || currentContent.type !== 'context') {\n currentContent = createContentGroup(\n 'context',\n deletionLineIndex,\n additionLineIndex\n );\n hunkData.hunkContent.push(currentContent);\n }\n additionLineIndex++;\n deletionLineIndex++;\n parsedAdditionLines++;\n parsedDeletionLines++;\n if (isPartial) {\n currentFile.deletionLines.push(line);\n currentFile.additionLines.push(line);\n }\n currentContent.lines++;\n lastLineType = 'context';\n } else if (type === 'metadata' && currentContent != null) {\n if (currentContent.type === 'context') {\n hunkData.noEOFCRAdditions = true;\n hunkData.noEOFCRDeletions = true;\n } else if (lastLineType === 'deletion') {\n hunkData.noEOFCRDeletions = true;\n } else if (lastLineType === 'addition') {\n hunkData.noEOFCRAdditions = true;\n }\n // If we're dealing with partial content from a diff, we need to strip\n // newlines manually from the content\n if (\n isPartial &&\n (lastLineType === 'addition' || lastLineType === 'context')\n ) {\n const lastIndex = currentFile.additionLines.length - 1;\n if (lastIndex >= 0) {\n currentFile.additionLines[lastIndex] = cleanLastNewline(\n currentFile.additionLines[lastIndex]\n );\n }\n }\n if (\n isPartial &&\n (lastLineType === 'deletion' || lastLineType === 'context')\n ) {\n const lastIndex = currentFile.deletionLines.length - 1;\n if (lastIndex >= 0) {\n currentFile.deletionLines[lastIndex] = cleanLastNewline(\n currentFile.deletionLines[lastIndex]\n );\n }\n }\n }\n }\n\n if (\n parsedAdditionLines !== hunkData.additionCount ||\n parsedDeletionLines !== hunkData.deletionCount\n ) {\n if (throwOnError) {\n throw Error('parsePatchContent: hunk line count mismatch');\n }\n console.error(\n `parsePatchContent: hunk line count mismatch: \"${firstLine.trimEnd()}\", declared old/new ${hunkData.deletionCount}/${hunkData.additionCount}, parsed old/new ${parsedDeletionLines}/${parsedAdditionLines}`\n );\n // Re-encode each original boundary using the repaired count. Zero-count\n // ranges use the boundary itself as their start; positive ranges use +1.\n const repairedAdditionStart =\n getHunkSideStartBoundary(\n hunkData.additionStart,\n hunkData.additionCount\n ) + (parsedAdditionLines === 0 ? 0 : 1);\n const repairedDeletionStart =\n getHunkSideStartBoundary(\n hunkData.deletionStart,\n hunkData.deletionCount\n ) + (parsedDeletionLines === 0 ? 0 : 1);\n\n // Hydrated hunks index into full-file line arrays, so their indexes must\n // move with repaired starts. Partial hunks index patch-built arrays.\n if (!isPartial) {\n const additionStartDelta =\n repairedAdditionStart - hunkData.additionStart;\n const deletionStartDelta =\n repairedDeletionStart - hunkData.deletionStart;\n hunkData.additionLineIndex += additionStartDelta;\n hunkData.deletionLineIndex += deletionStartDelta;\n for (const content of hunkData.hunkContent) {\n content.additionLineIndex += additionStartDelta;\n content.deletionLineIndex += deletionStartDelta;\n }\n }\n\n hunkData.additionStart = repairedAdditionStart;\n hunkData.deletionStart = repairedDeletionStart;\n hunkData.additionCount = parsedAdditionLines;\n hunkData.deletionCount = parsedDeletionLines;\n }\n\n hunkData.additionLines = additionLines;\n hunkData.deletionLines = deletionLines;\n\n hunkData.collapsedBefore = Math.max(\n getHunkSideStartBoundary(hunkData.additionStart, hunkData.additionCount) -\n lastHunkEnd,\n 0\n );\n currentFile.hunks.push(hunkData);\n lastHunkEnd = getHunkSideEndBoundary(\n hunkData.additionStart,\n hunkData.additionCount\n );\n for (const content of hunkData.hunkContent) {\n if (content.type === 'context') {\n hunkData.splitLineCount += content.lines;\n hunkData.unifiedLineCount += content.lines;\n } else {\n hunkData.splitLineCount += Math.max(\n content.additions,\n content.deletions\n );\n hunkData.unifiedLineCount += content.deletions + content.additions;\n }\n }\n hunkData.splitLineStart =\n currentFile.splitLineCount + hunkData.collapsedBefore;\n hunkData.unifiedLineStart =\n currentFile.unifiedLineCount + hunkData.collapsedBefore;\n\n currentFile.splitLineCount +=\n hunkData.collapsedBefore + hunkData.splitLineCount;\n currentFile.unifiedLineCount +=\n hunkData.collapsedBefore + hunkData.unifiedLineCount;\n }\n if (currentFile == null) {\n return undefined;\n }\n if (\n throwOnError &&\n isPartial &&\n !isGitDiff &&\n currentFile.hunks.length === 0\n ) {\n throw Error('parsePatchContent: unified file has no hunks');\n }\n\n // Account for collapsed lines after the final hunk and increment the\n // split/unified counts properly\n if (\n currentFile.hunks.length > 0 &&\n !isPartial &&\n currentFile.additionLines.length > 0 &&\n currentFile.deletionLines.length > 0\n ) {\n const lastHunk = currentFile.hunks[currentFile.hunks.length - 1];\n const lastHunkEnd = getHunkSideEndBoundary(\n lastHunk.additionStart,\n lastHunk.additionCount\n );\n const totalFileLines = currentFile.additionLines.length;\n const collapsedAfter = Math.max(totalFileLines - lastHunkEnd, 0);\n currentFile.splitLineCount += collapsedAfter;\n currentFile.unifiedLineCount += collapsedAfter;\n }\n\n // If this isn't a git diff style patch, then we'll need to sus out some\n // additional metadata manually\n if (!isGitDiff) {\n if (\n currentFile.prevName != null &&\n currentFile.name !== currentFile.prevName\n ) {\n if (currentFile.hunks.length > 0) {\n currentFile.type = 'rename-changed';\n } else {\n currentFile.type = 'rename-pure';\n }\n }\n // Sort of a hack for detecting deleted/added files...\n else if (\n (oldFile == null || oldFile.contents === '') &&\n newFile != null &&\n newFile.contents !== ''\n ) {\n currentFile.type = 'new';\n } else if (\n oldFile != null &&\n oldFile.contents !== '' &&\n (newFile == null || newFile.contents === '')\n ) {\n currentFile.type = 'deleted';\n }\n }\n if (\n currentFile.type !== 'rename-pure' &&\n currentFile.type !== 'rename-changed'\n ) {\n currentFile.prevName = undefined;\n }\n // Pair change-block lines by similarity instead of the patch's positional\n // ordering. Partial diffs work too: their hunk line indexes point into the\n // patch-built line arrays, which hold every line a block references.\n realignChangeContentBySimilarity(currentFile);\n return currentFile;\n}\n\n/**\n * Parses a patch file string into an array of parsed patches.\n *\n * @param data - The raw patch file content (supports multi-commit patches)\n * @param cacheKeyPrefix - Optional prefix for collision-safe cache keys derived\n * from the prefix, patch index, and file index. This enables caching of\n * rendered diff results in the worker pool.\n * @param throwOnError - When true, invalid data throws. When false, invalid data\n * is reported with `console.error` and the parser attempts to recover when\n * possible. Recovery is best-effort and does not guarantee valid output.\n */\nexport function parsePatchFiles(\n data: string,\n cacheKeyPrefix?: string,\n throwOnError = false\n): ParsedPatch[] {\n // NOTE(amadeus): This function is pretty forgiving in that it can accept a\n // patch file that includes commit metdata, multiple commits, or not\n const patches: ParsedPatch[] = [];\n const rawPatches = hasCommitMetadataBoundary(data)\n ? data.split(COMMIT_METADATA_SPLIT)\n : [data];\n for (const patch of rawPatches) {\n try {\n patches.push(\n _processPatch(\n patch,\n cacheKeyPrefix,\n throwOnError,\n cacheKeyPrefix != null ? patches.length : undefined\n )\n );\n } catch (error) {\n if (throwOnError) {\n throw error;\n } else {\n console.error(error);\n }\n } finally {\n releaseStringDetachBuffer();\n }\n }\n return patches;\n}\n\n// Decode only quoted tokens, preserving whitespace inside them. Git's a/ and\n// b/ prefixes are part of the quoted token and must be removed after decoding.\nfunction decodeDiffFileName(value: string, stripGitPrefix = false): string {\n const rawName = value.trim();\n const name = rawName.startsWith('\"')\n ? (parseQuotedDiffFileName(rawName)?.fileName ?? rawName)\n : rawName;\n return stripGitPrefix && (name.startsWith('a/') || name.startsWith('b/'))\n ? name.slice(2)\n : name;\n}\n\nfunction hasCommitMetadataBoundary(data: string): boolean {\n return data.startsWith('From ') || data.includes('\\nFrom ');\n}\n\nfunction splitFileContents(contents: string): string[] {\n const lines = splitWithNewlines(contents);\n for (let index = 0; index < lines.length; index++) {\n lines[index] = detachString(lines[index]);\n }\n return lines;\n}\n\nfunction splitWithNewlines(contents: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const lines: string[] = [];\n let startIndex = 0;\n for (;;) {\n const newlineIndex = contents.indexOf('\\n', startIndex);\n if (newlineIndex === -1) {\n break;\n }\n\n lines.push(contents.slice(startIndex, newlineIndex + 1));\n startIndex = newlineIndex + 1;\n }\n\n if (startIndex < contents.length) {\n lines.push(contents.slice(startIndex));\n }\n return lines;\n}\n\nfunction splitGitDiffFiles(contents: string): string[] {\n return splitAtLinePrefix(contents, 'diff --git');\n}\n\nfunction splitUnifiedDiffFiles(contents: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const parts: string[] = [];\n let partStartIndex = 0;\n let lineStartIndex = 0;\n let remainingDeletionLines = 0;\n let remainingAdditionLines = 0;\n let hasOpenedUnifiedFile = false;\n\n while (lineStartIndex < contents.length) {\n const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);\n if (remainingDeletionLines <= 0 && remainingAdditionLines <= 0) {\n if (isUnifiedDiffFileHeaderAt(contents, lineStartIndex)) {\n if (lineStartIndex > partStartIndex) {\n parts.push(contents.slice(partStartIndex, lineStartIndex));\n }\n partStartIndex = lineStartIndex;\n hasOpenedUnifiedFile = true;\n lineStartIndex = getNextLineStartIndex(contents, nextLineStartIndex);\n continue;\n }\n\n if (hasOpenedUnifiedFile && contents.startsWith('@@ -', lineStartIndex)) {\n const fileHeader = parseHunkHeader(\n contents.slice(lineStartIndex, nextLineStartIndex)\n );\n if (fileHeader != null) {\n remainingDeletionLines = fileHeader.deletionCount;\n remainingAdditionLines = fileHeader.additionCount;\n }\n }\n lineStartIndex = nextLineStartIndex;\n continue;\n }\n\n const firstChar = contents[lineStartIndex];\n if (firstChar === '\\\\') {\n lineStartIndex = nextLineStartIndex;\n continue;\n }\n\n if (firstChar === ' ') {\n remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);\n remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);\n } else if (firstChar === '-') {\n remainingDeletionLines = Math.max(remainingDeletionLines - 1, 0);\n } else if (firstChar === '+') {\n remainingAdditionLines = Math.max(remainingAdditionLines - 1, 0);\n }\n lineStartIndex = nextLineStartIndex;\n }\n\n parts.push(contents.slice(partStartIndex));\n return parts;\n}\n\nfunction startsWithUnifiedDiffFileHeader(contents: string): boolean {\n return isUnifiedDiffFileHeaderAt(contents, 0);\n}\n\nfunction isUnifiedDiffFileHeaderAt(contents: string, lineStartIndex: number) {\n const nextLineStartIndex = getNextLineStartIndex(contents, lineStartIndex);\n return (\n isUnifiedDiffHeaderLineAt(contents, lineStartIndex, '---') &&\n isUnifiedDiffHeaderLineAt(contents, nextLineStartIndex, '+++')\n );\n}\n\nfunction isUnifiedDiffHeaderLineAt(\n contents: string,\n lineStartIndex: number,\n prefix: '---' | '+++'\n): boolean {\n if (!contents.startsWith(prefix, lineStartIndex)) {\n return false;\n }\n\n const separator = contents[lineStartIndex + prefix.length];\n if (separator !== ' ' && separator !== '\\t') {\n return false;\n }\n\n for (\n let index = lineStartIndex + prefix.length + 1;\n index < contents.length;\n index++\n ) {\n const char = contents[index];\n if (char === '\\n' || char === '\\r') {\n break;\n }\n if (char !== ' ' && char !== '\\t') {\n return true;\n }\n }\n return false;\n}\n\nfunction getNextLineStartIndex(\n contents: string,\n lineStartIndex: number\n): number {\n const newlineIndex = contents.indexOf('\\n', lineStartIndex);\n return newlineIndex === -1 ? contents.length : newlineIndex + 1;\n}\n\nfunction isHunkBodyLine(line: string): boolean {\n const firstChar = line[0];\n return firstChar === '+' || firstChar === '-' || firstChar === ' ';\n}\n\nfunction isFormatPatchVersionSeparator(line: string): boolean {\n if (!line.startsWith('--')) {\n return false;\n }\n\n for (let index = 2; index < line.length; index++) {\n const char = line[index];\n if (char !== ' ' && char !== '\\t' && char !== '\\n' && char !== '\\r') {\n return false;\n }\n }\n return true;\n}\n\nfunction parseHunkHeader(line: string): ParsedHunkHeader | undefined {\n if (!line.startsWith('@@ -')) {\n return undefined;\n }\n\n let index = 4;\n const deletionStartResult = readPositiveInteger(line, index);\n if (deletionStartResult == null) {\n return undefined;\n }\n const deletionStart = deletionStartResult.value;\n index = deletionStartResult.endIndex;\n\n let deletionCount = 1;\n if (line[index] === ',') {\n const deletionCountResult = readPositiveInteger(line, index + 1);\n if (deletionCountResult == null) {\n return undefined;\n }\n deletionCount = deletionCountResult.value;\n index = deletionCountResult.endIndex;\n }\n\n if (line[index] !== ' ' || line[index + 1] !== '+') {\n return undefined;\n }\n index += 2;\n\n const additionStartResult = readPositiveInteger(line, index);\n if (additionStartResult == null) {\n return undefined;\n }\n const additionStart = additionStartResult.value;\n index = additionStartResult.endIndex;\n\n let additionCount = 1;\n if (line[index] === ',') {\n const additionCountResult = readPositiveInteger(line, index + 1);\n if (additionCountResult == null) {\n return undefined;\n }\n additionCount = additionCountResult.value;\n index = additionCountResult.endIndex;\n }\n\n if (\n line[index] !== ' ' ||\n line[index + 1] !== '@' ||\n line[index + 2] !== '@'\n ) {\n return undefined;\n }\n\n let hunkContext: string | undefined;\n const contextStartIndex = index + 3;\n if (line[contextStartIndex] === ' ') {\n hunkContext = trimLineEnd(line.slice(contextStartIndex + 1));\n }\n\n return {\n additionCount,\n additionStart,\n deletionCount,\n deletionStart,\n hunkContext,\n };\n}\n\nfunction readPositiveInteger(\n value: string,\n startIndex: number\n): { value: number; endIndex: number } | undefined {\n let index = startIndex;\n let parsedValue = 0;\n for (; index < value.length; index++) {\n const digit = value.charCodeAt(index) - 48;\n if (digit < 0 || digit > 9) {\n break;\n }\n parsedValue = parsedValue * 10 + digit;\n }\n\n if (index === startIndex) {\n return undefined;\n }\n return { value: parsedValue, endIndex: index };\n}\n\nfunction trimLineEnd(value: string): string {\n if (value.endsWith('\\r\\n')) {\n return value.slice(0, -2);\n }\n if (value.endsWith('\\n')) {\n return value.slice(0, -1);\n }\n return value;\n}\n\nfunction isGitDiffPatch(data: string): boolean {\n return data.startsWith('diff --git') || data.includes('\\ndiff --git');\n}\n\nfunction splitAtLinePrefix(contents: string, prefix: string): string[] {\n if (contents.length === 0) {\n return [''];\n }\n\n const newlinePrefix = `\\n${prefix}`;\n const firstBoundaryIndex = contents.startsWith(prefix)\n ? 0\n : findLinePrefixIndex(contents, newlinePrefix, 0);\n if (firstBoundaryIndex === -1) {\n return [contents];\n }\n\n const parts: string[] = [];\n if (firstBoundaryIndex > 0) {\n parts.push(contents.slice(0, firstBoundaryIndex));\n }\n\n let startIndex = firstBoundaryIndex;\n for (;;) {\n const nextBoundaryIndex = findLinePrefixIndex(\n contents,\n newlinePrefix,\n startIndex + 1\n );\n if (nextBoundaryIndex === -1) {\n break;\n }\n\n parts.push(contents.slice(startIndex, nextBoundaryIndex));\n startIndex = nextBoundaryIndex;\n }\n parts.push(contents.slice(startIndex));\n return parts;\n}\n\nfunction findLinePrefixIndex(\n contents: string,\n newlinePrefix: string,\n fromIndex: number\n): number {\n const index = contents.indexOf(newlinePrefix, fromIndex);\n return index === -1 ? -1 : index + 1;\n}\n\nfunction maybeDetachOptionalString<T extends string | undefined>(value: T): T {\n return (value == null ? value : detachString(value)) as T;\n}\n\nfunction parseRawLineType(\n firstChar: string | undefined\n): Exclude<HunkLineType, 'expanded'> {\n return firstChar === ' '\n ? 'context'\n : firstChar === '\\\\'\n ? 'metadata'\n : firstChar === '+'\n ? 'addition'\n : 'deletion';\n}\n\nfunction getParsedLineContent(rawLine: string): string {\n const processedLine = rawLine.slice(1);\n return detachString(processedLine === '' ? '\\n' : processedLine);\n}\n\nfunction createContentGroup(\n type: 'change',\n deletionLineIndex: number,\n additionLineIndex: number\n): ChangeContent;\nfunction createContentGroup(\n type: 'context',\n deletionLineIndex: number,\n additionLineIndex: number\n): ContextContent;\nfunction createContentGroup(\n type: 'change' | 'context',\n deletionLineIndex: number,\n additionLineIndex: number\n): ChangeContent | ContextContent {\n if (type === 'change') {\n return {\n type: 'change',\n additions: 0,\n deletions: 0,\n additionLineIndex,\n deletionLineIndex,\n };\n }\n return {\n type: 'context',\n lines: 0,\n additionLineIndex,\n deletionLineIndex,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/utils/parseQuotedDiffFileName.d.ts
|
|
2
|
+
declare function parseQuotedDiffFileName(input: string): {
|
|
3
|
+
fileName: string;
|
|
4
|
+
rawLength: number;
|
|
5
|
+
} | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { parseQuotedDiffFileName };
|
|
8
|
+
//# sourceMappingURL=parseQuotedDiffFileName.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseQuotedDiffFileName.d.ts","names":[],"sources":["../../src/utils/parseQuotedDiffFileName.ts"],"mappings":";iBAegB,wBACd;EACG;EAAkB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/utils/parseQuotedDiffFileName.ts
|
|
2
|
+
const filenameDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
3
|
+
const NAMED_ESCAPES = {
|
|
4
|
+
"\"": "\"",
|
|
5
|
+
"\\": "\\",
|
|
6
|
+
a: "\x07",
|
|
7
|
+
b: "\b",
|
|
8
|
+
t: " ",
|
|
9
|
+
n: "\n",
|
|
10
|
+
v: "\v",
|
|
11
|
+
f: "\f",
|
|
12
|
+
r: "\r"
|
|
13
|
+
};
|
|
14
|
+
function parseQuotedDiffFileName(input) {
|
|
15
|
+
if (input[0] !== "\"") return;
|
|
16
|
+
let fileName = "";
|
|
17
|
+
let index = 1;
|
|
18
|
+
while (index < input.length) {
|
|
19
|
+
const char = input[index];
|
|
20
|
+
if (char === "\"") return {
|
|
21
|
+
fileName,
|
|
22
|
+
rawLength: index + 1
|
|
23
|
+
};
|
|
24
|
+
if (char !== "\\") {
|
|
25
|
+
fileName += char;
|
|
26
|
+
index++;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const escaped = NAMED_ESCAPES[input[index + 1]];
|
|
30
|
+
if (escaped != null) {
|
|
31
|
+
fileName += escaped;
|
|
32
|
+
index += 2;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const bytes = [];
|
|
36
|
+
do {
|
|
37
|
+
const byte = readOctalByte(input, index + 1);
|
|
38
|
+
if (byte == null) return;
|
|
39
|
+
bytes.push(byte);
|
|
40
|
+
index += 4;
|
|
41
|
+
} while (input[index] === "\\" && input[index + 1] >= "0" && input[index + 1] <= "7");
|
|
42
|
+
fileName += filenameDecoder.decode(new Uint8Array(bytes));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readOctalByte(input, index) {
|
|
46
|
+
const first = input.charCodeAt(index) - 48;
|
|
47
|
+
const second = input.charCodeAt(index + 1) - 48;
|
|
48
|
+
const third = input.charCodeAt(index + 2) - 48;
|
|
49
|
+
if (first >= 0 && first <= 3 && second >= 0 && second <= 7 && third >= 0 && third <= 7) return first * 64 + second * 8 + third;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { parseQuotedDiffFileName };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=parseQuotedDiffFileName.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseQuotedDiffFileName.js","names":[],"sources":["../../src/utils/parseQuotedDiffFileName.ts"],"sourcesContent":["const filenameDecoder = new TextDecoder('utf-8', { ignoreBOM: true });\nconst NAMED_ESCAPES: Record<string, string | undefined> = {\n '\"': '\"',\n '\\\\': '\\\\',\n a: '\\x07',\n b: '\\b',\n t: '\\t',\n n: '\\n',\n v: '\\v',\n f: '\\f',\n r: '\\r',\n};\n\n// Reads one quoted Git/jsdiff filename and reports where the token ends.\n// Adjacent octal escapes encode UTF-8 bytes, so they must be decoded together.\nexport function parseQuotedDiffFileName(\n input: string\n): { fileName: string; rawLength: number } | undefined {\n if (input[0] !== '\"') {\n return undefined;\n }\n\n let fileName = '';\n let index = 1;\n while (index < input.length) {\n const char = input[index];\n if (char === '\"') {\n return { fileName, rawLength: index + 1 };\n }\n if (char !== '\\\\') {\n fileName += char;\n index++;\n continue;\n }\n\n const escaped = NAMED_ESCAPES[input[index + 1]];\n if (escaped != null) {\n fileName += escaped;\n index += 2;\n continue;\n }\n\n const bytes: number[] = [];\n do {\n const byte = readOctalByte(input, index + 1);\n if (byte == null) {\n return undefined;\n }\n bytes.push(byte);\n index += 4;\n } while (\n input[index] === '\\\\' &&\n input[index + 1] >= '0' &&\n input[index + 1] <= '7'\n );\n fileName += filenameDecoder.decode(new Uint8Array(bytes));\n }\n return undefined;\n}\n\n// Requires three octal digits in the byte range so malformed escapes cannot\n// be truncated or wrapped when copied into a Uint8Array.\nfunction readOctalByte(input: string, index: number): number | undefined {\n const first = input.charCodeAt(index) - 48;\n const second = input.charCodeAt(index + 1) - 48;\n const third = input.charCodeAt(index + 2) - 48;\n if (\n first >= 0 &&\n first <= 3 &&\n second >= 0 &&\n second <= 7 &&\n third >= 0 &&\n third <= 7\n ) {\n return first * 64 + second * 8 + third;\n }\n return undefined;\n}\n"],"mappings":";AAAA,MAAM,kBAAkB,IAAI,YAAY,SAAS,EAAE,WAAW,KAAK,CAAC;AACpE,MAAM,gBAAoD;CACxD,MAAK;CACL,MAAM;CACN,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAIA,SAAgB,wBACd,OACqD;CACrD,IAAI,MAAM,OAAO,MACf;CAGF,IAAI,WAAW;CACf,IAAI,QAAQ;CACZ,OAAO,QAAQ,MAAM,QAAQ;EAC3B,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,MACX,OAAO;GAAE;GAAU,WAAW,QAAQ;EAAE;EAE1C,IAAI,SAAS,MAAM;GACjB,YAAY;GACZ;GACA;EACF;EAEA,MAAM,UAAU,cAAc,MAAM,QAAQ;EAC5C,IAAI,WAAW,MAAM;GACnB,YAAY;GACZ,SAAS;GACT;EACF;EAEA,MAAM,QAAkB,CAAC;EACzB,GAAG;GACD,MAAM,OAAO,cAAc,OAAO,QAAQ,CAAC;GAC3C,IAAI,QAAQ,MACV;GAEF,MAAM,KAAK,IAAI;GACf,SAAS;EACX,SACE,MAAM,WAAW,QACjB,MAAM,QAAQ,MAAM,OACpB,MAAM,QAAQ,MAAM;EAEtB,YAAY,gBAAgB,OAAO,IAAI,WAAW,KAAK,CAAC;CAC1D;AAEF;AAIA,SAAS,cAAc,OAAe,OAAmC;CACvE,MAAM,QAAQ,MAAM,WAAW,KAAK,IAAI;CACxC,MAAM,SAAS,MAAM,WAAW,QAAQ,CAAC,IAAI;CAC7C,MAAM,QAAQ,MAAM,WAAW,QAAQ,CAAC,IAAI;CAC5C,IACE,SAAS,KACT,SAAS,KACT,UAAU,KACV,UAAU,KACV,SAAS,KACT,SAAS,GAET,OAAO,QAAQ,KAAK,SAAS,IAAI;AAGrC"}
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import { FileDiffMetadata, Hunk } from "../types.js";
|
|
1
|
+
import { ChangeContent, FileDiffMetadata, Hunk } from "../types.js";
|
|
2
2
|
//#region src/utils/realignChangeContent.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Re-split count-mismatched change blocks in every hunk so paired lines are
|
|
5
|
-
* chosen by content similarity instead of position
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* split/unified rows as the original).
|
|
5
|
+
* chosen by content similarity instead of position. Mutates `hunks` in place
|
|
6
|
+
* without moving changes across context lines; rendered row counts are
|
|
7
|
+
* unchanged (a split block covers the same split/unified rows as the original).
|
|
9
8
|
*/
|
|
10
9
|
declare function realignChangeContentBySimilarity(diff: Pick<FileDiffMetadata, 'hunks' | 'additionLines' | 'deletionLines'>): void;
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
* the blank run they sit in. Adding or removing a blank line next to
|
|
11
|
+
* During editing, slide pure insert/delete blocks made entirely of blank lines
|
|
12
|
+
* to the top of the blank run they sit in. Adding or removing a blank line next to
|
|
14
13
|
* existing blanks is ambiguous, and the diff library reports the change at
|
|
15
14
|
* the run's bottom — so pressing Enter at the end of a line marks a blank
|
|
16
15
|
* *below* the caret as inserted while the caret's own new line renders as
|
|
17
16
|
* context. Sliding up anchors the change to the content above it (the caret
|
|
18
17
|
* line after an Enter) instead.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* `resolveSlide` receives each block that qualifies along with the full
|
|
20
|
+
* distance to the run's top and returns how far to actually move it; 0 keeps
|
|
21
|
+
* the parsed position. Without it, every qualifying block slides to the top.
|
|
22
|
+
*
|
|
23
|
+
* The slide only applies when the block comes to rest directly beneath
|
|
24
|
+
* remaining in-hunk content. A slide that would consume the
|
|
22
25
|
* hunk's entire leading context was stopped by the hunk's edge — a context
|
|
23
26
|
* window cut, not the top of the blank run — and that landing spot is
|
|
24
27
|
* arbitrary, so the block keeps the library's bottom-of-run anchor (which
|
|
@@ -26,7 +29,7 @@ declare function realignChangeContentBySimilarity(diff: Pick<FileDiffMetadata, '
|
|
|
26
29
|
* code that merely ends like its neighbor (an added function before an
|
|
27
30
|
* identical `}`) keeps the library's canonical position.
|
|
28
31
|
*/
|
|
29
|
-
declare function slideBlankBoundaryBlocksUp(hunk: Hunk, diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'
|
|
32
|
+
declare function slideBlankBoundaryBlocksUp(hunk: Hunk, diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'>, resolveSlide?: (block: ChangeContent, maxSlide: number) => number): void;
|
|
30
33
|
//#endregion
|
|
31
34
|
export { realignChangeContentBySimilarity, slideBlankBoundaryBlocksUp };
|
|
32
35
|
//# sourceMappingURL=realignChangeContent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realignChangeContent.d.ts","names":[],"sources":["../../src/utils/realignChangeContent.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"realignChangeContent.d.ts","names":[],"sources":["../../src/utils/realignChangeContent.ts"],"mappings":";;;;;;;;iBAkCgB,iCACd,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;iBAuCG,2BACd,MAAM,MACN,MAAM,KAAK,sDACX,gBAAgB,OAAO,eAAe"}
|
|
@@ -3,36 +3,36 @@ const MAX_ALIGNMENT_COMPARISONS = 4096;
|
|
|
3
3
|
const MIN_IMPROVEMENT_PER_PAIR = .5;
|
|
4
4
|
/**
|
|
5
5
|
* Re-split count-mismatched change blocks in every hunk so paired lines are
|
|
6
|
-
* chosen by content similarity instead of position
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* split/unified rows as the original).
|
|
6
|
+
* chosen by content similarity instead of position. Mutates `hunks` in place
|
|
7
|
+
* without moving changes across context lines; rendered row counts are
|
|
8
|
+
* unchanged (a split block covers the same split/unified rows as the original).
|
|
10
9
|
*/
|
|
11
10
|
function realignChangeContentBySimilarity(diff) {
|
|
12
|
-
for (const hunk of diff.hunks) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
index += replacement.length - 1;
|
|
20
|
-
}
|
|
11
|
+
for (const hunk of diff.hunks) for (let index = 0; index < hunk.hunkContent.length; index++) {
|
|
12
|
+
const content = hunk.hunkContent[index];
|
|
13
|
+
if (content.type !== "change") continue;
|
|
14
|
+
const replacement = realignChangeBlock(diff, content);
|
|
15
|
+
if (replacement != null) {
|
|
16
|
+
hunk.hunkContent.splice(index, 1, ...replacement);
|
|
17
|
+
index += replacement.length - 1;
|
|
21
18
|
}
|
|
22
|
-
slideBlankBoundaryBlocksUp(hunk, diff);
|
|
23
19
|
}
|
|
24
20
|
}
|
|
25
21
|
/**
|
|
26
|
-
*
|
|
27
|
-
* the blank run they sit in. Adding or removing a blank line next to
|
|
22
|
+
* During editing, slide pure insert/delete blocks made entirely of blank lines
|
|
23
|
+
* to the top of the blank run they sit in. Adding or removing a blank line next to
|
|
28
24
|
* existing blanks is ambiguous, and the diff library reports the change at
|
|
29
25
|
* the run's bottom — so pressing Enter at the end of a line marks a blank
|
|
30
26
|
* *below* the caret as inserted while the caret's own new line renders as
|
|
31
27
|
* context. Sliding up anchors the change to the content above it (the caret
|
|
32
28
|
* line after an Enter) instead.
|
|
33
29
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* `resolveSlide` receives each block that qualifies along with the full
|
|
31
|
+
* distance to the run's top and returns how far to actually move it; 0 keeps
|
|
32
|
+
* the parsed position. Without it, every qualifying block slides to the top.
|
|
33
|
+
*
|
|
34
|
+
* The slide only applies when the block comes to rest directly beneath
|
|
35
|
+
* remaining in-hunk content. A slide that would consume the
|
|
36
36
|
* hunk's entire leading context was stopped by the hunk's edge — a context
|
|
37
37
|
* window cut, not the top of the blank run — and that landing spot is
|
|
38
38
|
* arbitrary, so the block keeps the library's bottom-of-run anchor (which
|
|
@@ -40,7 +40,7 @@ function realignChangeContentBySimilarity(diff) {
|
|
|
40
40
|
* code that merely ends like its neighbor (an added function before an
|
|
41
41
|
* identical `}`) keeps the library's canonical position.
|
|
42
42
|
*/
|
|
43
|
-
function slideBlankBoundaryBlocksUp(hunk, diff) {
|
|
43
|
+
function slideBlankBoundaryBlocksUp(hunk, diff, resolveSlide) {
|
|
44
44
|
const { hunkContent } = hunk;
|
|
45
45
|
for (let index = 1; index < hunkContent.length; index++) {
|
|
46
46
|
const block = hunkContent[index];
|
|
@@ -62,6 +62,10 @@ function slideBlankBoundaryBlocksUp(hunk, diff) {
|
|
|
62
62
|
while (slide < previous.lines && diff.additionLines[previous.additionLineIndex + previous.lines - 1 - slide] === unit) slide++;
|
|
63
63
|
if (slide === 0) continue;
|
|
64
64
|
if (index === 1 && slide === previous.lines) continue;
|
|
65
|
+
if (resolveSlide != null) {
|
|
66
|
+
slide = resolveSlide(block, slide);
|
|
67
|
+
if (slide === 0) continue;
|
|
68
|
+
}
|
|
65
69
|
block.additionLineIndex -= slide;
|
|
66
70
|
block.deletionLineIndex -= slide;
|
|
67
71
|
const blockAdditionEnd = block.additionLineIndex + block.additions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realignChangeContent.js","names":[],"sources":["../../src/utils/realignChangeContent.ts"],"sourcesContent":["import type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n} from '../types';\n\n// The diff library emits one change block per replaced run, ordering every\n// deleted line before every added line. Renderers pair a block's lines\n// positionally (deletion[i] across from addition[i] in split view), so a\n// block like { deletions: 1, additions: 2 } pairs the deleted line with\n// whichever addition happens to come first — even when a later addition is\n// the edited version of it (e.g. pressing Enter above a changed line pushes\n// a blank line in front of it). These helpers re-split such blocks so the\n// most similar lines pair up and the surplus renders as pure insert/delete\n// rows at the block's edges.\n\n// Skip realignment when a block would need more than this many line\n// comparisons; pathological blocks keep the library's positional pairing.\nconst MAX_ALIGNMENT_COMPARISONS = 4096;\n\n// A shifted pairing must beat the positional one by this much per paired\n// line before the block is re-split. Near-ties (e.g. lines that merely share\n// an import-statement shape) keep the library's canonical order; the\n// realignment is meant for decisive wins like a blank or unrelated inserted\n// line displacing an edited one, where the gap approaches 1.\nconst MIN_IMPROVEMENT_PER_PAIR = 0.5;\n\n/**\n * Re-split count-mismatched change blocks in every hunk so paired lines are\n * chosen by content similarity instead of position, then slide blank-line\n * insert/delete blocks to the top of their blank run. Mutates `hunks` in\n * place; rendered row counts are unchanged (a split block covers the same\n * split/unified rows as the original).\n */\nexport function realignChangeContentBySimilarity(\n diff: Pick<FileDiffMetadata, 'hunks' | 'additionLines' | 'deletionLines'>\n): void {\n for (const hunk of diff.hunks) {\n for (let index = 0; index < hunk.hunkContent.length; index++) {\n const content = hunk.hunkContent[index];\n if (content.type !== 'change') {\n continue;\n }\n const replacement = realignChangeBlock(diff, content);\n if (replacement != null) {\n hunk.hunkContent.splice(index, 1, ...replacement);\n index += replacement.length - 1;\n }\n }\n slideBlankBoundaryBlocksUp(hunk, diff);\n }\n}\n\n/**\n * Slide pure insert/delete blocks made entirely of blank lines to the top of\n * the blank run they sit in. Adding or removing a blank line next to\n * existing blanks is ambiguous, and the diff library reports the change at\n * the run's bottom — so pressing Enter at the end of a line marks a blank\n * *below* the caret as inserted while the caret's own new line renders as\n * context. Sliding up anchors the change to the content above it (the caret\n * line after an Enter) instead.\n *\n * The slide is all-or-nothing: it only applies when the block comes to rest\n * directly beneath remaining in-hunk content. A slide that would consume the\n * hunk's entire leading context was stopped by the hunk's edge — a context\n * window cut, not the top of the blank run — and that landing spot is\n * arbitrary, so the block keeps the library's bottom-of-run anchor (which\n * sits against the content below the run). Non-blank blocks never slide, so\n * code that merely ends like its neighbor (an added function before an\n * identical `}`) keeps the library's canonical position.\n */\nexport function slideBlankBoundaryBlocksUp(\n hunk: Hunk,\n diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'>\n): void {\n const { hunkContent } = hunk;\n for (let index = 1; index < hunkContent.length; index++) {\n const block = hunkContent[index];\n const previous = hunkContent[index - 1];\n if (\n block.type !== 'change' ||\n (block.additions > 0 && block.deletions > 0) ||\n previous.type !== 'context'\n ) {\n continue;\n }\n const isInsert = block.additions > 0;\n const lines = isInsert ? diff.additionLines : diff.deletionLines;\n const blockStart = isInsert\n ? block.additionLineIndex\n : block.deletionLineIndex;\n const blockLength = isInsert ? block.additions : block.deletions;\n\n // Sliding through identical lines is only well-defined when the block is\n // a uniform run; require every block line to equal the first one, and\n // that line to be blank.\n const unit = lines[blockStart] ?? '';\n if (unit.trim() !== '') {\n continue;\n }\n let uniform = true;\n for (let offset = 1; offset < blockLength; offset++) {\n if (lines[blockStart + offset] !== unit) {\n uniform = false;\n break;\n }\n }\n if (!uniform) {\n continue;\n }\n\n // Slide distance: how many trailing context lines match the block's line\n // exactly (context lines are identical on both sides by definition).\n let slide = 0;\n while (\n slide < previous.lines &&\n diff.additionLines[\n previous.additionLineIndex + previous.lines - 1 - slide\n ] === unit\n ) {\n slide++;\n }\n if (slide === 0) {\n continue;\n }\n // Stopped by the hunk's leading edge rather than by content: the blank\n // run extends to (and possibly beyond) the hunk's top, so there is no\n // in-hunk line to anchor beneath. Keep the bottom-of-run position.\n if (index === 1 && slide === previous.lines) {\n continue;\n }\n\n block.additionLineIndex -= slide;\n block.deletionLineIndex -= slide;\n const blockAdditionEnd = block.additionLineIndex + block.additions;\n const blockDeletionEnd = block.deletionLineIndex + block.deletions;\n const next = hunkContent[index + 1];\n if (next?.type === 'context') {\n next.lines += slide;\n next.additionLineIndex = blockAdditionEnd;\n next.deletionLineIndex = blockDeletionEnd;\n } else {\n hunkContent.splice(index + 1, 0, {\n type: 'context',\n lines: slide,\n additionLineIndex: blockAdditionEnd,\n deletionLineIndex: blockDeletionEnd,\n });\n }\n previous.lines -= slide;\n if (previous.lines === 0) {\n hunkContent.splice(index - 1, 1);\n index--;\n }\n }\n}\n\n// Returns the split blocks for one change block, or null when the block is\n// balanced, too large to scan, or already best paired positionally.\nfunction realignChangeBlock(\n diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'>,\n content: ChangeContent\n): (ContextContent | ChangeContent)[] | null {\n const { deletions, additions, deletionLineIndex, additionLineIndex } =\n content;\n const pairCount = Math.min(deletions, additions);\n const surplus = Math.abs(additions - deletions);\n if (\n pairCount === 0 ||\n surplus === 0 ||\n pairCount * (surplus + 1) > MAX_ALIGNMENT_COMPARISONS\n ) {\n return null;\n }\n\n // Whitespace (indentation, formatter churn, trailing spaces, the line\n // break itself) is noise for deciding which lines pair — the rendered diff\n // still shows every whitespace change on the paired row. Strip it once per\n // line here rather than per comparison.\n const strippedDeletions: string[] = [];\n for (let line = 0; line < deletions; line++) {\n strippedDeletions.push(\n stripWhitespace(diff.deletionLines[deletionLineIndex + line] ?? '')\n );\n }\n const strippedAdditions: string[] = [];\n for (let line = 0; line < additions; line++) {\n strippedAdditions.push(\n stripWhitespace(diff.additionLines[additionLineIndex + line] ?? '')\n );\n }\n\n // Score every offset of the shorter side along the longer side and keep\n // the best one only when it decisively beats the positional pairing.\n const additionsAreLonger = additions > deletions;\n let bestOffset = 0;\n let bestScore = -1;\n for (let offset = 0; offset <= surplus; offset++) {\n let score = 0;\n for (let pair = 0; pair < pairCount; pair++) {\n score += lineSimilarity(\n strippedDeletions[pair + (additionsAreLonger ? 0 : offset)],\n strippedAdditions[pair + (additionsAreLonger ? offset : 0)]\n );\n }\n if (offset === 0) {\n bestScore = score + pairCount * MIN_IMPROVEMENT_PER_PAIR;\n } else if (score > bestScore) {\n bestScore = score;\n bestOffset = offset;\n }\n }\n if (bestOffset === 0) {\n return null;\n }\n\n const blocks: ChangeContent[] = [];\n const pushBlock = (\n blockDeletions: number,\n blockAdditions: number,\n blockDeletionIndex: number,\n blockAdditionIndex: number\n ) => {\n if (blockDeletions > 0 || blockAdditions > 0) {\n blocks.push({\n type: 'change',\n deletions: blockDeletions,\n additions: blockAdditions,\n deletionLineIndex: blockDeletionIndex,\n additionLineIndex: blockAdditionIndex,\n });\n }\n };\n if (additionsAreLonger) {\n pushBlock(0, bestOffset, deletionLineIndex, additionLineIndex);\n pushBlock(\n pairCount,\n pairCount,\n deletionLineIndex,\n additionLineIndex + bestOffset\n );\n pushBlock(\n 0,\n additions - pairCount - bestOffset,\n deletionLineIndex + pairCount,\n additionLineIndex + bestOffset + pairCount\n );\n } else {\n pushBlock(bestOffset, 0, deletionLineIndex, additionLineIndex);\n pushBlock(\n pairCount,\n pairCount,\n deletionLineIndex + bestOffset,\n additionLineIndex\n );\n pushBlock(\n deletions - pairCount - bestOffset,\n 0,\n deletionLineIndex + bestOffset + pairCount,\n additionLineIndex + pairCount\n );\n }\n return blocks;\n}\n\nconst WHITESPACE = /\\s+/g;\n\nfunction stripWhitespace(line: string): string {\n return line.replace(WHITESPACE, '');\n}\n\n// Cheap 0..1 similarity over whitespace-stripped lines: shared prefix plus\n// shared suffix over the longer length. Exact for lines that differ only in\n// whitespace, 0 for a blank against content — enough to steer pairing\n// without a real edit-distance pass.\nfunction lineSimilarity(a: string, b: string): number {\n if (a === b) {\n return 1;\n }\n const maxLength = Math.max(a.length, b.length);\n const minLength = Math.min(a.length, b.length);\n if (minLength === 0) {\n return 0;\n }\n let prefix = 0;\n while (prefix < minLength && a[prefix] === b[prefix]) {\n prefix++;\n }\n let suffix = 0;\n while (\n suffix < minLength - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n ) {\n suffix++;\n }\n return (prefix + suffix) / maxLength;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"realignChangeContent.js","names":[],"sources":["../../src/utils/realignChangeContent.ts"],"sourcesContent":["import type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n} from '../types';\n\n// The diff library emits one change block per replaced run, ordering every\n// deleted line before every added line. Renderers pair a block's lines\n// positionally (deletion[i] across from addition[i] in split view), so a\n// block like { deletions: 1, additions: 2 } pairs the deleted line with\n// whichever addition happens to come first — even when a later addition is\n// the edited version of it (e.g. pressing Enter above a changed line pushes\n// a blank line in front of it). These helpers re-split such blocks so the\n// most similar lines pair up and the surplus renders as pure insert/delete\n// rows at the block's edges.\n\n// Skip realignment when a block would need more than this many line\n// comparisons; pathological blocks keep the library's positional pairing.\nconst MAX_ALIGNMENT_COMPARISONS = 4096;\n\n// A shifted pairing must beat the positional one by this much per paired\n// line before the block is re-split. Near-ties (e.g. lines that merely share\n// an import-statement shape) keep the library's canonical order; the\n// realignment is meant for decisive wins like a blank or unrelated inserted\n// line displacing an edited one, where the gap approaches 1.\nconst MIN_IMPROVEMENT_PER_PAIR = 0.5;\n\n/**\n * Re-split count-mismatched change blocks in every hunk so paired lines are\n * chosen by content similarity instead of position. Mutates `hunks` in place\n * without moving changes across context lines; rendered row counts are\n * unchanged (a split block covers the same split/unified rows as the original).\n */\nexport function realignChangeContentBySimilarity(\n diff: Pick<FileDiffMetadata, 'hunks' | 'additionLines' | 'deletionLines'>\n): void {\n for (const hunk of diff.hunks) {\n for (let index = 0; index < hunk.hunkContent.length; index++) {\n const content = hunk.hunkContent[index];\n if (content.type !== 'change') {\n continue;\n }\n const replacement = realignChangeBlock(diff, content);\n if (replacement != null) {\n hunk.hunkContent.splice(index, 1, ...replacement);\n index += replacement.length - 1;\n }\n }\n }\n}\n\n/**\n * During editing, slide pure insert/delete blocks made entirely of blank lines\n * to the top of the blank run they sit in. Adding or removing a blank line next to\n * existing blanks is ambiguous, and the diff library reports the change at\n * the run's bottom — so pressing Enter at the end of a line marks a blank\n * *below* the caret as inserted while the caret's own new line renders as\n * context. Sliding up anchors the change to the content above it (the caret\n * line after an Enter) instead.\n *\n * `resolveSlide` receives each block that qualifies along with the full\n * distance to the run's top and returns how far to actually move it; 0 keeps\n * the parsed position. Without it, every qualifying block slides to the top.\n *\n * The slide only applies when the block comes to rest directly beneath\n * remaining in-hunk content. A slide that would consume the\n * hunk's entire leading context was stopped by the hunk's edge — a context\n * window cut, not the top of the blank run — and that landing spot is\n * arbitrary, so the block keeps the library's bottom-of-run anchor (which\n * sits against the content below the run). Non-blank blocks never slide, so\n * code that merely ends like its neighbor (an added function before an\n * identical `}`) keeps the library's canonical position.\n */\nexport function slideBlankBoundaryBlocksUp(\n hunk: Hunk,\n diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'>,\n resolveSlide?: (block: ChangeContent, maxSlide: number) => number\n): void {\n const { hunkContent } = hunk;\n for (let index = 1; index < hunkContent.length; index++) {\n const block = hunkContent[index];\n const previous = hunkContent[index - 1];\n if (\n block.type !== 'change' ||\n (block.additions > 0 && block.deletions > 0) ||\n previous.type !== 'context'\n ) {\n continue;\n }\n const isInsert = block.additions > 0;\n const lines = isInsert ? diff.additionLines : diff.deletionLines;\n const blockStart = isInsert\n ? block.additionLineIndex\n : block.deletionLineIndex;\n const blockLength = isInsert ? block.additions : block.deletions;\n\n // Sliding through identical lines is only well-defined when the block is\n // a uniform run; require every block line to equal the first one, and\n // that line to be blank.\n const unit = lines[blockStart] ?? '';\n if (unit.trim() !== '') {\n continue;\n }\n let uniform = true;\n for (let offset = 1; offset < blockLength; offset++) {\n if (lines[blockStart + offset] !== unit) {\n uniform = false;\n break;\n }\n }\n if (!uniform) {\n continue;\n }\n\n // Slide distance: how many trailing context lines match the block's line\n // exactly (context lines are identical on both sides by definition).\n let slide = 0;\n while (\n slide < previous.lines &&\n diff.additionLines[\n previous.additionLineIndex + previous.lines - 1 - slide\n ] === unit\n ) {\n slide++;\n }\n if (slide === 0) {\n continue;\n }\n // Stopped by the hunk's leading edge rather than by content: the blank\n // run extends to (and possibly beyond) the hunk's top, so there is no\n // in-hunk line to anchor beneath. Keep the bottom-of-run position.\n if (index === 1 && slide === previous.lines) {\n continue;\n }\n if (resolveSlide != null) {\n slide = resolveSlide(block, slide);\n if (slide === 0) {\n continue;\n }\n }\n\n block.additionLineIndex -= slide;\n block.deletionLineIndex -= slide;\n const blockAdditionEnd = block.additionLineIndex + block.additions;\n const blockDeletionEnd = block.deletionLineIndex + block.deletions;\n const next = hunkContent[index + 1];\n if (next?.type === 'context') {\n next.lines += slide;\n next.additionLineIndex = blockAdditionEnd;\n next.deletionLineIndex = blockDeletionEnd;\n } else {\n hunkContent.splice(index + 1, 0, {\n type: 'context',\n lines: slide,\n additionLineIndex: blockAdditionEnd,\n deletionLineIndex: blockDeletionEnd,\n });\n }\n previous.lines -= slide;\n if (previous.lines === 0) {\n hunkContent.splice(index - 1, 1);\n index--;\n }\n }\n}\n\n// Returns the split blocks for one change block, or null when the block is\n// balanced, too large to scan, or already best paired positionally.\nfunction realignChangeBlock(\n diff: Pick<FileDiffMetadata, 'additionLines' | 'deletionLines'>,\n content: ChangeContent\n): (ContextContent | ChangeContent)[] | null {\n const { deletions, additions, deletionLineIndex, additionLineIndex } =\n content;\n const pairCount = Math.min(deletions, additions);\n const surplus = Math.abs(additions - deletions);\n if (\n pairCount === 0 ||\n surplus === 0 ||\n pairCount * (surplus + 1) > MAX_ALIGNMENT_COMPARISONS\n ) {\n return null;\n }\n\n // Whitespace (indentation, formatter churn, trailing spaces, the line\n // break itself) is noise for deciding which lines pair — the rendered diff\n // still shows every whitespace change on the paired row. Strip it once per\n // line here rather than per comparison.\n const strippedDeletions: string[] = [];\n for (let line = 0; line < deletions; line++) {\n strippedDeletions.push(\n stripWhitespace(diff.deletionLines[deletionLineIndex + line] ?? '')\n );\n }\n const strippedAdditions: string[] = [];\n for (let line = 0; line < additions; line++) {\n strippedAdditions.push(\n stripWhitespace(diff.additionLines[additionLineIndex + line] ?? '')\n );\n }\n\n // Score every offset of the shorter side along the longer side and keep\n // the best one only when it decisively beats the positional pairing.\n const additionsAreLonger = additions > deletions;\n let bestOffset = 0;\n let bestScore = -1;\n for (let offset = 0; offset <= surplus; offset++) {\n let score = 0;\n for (let pair = 0; pair < pairCount; pair++) {\n score += lineSimilarity(\n strippedDeletions[pair + (additionsAreLonger ? 0 : offset)],\n strippedAdditions[pair + (additionsAreLonger ? offset : 0)]\n );\n }\n if (offset === 0) {\n bestScore = score + pairCount * MIN_IMPROVEMENT_PER_PAIR;\n } else if (score > bestScore) {\n bestScore = score;\n bestOffset = offset;\n }\n }\n if (bestOffset === 0) {\n return null;\n }\n\n const blocks: ChangeContent[] = [];\n const pushBlock = (\n blockDeletions: number,\n blockAdditions: number,\n blockDeletionIndex: number,\n blockAdditionIndex: number\n ) => {\n if (blockDeletions > 0 || blockAdditions > 0) {\n blocks.push({\n type: 'change',\n deletions: blockDeletions,\n additions: blockAdditions,\n deletionLineIndex: blockDeletionIndex,\n additionLineIndex: blockAdditionIndex,\n });\n }\n };\n if (additionsAreLonger) {\n pushBlock(0, bestOffset, deletionLineIndex, additionLineIndex);\n pushBlock(\n pairCount,\n pairCount,\n deletionLineIndex,\n additionLineIndex + bestOffset\n );\n pushBlock(\n 0,\n additions - pairCount - bestOffset,\n deletionLineIndex + pairCount,\n additionLineIndex + bestOffset + pairCount\n );\n } else {\n pushBlock(bestOffset, 0, deletionLineIndex, additionLineIndex);\n pushBlock(\n pairCount,\n pairCount,\n deletionLineIndex + bestOffset,\n additionLineIndex\n );\n pushBlock(\n deletions - pairCount - bestOffset,\n 0,\n deletionLineIndex + bestOffset + pairCount,\n additionLineIndex + pairCount\n );\n }\n return blocks;\n}\n\nconst WHITESPACE = /\\s+/g;\n\nfunction stripWhitespace(line: string): string {\n return line.replace(WHITESPACE, '');\n}\n\n// Cheap 0..1 similarity over whitespace-stripped lines: shared prefix plus\n// shared suffix over the longer length. Exact for lines that differ only in\n// whitespace, 0 for a blank against content — enough to steer pairing\n// without a real edit-distance pass.\nfunction lineSimilarity(a: string, b: string): number {\n if (a === b) {\n return 1;\n }\n const maxLength = Math.max(a.length, b.length);\n const minLength = Math.min(a.length, b.length);\n if (minLength === 0) {\n return 0;\n }\n let prefix = 0;\n while (prefix < minLength && a[prefix] === b[prefix]) {\n prefix++;\n }\n let suffix = 0;\n while (\n suffix < minLength - prefix &&\n a[a.length - 1 - suffix] === b[b.length - 1 - suffix]\n ) {\n suffix++;\n }\n return (prefix + suffix) / maxLength;\n}\n"],"mappings":"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"}
|
|
@@ -132,12 +132,51 @@ function renderDiffWithHighlighter(diff, highlighter, options, { forcePlainText,
|
|
|
132
132
|
baseThemeType
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
|
+
function pushWordLineDecorations(lineDiff, { deletionLineIndex, additionLineIndex, deletionDecorations, additionDecorations }) {
|
|
136
|
+
let deletionOffset = 0;
|
|
137
|
+
let additionOffset = 0;
|
|
138
|
+
let deletionStart = -1;
|
|
139
|
+
let deletionEnd = 0;
|
|
140
|
+
let additionStart = -1;
|
|
141
|
+
let additionEnd = 0;
|
|
142
|
+
for (const { value, added, removed } of lineDiff) if (removed) {
|
|
143
|
+
if (deletionStart === -1) deletionStart = deletionOffset;
|
|
144
|
+
deletionOffset += value.length;
|
|
145
|
+
deletionEnd = deletionOffset;
|
|
146
|
+
} else if (added) {
|
|
147
|
+
if (additionStart === -1) additionStart = additionOffset;
|
|
148
|
+
additionOffset += value.length;
|
|
149
|
+
additionEnd = additionOffset;
|
|
150
|
+
} else {
|
|
151
|
+
deletionOffset += value.length;
|
|
152
|
+
additionOffset += value.length;
|
|
153
|
+
}
|
|
154
|
+
if (deletionStart !== -1) deletionDecorations.push(createDiffSpanDecoration({
|
|
155
|
+
line: deletionLineIndex,
|
|
156
|
+
spanStart: deletionStart,
|
|
157
|
+
spanLength: deletionEnd - deletionStart
|
|
158
|
+
}));
|
|
159
|
+
if (additionStart !== -1) additionDecorations.push(createDiffSpanDecoration({
|
|
160
|
+
line: additionLineIndex,
|
|
161
|
+
spanStart: additionStart,
|
|
162
|
+
spanLength: additionEnd - additionStart
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
135
165
|
function computeLineDiffDecorations({ deletionLine, additionLine, deletionLineIndex, additionLineIndex, deletionDecorations, additionDecorations, lineDiffType, maxLineDiffLength }) {
|
|
136
166
|
if (deletionLine == null || additionLine == null || lineDiffType === "none") return;
|
|
137
167
|
deletionLine = cleanLastNewline(deletionLine);
|
|
138
168
|
additionLine = cleanLastNewline(additionLine);
|
|
139
169
|
if (deletionLine.length > maxLineDiffLength || additionLine.length > maxLineDiffLength) return;
|
|
140
170
|
const lineDiff = lineDiffType === "char" ? diffChars(deletionLine, additionLine) : diffWordsWithSpace(deletionLine, additionLine);
|
|
171
|
+
if (lineDiffType === "word-line") {
|
|
172
|
+
pushWordLineDecorations(lineDiff, {
|
|
173
|
+
deletionLineIndex,
|
|
174
|
+
additionLineIndex,
|
|
175
|
+
deletionDecorations,
|
|
176
|
+
additionDecorations
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
141
180
|
const deletionSpans = [];
|
|
142
181
|
const additionSpans = [];
|
|
143
182
|
const enableJoin = lineDiffType === "word-alt";
|