94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
import { Box, Text, useInput } from "ink";
|
||
import React, { useState } from "react";
|
||
|
||
/**
|
||
* Simple scrollable view for displaying a diff.
|
||
* The component is intentionally lightweight and mirrors the UX of
|
||
* HistoryOverlay: Up/Down or j/k to scroll, PgUp/PgDn for paging and Esc to
|
||
* close. The caller is responsible for computing the diff text.
|
||
*/
|
||
export default function DiffOverlay({
|
||
diffText,
|
||
onExit,
|
||
}: {
|
||
diffText: string;
|
||
onExit: () => void;
|
||
}): JSX.Element {
|
||
const lines = diffText.length > 0 ? diffText.split("\n") : ["(no changes)"];
|
||
|
||
const [cursor, setCursor] = useState(0);
|
||
|
||
// Determine how many rows we can display – similar to HistoryOverlay.
|
||
const rows = process.stdout.rows || 24;
|
||
const headerRows = 2;
|
||
const footerRows = 1;
|
||
const maxVisible = Math.max(4, rows - headerRows - footerRows);
|
||
|
||
useInput((input, key) => {
|
||
if (key.escape || input === "q") {
|
||
onExit();
|
||
return;
|
||
}
|
||
|
||
if (key.downArrow || input === "j") {
|
||
setCursor((c) => Math.min(lines.length - 1, c + 1));
|
||
} else if (key.upArrow || input === "k") {
|
||
setCursor((c) => Math.max(0, c - 1));
|
||
} else if (key.pageDown) {
|
||
setCursor((c) => Math.min(lines.length - 1, c + maxVisible));
|
||
} else if (key.pageUp) {
|
||
setCursor((c) => Math.max(0, c - maxVisible));
|
||
} else if (input === "g") {
|
||
setCursor(0);
|
||
} else if (input === "G") {
|
||
setCursor(lines.length - 1);
|
||
}
|
||
});
|
||
|
||
const firstVisible = Math.min(
|
||
Math.max(0, cursor - Math.floor(maxVisible / 2)),
|
||
Math.max(0, lines.length - maxVisible),
|
||
);
|
||
const visible = lines.slice(firstVisible, firstVisible + maxVisible);
|
||
|
||
// Very small helper to colorize diff lines in a basic way.
|
||
function renderLine(line: string, idx: number): JSX.Element {
|
||
let color: "green" | "red" | "cyan" | undefined = undefined;
|
||
if (line.startsWith("+")) {
|
||
color = "green";
|
||
} else if (line.startsWith("-")) {
|
||
color = "red";
|
||
} else if (line.startsWith("@@") || line.startsWith("diff --git")) {
|
||
color = "cyan";
|
||
}
|
||
return (
|
||
<Text key={idx} color={color} wrap="truncate-end">
|
||
{line === "" ? " " : line}
|
||
</Text>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Box
|
||
flexDirection="column"
|
||
borderStyle="round"
|
||
borderColor="gray"
|
||
width={Math.min(120, process.stdout.columns || 120)}
|
||
>
|
||
<Box paddingX={1}>
|
||
<Text bold>Working tree diff ({lines.length} lines)</Text>
|
||
</Box>
|
||
|
||
<Box flexDirection="column" paddingX={1}>
|
||
{visible.map((line, idx) => {
|
||
return renderLine(line, firstVisible + idx);
|
||
})}
|
||
</Box>
|
||
|
||
<Box paddingX={1}>
|
||
<Text dimColor>esc Close ↑↓ Scroll PgUp/PgDn g/G First/Last</Text>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
}
|