mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,280 +1,140 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import type { StructuredPatchHunk } from 'diff';
|
||||
import { resolve } from 'path';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { getCwd } from '../../utils/cwd.js';
|
||||
import { readFileSafe } from '../../utils/file.js';
|
||||
import { Divider } from '../design-system/Divider.js';
|
||||
import { StructuredDiff } from '../StructuredDiff.js';
|
||||
import type { StructuredPatchHunk } from 'diff'
|
||||
import { resolve } from 'path'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { getCwd } from '../../utils/cwd.js'
|
||||
import { readFileSafe } from '../../utils/file.js'
|
||||
import { Divider } from '../design-system/Divider.js'
|
||||
import { StructuredDiff } from '../StructuredDiff.js'
|
||||
|
||||
type Props = {
|
||||
filePath: string;
|
||||
hunks: StructuredPatchHunk[];
|
||||
isLargeFile?: boolean;
|
||||
isBinary?: boolean;
|
||||
isTruncated?: boolean;
|
||||
isUntracked?: boolean;
|
||||
};
|
||||
filePath: string
|
||||
hunks: StructuredPatchHunk[]
|
||||
isLargeFile?: boolean
|
||||
isBinary?: boolean
|
||||
isTruncated?: boolean
|
||||
isUntracked?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the diff content for a single file.
|
||||
* Uses StructuredDiff for word-level diffing and syntax highlighting.
|
||||
* No scrolling - renders all lines (max 400 due to parsing limits).
|
||||
*/
|
||||
export function DiffDetailView(t0) {
|
||||
const $ = _c(53);
|
||||
const {
|
||||
filePath,
|
||||
hunks,
|
||||
isLargeFile,
|
||||
isBinary,
|
||||
isTruncated,
|
||||
isUntracked
|
||||
} = t0;
|
||||
const {
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
let t1;
|
||||
bb0: {
|
||||
export function DiffDetailView({
|
||||
filePath,
|
||||
hunks,
|
||||
isLargeFile,
|
||||
isBinary,
|
||||
isTruncated,
|
||||
isUntracked,
|
||||
}: Props): React.ReactNode {
|
||||
const { columns } = useTerminalSize()
|
||||
|
||||
// Read file content for syntax detection and multiline construct handling.
|
||||
// Only computed when this component is rendered (detail view mode).
|
||||
const { firstLine, fileContent } = useMemo(() => {
|
||||
if (!filePath) {
|
||||
let t2;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = {
|
||||
firstLine: null,
|
||||
fileContent: undefined
|
||||
};
|
||||
$[0] = t2;
|
||||
} else {
|
||||
t2 = $[0];
|
||||
}
|
||||
t1 = t2;
|
||||
break bb0;
|
||||
return { firstLine: null, fileContent: undefined }
|
||||
}
|
||||
let content;
|
||||
let t2;
|
||||
if ($[1] !== filePath) {
|
||||
const fullPath = resolve(getCwd(), filePath);
|
||||
content = readFileSafe(fullPath);
|
||||
t2 = content?.split("\n")[0] ?? null;
|
||||
$[1] = filePath;
|
||||
$[2] = content;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
content = $[2];
|
||||
t2 = $[3];
|
||||
const fullPath = resolve(getCwd(), filePath)
|
||||
const content = readFileSafe(fullPath)
|
||||
return {
|
||||
firstLine: content?.split('\n')[0] ?? null,
|
||||
fileContent: content ?? undefined,
|
||||
}
|
||||
const t3 = content ?? undefined;
|
||||
let t4;
|
||||
if ($[4] !== t2 || $[5] !== t3) {
|
||||
t4 = {
|
||||
firstLine: t2,
|
||||
fileContent: t3
|
||||
};
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
$[6] = t4;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
}
|
||||
t1 = t4;
|
||||
}
|
||||
const {
|
||||
firstLine,
|
||||
fileContent
|
||||
} = t1;
|
||||
}, [filePath])
|
||||
|
||||
// Handle untracked files
|
||||
if (isUntracked) {
|
||||
let t2;
|
||||
if ($[7] !== filePath) {
|
||||
t2 = <Text bold={true}>{filePath}</Text>;
|
||||
$[7] = filePath;
|
||||
$[8] = t2;
|
||||
} else {
|
||||
t2 = $[8];
|
||||
}
|
||||
let t3;
|
||||
if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = <Text dimColor={true}> (untracked)</Text>;
|
||||
$[9] = t3;
|
||||
} else {
|
||||
t3 = $[9];
|
||||
}
|
||||
let t4;
|
||||
if ($[10] !== t2) {
|
||||
t4 = <Box>{t2}{t3}</Box>;
|
||||
$[10] = t2;
|
||||
$[11] = t4;
|
||||
} else {
|
||||
t4 = $[11];
|
||||
}
|
||||
let t5;
|
||||
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t5 = <Divider padding={4} />;
|
||||
$[12] = t5;
|
||||
} else {
|
||||
t5 = $[12];
|
||||
}
|
||||
let t6;
|
||||
if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t6 = <Text dimColor={true} italic={true}>New file not yet staged.</Text>;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
let t7;
|
||||
if ($[14] !== filePath) {
|
||||
t7 = <Box flexDirection="column">{t6}<Text dimColor={true} italic={true}>Run `git add {filePath}` to see line counts.</Text></Box>;
|
||||
$[14] = filePath;
|
||||
$[15] = t7;
|
||||
} else {
|
||||
t7 = $[15];
|
||||
}
|
||||
let t8;
|
||||
if ($[16] !== t4 || $[17] !== t7) {
|
||||
t8 = <Box flexDirection="column" width="100%">{t4}{t5}{t7}</Box>;
|
||||
$[16] = t4;
|
||||
$[17] = t7;
|
||||
$[18] = t8;
|
||||
} else {
|
||||
t8 = $[18];
|
||||
}
|
||||
return t8;
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
<Box>
|
||||
<Text bold>{filePath}</Text>
|
||||
<Text dimColor> (untracked)</Text>
|
||||
</Box>
|
||||
<Divider padding={4} />
|
||||
<Box flexDirection="column">
|
||||
<Text dimColor italic>
|
||||
New file not yet staged.
|
||||
</Text>
|
||||
<Text dimColor italic>
|
||||
Run `git add {filePath}` to see line counts.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
// Handle binary files
|
||||
if (isBinary) {
|
||||
let t2;
|
||||
if ($[19] !== filePath) {
|
||||
t2 = <Box><Text bold={true}>{filePath}</Text></Box>;
|
||||
$[19] = filePath;
|
||||
$[20] = t2;
|
||||
} else {
|
||||
t2 = $[20];
|
||||
}
|
||||
let t3;
|
||||
if ($[21] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = <Divider padding={4} />;
|
||||
$[21] = t3;
|
||||
} else {
|
||||
t3 = $[21];
|
||||
}
|
||||
let t4;
|
||||
if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Box flexDirection="column"><Text dimColor={true} italic={true}>Binary file - cannot display diff</Text></Box>;
|
||||
$[22] = t4;
|
||||
} else {
|
||||
t4 = $[22];
|
||||
}
|
||||
let t5;
|
||||
if ($[23] !== t2) {
|
||||
t5 = <Box flexDirection="column" width="100%">{t2}{t3}{t4}</Box>;
|
||||
$[23] = t2;
|
||||
$[24] = t5;
|
||||
} else {
|
||||
t5 = $[24];
|
||||
}
|
||||
return t5;
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
<Box>
|
||||
<Text bold>{filePath}</Text>
|
||||
</Box>
|
||||
<Divider padding={4} />
|
||||
<Box flexDirection="column">
|
||||
<Text dimColor italic>
|
||||
Binary file - cannot display diff
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
// Handle large files
|
||||
if (isLargeFile) {
|
||||
let t2;
|
||||
if ($[25] !== filePath) {
|
||||
t2 = <Box><Text bold={true}>{filePath}</Text></Box>;
|
||||
$[25] = filePath;
|
||||
$[26] = t2;
|
||||
} else {
|
||||
t2 = $[26];
|
||||
}
|
||||
let t3;
|
||||
if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t3 = <Divider padding={4} />;
|
||||
$[27] = t3;
|
||||
} else {
|
||||
t3 = $[27];
|
||||
}
|
||||
let t4;
|
||||
if ($[28] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Box flexDirection="column"><Text dimColor={true} italic={true}>Large file - diff exceeds 1 MB limit</Text></Box>;
|
||||
$[28] = t4;
|
||||
} else {
|
||||
t4 = $[28];
|
||||
}
|
||||
let t5;
|
||||
if ($[29] !== t2) {
|
||||
t5 = <Box flexDirection="column" width="100%">{t2}{t3}{t4}</Box>;
|
||||
$[29] = t2;
|
||||
$[30] = t5;
|
||||
} else {
|
||||
t5 = $[30];
|
||||
}
|
||||
return t5;
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
<Box>
|
||||
<Text bold>{filePath}</Text>
|
||||
</Box>
|
||||
<Divider padding={4} />
|
||||
<Box flexDirection="column">
|
||||
<Text dimColor italic>
|
||||
Large file - diff exceeds 1 MB limit
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
let t2;
|
||||
if ($[31] !== filePath) {
|
||||
t2 = <Text bold={true}>{filePath}</Text>;
|
||||
$[31] = filePath;
|
||||
$[32] = t2;
|
||||
} else {
|
||||
t2 = $[32];
|
||||
}
|
||||
let t3;
|
||||
if ($[33] !== isTruncated) {
|
||||
t3 = isTruncated && <Text dimColor={true}> (truncated)</Text>;
|
||||
$[33] = isTruncated;
|
||||
$[34] = t3;
|
||||
} else {
|
||||
t3 = $[34];
|
||||
}
|
||||
let t4;
|
||||
if ($[35] !== t2 || $[36] !== t3) {
|
||||
t4 = <Box>{t2}{t3}</Box>;
|
||||
$[35] = t2;
|
||||
$[36] = t3;
|
||||
$[37] = t4;
|
||||
} else {
|
||||
t4 = $[37];
|
||||
}
|
||||
let t5;
|
||||
if ($[38] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t5 = <Divider padding={4} />;
|
||||
$[38] = t5;
|
||||
} else {
|
||||
t5 = $[38];
|
||||
}
|
||||
let t6;
|
||||
if ($[39] !== columns || $[40] !== fileContent || $[41] !== filePath || $[42] !== firstLine || $[43] !== hunks) {
|
||||
t6 = hunks.length === 0 ? <Text dimColor={true}>No diff content</Text> : hunks.map((hunk, index) => <StructuredDiff key={index} patch={hunk} filePath={filePath} firstLine={firstLine} fileContent={fileContent} dim={false} width={columns - 2 - 2} />);
|
||||
$[39] = columns;
|
||||
$[40] = fileContent;
|
||||
$[41] = filePath;
|
||||
$[42] = firstLine;
|
||||
$[43] = hunks;
|
||||
$[44] = t6;
|
||||
} else {
|
||||
t6 = $[44];
|
||||
}
|
||||
let t7;
|
||||
if ($[45] !== t6) {
|
||||
t7 = <Box flexDirection="column">{t6}</Box>;
|
||||
$[45] = t6;
|
||||
$[46] = t7;
|
||||
} else {
|
||||
t7 = $[46];
|
||||
}
|
||||
let t8;
|
||||
if ($[47] !== isTruncated) {
|
||||
t8 = isTruncated && <Text dimColor={true} italic={true}>… diff truncated (exceeded 400 line limit)</Text>;
|
||||
$[47] = isTruncated;
|
||||
$[48] = t8;
|
||||
} else {
|
||||
t8 = $[48];
|
||||
}
|
||||
let t9;
|
||||
if ($[49] !== t4 || $[50] !== t7 || $[51] !== t8) {
|
||||
t9 = <Box flexDirection="column" width="100%">{t4}{t5}{t7}{t8}</Box>;
|
||||
$[49] = t4;
|
||||
$[50] = t7;
|
||||
$[51] = t8;
|
||||
$[52] = t9;
|
||||
} else {
|
||||
t9 = $[52];
|
||||
}
|
||||
return t9;
|
||||
|
||||
const outerPaddingX = 1
|
||||
const outerBorderWidth = 1
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
<Box>
|
||||
<Text bold>{filePath}</Text>
|
||||
{isTruncated && <Text dimColor> (truncated)</Text>}
|
||||
</Box>
|
||||
|
||||
<Divider padding={4} />
|
||||
<Box flexDirection="column">
|
||||
{hunks.length === 0 ? (
|
||||
<Text dimColor>No diff content</Text>
|
||||
) : (
|
||||
hunks.map((hunk, index) => (
|
||||
<StructuredDiff
|
||||
key={index}
|
||||
patch={hunk}
|
||||
filePath={filePath}
|
||||
firstLine={firstLine}
|
||||
fileContent={fileContent}
|
||||
dim={false}
|
||||
width={columns - 2 * outerPaddingX - 2 * outerBorderWidth}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isTruncated && (
|
||||
<Text dimColor italic>
|
||||
… diff truncated (exceeded 400 line limit)
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,382 +1,289 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import type { StructuredPatchHunk } from 'diff';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { CommandResultDisplay } from '../../commands.js';
|
||||
import { useRegisterOverlay } from '../../context/overlayContext.js';
|
||||
import { type DiffData, useDiffData } from '../../hooks/useDiffData.js';
|
||||
import { type TurnDiff, useTurnDiffs } from '../../hooks/useTurnDiffs.js';
|
||||
import { Box, Text } from '../../ink.js';
|
||||
import { useKeybindings } from '../../keybindings/useKeybinding.js';
|
||||
import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js';
|
||||
import type { Message } from '../../types/message.js';
|
||||
import { plural } from '../../utils/stringUtils.js';
|
||||
import { Byline } from '../design-system/Byline.js';
|
||||
import { Dialog } from '../design-system/Dialog.js';
|
||||
import { DiffDetailView } from './DiffDetailView.js';
|
||||
import { DiffFileList } from './DiffFileList.js';
|
||||
import type { StructuredPatchHunk } from 'diff'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { CommandResultDisplay } from '../../commands.js'
|
||||
import { useRegisterOverlay } from '../../context/overlayContext.js'
|
||||
import { type DiffData, useDiffData } from '../../hooks/useDiffData.js'
|
||||
import { type TurnDiff, useTurnDiffs } from '../../hooks/useTurnDiffs.js'
|
||||
import { Box, Text } from '../../ink.js'
|
||||
import { useKeybindings } from '../../keybindings/useKeybinding.js'
|
||||
import { useShortcutDisplay } from '../../keybindings/useShortcutDisplay.js'
|
||||
import type { Message } from '../../types/message.js'
|
||||
import { plural } from '../../utils/stringUtils.js'
|
||||
import { Byline } from '../design-system/Byline.js'
|
||||
import { Dialog } from '../design-system/Dialog.js'
|
||||
import { DiffDetailView } from './DiffDetailView.js'
|
||||
import { DiffFileList } from './DiffFileList.js'
|
||||
|
||||
type Props = {
|
||||
messages: Message[];
|
||||
onDone: (result?: string, options?: {
|
||||
display?: CommandResultDisplay;
|
||||
}) => void;
|
||||
};
|
||||
type ViewMode = 'list' | 'detail';
|
||||
type DiffSource = {
|
||||
type: 'current';
|
||||
} | {
|
||||
type: 'turn';
|
||||
turn: TurnDiff;
|
||||
};
|
||||
messages: Message[]
|
||||
onDone: (
|
||||
result?: string,
|
||||
options?: { display?: CommandResultDisplay },
|
||||
) => void
|
||||
}
|
||||
|
||||
type ViewMode = 'list' | 'detail'
|
||||
|
||||
type DiffSource = { type: 'current' } | { type: 'turn'; turn: TurnDiff }
|
||||
|
||||
function turnDiffToDiffData(turn: TurnDiff): DiffData {
|
||||
const files = Array.from(turn.files.values()).map(f => ({
|
||||
path: f.filePath,
|
||||
linesAdded: f.linesAdded,
|
||||
linesRemoved: f.linesRemoved,
|
||||
isBinary: false,
|
||||
isLargeFile: false,
|
||||
isTruncated: false,
|
||||
isNewFile: f.isNewFile
|
||||
})).sort((a, b) => a.path.localeCompare(b.path));
|
||||
const hunks = new Map<string, StructuredPatchHunk[]>();
|
||||
const files = Array.from(turn.files.values())
|
||||
.map(f => ({
|
||||
path: f.filePath,
|
||||
linesAdded: f.linesAdded,
|
||||
linesRemoved: f.linesRemoved,
|
||||
isBinary: false,
|
||||
isLargeFile: false,
|
||||
isTruncated: false,
|
||||
isNewFile: f.isNewFile,
|
||||
}))
|
||||
.sort((a, b) => a.path.localeCompare(b.path))
|
||||
|
||||
const hunks = new Map<string, StructuredPatchHunk[]>()
|
||||
for (const f of turn.files.values()) {
|
||||
hunks.set(f.filePath, f.hunks);
|
||||
hunks.set(f.filePath, f.hunks)
|
||||
}
|
||||
|
||||
return {
|
||||
stats: {
|
||||
filesCount: turn.stats.filesChanged,
|
||||
linesAdded: turn.stats.linesAdded,
|
||||
linesRemoved: turn.stats.linesRemoved
|
||||
linesRemoved: turn.stats.linesRemoved,
|
||||
},
|
||||
files,
|
||||
hunks,
|
||||
loading: false
|
||||
};
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
export function DiffDialog(t0) {
|
||||
const $ = _c(73);
|
||||
const {
|
||||
messages,
|
||||
onDone
|
||||
} = t0;
|
||||
const gitDiffData = useDiffData();
|
||||
const turnDiffs = useTurnDiffs(messages);
|
||||
const [viewMode, setViewMode] = useState("list");
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [sourceIndex, setSourceIndex] = useState(0);
|
||||
let t1;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t1 = {
|
||||
type: "current"
|
||||
};
|
||||
$[0] = t1;
|
||||
} else {
|
||||
t1 = $[0];
|
||||
}
|
||||
let t2;
|
||||
if ($[1] !== turnDiffs) {
|
||||
t2 = [t1, ...turnDiffs.map(_temp)];
|
||||
$[1] = turnDiffs;
|
||||
$[2] = t2;
|
||||
} else {
|
||||
t2 = $[2];
|
||||
}
|
||||
const sources = t2;
|
||||
const currentSource = sources[sourceIndex];
|
||||
const currentTurn = currentSource?.type === "turn" ? currentSource.turn : null;
|
||||
let t3;
|
||||
if ($[3] !== currentTurn || $[4] !== gitDiffData) {
|
||||
t3 = currentTurn ? turnDiffToDiffData(currentTurn) : gitDiffData;
|
||||
$[3] = currentTurn;
|
||||
$[4] = gitDiffData;
|
||||
$[5] = t3;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
}
|
||||
const diffData = t3;
|
||||
const selectedFile = diffData.files[selectedIndex];
|
||||
let t4;
|
||||
if ($[6] !== diffData.hunks || $[7] !== selectedFile) {
|
||||
t4 = selectedFile ? diffData.hunks.get(selectedFile.path) || [] : [];
|
||||
$[6] = diffData.hunks;
|
||||
$[7] = selectedFile;
|
||||
$[8] = t4;
|
||||
} else {
|
||||
t4 = $[8];
|
||||
}
|
||||
const selectedHunks = t4;
|
||||
let t5;
|
||||
let t6;
|
||||
if ($[9] !== sourceIndex || $[10] !== sources.length) {
|
||||
t5 = () => {
|
||||
if (sourceIndex >= sources.length) {
|
||||
setSourceIndex(Math.max(0, sources.length - 1));
|
||||
}
|
||||
};
|
||||
t6 = [sources.length, sourceIndex];
|
||||
$[9] = sourceIndex;
|
||||
$[10] = sources.length;
|
||||
$[11] = t5;
|
||||
$[12] = t6;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
t6 = $[12];
|
||||
}
|
||||
useEffect(t5, t6);
|
||||
const prevSourceIndex = useRef(sourceIndex);
|
||||
let t7;
|
||||
let t8;
|
||||
if ($[13] !== sourceIndex) {
|
||||
t7 = () => {
|
||||
if (prevSourceIndex.current !== sourceIndex) {
|
||||
setSelectedIndex(0);
|
||||
prevSourceIndex.current = sourceIndex;
|
||||
}
|
||||
};
|
||||
t8 = [sourceIndex];
|
||||
$[13] = sourceIndex;
|
||||
$[14] = t7;
|
||||
$[15] = t8;
|
||||
} else {
|
||||
t7 = $[14];
|
||||
t8 = $[15];
|
||||
}
|
||||
useEffect(t7, t8);
|
||||
useRegisterOverlay("diff-dialog", undefined);
|
||||
let t10;
|
||||
let t9;
|
||||
if ($[16] !== sources.length || $[17] !== viewMode) {
|
||||
t9 = () => {
|
||||
if (viewMode === "detail") {
|
||||
setViewMode("list");
|
||||
} else {
|
||||
if (viewMode === "list" && sources.length > 1) {
|
||||
setSourceIndex(_temp2);
|
||||
|
||||
export function DiffDialog({ messages, onDone }: Props): React.ReactNode {
|
||||
const gitDiffData = useDiffData()
|
||||
const turnDiffs = useTurnDiffs(messages)
|
||||
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('list')
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0)
|
||||
const [sourceIndex, setSourceIndex] = useState<number>(0)
|
||||
|
||||
const sources: DiffSource[] = useMemo(
|
||||
() => [
|
||||
{ type: 'current' },
|
||||
...turnDiffs.map((turn): DiffSource => ({ type: 'turn', turn })),
|
||||
],
|
||||
[turnDiffs],
|
||||
)
|
||||
|
||||
const currentSource = sources[sourceIndex]
|
||||
const currentTurn = currentSource?.type === 'turn' ? currentSource.turn : null
|
||||
|
||||
const diffData = useMemo((): DiffData => {
|
||||
return currentTurn ? turnDiffToDiffData(currentTurn) : gitDiffData
|
||||
}, [currentTurn, gitDiffData])
|
||||
|
||||
const selectedFile = diffData.files[selectedIndex]
|
||||
const selectedHunks = useMemo(() => {
|
||||
return selectedFile ? diffData.hunks.get(selectedFile.path) || [] : []
|
||||
}, [selectedFile, diffData.hunks])
|
||||
|
||||
// Clamp sourceIndex when sources shrink (e.g., conversation rewind)
|
||||
useEffect(() => {
|
||||
if (sourceIndex >= sources.length) {
|
||||
setSourceIndex(Math.max(0, sources.length - 1))
|
||||
}
|
||||
}, [sources.length, sourceIndex])
|
||||
|
||||
// Reset file selection when source changes
|
||||
const prevSourceIndex = useRef(sourceIndex)
|
||||
useEffect(() => {
|
||||
if (prevSourceIndex.current !== sourceIndex) {
|
||||
setSelectedIndex(0)
|
||||
prevSourceIndex.current = sourceIndex
|
||||
}
|
||||
}, [sourceIndex])
|
||||
|
||||
// Register as modal overlay so Chat keybindings and CancelRequestHandler
|
||||
// are disabled while DiffDialog is showing
|
||||
useRegisterOverlay('diff-dialog')
|
||||
|
||||
// Diff dialog navigation keybindings
|
||||
// View-mode dependent: left/right arrows have different behavior based on mode
|
||||
// (source tab switching vs back navigation), and up/down/enter are
|
||||
// context-sensitive to viewMode
|
||||
//
|
||||
// Note: Escape handling (diff:dismiss) is NOT registered here because Dialog's
|
||||
// built-in useKeybinding('confirm:no', handleCancel) already handles it.
|
||||
// Having both would be dead code since Dialog's child effect registers first
|
||||
// and calls stopImmediatePropagation(). The diff:dismiss binding in
|
||||
// defaultBindings.ts is kept for useShortcutDisplay to show the "esc close" hint.
|
||||
useKeybindings(
|
||||
{
|
||||
// Left arrow: in detail mode goes back, in list mode switches source
|
||||
'diff:previousSource': () => {
|
||||
if (viewMode === 'detail') {
|
||||
setViewMode('list')
|
||||
} else if (viewMode === 'list' && sources.length > 1) {
|
||||
setSourceIndex(prev => Math.max(0, prev - 1))
|
||||
}
|
||||
}
|
||||
};
|
||||
t10 = () => {
|
||||
if (viewMode === "list" && sources.length > 1) {
|
||||
setSourceIndex(prev_0 => Math.min(sources.length - 1, prev_0 + 1));
|
||||
}
|
||||
};
|
||||
$[16] = sources.length;
|
||||
$[17] = viewMode;
|
||||
$[18] = t10;
|
||||
$[19] = t9;
|
||||
} else {
|
||||
t10 = $[18];
|
||||
t9 = $[19];
|
||||
}
|
||||
let t11;
|
||||
if ($[20] !== viewMode) {
|
||||
t11 = () => {
|
||||
if (viewMode === "detail") {
|
||||
setViewMode("list");
|
||||
}
|
||||
};
|
||||
$[20] = viewMode;
|
||||
$[21] = t11;
|
||||
} else {
|
||||
t11 = $[21];
|
||||
}
|
||||
let t12;
|
||||
if ($[22] !== selectedFile || $[23] !== viewMode) {
|
||||
t12 = () => {
|
||||
if (viewMode === "list" && selectedFile) {
|
||||
setViewMode("detail");
|
||||
}
|
||||
};
|
||||
$[22] = selectedFile;
|
||||
$[23] = viewMode;
|
||||
$[24] = t12;
|
||||
} else {
|
||||
t12 = $[24];
|
||||
}
|
||||
let t13;
|
||||
if ($[25] !== viewMode) {
|
||||
t13 = () => {
|
||||
if (viewMode === "list") {
|
||||
setSelectedIndex(_temp3);
|
||||
}
|
||||
};
|
||||
$[25] = viewMode;
|
||||
$[26] = t13;
|
||||
} else {
|
||||
t13 = $[26];
|
||||
}
|
||||
let t14;
|
||||
if ($[27] !== diffData.files.length || $[28] !== viewMode) {
|
||||
t14 = () => {
|
||||
if (viewMode === "list") {
|
||||
setSelectedIndex(prev_2 => Math.min(diffData.files.length - 1, prev_2 + 1));
|
||||
}
|
||||
};
|
||||
$[27] = diffData.files.length;
|
||||
$[28] = viewMode;
|
||||
$[29] = t14;
|
||||
} else {
|
||||
t14 = $[29];
|
||||
}
|
||||
let t15;
|
||||
if ($[30] !== t10 || $[31] !== t11 || $[32] !== t12 || $[33] !== t13 || $[34] !== t14 || $[35] !== t9) {
|
||||
t15 = {
|
||||
"diff:previousSource": t9,
|
||||
"diff:nextSource": t10,
|
||||
"diff:back": t11,
|
||||
"diff:viewDetails": t12,
|
||||
"diff:previousFile": t13,
|
||||
"diff:nextFile": t14
|
||||
};
|
||||
$[30] = t10;
|
||||
$[31] = t11;
|
||||
$[32] = t12;
|
||||
$[33] = t13;
|
||||
$[34] = t14;
|
||||
$[35] = t9;
|
||||
$[36] = t15;
|
||||
} else {
|
||||
t15 = $[36];
|
||||
}
|
||||
let t16;
|
||||
if ($[37] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t16 = {
|
||||
context: "DiffDialog"
|
||||
};
|
||||
$[37] = t16;
|
||||
} else {
|
||||
t16 = $[37];
|
||||
}
|
||||
useKeybindings(t15, t16);
|
||||
let t17;
|
||||
if ($[38] !== diffData.stats) {
|
||||
t17 = diffData.stats ? <Text dimColor={true}>{diffData.stats.filesCount} {plural(diffData.stats.filesCount, "file")}{" "}changed{diffData.stats.linesAdded > 0 && <Text color="diffAddedWord"> +{diffData.stats.linesAdded}</Text>}{diffData.stats.linesRemoved > 0 && <Text color="diffRemovedWord"> -{diffData.stats.linesRemoved}</Text>}</Text> : null;
|
||||
$[38] = diffData.stats;
|
||||
$[39] = t17;
|
||||
} else {
|
||||
t17 = $[39];
|
||||
}
|
||||
const subtitle = t17;
|
||||
const headerTitle = currentTurn ? `Turn ${currentTurn.turnIndex}` : "Uncommitted changes";
|
||||
const headerSubtitle = currentTurn ? currentTurn.userPromptPreview ? `"${currentTurn.userPromptPreview}"` : "" : "(git diff HEAD)";
|
||||
let t18;
|
||||
if ($[40] !== sourceIndex || $[41] !== sources) {
|
||||
t18 = sources.length > 1 ? <Box>{sourceIndex > 0 && <Text dimColor={true}>◀ </Text>}{sources.map((source, i) => {
|
||||
const isSelected = i === sourceIndex;
|
||||
const label = source.type === "current" ? "Current" : `T${source.turn.turnIndex}`;
|
||||
return <Text key={i} dimColor={!isSelected} bold={isSelected}>{i > 0 ? " \xB7 " : ""}{label}</Text>;
|
||||
})}{sourceIndex < sources.length - 1 && <Text dimColor={true}> ▶</Text>}</Box> : null;
|
||||
$[40] = sourceIndex;
|
||||
$[41] = sources;
|
||||
$[42] = t18;
|
||||
} else {
|
||||
t18 = $[42];
|
||||
}
|
||||
const sourceSelector = t18;
|
||||
const dismissShortcut = useShortcutDisplay("diff:dismiss", "DiffDialog", "esc");
|
||||
let t19;
|
||||
bb0: {
|
||||
},
|
||||
'diff:nextSource': () => {
|
||||
if (viewMode === 'list' && sources.length > 1) {
|
||||
setSourceIndex(prev => Math.min(sources.length - 1, prev + 1))
|
||||
}
|
||||
},
|
||||
'diff:back': () => {
|
||||
if (viewMode === 'detail') {
|
||||
setViewMode('list')
|
||||
}
|
||||
},
|
||||
'diff:viewDetails': () => {
|
||||
if (viewMode === 'list' && selectedFile) {
|
||||
setViewMode('detail')
|
||||
}
|
||||
},
|
||||
'diff:previousFile': () => {
|
||||
if (viewMode === 'list') {
|
||||
setSelectedIndex(prev => Math.max(0, prev - 1))
|
||||
}
|
||||
},
|
||||
'diff:nextFile': () => {
|
||||
if (viewMode === 'list') {
|
||||
setSelectedIndex(prev =>
|
||||
Math.min(diffData.files.length - 1, prev + 1),
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ context: 'DiffDialog' },
|
||||
)
|
||||
|
||||
const subtitle = diffData.stats ? (
|
||||
<Text dimColor>
|
||||
{diffData.stats.filesCount} {plural(diffData.stats.filesCount, 'file')}{' '}
|
||||
changed
|
||||
{diffData.stats.linesAdded > 0 && (
|
||||
<Text color="diffAddedWord"> +{diffData.stats.linesAdded}</Text>
|
||||
)}
|
||||
{diffData.stats.linesRemoved > 0 && (
|
||||
<Text color="diffRemovedWord"> -{diffData.stats.linesRemoved}</Text>
|
||||
)}
|
||||
</Text>
|
||||
) : null
|
||||
|
||||
// Build header based on current source
|
||||
const headerTitle = currentTurn
|
||||
? `Turn ${currentTurn.turnIndex}`
|
||||
: 'Uncommitted changes'
|
||||
const headerSubtitle = currentTurn
|
||||
? currentTurn.userPromptPreview
|
||||
? `"${currentTurn.userPromptPreview}"`
|
||||
: ''
|
||||
: '(git diff HEAD)'
|
||||
|
||||
// Source selector pills
|
||||
const sourceSelector =
|
||||
sources.length > 1 ? (
|
||||
<Box>
|
||||
{sourceIndex > 0 && <Text dimColor>◀ </Text>}
|
||||
{sources.map((source, i) => {
|
||||
const isSelected = i === sourceIndex
|
||||
const label =
|
||||
source.type === 'current' ? 'Current' : `T${source.turn.turnIndex}`
|
||||
return (
|
||||
<Text key={i} dimColor={!isSelected} bold={isSelected}>
|
||||
{i > 0 ? ' · ' : ''}
|
||||
{label}
|
||||
</Text>
|
||||
)
|
||||
})}
|
||||
{sourceIndex < sources.length - 1 && <Text dimColor> ▶</Text>}
|
||||
</Box>
|
||||
) : null
|
||||
|
||||
const dismissShortcut = useShortcutDisplay(
|
||||
'diff:dismiss',
|
||||
'DiffDialog',
|
||||
'esc',
|
||||
)
|
||||
// Determine the appropriate message when no files are shown
|
||||
const emptyMessage = (() => {
|
||||
if (diffData.loading) {
|
||||
t19 = "Loading diff\u2026";
|
||||
break bb0;
|
||||
return 'Loading diff…'
|
||||
}
|
||||
if (currentTurn) {
|
||||
t19 = "No file changes in this turn";
|
||||
break bb0;
|
||||
return 'No file changes in this turn'
|
||||
}
|
||||
if (diffData.stats && diffData.stats.filesCount > 0 && diffData.files.length === 0) {
|
||||
t19 = "Too many files to display details";
|
||||
break bb0;
|
||||
// Check if we have stats but no files (too many files case)
|
||||
if (
|
||||
diffData.stats &&
|
||||
diffData.stats.filesCount > 0 &&
|
||||
diffData.files.length === 0
|
||||
) {
|
||||
return 'Too many files to display details'
|
||||
}
|
||||
return 'Working tree is clean'
|
||||
})()
|
||||
|
||||
// Build title with header subtitle inline
|
||||
const title = (
|
||||
<Text>
|
||||
{headerTitle}
|
||||
{headerSubtitle && <Text dimColor> {headerSubtitle}</Text>}
|
||||
</Text>
|
||||
)
|
||||
|
||||
// Handle cancel/dismiss - in detail mode goes back, in list mode dismisses
|
||||
function handleCancel(): void {
|
||||
if (viewMode === 'detail') {
|
||||
setViewMode('list')
|
||||
} else {
|
||||
onDone('Diff dialog dismissed', { display: 'system' })
|
||||
}
|
||||
t19 = "Working tree is clean";
|
||||
}
|
||||
const emptyMessage = t19;
|
||||
let t20;
|
||||
if ($[43] !== headerSubtitle) {
|
||||
t20 = headerSubtitle && <Text dimColor={true}> {headerSubtitle}</Text>;
|
||||
$[43] = headerSubtitle;
|
||||
$[44] = t20;
|
||||
} else {
|
||||
t20 = $[44];
|
||||
}
|
||||
let t21;
|
||||
if ($[45] !== headerTitle || $[46] !== t20) {
|
||||
t21 = <Text>{headerTitle}{t20}</Text>;
|
||||
$[45] = headerTitle;
|
||||
$[46] = t20;
|
||||
$[47] = t21;
|
||||
} else {
|
||||
t21 = $[47];
|
||||
}
|
||||
const title = t21;
|
||||
let t22;
|
||||
if ($[48] !== onDone || $[49] !== viewMode) {
|
||||
t22 = function handleCancel() {
|
||||
if (viewMode === "detail") {
|
||||
setViewMode("list");
|
||||
} else {
|
||||
onDone("Diff dialog dismissed", {
|
||||
display: "system"
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={title}
|
||||
onCancel={handleCancel}
|
||||
color="background"
|
||||
inputGuide={exitState =>
|
||||
exitState.pending ? (
|
||||
<Text>Press {exitState.keyName} again to exit</Text>
|
||||
) : viewMode === 'list' ? (
|
||||
<Byline>
|
||||
{sources.length > 1 && <Text>←/→ source</Text>}
|
||||
<Text>↑/↓ select</Text>
|
||||
<Text>Enter view</Text>
|
||||
<Text>{dismissShortcut} close</Text>
|
||||
</Byline>
|
||||
) : (
|
||||
<Byline>
|
||||
<Text>← back</Text>
|
||||
<Text>{dismissShortcut} close</Text>
|
||||
</Byline>
|
||||
)
|
||||
}
|
||||
};
|
||||
$[48] = onDone;
|
||||
$[49] = viewMode;
|
||||
$[50] = t22;
|
||||
} else {
|
||||
t22 = $[50];
|
||||
}
|
||||
const handleCancel = t22;
|
||||
let t23;
|
||||
if ($[51] !== dismissShortcut || $[52] !== sources.length || $[53] !== viewMode) {
|
||||
t23 = exitState => exitState.pending ? <Text>Press {exitState.keyName} again to exit</Text> : viewMode === "list" ? <Byline>{sources.length > 1 && <Text>←/→ source</Text>}<Text>↑/↓ select</Text><Text>Enter view</Text><Text>{dismissShortcut} close</Text></Byline> : <Byline><Text>← back</Text><Text>{dismissShortcut} close</Text></Byline>;
|
||||
$[51] = dismissShortcut;
|
||||
$[52] = sources.length;
|
||||
$[53] = viewMode;
|
||||
$[54] = t23;
|
||||
} else {
|
||||
t23 = $[54];
|
||||
}
|
||||
let t24;
|
||||
if ($[55] !== diffData.files || $[56] !== emptyMessage || $[57] !== selectedFile?.isBinary || $[58] !== selectedFile?.isLargeFile || $[59] !== selectedFile?.isTruncated || $[60] !== selectedFile?.isUntracked || $[61] !== selectedFile?.path || $[62] !== selectedHunks || $[63] !== selectedIndex || $[64] !== viewMode) {
|
||||
t24 = diffData.files.length === 0 ? <Box marginTop={1}><Text dimColor={true}>{emptyMessage}</Text></Box> : viewMode === "list" ? <Box flexDirection="column" marginTop={1}><DiffFileList files={diffData.files} selectedIndex={selectedIndex} /></Box> : <Box flexDirection="column" marginTop={1}><DiffDetailView filePath={selectedFile?.path || ""} hunks={selectedHunks} isLargeFile={selectedFile?.isLargeFile} isBinary={selectedFile?.isBinary} isTruncated={selectedFile?.isTruncated} isUntracked={selectedFile?.isUntracked} /></Box>;
|
||||
$[55] = diffData.files;
|
||||
$[56] = emptyMessage;
|
||||
$[57] = selectedFile?.isBinary;
|
||||
$[58] = selectedFile?.isLargeFile;
|
||||
$[59] = selectedFile?.isTruncated;
|
||||
$[60] = selectedFile?.isUntracked;
|
||||
$[61] = selectedFile?.path;
|
||||
$[62] = selectedHunks;
|
||||
$[63] = selectedIndex;
|
||||
$[64] = viewMode;
|
||||
$[65] = t24;
|
||||
} else {
|
||||
t24 = $[65];
|
||||
}
|
||||
let t25;
|
||||
if ($[66] !== handleCancel || $[67] !== sourceSelector || $[68] !== subtitle || $[69] !== t23 || $[70] !== t24 || $[71] !== title) {
|
||||
t25 = <Dialog title={title} onCancel={handleCancel} color="background" inputGuide={t23}>{sourceSelector}{subtitle}{t24}</Dialog>;
|
||||
$[66] = handleCancel;
|
||||
$[67] = sourceSelector;
|
||||
$[68] = subtitle;
|
||||
$[69] = t23;
|
||||
$[70] = t24;
|
||||
$[71] = title;
|
||||
$[72] = t25;
|
||||
} else {
|
||||
t25 = $[72];
|
||||
}
|
||||
return t25;
|
||||
}
|
||||
function _temp3(prev_1) {
|
||||
return Math.max(0, prev_1 - 1);
|
||||
}
|
||||
function _temp2(prev) {
|
||||
return Math.max(0, prev - 1);
|
||||
}
|
||||
function _temp(turn) {
|
||||
return {
|
||||
type: "turn",
|
||||
turn
|
||||
};
|
||||
>
|
||||
{sourceSelector}
|
||||
{subtitle}
|
||||
{diffData.files.length === 0 ? (
|
||||
<Box marginTop={1}>
|
||||
<Text dimColor>{emptyMessage}</Text>
|
||||
</Box>
|
||||
) : viewMode === 'list' ? (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<DiffFileList files={diffData.files} selectedIndex={selectedIndex} />
|
||||
</Box>
|
||||
) : (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<DiffDetailView
|
||||
filePath={selectedFile?.path || ''}
|
||||
hunks={selectedHunks}
|
||||
isLargeFile={selectedFile?.isLargeFile}
|
||||
isBinary={selectedFile?.isBinary}
|
||||
isTruncated={selectedFile?.isTruncated}
|
||||
isUntracked={selectedFile?.isUntracked}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,291 +1,153 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
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 '../../ink.js';
|
||||
import { truncateStartToWidth } from '../../utils/format.js';
|
||||
import { plural } from '../../utils/stringUtils.js';
|
||||
const MAX_VISIBLE_FILES = 5;
|
||||
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 '../../ink.js'
|
||||
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(t0) {
|
||||
const $ = _c(36);
|
||||
const {
|
||||
files,
|
||||
selectedIndex
|
||||
} = t0;
|
||||
const {
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
let t1;
|
||||
bb0: {
|
||||
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) {
|
||||
let t2;
|
||||
if ($[0] !== files.length) {
|
||||
t2 = {
|
||||
startIndex: 0,
|
||||
endIndex: files.length
|
||||
};
|
||||
$[0] = files.length;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
break bb0;
|
||||
return { startIndex: 0, endIndex: files.length }
|
||||
}
|
||||
let start = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE_FILES / 2));
|
||||
let end = start + MAX_VISIBLE_FILES;
|
||||
|
||||
// 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);
|
||||
end = files.length
|
||||
start = Math.max(0, end - MAX_VISIBLE_FILES)
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== end || $[3] !== start) {
|
||||
t2 = {
|
||||
startIndex: start,
|
||||
endIndex: end
|
||||
};
|
||||
$[2] = end;
|
||||
$[3] = start;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
t1 = t2;
|
||||
}
|
||||
const {
|
||||
startIndex,
|
||||
endIndex
|
||||
} = t1;
|
||||
|
||||
return { startIndex: start, endIndex: end }
|
||||
}, [files.length, selectedIndex])
|
||||
|
||||
if (files.length === 0) {
|
||||
let t2;
|
||||
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t2 = <Text dimColor={true}>No changed files</Text>;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
return <Text dimColor>No changed files</Text>
|
||||
}
|
||||
let T0;
|
||||
let hasMoreBelow;
|
||||
let needsPagination;
|
||||
let t2;
|
||||
let t3;
|
||||
let t4;
|
||||
if ($[6] !== columns || $[7] !== endIndex || $[8] !== files || $[9] !== selectedIndex || $[10] !== startIndex) {
|
||||
const visibleFiles = files.slice(startIndex, endIndex);
|
||||
const hasMoreAbove = startIndex > 0;
|
||||
hasMoreBelow = endIndex < files.length;
|
||||
needsPagination = files.length > MAX_VISIBLE_FILES;
|
||||
const maxPathWidth = Math.max(20, columns - 16 - 3 - 4);
|
||||
T0 = Box;
|
||||
t2 = "column";
|
||||
if ($[17] !== hasMoreAbove || $[18] !== needsPagination || $[19] !== startIndex) {
|
||||
t3 = needsPagination && <Text dimColor={true}>{hasMoreAbove ? ` ↑ ${startIndex} more ${plural(startIndex, "file")}` : " "}</Text>;
|
||||
$[17] = hasMoreAbove;
|
||||
$[18] = needsPagination;
|
||||
$[19] = startIndex;
|
||||
$[20] = t3;
|
||||
} else {
|
||||
t3 = $[20];
|
||||
}
|
||||
let t5;
|
||||
if ($[21] !== maxPathWidth || $[22] !== selectedIndex || $[23] !== startIndex) {
|
||||
t5 = (file, index) => <FileItem key={file.path} file={file} isSelected={startIndex + index === selectedIndex} maxPathWidth={maxPathWidth} />;
|
||||
$[21] = maxPathWidth;
|
||||
$[22] = selectedIndex;
|
||||
$[23] = startIndex;
|
||||
$[24] = t5;
|
||||
} else {
|
||||
t5 = $[24];
|
||||
}
|
||||
t4 = visibleFiles.map(t5);
|
||||
$[6] = columns;
|
||||
$[7] = endIndex;
|
||||
$[8] = files;
|
||||
$[9] = selectedIndex;
|
||||
$[10] = startIndex;
|
||||
$[11] = T0;
|
||||
$[12] = hasMoreBelow;
|
||||
$[13] = needsPagination;
|
||||
$[14] = t2;
|
||||
$[15] = t3;
|
||||
$[16] = t4;
|
||||
} else {
|
||||
T0 = $[11];
|
||||
hasMoreBelow = $[12];
|
||||
needsPagination = $[13];
|
||||
t2 = $[14];
|
||||
t3 = $[15];
|
||||
t4 = $[16];
|
||||
}
|
||||
let t5;
|
||||
if ($[25] !== endIndex || $[26] !== files.length || $[27] !== hasMoreBelow || $[28] !== needsPagination) {
|
||||
t5 = needsPagination && <Text dimColor={true}>{hasMoreBelow ? ` ↓ ${files.length - endIndex} more ${plural(files.length - endIndex, "file")}` : " "}</Text>;
|
||||
$[25] = endIndex;
|
||||
$[26] = files.length;
|
||||
$[27] = hasMoreBelow;
|
||||
$[28] = needsPagination;
|
||||
$[29] = t5;
|
||||
} else {
|
||||
t5 = $[29];
|
||||
}
|
||||
let t6;
|
||||
if ($[30] !== T0 || $[31] !== t2 || $[32] !== t3 || $[33] !== t4 || $[34] !== t5) {
|
||||
t6 = <T0 flexDirection={t2}>{t3}{t4}{t5}</T0>;
|
||||
$[30] = T0;
|
||||
$[31] = t2;
|
||||
$[32] = t3;
|
||||
$[33] = t4;
|
||||
$[34] = t5;
|
||||
$[35] = t6;
|
||||
} else {
|
||||
t6 = $[35];
|
||||
}
|
||||
return t6;
|
||||
|
||||
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 (
|
||||
<Box flexDirection="column">
|
||||
{needsPagination && (
|
||||
<Text dimColor>
|
||||
{hasMoreAbove
|
||||
? ` ↑ ${startIndex} more ${plural(startIndex, 'file')}`
|
||||
: ' '}
|
||||
</Text>
|
||||
)}
|
||||
{visibleFiles.map((file, index) => (
|
||||
<FileItem
|
||||
key={file.path}
|
||||
file={file}
|
||||
isSelected={startIndex + index === selectedIndex}
|
||||
maxPathWidth={maxPathWidth}
|
||||
/>
|
||||
))}
|
||||
{needsPagination && (
|
||||
<Text dimColor>
|
||||
{hasMoreBelow
|
||||
? ` ↓ ${files.length - endIndex} more ${plural(files.length - endIndex, 'file')}`
|
||||
: ' '}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
function FileItem(t0) {
|
||||
const $ = _c(14);
|
||||
const {
|
||||
file,
|
||||
isSelected,
|
||||
maxPathWidth
|
||||
} = t0;
|
||||
let t1;
|
||||
if ($[0] !== file.path || $[1] !== maxPathWidth) {
|
||||
t1 = truncateStartToWidth(file.path, maxPathWidth);
|
||||
$[0] = file.path;
|
||||
$[1] = maxPathWidth;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
const displayPath = t1;
|
||||
const pointer = isSelected ? figures.pointer + " " : " ";
|
||||
const line = `${pointer}${displayPath}`;
|
||||
const t2 = isSelected ? "background" : undefined;
|
||||
let t3;
|
||||
if ($[3] !== isSelected || $[4] !== line || $[5] !== t2) {
|
||||
t3 = <Text bold={isSelected} color={t2} inverse={isSelected}>{line}</Text>;
|
||||
$[3] = isSelected;
|
||||
$[4] = line;
|
||||
$[5] = t2;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
let t4;
|
||||
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t4 = <Box flexGrow={1} />;
|
||||
$[7] = t4;
|
||||
} else {
|
||||
t4 = $[7];
|
||||
}
|
||||
let t5;
|
||||
if ($[8] !== file || $[9] !== isSelected) {
|
||||
t5 = <FileStats file={file} isSelected={isSelected} />;
|
||||
$[8] = file;
|
||||
$[9] = isSelected;
|
||||
$[10] = t5;
|
||||
} else {
|
||||
t5 = $[10];
|
||||
}
|
||||
let t6;
|
||||
if ($[11] !== t3 || $[12] !== t5) {
|
||||
t6 = <Box flexDirection="row">{t3}{t4}{t5}</Box>;
|
||||
$[11] = t3;
|
||||
$[12] = t5;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
return t6;
|
||||
|
||||
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 (
|
||||
<Box flexDirection="row">
|
||||
<Text
|
||||
bold={isSelected}
|
||||
color={isSelected ? 'background' : undefined}
|
||||
inverse={isSelected}
|
||||
>
|
||||
{line}
|
||||
</Text>
|
||||
<Box flexGrow={1} />
|
||||
<FileStats file={file} isSelected={isSelected} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
function FileStats(t0) {
|
||||
const $ = _c(20);
|
||||
const {
|
||||
file,
|
||||
isSelected
|
||||
} = t0;
|
||||
|
||||
function FileStats({
|
||||
file,
|
||||
isSelected,
|
||||
}: {
|
||||
file: DiffFile
|
||||
isSelected: boolean
|
||||
}): React.ReactNode {
|
||||
if (file.isUntracked) {
|
||||
const t1 = !isSelected;
|
||||
let t2;
|
||||
if ($[0] !== t1) {
|
||||
t2 = <Text dimColor={t1} italic={true}>untracked</Text>;
|
||||
$[0] = t1;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
return (
|
||||
<Text dimColor={!isSelected} italic>
|
||||
untracked
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
if (file.isBinary) {
|
||||
const t1 = !isSelected;
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = <Text dimColor={t1} italic={true}>Binary file</Text>;
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
return (
|
||||
<Text dimColor={!isSelected} italic>
|
||||
Binary file
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
if (file.isLargeFile) {
|
||||
const t1 = !isSelected;
|
||||
let t2;
|
||||
if ($[4] !== t1) {
|
||||
t2 = <Text dimColor={t1} italic={true}>Large file modified</Text>;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
return (
|
||||
<Text dimColor={!isSelected} italic>
|
||||
Large file modified
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
let t1;
|
||||
if ($[6] !== file.linesAdded || $[7] !== isSelected) {
|
||||
t1 = file.linesAdded > 0 && <Text color="diffAddedWord" bold={isSelected}>+{file.linesAdded}</Text>;
|
||||
$[6] = file.linesAdded;
|
||||
$[7] = isSelected;
|
||||
$[8] = t1;
|
||||
} else {
|
||||
t1 = $[8];
|
||||
}
|
||||
const t2 = file.linesAdded > 0 && file.linesRemoved > 0 && " ";
|
||||
let t3;
|
||||
if ($[9] !== file.linesRemoved || $[10] !== isSelected) {
|
||||
t3 = file.linesRemoved > 0 && <Text color="diffRemovedWord" bold={isSelected}>-{file.linesRemoved}</Text>;
|
||||
$[9] = file.linesRemoved;
|
||||
$[10] = isSelected;
|
||||
$[11] = t3;
|
||||
} else {
|
||||
t3 = $[11];
|
||||
}
|
||||
let t4;
|
||||
if ($[12] !== file.isTruncated || $[13] !== isSelected) {
|
||||
t4 = file.isTruncated && <Text dimColor={!isSelected}> (truncated)</Text>;
|
||||
$[12] = file.isTruncated;
|
||||
$[13] = isSelected;
|
||||
$[14] = t4;
|
||||
} else {
|
||||
t4 = $[14];
|
||||
}
|
||||
let t5;
|
||||
if ($[15] !== t1 || $[16] !== t2 || $[17] !== t3 || $[18] !== t4) {
|
||||
t5 = <Text>{t1}{t2}{t3}{t4}</Text>;
|
||||
$[15] = t1;
|
||||
$[16] = t2;
|
||||
$[17] = t3;
|
||||
$[18] = t4;
|
||||
$[19] = t5;
|
||||
} else {
|
||||
t5 = $[19];
|
||||
}
|
||||
return t5;
|
||||
// Normal or truncated file - show line counts
|
||||
return (
|
||||
<Text>
|
||||
{file.linesAdded > 0 && (
|
||||
<Text color="diffAddedWord" bold={isSelected}>
|
||||
+{file.linesAdded}
|
||||
</Text>
|
||||
)}
|
||||
{file.linesAdded > 0 && file.linesRemoved > 0 && ' '}
|
||||
{file.linesRemoved > 0 && (
|
||||
<Text color="diffRemovedWord" bold={isSelected}>
|
||||
-{file.linesRemoved}
|
||||
</Text>
|
||||
)}
|
||||
{file.isTruncated && <Text dimColor={!isSelected}> (truncated)</Text>}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user