opencode/packages/app/src/pages/session/v2/review-diff-kinds.ts
2026-07-24 18:20:54 +08:00

62 lines
2.1 KiB
TypeScript

import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
import type { FileDiffInfo } from "@opencode-ai/client/promise"
import type { Kind } from "@/components/file-tree-v2"
import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model"
export type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff
export function normalizePath(p: string) {
return normalizeFileTreeV2Path(p)
}
export function filterRenderableDiff(value: FileDiffInfo | SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
return typeof value.file === "string"
}
export function reviewDiffNeedsLoad(diff: RenderDiff) {
if (diff.additions === 0 && diff.deletions === 0) return false
return !diff.patch || !/^@@ /m.test(diff.patch)
}
export function reviewRootDirectory(root: string) {
return root === "/" || /^[A-Za-z]:[/\\]?$/.test(root) ? root : root.replace(/[/\\]+$/, "")
}
export function reviewDiffDirectory(root: string, file: string) {
const path = normalizePath(file)
const index = path.lastIndexOf("/")
const separator = root.includes("\\") ? "\\" : "/"
const base = reviewRootDirectory(root)
if (index < 0) return base
return `${base.endsWith(separator) ? base : base + separator}${path.slice(0, index).replaceAll("/", separator)}`
}
export function reviewDiffKinds(diffs: RenderDiff[]) {
const merge = (a: Kind | undefined, b: Kind) => {
if (!a) return b
if (a === b) return a
return "mix" as const
}
const out = new Map<string, Kind>()
for (const diff of diffs) {
const file = normalizePath(diff.file)
const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix"
out.set(file, kind)
const parts = file.split("/")
parts.slice(0, -1).forEach((_, idx) => {
const dir = parts.slice(0, idx + 1).join("/")
if (!dir) return
out.set(dir, merge(out.get(dir), kind))
})
}
return out
}
export function filterReviewFiles(files: string[], query: string) {
const value = query.trim().toLowerCase()
if (!value) return files
return files.filter((file) => file.toLowerCase().includes(value))
}