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,