Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .changeset/brave-rows-narrow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
3 changes: 2 additions & 1 deletion packages/hunk/src/ui/diff/CodeRowView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { testRender } from "@opentui/react/test-utils";
import { act } from "react";
import { capturedTestColorToHex } from "../../../../../test/helpers/test-color-helpers";
import { resolveTheme } from "../themes";
import { CodeRowView, type PlannedCodeReviewRow } from "./CodeRowView";
import { CodeRowView } from "./CodeRowView";
import type { PlannedCodeReviewRow } from "./reviewRenderPlan";
import { cursorLineHighlightBg, selectionHighlightBg, stackCellPalette } from "./rowStyle";

/** Return the normalized background painted behind matching captured text. */
Expand Down
15 changes: 2 additions & 13 deletions packages/hunk/src/ui/diff/CodeRowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import type { UserNoteLineTarget } from "../../core/liveComments";
import type { CopySelectedRowRange } from "../lib/diffSpatial";
import type { AppTheme } from "../themes";
import { CODE_ROW_ADD_NOTE_BADGE_TEXT, CODE_ROW_ADD_NOTE_BADGE_WIDTH } from "./codeRowAffordance";
import {
planCodeRowLayout,
type CodeRowLayoutPlan,
type PlannedDiffReviewRow,
} from "./codeRowLayout";
import { planCodeRowLayout, type CodeRowLayoutPlan } from "./codeRowLayout";
import { codeCellView, FULL_CODE_CELL_COL_RANGE, type CodeCellHighlight } from "./CodeCellView";
import type { CursorHighlight } from "./cursorHighlight";
import type { DiffRow } from "./diffRows";
import type { LineHighlightPaintIndex } from "./lineHighlightPaint";
import type { CodeDiffRow, PlannedCodeReviewRow } from "./reviewRenderPlan";
import {
cursorLineHighlightBg,
diffRailMarker,
Expand All @@ -22,13 +18,6 @@ import {
} from "./rowStyle";
import { markNestedRowMouseAction } from "./rowMouseActions";

type CodeDiffRow = Extract<DiffRow, { type: "split-line" | "stack-line" }>;

/** Planned review row carrying split or stack code cells. */
export type PlannedCodeReviewRow = Omit<PlannedDiffReviewRow, "row"> & {
row: CodeDiffRow;
};

export interface CodeRowViewProps {
plannedRow: PlannedCodeReviewRow;
width: number;
Expand Down
10 changes: 1 addition & 9 deletions packages/hunk/src/ui/diff/DiffMetaRowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@ import type { UserNoteLineTarget } from "../../core/liveComments";
import { reviewGapId } from "../../core/review/expansion";
import type { AppTheme } from "../themes";
import { CODE_ROW_ADD_NOTE_BADGE_TEXT } from "./codeRowAffordance";
import type { PlannedDiffReviewRow } from "./codeRowLayout";
import type { DiffRow } from "./diffRows";
import type { PlannedDiffMetaReviewRow } from "./reviewRenderPlan";
import { fitText } from "./plannedRowText";
import { diffRailMarker, dimRailColor, neutralRailColor } from "./rowStyle";
import { markNestedRowMouseAction } from "./rowMouseActions";

type DiffMetaRow = Extract<DiffRow, { type: "collapsed" | "hunk-header" }>;

/** Planned review row carrying metadata rather than code cells. */
export type PlannedDiffMetaReviewRow = Omit<PlannedDiffReviewRow, "row"> & {
row: DiffMetaRow;
};

export interface DiffMetaRowViewProps {
plannedRow: PlannedDiffMetaReviewRow;
width: number;
Expand Down
29 changes: 17 additions & 12 deletions packages/hunk/src/ui/diff/DiffRowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { memo } from "react";
import type { UserNoteLineTarget } from "../../core/liveComments";
import type { CopySelectedRowRange } from "../lib/diffSpatial";
import type { AppTheme } from "../themes";
import { CodeRowView, type PlannedCodeReviewRow } from "./CodeRowView";
import type { PlannedDiffReviewRow } from "./codeRowLayout";
import { CodeRowView } from "./CodeRowView";
import type { CursorHighlight } from "./cursorHighlight";
import { DiffMetaRowView, type PlannedDiffMetaReviewRow } from "./DiffMetaRowView";
import { DiffMetaRowView } from "./DiffMetaRowView";
import type { LineHighlightPaintIndex } from "./lineHighlightPaint";
import {
isPlannedCodeReviewRow,
isPlannedDiffMetaReviewRow,
type PlannedDiffReviewRow,
} from "./reviewRenderPlan";

/** Inputs accepted by the memoized diff-row facade. */
export interface DiffRowViewProps {
Expand All @@ -32,6 +36,11 @@ export interface DiffRowViewProps {
onToggleGap?: (gapKey: string) => void;
}

/** Reject a planned row variant that lacks a mounted row view. */
function unsupportedPlannedDiffRow(plannedRow: never): never {
throw new Error(`Unsupported planned diff row: ${JSON.stringify(plannedRow)}`);
}

/**
* Render one diff row, memoized to avoid unnecessary rerenders.
*
Expand All @@ -58,10 +67,10 @@ export const DiffRowView = memo(function DiffRowViewComponent({
onStartUserNoteAtHunk,
onToggleGap,
}: DiffRowViewProps) {
if (plannedRow.row.type === "collapsed" || plannedRow.row.type === "hunk-header") {
if (isPlannedDiffMetaReviewRow(plannedRow)) {
return (
<DiffMetaRowView
plannedRow={plannedRow as PlannedDiffMetaReviewRow}
plannedRow={plannedRow}
width={width}
theme={theme}
selected={selected || copySelectedRowRange !== undefined}
Expand All @@ -74,10 +83,10 @@ export const DiffRowView = memo(function DiffRowViewComponent({
);
}

if (plannedRow.row.type === "split-line" || plannedRow.row.type === "stack-line") {
if (isPlannedCodeReviewRow(plannedRow)) {
return (
<CodeRowView
plannedRow={plannedRow as PlannedCodeReviewRow}
plannedRow={plannedRow}
width={width}
lineNumberDigits={lineNumberDigits}
showLineNumbers={showLineNumbers}
Expand All @@ -96,9 +105,5 @@ export const DiffRowView = memo(function DiffRowViewComponent({
);
}

return (
<box style={{ width: "100%", height: 1 }}>
<text fg={theme.muted}>Unsupported row.</text>
</box>
);
return unsupportedPlannedDiffRow(plannedRow);
});
15 changes: 7 additions & 8 deletions packages/hunk/src/ui/diff/codeRowLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import {
} from "./codeColumns";
import { CODE_ROW_ADD_NOTE_BADGE_WIDTH } from "./codeRowAffordance";
import type { DiffRow, RenderSpan } from "./diffRowModel";
import type { PlannedReviewRow } from "./reviewRenderPlan";
import {
createPlannedDiffReviewRow,
type PlannedDiffReviewRow,
type PlannedReviewRow,
} from "./reviewRenderPlan";
import { measureWrappedSpansLineCount } from "./styledSpanLayout";

/** Planned review row that carries one terminal diff row. */
export type PlannedDiffReviewRow = Extract<PlannedReviewRow, { kind: "diff-row" }>;

/** Concrete width and wrapping decisions for one rendered code cell. */
export interface CodeCellLayoutPlan {
width: number;
Expand Down Expand Up @@ -186,16 +187,14 @@ export function legacyPlannedDiffRow(
anchorId?: string,
noteGuideSide?: "old" | "new",
): PlannedDiffReviewRow {
return {
kind: "diff-row",
return createPlannedDiffReviewRow(row, {
key: row.key,
stableKey: row.key,
fileId: row.fileId,
hunkIndex: row.hunkIndex,
row,
anchorId,
noteGuideSide,
};
});
}

/** Measure how many terminal rows one complete planned diff row occupies. */
Expand Down
99 changes: 77 additions & 22 deletions packages/hunk/src/ui/diff/reviewRenderPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,26 @@ interface InlineVisibleNotePlacement {
noteIndex: number;
}

interface PlannedDiffReviewRowFields {
kind: "diff-row";
key: string;
stableKey: string;
stableAliasKeys?: string[];
fileId: string;
hunkIndex: number;
anchorId?: string;
noteGuideSide?: "old" | "new";
}

/** Planned review row carrying one terminal diff row subtype. */
export type PlannedDiffReviewRow<Row extends DiffRow = DiffRow> = Row extends DiffRow
? PlannedDiffReviewRowFields & { row: Row }
: never;

export type PlannedDiffReviewRowInput = Omit<PlannedDiffReviewRowFields, "kind">;

export type PlannedReviewRow =
| {
kind: "diff-row";
key: string;
stableKey: string;
stableAliasKeys?: string[];
fileId: string;
hunkIndex: number;
row: DiffRow;
anchorId?: string;
noteGuideSide?: "old" | "new";
}
| PlannedDiffReviewRow
| {
kind: "inline-note";
key: string;
Expand All @@ -56,6 +64,53 @@ export type PlannedReviewRow =
height: number;
};

/** Create a planned diff row while preserving its concrete row subtype. */
export function createPlannedDiffReviewRow<Row extends DiffRow>(
row: Row,
fields: PlannedDiffReviewRowInput,
): PlannedDiffReviewRow<Row>;
export function createPlannedDiffReviewRow(
row: DiffRow,
fields: PlannedDiffReviewRowInput,
): PlannedDiffReviewRow {
switch (row.type) {
case "collapsed":
return { kind: "diff-row", ...fields, row };
case "hunk-header":
return { kind: "diff-row", ...fields, row };
case "split-line":
return { kind: "diff-row", ...fields, row };
case "stack-line":
return { kind: "diff-row", ...fields, row };
}
}

/** Split or stack code row accepted by code-row rendering and interaction policy. */
export type CodeDiffRow = Extract<DiffRow, { type: "split-line" | "stack-line" }>;

/** Collapsed gap or hunk-header row accepted by metadata rendering. */
export type DiffMetaRow = Extract<DiffRow, { type: "collapsed" | "hunk-header" }>;

/** Planned review row carrying split or stack code cells. */
export type PlannedCodeReviewRow = PlannedDiffReviewRow<CodeDiffRow>;

/** Planned review row carrying metadata rather than code cells. */
export type PlannedDiffMetaReviewRow = PlannedDiffReviewRow<DiffMetaRow>;

/** Return whether a planned diff row carries renderable code cells. */
export function isPlannedCodeReviewRow(
plannedRow: PlannedDiffReviewRow,
): plannedRow is PlannedCodeReviewRow {
return plannedRow.row.type === "split-line" || plannedRow.row.type === "stack-line";
}

/** Return whether a planned diff row carries a gap or hunk header. */
export function isPlannedDiffMetaReviewRow(
plannedRow: PlannedDiffReviewRow,
): plannedRow is PlannedDiffMetaReviewRow {
return plannedRow.row.type === "collapsed" || plannedRow.row.type === "hunk-header";
}

function lineRows(rows: DiffRow[]) {
return rows.filter(
(row): row is DiffLineRow => row.type === "split-line" || row.type === "stack-line",
Expand Down Expand Up @@ -396,17 +451,17 @@ export function buildReviewRenderPlan({
anchoredHunks.add(row.hunkIndex);
}

plannedRows.push({
kind: "diff-row",
key: `diff-row:${row.key}`,
stableKey: diffStableKey,
stableAliasKeys: diffStableAliasKeys,
fileId: row.fileId,
hunkIndex: row.hunkIndex,
row,
anchorId,
noteGuideSide: noteGuideSideByRowKey.get(row.key),
});
plannedRows.push(
createPlannedDiffReviewRow(row, {
key: `diff-row:${row.key}`,
stableKey: diffStableKey,
stableAliasKeys: diffStableAliasKeys,
fileId: row.fileId,
hunkIndex: row.hunkIndex,
anchorId,
noteGuideSide: noteGuideSideByRowKey.get(row.key),
}),
);

const anchoredNotes = placementsByAnchor.get(row.key) ?? [];
anchoredNotes.forEach((placement) => {
Expand Down
Loading