mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 16:02:03 +00:00
214 lines
6.2 KiB
TypeScript
214 lines
6.2 KiB
TypeScript
export type GitHubPrFileDiffLine = {
|
|
key: string
|
|
kind: 'context' | 'added' | 'removed'
|
|
oldLineNumber?: number
|
|
newLineNumber?: number
|
|
text: string
|
|
}
|
|
|
|
export type GitHubPrFileDiffPreview = {
|
|
lines: GitHubPrFileDiffLine[]
|
|
totalLineCount: number
|
|
}
|
|
|
|
type DiffOperation =
|
|
| { kind: 'context'; oldLine: string; newLine: string }
|
|
| { kind: 'removed'; oldLine: string }
|
|
| { kind: 'added'; newLine: string }
|
|
|
|
const EXACT_DIFF_CELL_LIMIT = 160_000
|
|
|
|
function splitContentLines(value: string): string[] {
|
|
if (!value) {
|
|
return []
|
|
}
|
|
const lines = value.split(/\r?\n/)
|
|
return lines.at(-1) === '' ? lines.slice(0, -1) : lines
|
|
}
|
|
|
|
function appendExactLineDiff(
|
|
original: string[],
|
|
modified: string[],
|
|
appendOperation: (operation: DiffOperation) => void
|
|
): void {
|
|
const rowWidth = modified.length + 1
|
|
const table = new Uint16Array((original.length + 1) * rowWidth)
|
|
|
|
for (let oldIndex = original.length - 1; oldIndex >= 0; oldIndex -= 1) {
|
|
for (let newIndex = modified.length - 1; newIndex >= 0; newIndex -= 1) {
|
|
const cell = oldIndex * rowWidth + newIndex
|
|
if (original[oldIndex] === modified[newIndex]) {
|
|
table[cell] = table[(oldIndex + 1) * rowWidth + newIndex + 1] + 1
|
|
} else {
|
|
table[cell] = Math.max(
|
|
table[(oldIndex + 1) * rowWidth + newIndex],
|
|
table[oldIndex * rowWidth + newIndex + 1]
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
let oldIndex = 0
|
|
let newIndex = 0
|
|
while (oldIndex < original.length && newIndex < modified.length) {
|
|
const oldLine = original[oldIndex]
|
|
const newLine = modified[newIndex]
|
|
if (oldLine === newLine) {
|
|
appendOperation({ kind: 'context', oldLine, newLine })
|
|
oldIndex += 1
|
|
newIndex += 1
|
|
continue
|
|
}
|
|
const removeScore = table[(oldIndex + 1) * rowWidth + newIndex]
|
|
const addScore = table[oldIndex * rowWidth + newIndex + 1]
|
|
if (removeScore >= addScore) {
|
|
appendOperation({ kind: 'removed', oldLine })
|
|
oldIndex += 1
|
|
} else {
|
|
appendOperation({ kind: 'added', newLine })
|
|
newIndex += 1
|
|
}
|
|
}
|
|
while (oldIndex < original.length) {
|
|
appendOperation({ kind: 'removed', oldLine: original[oldIndex] })
|
|
oldIndex += 1
|
|
}
|
|
while (newIndex < modified.length) {
|
|
appendOperation({ kind: 'added', newLine: modified[newIndex] })
|
|
newIndex += 1
|
|
}
|
|
}
|
|
|
|
function appendMiddleDiff(
|
|
original: string[],
|
|
modified: string[],
|
|
appendOperation: (operation: DiffOperation) => void
|
|
): void {
|
|
if (original.length === 0) {
|
|
for (const newLine of modified) {
|
|
appendOperation({ kind: 'added', newLine })
|
|
}
|
|
return
|
|
}
|
|
if (modified.length === 0) {
|
|
for (const oldLine of original) {
|
|
appendOperation({ kind: 'removed', oldLine })
|
|
}
|
|
return
|
|
}
|
|
if (original.length * modified.length <= EXACT_DIFF_CELL_LIMIT) {
|
|
appendExactLineDiff(original, modified, appendOperation)
|
|
return
|
|
}
|
|
// Why: the Tasks diff UI renders a capped preview. Stream fallback rows so a
|
|
// generated PR file does not allocate thousands of discarded row objects.
|
|
for (const oldLine of original) {
|
|
appendOperation({ kind: 'removed', oldLine })
|
|
}
|
|
for (const newLine of modified) {
|
|
appendOperation({ kind: 'added', newLine })
|
|
}
|
|
}
|
|
|
|
export function buildGitHubPrFileDiffLines(
|
|
originalContent: string,
|
|
modifiedContent: string
|
|
): GitHubPrFileDiffLine[] {
|
|
return buildGitHubPrFileDiffPreview(originalContent, modifiedContent).lines
|
|
}
|
|
|
|
export function buildGitHubPrFileDiffPreview(
|
|
originalContent: string,
|
|
modifiedContent: string,
|
|
maxLines = Number.POSITIVE_INFINITY
|
|
): GitHubPrFileDiffPreview {
|
|
const originalLines = splitContentLines(originalContent)
|
|
const modifiedLines = splitContentLines(modifiedContent)
|
|
let prefixLength = 0
|
|
while (
|
|
prefixLength < originalLines.length &&
|
|
prefixLength < modifiedLines.length &&
|
|
originalLines[prefixLength] === modifiedLines[prefixLength]
|
|
) {
|
|
prefixLength += 1
|
|
}
|
|
|
|
let suffixLength = 0
|
|
while (
|
|
suffixLength < originalLines.length - prefixLength &&
|
|
suffixLength < modifiedLines.length - prefixLength &&
|
|
originalLines[originalLines.length - suffixLength - 1] ===
|
|
modifiedLines[modifiedLines.length - suffixLength - 1]
|
|
) {
|
|
suffixLength += 1
|
|
}
|
|
|
|
const originalMiddle = originalLines.slice(
|
|
prefixLength,
|
|
suffixLength === 0 ? originalLines.length : originalLines.length - suffixLength
|
|
)
|
|
const modifiedMiddle = modifiedLines.slice(
|
|
prefixLength,
|
|
suffixLength === 0 ? modifiedLines.length : modifiedLines.length - suffixLength
|
|
)
|
|
|
|
const result: GitHubPrFileDiffLine[] = []
|
|
let oldLineNumber = 1
|
|
let newLineNumber = 1
|
|
let operationIndex = 0
|
|
let totalLineCount = 0
|
|
const normalizedMaxLines = Math.max(0, Math.floor(maxLines))
|
|
function appendOperation(operation: DiffOperation): void {
|
|
const index = operationIndex
|
|
operationIndex += 1
|
|
totalLineCount += 1
|
|
if (operation.kind === 'context') {
|
|
if (result.length < normalizedMaxLines) {
|
|
result.push({
|
|
key: `${index}:context:${oldLineNumber}:${newLineNumber}`,
|
|
kind: 'context',
|
|
oldLineNumber,
|
|
newLineNumber,
|
|
text: operation.newLine
|
|
})
|
|
}
|
|
oldLineNumber += 1
|
|
newLineNumber += 1
|
|
return
|
|
}
|
|
if (operation.kind === 'removed') {
|
|
if (result.length < normalizedMaxLines) {
|
|
result.push({
|
|
key: `${index}:removed:${oldLineNumber}`,
|
|
kind: 'removed',
|
|
oldLineNumber,
|
|
text: operation.oldLine
|
|
})
|
|
}
|
|
oldLineNumber += 1
|
|
return
|
|
}
|
|
if (result.length < normalizedMaxLines) {
|
|
result.push({
|
|
key: `${index}:added:${newLineNumber}`,
|
|
kind: 'added',
|
|
newLineNumber,
|
|
text: operation.newLine
|
|
})
|
|
}
|
|
newLineNumber += 1
|
|
}
|
|
|
|
for (let i = 0; i < prefixLength; i += 1) {
|
|
const line = originalLines[i] ?? ''
|
|
appendOperation({ kind: 'context', oldLine: line, newLine: line })
|
|
}
|
|
appendMiddleDiff(originalMiddle, modifiedMiddle, appendOperation)
|
|
for (let i = originalLines.length - suffixLength; i < originalLines.length; i += 1) {
|
|
const line = originalLines[i] ?? ''
|
|
appendOperation({ kind: 'context', oldLine: line, newLine: line })
|
|
}
|
|
|
|
return { lines: result, totalLineCount }
|
|
}
|