Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 1 addition & 201 deletions components/MessageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { copyText } from "@/lib/clipboard";
import { useI18n } from "@/hooks/useI18n";
import { parseCompactionSummary } from "@/lib/compaction-summary";
import { getAssistantErrorMessage, getThinkingPreview, isEmptyThinkingBlock } from "@/lib/message-display";
import { parseUnifiedPatch, type SplitDiffCell } from "@/lib/patch";
import { isEditToolName } from "@/lib/tool-names";
import { isThinkingExpandedByDefault, THINKING_EXPANDED_EVENT } from "@/lib/thinking-expansion-preference";
import { SplitPatchView } from "./SplitPatchView";
import { TurnWrittenFiles } from "./TurnWrittenFiles";
import type { WrittenFile } from "@/lib/turn-written-files";
import { skillExpansionToCommand } from "@/lib/slash-display";
Expand Down Expand Up @@ -1149,206 +1149,6 @@ function PairedDiffResult({ diff }: {
);
}

function SplitPatchView({ text }: { text: string }) {
const { t } = useI18n();
const files = useMemo(() => parseUnifiedPatch(text), [text]);
if (!files) return <PatchTextView text={text} />;
const showFileHeaders = files.length > 1;

return (
<div style={{ maxHeight: 560, overflowY: "auto", overflowX: "hidden", background: "var(--bg)" }}>
{files.map((file, fileIndex) => (
<div
key={fileIndex}
style={{
minWidth: 0,
borderTop: fileIndex === 0 ? "none" : "1px solid var(--border)",
fontFamily: "var(--font-mono)",
fontSize: "calc(12px + var(--chat-font-size-offset, 0px))",
lineHeight: 1.55,
}}
>
{showFileHeaders && (
<div
style={{
display: "grid",
gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)",
position: "sticky",
top: 0,
zIndex: 1,
background: "var(--bg-panel)",
borderBottom: "1px solid var(--border)",
}}
>
<SplitDiffHeader title={file.oldPath || t("i18n.before")} side="left" />
<SplitDiffHeader title={file.newPath || t("i18n.after")} side="right" />
</div>
)}

<div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1fr) minmax(0, 1fr)" }}>
{file.rows.map((row, rowIndex) => {
if (row.type === "hunk") {
return null;
}

return (
<div key={rowIndex} style={{ display: "contents" }}>
<SplitDiffCellView cell={row.left} side="left" />
<SplitDiffCellView cell={row.right} side="right" />
</div>
);
})}
</div>
</div>
))}
</div>
);
}

function SplitDiffHeader({ title, side }: { title: string; side: "left" | "right" }) {
return (
<div
title={title}
style={{
padding: "5px 10px",
color: "var(--text-dim)",
borderRight: side === "left" ? "1px solid var(--border)" : "none",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{title}
</div>
);
}

function SplitDiffCellView({ cell, side }: { cell: SplitDiffCell; side: "left" | "right" }) {
const bg =
cell.type === "added"
? "rgba(34,197,94,0.12)"
: cell.type === "removed"
? "rgba(248,113,113,0.13)"
: cell.type === "empty"
? "var(--bg-subtle)"
: "transparent";
const marker =
cell.type === "added" ? "+" : cell.type === "removed" ? "-" : " ";
const markerColor =
cell.type === "added" ? "#22c55e" : cell.type === "removed" ? "#f87171" : "var(--text-dim)";

return (
<div
style={{
display: "flex",
minWidth: 0,
background: bg,
borderRight: side === "left" ? "1px solid var(--border)" : "none",
}}
>
<span
style={{
width: 42,
padding: "0 6px",
textAlign: "right",
color: "var(--text-dim)",
userSelect: "none",
background: "var(--bg-panel)",
borderRight: "1px solid var(--border)",
flexShrink: 0,
}}
>
{cell.lineNo ?? ""}
</span>
<span
style={{
width: 18,
padding: "0 5px",
color: markerColor,
userSelect: "none",
fontWeight: cell.type === "context" || cell.type === "empty" ? 400 : 700,
flexShrink: 0,
}}
>
{marker}
</span>
<span
style={{
flex: 1,
minWidth: 0,
padding: "0 10px 0 0",
color: cell.type === "empty" ? "var(--text-dim)" : "var(--text)",
whiteSpace: "pre-wrap",
overflowWrap: "anywhere",
}}
>
{cell.text || "\u00a0"}
</span>
</div>
);
}

function PatchTextView({ text }: { text: string }) {
const lines = text.split(/\r?\n/);

return (
<div style={{ maxHeight: 520, overflowY: "auto", overflowX: "hidden", fontFamily: "var(--font-mono)", fontSize: "calc(12px + var(--chat-font-size-offset, 0px))", lineHeight: 1.55, minWidth: 0 }}>
{lines.map((line, i) => {
const kind =
line.startsWith("@@") ? "hunk" :
line.startsWith("+") && !line.startsWith("+++") ? "added" :
line.startsWith("-") && !line.startsWith("---") ? "removed" :
"context";
const bg =
kind === "added" ? "rgba(34,197,94,0.12)" :
kind === "removed" ? "rgba(248,113,113,0.13)" :
kind === "hunk" ? "rgba(96,165,250,0.12)" :
"transparent";
const color =
kind === "added" ? "#22c55e" :
kind === "removed" ? "#f87171" :
kind === "hunk" ? "var(--accent)" :
"var(--text)";

return (
<div
key={i}
style={{
display: "flex",
background: bg,
borderLeft: kind === "added"
? "3px solid #22c55e"
: kind === "removed"
? "3px solid #f87171"
: kind === "hunk"
? "3px solid var(--accent)"
: "3px solid transparent",
}}
>
<span
style={{
width: 48,
padding: "0 8px",
color: "var(--text-dim)",
background: "var(--bg-panel)",
borderRight: "1px solid var(--border)",
textAlign: "right",
userSelect: "none",
flexShrink: 0,
}}
>
{i + 1}
</span>
<span style={{ padding: "0 10px", whiteSpace: "pre-wrap", overflowWrap: "anywhere", color }}>
{line || "\u00a0"}
</span>
</div>
);
})}
</div>
);
}

function getResultDiff(result: ToolResultMessage): ResultDiff | null {
const details = (result as ToolResultMessage & { details?: unknown }).details;
if (!isRecord(details)) return null;
Expand Down
Loading