import { useState, useCallback } from "react"; import { View, Text, ActivityIndicator, Pressable, RefreshControl } from "react-native"; import { ScrollView } from "react-native-gesture-handler"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { ChevronRight } from "lucide-react-native"; import { useSessionStore } from "@/stores/session-store"; import { useHighlightedDiffQuery, type ParsedDiffFile, type DiffLine, type HighlightToken, } from "@/hooks/use-highlighted-diff-query"; type HighlightStyle = NonNullable; interface HighlightedTextProps { tokens: HighlightToken[]; baseStyle: HighlightStyle | null; lineType: "add" | "remove" | "context" | "header"; } function HighlightedText({ tokens, lineType }: HighlightedTextProps) { const { theme } = useUnistyles(); // Get color for a highlight style using GitHub Dark theme // Text colors are the same regardless of line type - only background changes const getTokenColor = (style: HighlightStyle | null): string => { const baseColor = "#c9d1d9"; // GitHub foreground if (!style) return baseColor; // GitHub Dark theme colors const highlightColors: Record = { keyword: "#ff7b72", // red comment: "#8b949e", // gray string: "#a5d6ff", // light blue number: "#79c0ff", // blue literal: "#79c0ff", // blue function: "#d2a8ff", // purple definition: "#d2a8ff", // purple class: "#ffa657", // orange type: "#ff7b72", // red (same as keyword in GitHub) tag: "#7ee787", // green attribute: "#79c0ff", // blue property: "#79c0ff", // blue variable: baseColor, operator: "#79c0ff", // blue punctuation: "#c9d1d9", // foreground regexp: "#a5d6ff", // light blue escape: "#79c0ff", // blue meta: "#8b949e", // gray heading: "#79c0ff", // blue link: "#a5d6ff", // light blue }; return highlightColors[style] ?? baseColor; }; return ( {tokens.map((token, index) => ( {token.text} ))} ); } interface DiffFileSectionProps { file: ParsedDiffFile; defaultExpanded?: boolean; } function DiffLineView({ line }: { line: DiffLine }) { return ( {line.tokens && line.type !== "header" ? ( ) : ( {line.content || " "} )} ); } function DiffFileSection({ file, defaultExpanded = true }: DiffFileSectionProps) { const { theme } = useUnistyles(); const [isExpanded, setIsExpanded] = useState(defaultExpanded); const toggleExpanded = useCallback(() => { setIsExpanded((prev) => !prev); }, []); return ( [ styles.fileHeader, pressed && styles.fileHeaderPressed, ]} onPress={toggleExpanded} > {file.path} {file.isNew && ( New )} +{file.additions} -{file.deletions} {isExpanded && ( {file.hunks.map((hunk, hunkIndex) => hunk.lines.map((line, lineIndex) => ( )) )} )} ); } interface GitDiffPaneProps { serverId: string; agentId: string; } export function GitDiffPane({ serverId, agentId }: GitDiffPaneProps) { const { theme } = useUnistyles(); const { files, isLoading, isFetching, isError, error, refresh } = useHighlightedDiffQuery({ serverId, agentId, }); const agent = useSessionStore((state) => state.sessions[serverId]?.agents?.get(agentId) ); if (!agent) { return ( Agent not found ); } const hasChanges = files.length > 0; const errorMessage = isError && error instanceof Error ? error.message : null; return ( } > {isLoading ? ( Loading changes... ) : isError ? ( {errorMessage ?? "Failed to load changes"} ) : !hasChanges ? ( No changes ) : ( files.map((file, fileIndex) => ( )) )} ); } const styles = StyleSheet.create((theme) => ({ scrollView: { flex: 1, }, contentContainer: { paddingHorizontal: theme.spacing[2], paddingTop: theme.spacing[2], paddingBottom: theme.spacing[8], }, loadingContainer: { flex: 1, alignItems: "center", justifyContent: "center", paddingTop: theme.spacing[16], gap: theme.spacing[4], }, loadingText: { fontSize: theme.fontSize.base, color: theme.colors.mutedForeground, }, errorContainer: { flex: 1, alignItems: "center", justifyContent: "center", paddingTop: theme.spacing[16], paddingHorizontal: theme.spacing[6], }, errorText: { fontSize: theme.fontSize.base, color: theme.colors.destructive, textAlign: "center", }, emptyContainer: { flex: 1, alignItems: "center", justifyContent: "center", paddingTop: theme.spacing[16], }, emptyText: { fontSize: theme.fontSize.lg, color: theme.colors.mutedForeground, }, fileSection: { borderRadius: theme.borderRadius.lg, overflow: "hidden", backgroundColor: theme.colors.muted, borderWidth: theme.borderWidth[1], borderColor: theme.colors.border, marginBottom: theme.spacing[2], }, fileHeader: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", padding: theme.spacing[2], paddingVertical: theme.spacing[2], gap: theme.spacing[2], }, fileHeaderPressed: { opacity: 0.7, }, fileHeaderLeft: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], flex: 1, minWidth: 0, }, fileHeaderRight: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], flexShrink: 0, }, chevronContainer: { transform: [{ rotate: "0deg" }], }, chevronExpanded: { transform: [{ rotate: "90deg" }], }, filePath: { fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, color: theme.colors.foreground, fontFamily: "monospace", flex: 1, }, newBadge: { backgroundColor: "rgba(46, 160, 67, 0.2)", paddingHorizontal: theme.spacing[2], paddingVertical: theme.spacing[1], borderRadius: theme.borderRadius.md, flexShrink: 0, }, newBadgeText: { fontSize: theme.fontSize.xs, fontWeight: theme.fontWeight.normal, color: theme.colors.palette.green[400], }, additions: { fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, color: theme.colors.palette.green[400], fontFamily: "monospace", }, deletions: { fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.normal, color: theme.colors.palette.red[500], fontFamily: "monospace", }, diffContent: { borderTopWidth: theme.borderWidth[1], borderTopColor: theme.colors.border, backgroundColor: "#0d1117", // GitHub dark background }, diffContentInner: { flexDirection: "column", }, linesContainer: { alignSelf: "flex-start", minWidth: "100%", }, diffLineContainer: { paddingHorizontal: theme.spacing[3], paddingVertical: theme.spacing[1], }, diffLineText: { fontSize: theme.fontSize.xs, fontFamily: "monospace", color: theme.colors.foreground, }, addLineContainer: { backgroundColor: "rgba(46, 160, 67, 0.1)", // GitHub green with transparency }, addLineText: { color: "#c9d1d9", // Same text color as all code }, removeLineContainer: { backgroundColor: "rgba(248, 81, 73, 0.1)", // GitHub red with transparency }, removeLineText: { color: "#c9d1d9", // Same text color as all code }, headerLineContainer: { backgroundColor: "#161b22", // GitHub dark header }, headerLineText: { color: theme.colors.mutedForeground, }, contextLineContainer: { backgroundColor: "#0d1117", // GitHub dark background }, contextLineText: { color: theme.colors.mutedForeground, }, }));