refactor(ui): isolate session components (#33670)
This commit is contained in:
parent
1ef0fd5d01
commit
04c1730c90
126 changed files with 294 additions and 142 deletions
144
packages/session-ui/src/components/session-diff.ts
Normal file
144
packages/session-ui/src/components/session-diff.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
|
||||
import { parsePatch } from "diff"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
||||
|
||||
type LegacyDiff = {
|
||||
file: string
|
||||
patch?: string
|
||||
before?: string
|
||||
after?: string
|
||||
additions: number
|
||||
deletions: number
|
||||
status?: "added" | "deleted" | "modified"
|
||||
}
|
||||
|
||||
type SnapshotDiff = SnapshotFileDiff & { file: string }
|
||||
type ReviewDiff = SnapshotDiff | VcsFileDiff | LegacyDiff
|
||||
export 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 = 16
|
||||
const 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
|
||||
}
|
||||
|
||||
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: "" })
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue