import { ClientOnly } from "@tanstack/react-router"; import { parsePatchFiles } from "@pierre/diffs"; import type { DiffLineAnnotation, FileDiffMetadata } from "@pierre/diffs"; import { FileDiff } from "@pierre/diffs/react"; import { useState } from "react"; import { Button } from "./ui/Button"; import { EmptyState } from "./ui/Primitives"; import type { DiffResult } from "~/lib/types"; export type DiffSide = "LEFT" | "RIGHT"; export type LineAnchor = { path: string; line: number; side: DiffSide; data: T }; type Props = { diff: DiffResult; anchors?: LineAnchor[]; renderAnchor?: (anchor: LineAnchor) => React.ReactNode; onLineAction?: (path: string, line: number, side: DiffSide) => void; }; const DIFF_STYLES = ["split", "unified"] as const; function toAnnotationSide(side: DiffSide): "additions" | "deletions" { return side === "RIGHT" ? "additions" : "deletions"; } function FileBlock({ fileDiff, diffStyle, anchors, renderAnchor, onLineAction, }: { fileDiff: FileDiffMetadata; diffStyle: (typeof DIFF_STYLES)[number]; anchors: LineAnchor[]; renderAnchor?: (anchor: LineAnchor) => React.ReactNode; onLineAction?: (path: string, line: number, side: DiffSide) => void; }) { const annotations: DiffLineAnnotation>[] = anchors.map((anchor) => ({ side: toAnnotationSide(anchor.side), lineNumber: anchor.line, metadata: anchor, })); return (
> fileDiff={fileDiff} lineAnnotations={annotations} renderAnnotation={(annotation) => (renderAnchor ? renderAnchor(annotation.metadata) : null)} options={{ theme: "pierre-dark", diffStyle, diffIndicators: "bars", hunkSeparators: "line-info", overflow: "scroll", enableGutterUtility: Boolean(onLineAction), onGutterUtilityClick: (range) => onLineAction?.(fileDiff.name, range.start, range.side === "deletions" ? "LEFT" : "RIGHT"), }} />
); } export function DiffView({ diff, anchors = [], renderAnchor, onLineAction }: Props) { const [diffStyle, setDiffStyle] = useState<(typeof DIFF_STYLES)[number]>("split"); if (!diff.patch.trim()) return ; const files = parsePatchFiles(diff.patch).flatMap((patch) => patch.files); const truncatedFiles = diff.files.filter((file) => file.truncated); return (
{diff.files.length} file{diff.files.length === 1 ? "" : "s"} changed +{diff.additions} −{diff.deletions}
{truncatedFiles.length > 0 && (

{truncatedFiles.length} large file(s) are not rendered:{" "} {truncatedFiles .slice(0, 5) .map((file) => file.path) .join(", ")}

)} Rendering diff…

}> {files.map((fileDiff) => ( anchor.path === fileDiff.name)} renderAnchor={renderAnchor} onLineAction={onLineAction} /> ))}
); }