import { FileIcon } from "@/components/icons"; import { Box, ButtonLink, Label } from "@/components/ui"; import type { FileDiff } from "@/lib/git"; const MAX_RENDERED_LINES = 3000; /** How many lines each file may render: what is left of the budget after the files above it. */ export function diffBudgets(files: FileDiff[]) { return files.map((_, i) => MAX_RENDERED_LINES - files.slice(0, i).reduce((n, f) => n + f.lines.length, 0)); } const lineStyles = { add: { row: "bg-[var(--diff-add)]", num: "bg-[var(--diff-add-num)]", sign: "+" }, del: { row: "bg-[var(--diff-del)]", num: "bg-[var(--diff-del-num)]", sign: "-" }, ctx: { row: "", num: "", sign: " " }, }; /** The change as five lamps: green for the added share, red for the deleted, dark when nothing changed. */ function DiffStat({ additions, deletions }: { additions: number; deletions: number }) { const total = additions + deletions; const greens = total ? Math.round((additions / total) * 5) : 0; return ( +{additions} −{deletions} {Array.from({ length: 5 }, (_, i) => ( ))} ); } /** * One file's changes on a plate: its name and stat in a sticky header, then the lines. `viewHref` * links the file as it is after the change. */ export function FileDiffView({ file, viewHref, budget }: { file: FileDiff; viewHref?: string; budget: number }) { const name = file.status === "renamed" ? `${file.oldPath} → ${file.newPath}` : file.newPath; const truncated = file.lines.length > budget; return (
{name} {file.status !== "modified" && ( )} {viewHref && file.status !== "deleted" && ( } className="ml-auto shrink-0" > View File )}
{file.binary ? (

Binary file not shown.

) : file.lines.length === 0 ? (

File mode or name changed only.

) : truncated ? (

Large diffs are not rendered by default.

) : (
{file.lines.map((line, i) => line.kind === "hunk" ? ( ) : ( ), )}
{line.text}
{line.oldNo} {line.newNo} {lineStyles[line.kind].sign} {line.text}
)}
); }