diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx
index e790af7f2..ea9e86320 100644
--- a/packages/app/src/components/git-diff-pane.tsx
+++ b/packages/app/src/components/git-diff-pane.tsx
@@ -56,7 +56,7 @@ import {
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
import { Fonts } from "@/constants/theme";
import { shouldAnchorHeaderBeforeCollapse } from "@/utils/git-diff-scroll";
-import { buildSplitDiffRows, type SplitDiffDisplayLine } from "@/utils/diff-layout";
+import { buildSplitDiffRows, type SplitDiffDisplayLine, type SplitDiffRow } from "@/utils/diff-layout";
import {
DropdownMenu,
DropdownMenuContent,
@@ -138,6 +138,96 @@ interface DiffFileSectionProps {
testID?: string;
}
+function lineTypeBackground(type: DiffLine["type"] | undefined | null) {
+ if (!type) return styles.emptySplitCell;
+ if (type === "add") return styles.addLineContainer;
+ if (type === "remove") return styles.removeLineContainer;
+ if (type === "header") return styles.headerLineContainer;
+ return styles.contextLineContainer;
+}
+
+function DiffGutterCell({
+ lineNumber,
+ type,
+ gutterWidth,
+}: {
+ lineNumber: number | null;
+ type: DiffLine["type"] | undefined | null;
+ gutterWidth: number;
+}) {
+ return (
+
+
+ {lineNumber != null ? String(lineNumber) : ""}
+
+
+ );
+}
+
+function DiffTextLine({
+ line,
+ wrapLines,
+}: {
+ line: DiffLine;
+ wrapLines: boolean;
+}) {
+ return (
+
+ {line.tokens && line.type !== "header" ? (
+
+ ) : (
+
+ {line.content || " "}
+
+ )}
+
+ );
+}
+
+function SplitTextLine({
+ line,
+ wrapLines,
+}: {
+ line: SplitDiffDisplayLine | null;
+ wrapLines: boolean;
+}) {
+ return (
+
+ {line?.tokens ? (
+
+ ) : (
+
+ {line?.content ?? ""}
+
+ )}
+
+ );
+}
+
function DiffLineView({
line,
lineNumber,
@@ -153,10 +243,7 @@ function DiffLineView({
@@ -190,26 +277,20 @@ function DiffLineView({
);
}
-function SplitDiffCell({
+function SplitDiffLine({
line,
gutterWidth,
wrapLines,
- showDivider = false,
}: {
line: SplitDiffDisplayLine | null;
gutterWidth: number;
wrapLines: boolean;
- showDivider?: boolean;
}) {
return (
@@ -229,7 +310,7 @@ function SplitDiffCell({
+
+ {rows.map((row, i) => {
+ if (row.kind === "header") {
+ return (
+
+ {row.content}
+
+ );
+ }
+ return (
+
+ );
+ })}
+
+
+ );
+ }
+
+ return (
+
+
+ {rows.map((row, i) => {
+ if (row.kind === "header") {
+ return ;
+ }
+ const line = side === "left" ? row.left : row.right;
+ return ;
+ })}
+
+
+ 0 && { minWidth: scrollWidth }]}>
+ {rows.map((row, i) => {
+ if (row.kind === "header") {
+ return (
+
+ {row.content}
+
+ );
+ }
+ return ;
+ })}
+
+
+
+ );
+}
+
const DiffFileHeader = memo(function DiffFileHeader({
file,
isExpanded,
@@ -368,85 +524,70 @@ function DiffFileBody({
}
const gutterWidth = lineNumberGutterWidth(maxLineNo);
- const linesContent =
- layout === "split"
- ? buildSplitDiffRows(file).map((row, rowIndex) => {
- if (row.kind === "header") {
- return (
-
- {row.content}
-
- );
- }
-
- return (
-
-
-
-
- );
- })
- : file.hunks.map((hunk, hunkIndex) => {
- let oldLineNo = hunk.oldStart;
- let newLineNo = hunk.newStart;
- return hunk.lines.map((line, lineIndex) => {
- let lineNumber: number | null = null;
- if (line.type === "remove") {
- lineNumber = oldLineNo;
- oldLineNo++;
- } else if (line.type === "add") {
- lineNumber = newLineNo;
- newLineNo++;
- } else if (line.type === "context") {
- lineNumber = newLineNo;
- oldLineNo++;
- newLineNo++;
- }
- return (
-
- );
- });
- });
-
- const availableWidth = bodyWidth > 0 ? bodyWidth : scrollViewWidth;
- const contentContainer = (
- 0 &&
- (layout === "split"
- ? { width: availableWidth, minWidth: availableWidth, maxWidth: availableWidth }
- : { minWidth: availableWidth }),
- ]}
- >
- {linesContent}
-
- );
-
- if (wrapLines) {
- return {contentContainer};
+ if (layout === "split") {
+ const rows = buildSplitDiffRows(file);
+ return (
+
+
+
+
+ );
}
+ const computedLines: { line: DiffLine; lineNumber: number | null; key: string }[] = [];
+ for (const [hunkIndex, hunk] of file.hunks.entries()) {
+ let oldLineNo = hunk.oldStart;
+ let newLineNo = hunk.newStart;
+ for (const [lineIndex, line] of hunk.lines.entries()) {
+ let lineNumber: number | null = null;
+ if (line.type === "remove") {
+ lineNumber = oldLineNo;
+ oldLineNo++;
+ } else if (line.type === "add") {
+ lineNumber = newLineNo;
+ newLineNo++;
+ } else if (line.type === "context") {
+ lineNumber = newLineNo;
+ oldLineNo++;
+ newLineNo++;
+ }
+ computedLines.push({ line, lineNumber, key: `${hunkIndex}-${lineIndex}` });
+ }
+ }
+
+ if (wrapLines) {
+ return (
+
+
+ {computedLines.map(({ line, lineNumber, key }) => (
+
+ ))}
+
+
+ );
+ }
+
+ const availableWidth = bodyWidth > 0 ? bodyWidth : scrollViewWidth;
return (
-
- {contentContainer}
-
+
+
+ {computedLines.map(({ line, lineNumber, key }) => (
+
+ ))}
+
+
+ 0 && { minWidth: availableWidth }]}>
+ {computedLines.map(({ line, key }) => (
+
+ ))}
+
+
+
);
})()}
@@ -1612,21 +1753,35 @@ const styles = StyleSheet.create((theme) => ({
borderTopColor: theme.colors.border,
backgroundColor: theme.colors.surface1,
},
+ diffContentRow: {
+ flexDirection: "row",
+ alignItems: "stretch",
+ },
diffContentInner: {
flexDirection: "column",
},
linesContainer: {
backgroundColor: theme.colors.surface1,
},
- splitLinesContainer: {
+ gutterColumn: {
backgroundColor: theme.colors.surface1,
- minWidth: 760,
+ },
+ gutterCell: {
+ borderRightWidth: theme.borderWidth[1],
+ borderRightColor: theme.colors.border,
+ justifyContent: "flex-start",
+ },
+ textLineContainer: {
+ flexDirection: "row",
+ alignItems: "stretch",
+ paddingLeft: theme.spacing[2],
},
splitRow: {
flexDirection: "row",
alignItems: "stretch",
- width: "100%",
- minWidth: "100%",
+ },
+ splitColumnScroll: {
+ flex: 1,
},
splitHeaderRow: {
backgroundColor: theme.colors.surface2,
@@ -1635,11 +1790,11 @@ const styles = StyleSheet.create((theme) => ({
splitCell: {
flex: 1,
flexBasis: 0,
- minWidth: 0,
+ backgroundColor: theme.colors.surface2,
+ },
+ splitCellRow: {
flexDirection: "row",
alignItems: "stretch",
- overflow: "hidden",
- backgroundColor: theme.colors.surface2,
},
emptySplitCell: {
backgroundColor: theme.colors.surfaceDiffEmpty,