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
76 changes: 52 additions & 24 deletions src/ui/diff/PierreDiffView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,33 @@ function trimSpans(spans: RenderSpan[], width: number) {
};
}

/** Render the left-edge hunk marker without changing row width. */
function marker(selected: boolean) {
return selected ? "▌" : " ";
/** Parse a hex color string into RGB components. */
function hexToRgb(hex: string) {
const n = parseInt(hex.slice(1), 16);
return { r: (n >> 16) & 0xff, g: (n >> 8) & 0xff, b: n & 0xff };
}

/** Blend a color toward a background at a given ratio (0 = bg, 1 = fg). */
function blendHex(fg: string, bg: string, ratio: number) {
const f = hexToRgb(fg);
const b = hexToRgb(bg);
const mix = (a: number, z: number) => Math.round(z + (a - z) * ratio);
const r = mix(f.r, b.r);
const g = mix(f.g, b.g);
const bl = mix(f.b, b.b);
return `#${((r << 16) | (g << 8) | bl).toString(16).padStart(6, "0")}`;
}

const INACTIVE_RAIL_BLEND = 0.35;

/** Dim a rail color for inactive hunks by blending toward the panel background. */
function dimRailColor(color: string, theme: AppTheme) {
return blendHex(color, theme.panel, INACTIVE_RAIL_BLEND);
}

/** The rail marker is always visible. */
function marker() {
return "▌";
}

/** Return the neutral active-hunk rail color for the current theme. */
Expand All @@ -93,26 +117,30 @@ function neutralRailColor(theme: AppTheme) {
}

/** Pick the stack-view rail color for one rendered row. */
function stackRailColor(kind: StackLineCell["kind"], theme: AppTheme) {
if (kind === "addition") {
return theme.addedSignColor;
}
function stackRailColor(kind: StackLineCell["kind"], theme: AppTheme, selected: boolean) {
let color: string;

if (kind === "deletion") {
return theme.removedSignColor;
if (kind === "addition") {
color = theme.addedSignColor;
} else if (kind === "deletion") {
color = theme.removedSignColor;
} else {
color = neutralRailColor(theme);
}

return neutralRailColor(theme);
return selected ? color : dimRailColor(color, theme);
}

/** Pick the left split-view rail color from the old-side cell state. */
function splitLeftRailColor(kind: SplitLineCell["kind"], theme: AppTheme) {
return kind === "deletion" ? theme.removedSignColor : neutralRailColor(theme);
function splitLeftRailColor(kind: SplitLineCell["kind"], theme: AppTheme, selected: boolean) {
const color = kind === "deletion" ? theme.removedSignColor : neutralRailColor(theme);
return selected ? color : dimRailColor(color, theme);
}

/** Pick the right split-view rail color from the new-side cell state. */
function splitRightRailColor(kind: SplitLineCell["kind"], theme: AppTheme) {
return kind === "addition" ? theme.addedSignColor : neutralRailColor(theme);
function splitRightRailColor(kind: SplitLineCell["kind"], theme: AppTheme, selected: boolean) {
const color = kind === "addition" ? theme.addedSignColor : neutralRailColor(theme);
return selected ? color : dimRailColor(color, theme);
}

/** Pick split-view colors from the semantic diff cell kind. */
Expand Down Expand Up @@ -510,8 +538,8 @@ function renderHeaderRow(
}}
>
<text>
<span fg={selected ? neutralRailColor(theme) : theme.panelAlt} bg={theme.panelAlt}>
{marker(selected)}
<span fg={selected ? neutralRailColor(theme) : dimRailColor(neutralRailColor(theme), theme)} bg={theme.panelAlt}>
{marker()}
</span>
<span fg={row.type === "collapsed" ? theme.muted : theme.badgeNeutral} bg={theme.panelAlt}>
{label}
Expand All @@ -534,8 +562,8 @@ function renderHeaderRow(
>
<box style={{ width: Math.max(0, width - badgeWidth), height: 1 }}>
<text>
<span fg={selected ? neutralRailColor(theme) : theme.panelAlt} bg={theme.panelAlt}>
{marker(selected)}
<span fg={selected ? neutralRailColor(theme) : dimRailColor(neutralRailColor(theme), theme)} bg={theme.panelAlt}>
{marker()}
</span>
<span fg={row.type === "collapsed" ? theme.muted : theme.badgeNeutral} bg={theme.panelAlt}>
{label}
Expand Down Expand Up @@ -684,13 +712,13 @@ function renderRow(
const leftWidth = Math.max(0, markerWidth + Math.floor(usableWidth / 2));
const rightWidth = Math.max(0, separatorWidth + usableWidth - Math.floor(usableWidth / 2));
const leftPrefix = {
text: marker(selected),
fg: selected ? splitLeftRailColor(row.left.kind, theme) : theme.panel,
text: marker(),
fg: splitLeftRailColor(row.left.kind, theme, selected),
bg: theme.panel,
};
const rightPrefix = {
text: selected ? "▌" : "│",
fg: selected ? splitRightRailColor(row.right.kind, theme) : theme.border,
text: "▌",
fg: splitRightRailColor(row.right.kind, theme, selected),
bg: theme.panel,
};

Expand Down Expand Up @@ -744,8 +772,8 @@ function renderRow(
}
} else if (row.type === "stack-line") {
const prefix = {
text: marker(selected),
fg: selected ? stackRailColor(row.cell.kind, theme) : theme.panel,
text: marker(),
fg: stackRailColor(row.cell.kind, theme, selected),
bg: theme.panel,
};

Expand Down
14 changes: 7 additions & 7 deletions test/app-responsive.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ describe("responsive shell", () => {
expect(full).toContain("M alpha.ts");
expect(full).not.toContain("Changeset summary");
expect(full).toContain("drag divider resize");
expect(full).toContain("│");
expect(full).toMatch(/▌.*▌/);

expect(medium).not.toContain("Files");
expect(medium).not.toContain("Changeset summary");
expect(medium).toContain("│");
expect(medium).toMatch(/▌.*▌/);
expect(medium).not.toContain("drag divider resize");

expect(tight).not.toContain("Files");
expect(tight).not.toContain("Changeset summary");
expect(tight).not.toContain("│");
expect(tight).not.toMatch(/▌.*▌/);
expect(tight).not.toContain("drag divider resize");
});

Expand All @@ -159,12 +159,12 @@ describe("responsive shell", () => {

expect(forcedSplit).not.toContain("Files");
expect(forcedSplit).not.toContain("Changeset summary");
expect(forcedSplit).toContain("│");
expect(forcedSplit).toMatch(/▌.*▌/);
expect(forcedSplit).not.toContain("drag divider resize");

expect(forcedStack).not.toContain("Files");
expect(forcedStack).not.toContain("Changeset summary");
expect(forcedStack).not.toContain("│");
expect(forcedStack).not.toMatch(/▌.*▌/);
expect(forcedStack).not.toContain("drag divider resize");
});

Expand All @@ -175,12 +175,12 @@ describe("responsive shell", () => {
expect(wide).not.toContain("File View Navigate Theme Agent Help");
expect(wide).not.toContain("F10 menu");
expect(wide).not.toContain("M alpha.ts");
expect(wide).toContain("│");
expect(wide).toMatch(/▌.*▌/);

expect(narrow).not.toContain("File View Navigate Theme Agent Help");
expect(narrow).not.toContain("F10 menu");
expect(narrow).not.toContain("M alpha.ts");
expect(narrow).not.toContain("│");
expect(narrow).not.toMatch(/▌.*▌/);
});

test("filter focus suppresses global shortcut keys like quit", async () => {
Expand Down
Loading