Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
4.9 kB · 144 lines
TypeScript
at systemd
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"import { parsePatch } from "diff"import type { FileDiffInfo } from "@opencode-ai/client/promise"import type { PresentationFileDiff } from "../file-presentation"
type LegacyDiff = { file: string patch?: string before?: string after?: string additions: number deletions: number status?: "added" | "deleted" | "modified"}
type ReviewDiff = (PresentationFileDiff & { file: string }) | FileDiffInfo | LegacyDiffexport type DiffSource = Pick<LegacyDiff, "file" | "patch" | "before" | "after">
export type ViewDiff = { file: string additions: number deletions: number status?: "added" | "deleted" | "modified" fileDiff: FileDiffMetadata}
const diffCacheLimit = 16const patchFileDiffCache = new Map<string, FileDiffMetadata>()
export function resolveFileDiff(diff: DiffSource) { if (typeof diff.patch === "string") return fileDiffFromPatch(diff.file, diff.patch) return fileDiffFromContent( diff.file, typeof diff.before === "string" ? diff.before : "", typeof diff.after === "string" ? diff.after : "", )}
export function normalize(diff: ReviewDiff): ViewDiff { return { file: diff.file, additions: diff.additions, deletions: diff.deletions, status: diff.status, fileDiff: resolveFileDiff(diff), }}
export function text(diff: ViewDiff, side: "deletions" | "additions") { if (side === "deletions") return diff.fileDiff.deletionLines.join("") return diff.fileDiff.additionLines.join("")}
function fileDiffFromPatch(file: string, patch: string) { const key = `${file}\0${patch}` const hit = patchFileDiffCache.get(key) if (hit) { patchFileDiffCache.delete(key) patchFileDiffCache.set(key, hit) return hit }
const contents = completePatchContents(patch) const input = contents ? undefined : patchInput(file, patch) const value = contents ? fileDiffFromContent(file, contents.before, contents.after) : ((input ? parsePatchFiles(input)[0]?.files[0] : undefined) ?? emptyFileDiff(file)) patchFileDiffCache.set(key, value) while (patchFileDiffCache.size > diffCacheLimit) patchFileDiffCache.delete(patchFileDiffCache.keys().next().value!) return value}
export function completePatchContents(patch: string) { try { const parsed = parsePatch(patch)[0] if (!parsed || (!parsed.index && !parsed.oldFileName && !parsed.newFileName)) return // Snapshot and VCS producers request full context. Tool patches use jsdiff's shorter default context. if (!patch.startsWith("diff --git ") && !/^--- [^\n]*\t\r?\n\+\+\+ [^\n]*\t(?:\r?\n|$)/m.test(patch)) return // Full patches collapse into one leading hunk. Separated hunks omit ranges and must stay partial. if (parsed.hunks.length !== 1) return
const hunk = parsed.hunks[0] if (!hunk || hunk.oldStart > 1 || hunk.newStart > 1) return
const before: Array<{ text: string; newline: boolean }> = [] const after: Array<{ text: string; newline: boolean }> = [] let previous: "-" | "+" | " " | undefined
for (const line of hunk.lines) { if (line.startsWith("\\")) { if (previous === "-" || previous === " ") { const value = before.at(-1) if (value) value.newline = false } if (previous === "+" || previous === " ") { const value = after.at(-1) if (value) value.newline = false } continue } if (line.startsWith("-")) { before.push({ text: line.slice(1), newline: true }) previous = "-" continue } if (line.startsWith("+")) { after.push({ text: line.slice(1), newline: true }) previous = "+" continue } if (!line.startsWith(" ")) return before.push({ text: line.slice(1), newline: true }) after.push({ text: line.slice(1), newline: true }) previous = " " }
const text = (lines: Array<{ text: string; newline: boolean }>) => lines.map((line) => line.text + (line.newline ? "\n" : "")).join("") return { before: text(before), after: text(after) } } catch { return }}
function patchInput(file: string, patch: string) { try { const parsed = parsePatch(patch)[0] if (!parsed) return if (parsed.index || parsed.oldFileName || parsed.newFileName) return patch if (!parsed.hunks.length) return return `Index: ${file}\n===================================================================\n--- ${file}\t\n+++ ${file}\t\n${patch}` } catch { return }}
function fileDiffFromContent(file: string, before: string, after: string) { if (!before && !after) return emptyFileDiff(file) return parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })}
function emptyFileDiff(file: string) { return parseDiffFromFile({ name: file, contents: "" }, { name: file, contents: "" })}