mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-06 23:44:36 +00:00
Adds TuiAltScreenOptions.scrollToEndIndicator, composited onto the last row of the follow-end primary scroll view while it is scrolled up. A left press on the label resumes end-following. Minimal rebuild of #9080.
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
import type { Terminal } from "@earendil-works/pi-tui";
|
|
import { ProcessTerminal, type TUI, TuiAltScreen, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
import { copyToClipboard } from "../../utils/clipboard.ts";
|
|
import { openBrowser } from "../../utils/open-browser.ts";
|
|
import { keyDisplayText } from "./components/keybinding-hints.ts";
|
|
import { theme } from "./theme/theme.ts";
|
|
|
|
export interface InteractiveTuiOptions {
|
|
readonly tuiMode: "regular" | "fullscreen";
|
|
readonly showHardwareCursor: boolean;
|
|
readonly logDirectory: string;
|
|
readonly terminal?: Terminal;
|
|
readonly onRightClickPaste?: () => void;
|
|
readonly fullscreenCopyOnSelect?: boolean;
|
|
}
|
|
|
|
/** Composition root shared by coding-agent presentations. */
|
|
export function createInteractiveTui(options: InteractiveTuiOptions & { readonly tuiMode: "fullscreen" }): TuiAltScreen;
|
|
export function createInteractiveTui(options: InteractiveTuiOptions & { readonly tuiMode: "regular" }): TuiMainScreen;
|
|
export function createInteractiveTui(options: InteractiveTuiOptions): TuiMainScreen | TuiAltScreen;
|
|
export function createInteractiveTui(options: InteractiveTuiOptions): TuiMainScreen | TuiAltScreen {
|
|
const terminal = options.terminal ?? new ProcessTerminal();
|
|
if (options.tuiMode === "fullscreen") {
|
|
const styleSearchMatch = (text: string) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
|
|
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
searchNavigationButtonStyle: (text, hovered) => (hovered ? theme.underline(text) : text),
|
|
scrollToEndIndicator: () => {
|
|
const shortcut = keyDisplayText("tui.altScreen.bottom");
|
|
const label = ` ↓ Jump to latest message${shortcut ? ` · ${shortcut}` : ""} `;
|
|
return theme.bg("selectedBg", theme.fg("text", label));
|
|
},
|
|
openUrl: openBrowser,
|
|
onRightClickPaste: options.onRightClickPaste,
|
|
copyOnSelect: options.fullscreenCopyOnSelect,
|
|
copySelection: async (text) => {
|
|
try {
|
|
await copyToClipboard(text);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
},
|
|
});
|
|
}
|
|
return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
|
|
}
|
|
|
|
/** Stable reference for components while InteractiveMode replaces the active renderer. */
|
|
export function createInteractiveTuiReference(getTui: () => TUI): TUI {
|
|
return new Proxy({} as TUI, {
|
|
get: (_target, property) => {
|
|
const tui = getTui();
|
|
const value = Reflect.get(tui, property, tui);
|
|
if (typeof value !== "function") return value;
|
|
let methodTui = tui;
|
|
let method = value;
|
|
return (...args: unknown[]) => {
|
|
const currentTui = getTui();
|
|
if (currentTui !== methodTui) {
|
|
const currentMethod = Reflect.get(currentTui, property, currentTui);
|
|
if (typeof currentMethod !== "function") {
|
|
throw new TypeError(`TUI property ${String(property)} is not callable`);
|
|
}
|
|
methodTui = currentTui;
|
|
method = currentMethod;
|
|
}
|
|
return Reflect.apply(method, methodTui, args);
|
|
};
|
|
},
|
|
set: (_target, property, value) => {
|
|
const tui = getTui();
|
|
return Reflect.set(tui, property, value, tui);
|
|
},
|
|
has: (_target, property) => Reflect.has(getTui(), property),
|
|
getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
|
|
});
|
|
}
|