import { useMemo } from 'react' import { Pressable, Text, View } from 'react-native' import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' import { ChevronRight, FileMinus2, FilePen, FilePlus2 } from 'lucide-react-native' import { NATIVE_CHAT_DIFF_CARD_COPY, nativeChatEditFilePatchText } from '../../../src/shared/native-chat/tools/native-chat-edit-cards' import { unifiedLineNumber, type NativeChatEditFile, type NativeChatEditLine } from '../../../src/shared/native-chat/tools/native-chat-edit-model' import { NATIVE_CHAT_TOOL_ROW_VERB_COPY } from '../../../src/shared/native-chat/tools/native-chat-tool-line-label' import { MobileSourceCopyButton } from '../components/MobileSourceCopyButton' import { colors } from '../theme/mobile-theme' import { useMobileNativeChatOpenedDetail } from './mobile-native-chat-opened-detail' import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles' function baseName(path: string): string { return path.split(/[\\/]/).at(-1) || path } function VerbIcon({ kind, size }: { kind: NativeChatEditFile['changeKind']; size: number }) { const Icon = kind === 'added' ? FilePlus2 : kind === 'deleted' ? FileMinus2 : FilePen return } function DiffRow({ line, gutterChars, type }: { line: NativeChatEditLine gutterChars: number type: ReturnType['code'] }): React.JSX.Element { if (line.kind === 'gap') { return ( ⋯ ) } return ( {gutterChars > 0 ? ( {unifiedLineNumber(line) ?? ''} ) : null} {line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ' '} {line.text} ) } // Mount rows only when expanded so collapsed cards avoid diff work. function DiffCardRows({ file, fontScale, disclosureKey, rootIdentity }: { file: NativeChatEditFile fontScale: number disclosureKey?: string rootIdentity?: string }) { const onLayout = useMobileNativeChatOpenedDetail() const type = toolType(fontScale).code // Leave unresolved gutters blank; snippet-relative numbers would imply real file positions. const widest = file.lineNumbersKnown ? file.lines.reduce((max, line) => Math.max(max, unifiedLineNumber(line) ?? 0), 0) : 0 const gutterChars = file.lineNumbersKnown ? Math.max(3, String(widest).length + 1) : 0 const seen = new Map() return ( {file.lines.map((line) => { const signature = `${line.kind}:${line.oldLineNumber}:${line.newLineNumber}:${line.text}` const occurrence = seen.get(signature) ?? 0 seen.set(signature, occurrence + 1) return ( ) })} ) } export function MobileNativeChatDiffCard({ file, fontScale, disclosureKey, rootIdentity, onOpenFile }: { file: NativeChatEditFile fontScale: number disclosureKey?: string rootIdentity?: string onOpenFile?: (path: string) => void }): React.JSX.Element { const [expanded, setExpanded] = useMobileNativeChatDisclosure(disclosureKey, false) const hasBody = file.lines.length > 0 const copyText = useMemo(() => nativeChatEditFilePatchText(file.lines), [file.lines]) const type = toolType(fontScale) const iconSize = 15 * fontScale const openFile = file.changeKind === 'deleted' ? undefined : onOpenFile return ( hasBody && setExpanded(!expanded)} disabled={!hasBody} accessibilityRole="button" accessibilityState={hasBody ? { expanded } : undefined} > {NATIVE_CHAT_TOOL_ROW_VERB_COPY[file.changeKind]} {file.oldPath ? ( <> {baseName(file.oldPath)} → ) : null} openFile(file.path) : undefined} suppressHighlighting={!openFile} accessibilityRole={openFile ? 'link' : undefined} > {baseName(file.path)} {file.added > 0 ? {`+${file.added}`} : null} {file.added > 0 && file.removed > 0 ? ' ' : null} {file.removed > 0 ? ( {`-${file.removed}`} ) : null} {file.truncated ? ( {NATIVE_CHAT_DIFF_CARD_COPY.diffTruncated} ) : null} {hasBody ? ( ) : null} {hasBody && expanded ? ( ) : null} ) }