refactor(file-explorer): add deep linking, path navigation, diff preview, and Codex session persistence
This commit is contained in:
@@ -448,7 +448,8 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.muted,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
color: theme.colors.foreground,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontSize: theme.fontSize.lg,
|
||||
lineHeight: theme.fontSize.lg * 1.4,
|
||||
},
|
||||
buttonRow: {
|
||||
flexDirection: "row",
|
||||
|
||||
@@ -14,17 +14,20 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { BottomSheetModal } from "@gorhom/bottom-sheet";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import { ChevronDown } from "lucide-react-native";
|
||||
import { useRouter } from "expo-router";
|
||||
import {
|
||||
AssistantMessage,
|
||||
UserMessage,
|
||||
ActivityLog,
|
||||
ToolCall,
|
||||
AgentThoughtMessage,
|
||||
type InlinePathTarget,
|
||||
} from "./message";
|
||||
import { ToolCallBottomSheet } from "./tool-call-bottom-sheet";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import type { SelectedToolCall, PendingPermission } from "@/types/shared";
|
||||
import type { Agent } from "@/contexts/session-context";
|
||||
import { useSession } from "@/contexts/session-context";
|
||||
|
||||
export interface AgentStreamViewProps {
|
||||
agentId: string;
|
||||
@@ -52,6 +55,8 @@ export function AgentStreamView({
|
||||
const isProgrammaticScrollRef = useRef(false);
|
||||
const isNearBottomRef = useRef(true);
|
||||
const isUserScrollingRef = useRef(false);
|
||||
const router = useRouter();
|
||||
const { requestDirectoryListing, requestFilePreview } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
hasScrolledInitially.current = false;
|
||||
@@ -71,6 +76,40 @@ export function AgentStreamView({
|
||||
setSelectedToolCall(null);
|
||||
}
|
||||
|
||||
const handleInlinePathPress = useCallback(
|
||||
(target: InlinePathTarget) => {
|
||||
if (!target.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizeInlinePath(target.path);
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestDirectoryListing(agentId, normalized.directory);
|
||||
if (normalized.file) {
|
||||
requestFilePreview(agentId, normalized.file);
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: "/file-explorer",
|
||||
params: {
|
||||
agentId,
|
||||
path: normalized.directory,
|
||||
...(normalized.file ? { file: normalized.file } : {}),
|
||||
...(target.lineStart !== undefined
|
||||
? { lineStart: String(target.lineStart) }
|
||||
: {}),
|
||||
...(target.lineEnd !== undefined
|
||||
? { lineEnd: String(target.lineEnd) }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
},
|
||||
[agentId, requestDirectoryListing, requestFilePreview, router]
|
||||
);
|
||||
|
||||
const handleScroll = useCallback(
|
||||
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
const { contentOffset } = event.nativeEvent;
|
||||
@@ -179,6 +218,7 @@ export function AgentStreamView({
|
||||
<AssistantMessage
|
||||
message={item.text}
|
||||
timestamp={item.timestamp.getTime()}
|
||||
onInlinePathPress={handleInlinePathPress}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -324,6 +364,48 @@ export function AgentStreamView({
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeInlinePath(rawPath: string):
|
||||
| { directory: string; file?: string }
|
||||
| null {
|
||||
if (!rawPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let value = rawPath.trim();
|
||||
value = value.replace(/^['"`]/, "").replace(/['"`]$/, "");
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let normalized = value.replace(/\\/g, "/");
|
||||
normalized = normalized.replace(/\/{2,}/g, "/");
|
||||
|
||||
if (normalized.startsWith("./")) {
|
||||
normalized = normalized.slice(2) || ".";
|
||||
}
|
||||
|
||||
if (!normalized.length) {
|
||||
normalized = ".";
|
||||
}
|
||||
|
||||
if (normalized === ".") {
|
||||
return { directory: "." };
|
||||
}
|
||||
|
||||
if (normalized.endsWith("/")) {
|
||||
const dir = normalized.replace(/\/+$/, "");
|
||||
return { directory: dir.length > 0 ? dir : "." };
|
||||
}
|
||||
|
||||
const lastSlash = normalized.lastIndexOf("/");
|
||||
const directory = lastSlash >= 0 ? normalized.slice(0, lastSlash) : ".";
|
||||
|
||||
return {
|
||||
directory: directory.length > 0 ? directory : ".",
|
||||
file: normalized,
|
||||
};
|
||||
}
|
||||
|
||||
// Permission Request Card Component
|
||||
function PermissionRequestCard({
|
||||
permission,
|
||||
|
||||
@@ -125,10 +125,18 @@ export const UserMessage = memo(function UserMessage({
|
||||
);
|
||||
});
|
||||
|
||||
export interface InlinePathTarget {
|
||||
raw: string;
|
||||
path: string;
|
||||
lineStart?: number;
|
||||
lineEnd?: number;
|
||||
}
|
||||
|
||||
interface AssistantMessageProps {
|
||||
message: string;
|
||||
timestamp: number;
|
||||
isStreaming?: boolean;
|
||||
onInlinePathPress?: (target: InlinePathTarget) => void;
|
||||
}
|
||||
|
||||
export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
||||
@@ -211,6 +219,19 @@ export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
fontStyle: "italic" as const,
|
||||
},
|
||||
pathChip: {
|
||||
backgroundColor: theme.colors.secondary,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: 2,
|
||||
marginRight: theme.spacing[1],
|
||||
marginVertical: 2,
|
||||
},
|
||||
pathChipText: {
|
||||
color: theme.colors.secondaryForeground,
|
||||
fontFamily: "monospace",
|
||||
fontSize: 13,
|
||||
},
|
||||
}));
|
||||
|
||||
const markdownStyles = {
|
||||
@@ -229,12 +250,129 @@ const markdownStyles = {
|
||||
blockquote_text: assistantMessageStylesheet.markdownBlockquoteText,
|
||||
};
|
||||
|
||||
function isLikelyPathToken(value: string): boolean {
|
||||
if (!value || value.length > 300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/\s/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasSlash = value.includes("/") || value.includes("\\");
|
||||
const hasExtension = /\.[a-zA-Z0-9]{1,8}$/.test(value);
|
||||
|
||||
if (!hasSlash && !hasExtension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const looksLikeDir = value.endsWith("/") || value.startsWith("./") || value.startsWith("../");
|
||||
|
||||
return hasExtension || looksLikeDir || value.includes("/");
|
||||
}
|
||||
|
||||
function normalizeInlinePathValue(value: string): string | null {
|
||||
const trimmed = value.trim().replace(/^['"`]/, "").replace(/['"`]$/, "");
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
function parseInlinePathToken(
|
||||
value: string,
|
||||
lastPathRef: React.MutableRefObject<string | null>
|
||||
): InlinePathTarget | null {
|
||||
const rawValue = value ?? "";
|
||||
const trimmed = rawValue.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rangeOnlyMatch = trimmed.match(/^:([0-9]+)(?:-([0-9]+))?$/);
|
||||
if (rangeOnlyMatch) {
|
||||
const basePath = lastPathRef.current;
|
||||
if (!basePath) {
|
||||
return null;
|
||||
}
|
||||
const lineStart = parseInt(rangeOnlyMatch[1], 10);
|
||||
const lineEnd = rangeOnlyMatch[2] ? parseInt(rangeOnlyMatch[2], 10) : undefined;
|
||||
return {
|
||||
raw: rawValue,
|
||||
path: basePath,
|
||||
lineStart,
|
||||
lineEnd,
|
||||
};
|
||||
}
|
||||
|
||||
const pathMatch = trimmed.match(/^(.*?)(?::([0-9]+)(?:-([0-9]+))?)?$/);
|
||||
if (!pathMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const basePath = pathMatch[1]?.trim();
|
||||
if (!basePath || !isLikelyPathToken(basePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedPath = normalizeInlinePathValue(basePath);
|
||||
if (!normalizedPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
lastPathRef.current = normalizedPath;
|
||||
|
||||
const lineStart = pathMatch[2] ? parseInt(pathMatch[2], 10) : undefined;
|
||||
const lineEnd = pathMatch[3] ? parseInt(pathMatch[3], 10) : undefined;
|
||||
|
||||
return {
|
||||
raw: rawValue,
|
||||
path: normalizedPath,
|
||||
lineStart,
|
||||
lineEnd,
|
||||
};
|
||||
}
|
||||
|
||||
export const AssistantMessage = memo(function AssistantMessage({
|
||||
message,
|
||||
timestamp,
|
||||
isStreaming = false,
|
||||
onInlinePathPress,
|
||||
}: AssistantMessageProps) {
|
||||
const fadeAnim = useRef(new Animated.Value(0.3)).current;
|
||||
const lastPathRef = useRef<string | null>(null);
|
||||
|
||||
const markdownRules = useMemo(() => {
|
||||
if (!onInlinePathPress) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
code_inline: (node: any) => {
|
||||
const content = node.content ?? "";
|
||||
const parsed = parseInlinePathToken(content, lastPathRef);
|
||||
|
||||
if (!parsed) {
|
||||
return (
|
||||
<Text key={node.key} style={assistantMessageStylesheet.markdownCodeInline}>
|
||||
{content}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
key={node.key}
|
||||
onPress={() => parsed && onInlinePathPress?.(parsed)}
|
||||
style={[assistantMessageStylesheet.pathChip, assistantMessageStylesheet.pathChipText]}
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
};
|
||||
}, [onInlinePathPress]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isStreaming) {
|
||||
@@ -260,7 +398,9 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
|
||||
return (
|
||||
<View style={assistantMessageStylesheet.container}>
|
||||
<Markdown style={markdownStyles}>{message}</Markdown>
|
||||
<Markdown style={markdownStyles} rules={markdownRules}>
|
||||
{message}
|
||||
</Markdown>
|
||||
{isStreaming && (
|
||||
<Animated.View
|
||||
style={[
|
||||
|
||||
@@ -12,6 +12,164 @@ import {
|
||||
} from "@gorhom/bottom-sheet";
|
||||
import type { SelectedToolCall } from "@/types/shared";
|
||||
|
||||
type DiffLine = {
|
||||
type: "add" | "remove" | "context" | "header";
|
||||
content: string;
|
||||
};
|
||||
|
||||
type EditEntry = {
|
||||
filePath?: string;
|
||||
diffLines: DiffLine[];
|
||||
};
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function getString(value: unknown): string | undefined {
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function splitIntoLines(text: string): string[] {
|
||||
if (!text) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return text.replace(/\r\n/g, "\n").split("\n");
|
||||
}
|
||||
|
||||
function buildLineDiff(originalText: string, updatedText: string): DiffLine[] {
|
||||
const originalLines = splitIntoLines(originalText);
|
||||
const updatedLines = splitIntoLines(updatedText);
|
||||
|
||||
const hasAnyContent = originalLines.length > 0 || updatedLines.length > 0;
|
||||
if (!hasAnyContent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const m = originalLines.length;
|
||||
const n = updatedLines.length;
|
||||
const dp: number[][] = Array.from({ length: m + 1 }, () =>
|
||||
Array(n + 1).fill(0)
|
||||
);
|
||||
|
||||
for (let i = m - 1; i >= 0; i -= 1) {
|
||||
for (let j = n - 1; j >= 0; j -= 1) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
dp[i][j] = dp[i + 1][j + 1] + 1;
|
||||
} else {
|
||||
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diff: DiffLine[] = [];
|
||||
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < m && j < n) {
|
||||
if (originalLines[i] === updatedLines[j]) {
|
||||
diff.push({ type: "context", content: ` ${originalLines[i]}` });
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
} else {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < m) {
|
||||
diff.push({ type: "remove", content: `-${originalLines[i]}` });
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while (j < n) {
|
||||
diff.push({ type: "add", content: `+${updatedLines[j]}` });
|
||||
j += 1;
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
function parseUnifiedDiff(diffText?: string): DiffLine[] {
|
||||
if (!diffText) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines = splitIntoLines(diffText);
|
||||
const diff: DiffLine[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.length) {
|
||||
diff.push({ type: "context", content: line });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("@@")) {
|
||||
diff.push({ type: "header", content: line });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("+")) {
|
||||
if (!line.startsWith("+++")) {
|
||||
diff.push({ type: "add", content: line });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("-")) {
|
||||
if (!line.startsWith("---")) {
|
||||
diff.push({ type: "remove", content: line });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
line.startsWith("diff --git") ||
|
||||
line.startsWith("index ") ||
|
||||
line.startsWith("---") ||
|
||||
line.startsWith("+++")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("\\ No newline")) {
|
||||
diff.push({ type: "header", content: line });
|
||||
continue;
|
||||
}
|
||||
|
||||
diff.push({ type: "context", content: line });
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
function deriveDiffLines({
|
||||
unifiedDiff,
|
||||
original,
|
||||
updated,
|
||||
}: {
|
||||
unifiedDiff?: string;
|
||||
original?: string;
|
||||
updated?: string;
|
||||
}): DiffLine[] {
|
||||
if (unifiedDiff) {
|
||||
const parsed = parseUnifiedDiff(unifiedDiff);
|
||||
if (parsed.length > 0) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
if (original !== undefined || updated !== undefined) {
|
||||
return buildLineDiff(original ?? "", updated ?? "");
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
interface ToolCallBottomSheetProps {
|
||||
bottomSheetRef: React.RefObject<BottomSheetModal | null>;
|
||||
selectedToolCall: SelectedToolCall | null;
|
||||
@@ -81,6 +239,110 @@ export function ToolCallBottomSheet({
|
||||
}
|
||||
}, [selectedToolCall]);
|
||||
|
||||
const editEntries = useMemo(() => {
|
||||
if (!args || !isRecord(args)) {
|
||||
return [] as EditEntry[];
|
||||
}
|
||||
|
||||
const rawArgs = args as Record<string, unknown>;
|
||||
const changesValue = rawArgs["changes"];
|
||||
|
||||
if (isRecord(changesValue)) {
|
||||
const changeEntries = Object.entries(
|
||||
changesValue as Record<string, unknown>
|
||||
);
|
||||
|
||||
return changeEntries
|
||||
.map<EditEntry | null>(([filePath, value]) => {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let changeBlock: Record<string, unknown> | null = null;
|
||||
if (isRecord(value["update"])) {
|
||||
changeBlock = value["update"] as Record<string, unknown>;
|
||||
} else if (isRecord(value["create"])) {
|
||||
changeBlock = value["create"] as Record<string, unknown>;
|
||||
} else if (isRecord(value["delete"])) {
|
||||
changeBlock = value["delete"] as Record<string, unknown>;
|
||||
} else {
|
||||
changeBlock = value;
|
||||
}
|
||||
|
||||
if (!changeBlock) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const diffLines = deriveDiffLines({
|
||||
unifiedDiff: getString(
|
||||
changeBlock["unified_diff"] ??
|
||||
changeBlock["diff"] ??
|
||||
changeBlock["patch"] ??
|
||||
changeBlock["unifiedDiff"]
|
||||
),
|
||||
original:
|
||||
getString(
|
||||
changeBlock["old_content"] ??
|
||||
changeBlock["oldContent"] ??
|
||||
changeBlock["old_string"] ??
|
||||
changeBlock["previous_content"] ??
|
||||
changeBlock["previousContent"] ??
|
||||
changeBlock["base_content"] ??
|
||||
changeBlock["baseContent"]
|
||||
) ?? undefined,
|
||||
updated:
|
||||
getString(
|
||||
changeBlock["new_content"] ??
|
||||
changeBlock["newContent"] ??
|
||||
changeBlock["new_string"] ??
|
||||
changeBlock["content"] ??
|
||||
changeBlock["replace_with"] ??
|
||||
changeBlock["replaceWith"]
|
||||
) ?? undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
filePath,
|
||||
diffLines,
|
||||
};
|
||||
})
|
||||
.filter((entry): entry is EditEntry => Boolean(entry));
|
||||
}
|
||||
|
||||
const filePath =
|
||||
getString(
|
||||
rawArgs["file_path"] ?? rawArgs["filePath"] ?? rawArgs["path"]
|
||||
) || undefined;
|
||||
const diffLines = deriveDiffLines({
|
||||
original:
|
||||
getString(
|
||||
rawArgs["old_string"] ??
|
||||
rawArgs["oldString"] ??
|
||||
rawArgs["old_content"] ??
|
||||
rawArgs["previous_content"] ??
|
||||
rawArgs["base_content"]
|
||||
) ?? undefined,
|
||||
updated:
|
||||
getString(
|
||||
rawArgs["new_string"] ??
|
||||
rawArgs["newString"] ??
|
||||
rawArgs["new_content"] ??
|
||||
rawArgs["content"]
|
||||
) ?? undefined,
|
||||
});
|
||||
|
||||
if (!filePath && diffLines.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
filePath,
|
||||
diffLines,
|
||||
},
|
||||
];
|
||||
}, [args]);
|
||||
|
||||
return (
|
||||
<BottomSheetModal
|
||||
ref={bottomSheetRef}
|
||||
@@ -102,6 +364,68 @@ export function ToolCallBottomSheet({
|
||||
contentContainerStyle={styles.sheetContent}
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
{editEntries.length > 0 &&
|
||||
editEntries.map((entry, index) => (
|
||||
<View key={`${entry.filePath ?? "file"}-${index}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>File</Text>
|
||||
<View style={styles.fileInfoContainer}>
|
||||
<Text style={styles.fileInfoText}>
|
||||
{entry.filePath ?? "Unknown file"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.sectionTitle}>Diff</Text>
|
||||
<View style={styles.diffContainer}>
|
||||
{entry.diffLines.length === 0 ? (
|
||||
<View style={styles.diffEmptyState}>
|
||||
<Text style={styles.diffEmptyText}>No changes to display</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={styles.diffScrollVertical}
|
||||
contentContainerStyle={styles.diffVerticalContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator
|
||||
contentContainerStyle={styles.diffScrollContent}
|
||||
>
|
||||
<View style={styles.diffLinesContainer}>
|
||||
{entry.diffLines.map((line, lineIndex) => (
|
||||
<View
|
||||
key={`${line.type}-${lineIndex}`}
|
||||
style={[
|
||||
styles.diffLine,
|
||||
line.type === "header" && styles.diffHeaderLine,
|
||||
line.type === "add" && styles.diffAddLine,
|
||||
line.type === "remove" && styles.diffRemoveLine,
|
||||
line.type === "context" && styles.diffContextLine,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.diffLineText,
|
||||
line.type === "header" && styles.diffHeaderText,
|
||||
line.type === "add" && styles.diffAddText,
|
||||
line.type === "remove" && styles.diffRemoveText,
|
||||
line.type === "context" && styles.diffContextText,
|
||||
]}
|
||||
>
|
||||
{line.content}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* Content sections */}
|
||||
{args !== undefined && (
|
||||
<View style={styles.section}>
|
||||
@@ -196,6 +520,81 @@ const styles = StyleSheet.create((theme) => ({
|
||||
textTransform: "uppercase" as const,
|
||||
letterSpacing: 0.5,
|
||||
},
|
||||
fileInfoContainer: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
padding: theme.spacing[3],
|
||||
marginBottom: theme.spacing[4],
|
||||
},
|
||||
fileInfoText: {
|
||||
fontFamily: "monospace",
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
diffContainer: {
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
backgroundColor: theme.colors.card,
|
||||
overflow: "hidden",
|
||||
},
|
||||
diffScrollVertical: {
|
||||
maxHeight: 280,
|
||||
},
|
||||
diffVerticalContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
diffScrollContent: {
|
||||
flexDirection: "column" as const,
|
||||
},
|
||||
diffLinesContainer: {
|
||||
alignSelf: "flex-start",
|
||||
},
|
||||
diffLine: {
|
||||
minWidth: "100%",
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: theme.spacing[1],
|
||||
},
|
||||
diffLineText: {
|
||||
fontFamily: "monospace",
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
diffHeaderLine: {
|
||||
backgroundColor: theme.colors.muted,
|
||||
},
|
||||
diffHeaderText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
},
|
||||
diffAddLine: {
|
||||
backgroundColor: theme.colors.palette.green[900],
|
||||
},
|
||||
diffAddText: {
|
||||
color: theme.colors.palette.green[200],
|
||||
},
|
||||
diffRemoveLine: {
|
||||
backgroundColor: theme.colors.palette.red[900],
|
||||
},
|
||||
diffRemoveText: {
|
||||
color: theme.colors.palette.red[200],
|
||||
},
|
||||
diffContextLine: {
|
||||
backgroundColor: theme.colors.card,
|
||||
},
|
||||
diffContextText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
},
|
||||
diffEmptyState: {
|
||||
padding: theme.spacing[4],
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "center" as const,
|
||||
},
|
||||
diffEmptyText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.mutedForeground,
|
||||
},
|
||||
jsonContainer: {
|
||||
backgroundColor: theme.colors.background,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
|
||||
Reference in New Issue
Block a user