import type { UiTheme } from "../theme" import { SectionDivider } from "./section-divider" import { getVisibleRange } from "../list-range" import type { FileRow, FocusTarget } from "../types" import { fitPathPartsForWidth } from "../utils" import { ViewFrame, resolveViewContentWidth, resolveVisibleRows } from "./view-frame" type DiffWorkspaceProps = { hasSnapshot: boolean fileRows: FileRow[] fileIndex: number selectedFilePath: string | null focus: FocusTarget terminalWidth: number terminalHeight: number diffText: string diffMessage: string | null diffFiletype: string | undefined diffView: "unified" | "split" onFileClick: (index: number) => void onFileScroll: (direction: "up" | "down") => void theme: UiTheme } export function DiffWorkspace({ hasSnapshot, fileRows, fileIndex, selectedFilePath, focus, terminalWidth, terminalHeight, diffText, diffMessage, diffFiletype, diffView, onFileClick, onFileScroll, theme, }: DiffWorkspaceProps) { const visibleRows = resolveVisibleRows(terminalHeight, 6) const contentWidth = resolveViewContentWidth(terminalWidth) const changesPaneWidth = getChangesPaneWidth(contentWidth) const filePathWidth = Math.max(changesPaneWidth - 6, 1) const { start, end } = getVisibleRange(fileRows.length, fileIndex, visibleRows) const rows = fileRows.slice(start, end) const showLoadingState = !hasSnapshot const showCleanState = hasSnapshot && fileRows.length === 0 const showUnselectedState = hasSnapshot && fileRows.length > 0 && !selectedFilePath const paneLabel = selectedFilePath ?? (showLoadingState ? "loading" : showCleanState ? "overview" : "no file selected") return ( changes ({fileRows.length}) { const direction = event.scroll?.direction if (direction !== "up" && direction !== "down") return event.preventDefault() event.stopPropagation() onFileScroll(direction) }} > {rows.length === 0 ? ( {showLoadingState ? "loading changes..." : "working tree clean"} ) : ( rows.map((row, rowIndex) => { const absoluteIndex = start + rowIndex const selected = absoluteIndex === fileIndex const fittedPath = fitPathPartsForWidth(row.directory, row.filename, filePathWidth) return ( { event.preventDefault() event.stopPropagation() onFileClick(absoluteIndex) }} > {row.included ? "[x]" : "[ ]"} {row.statusSymbol} {fittedPath.directory ? ( {fittedPath.directory} ) : null} {fittedPath.filename} ) }) )} {paneLabel} {showLoadingState ? ( ) : showCleanState ? ( ) : showUnselectedState ? ( ) : diffMessage ? ( {diffMessage} ) : ( )} ) } type EmptyStatePanelProps = { title: string subtitle: string hint: string theme: UiTheme } function EmptyStatePanel({ title, subtitle, hint, theme }: EmptyStatePanelProps) { return ( {title} {subtitle} {hint} ) } function getChangesPaneWidth(terminalWidth: number): number { if (!Number.isFinite(terminalWidth) || terminalWidth <= 0) return 42 const minimumWidth = 24 const minimumDiffWidth = 48 const preferredWidth = Math.floor(terminalWidth * 0.28) const maxAllowedWidth = Math.max(minimumWidth, terminalWidth - minimumDiffWidth) return clamp(preferredWidth, minimumWidth, maxAllowedWidth) } function clamp(value: number, min: number, max: number): number { return Math.max(min, Math.min(max, value)) }