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))
}