import figures from 'figures' import React, { useMemo } from 'react' import type { DiffFile } from '../../hooks/useDiffData.js' import { useTerminalSize } from '../../hooks/useTerminalSize.js' import { Box, Text } from '@anthropic/ink' import { truncateStartToWidth } from '../../utils/format.js' import { plural } from '../../utils/stringUtils.js' const MAX_VISIBLE_FILES = 5 type Props = { files: DiffFile[] selectedIndex: number } export function DiffFileList({ files, selectedIndex }: Props): React.ReactNode { const { columns } = useTerminalSize() // Calculate scroll window - must be before early return for hooks rules const { startIndex, endIndex } = useMemo(() => { if (files.length === 0 || files.length <= MAX_VISIBLE_FILES) { return { startIndex: 0, endIndex: files.length } } // Keep selected item roughly in the middle let start = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE_FILES / 2)) let end = start + MAX_VISIBLE_FILES // Adjust if we're at the end if (end > files.length) { end = files.length start = Math.max(0, end - MAX_VISIBLE_FILES) } return { startIndex: start, endIndex: end } }, [files.length, selectedIndex]) if (files.length === 0) { return No changed files } const visibleFiles = files.slice(startIndex, endIndex) const hasMoreAbove = startIndex > 0 const hasMoreBelow = endIndex < files.length const needsPagination = files.length > MAX_VISIBLE_FILES const statsWidth = 16 const pointerWidth = 3 const maxPathWidth = Math.max(20, columns - statsWidth - pointerWidth - 4) return ( {needsPagination && ( {hasMoreAbove ? ` ↑ ${startIndex} more ${plural(startIndex, 'file')}` : ' '} )} {visibleFiles.map((file, index) => ( ))} {needsPagination && ( {hasMoreBelow ? ` ↓ ${files.length - endIndex} more ${plural(files.length - endIndex, 'file')}` : ' '} )} ) } function FileItem({ file, isSelected, maxPathWidth, }: { file: DiffFile isSelected: boolean maxPathWidth: number }): React.ReactNode { const displayPath = truncateStartToWidth(file.path, maxPathWidth) const pointer = isSelected ? figures.pointer + ' ' : ' ' const line = `${pointer}${displayPath}` return ( {line} ) } function FileStats({ file, isSelected, }: { file: DiffFile isSelected: boolean }): React.ReactNode { if (file.isUntracked) { return ( untracked ) } if (file.isBinary) { return ( Binary file ) } if (file.isLargeFile) { return ( Large file modified ) } // Normal or truncated file - show line counts return ( {file.linesAdded > 0 && ( +{file.linesAdded} )} {file.linesAdded > 0 && file.linesRemoved > 0 && ' '} {file.linesRemoved > 0 && ( -{file.linesRemoved} )} {file.isTruncated && (truncated)} ) }