Files
dev/codex-cli/src/utils/terminal.ts
T
Ilan Bigio bce28c1ace Initial commit
Signed-off-by: Ilan Bigio <[email protected]>
2025-04-16 12:56:08 -04:00

83 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Instance } from "ink";
import type React from "react";
let inkRenderer: Instance | null = null;
// Track whether the clean‑up routine has already executed so repeat calls are
// silently ignored. This can happen when different exit paths (e.g. the raw
// Ctrl‑C handler and the process "exit" event) both attempt to tidy up.
let didRunOnExit = false;
export function setInkRenderer(renderer: Instance): void {
inkRenderer = renderer;
if (process.env["CODEX_FPS_DEBUG"]) {
let last = Date.now();
const logFrame = () => {
const now = Date.now();
// eslint-disable-next-line no-console
console.error(`[fps] frame in ${now - last}ms`);
last = now;
};
// Monkey‑patch the public rerender/unmount methods so we know when Ink
// flushes a new frame. React’s internal renders eventually call
// `rerender()` so this gives us a good approximation without poking into
// private APIs.
const origRerender = renderer.rerender.bind(renderer);
renderer.rerender = (node: React.ReactNode) => {
logFrame();
return origRerender(node);
};
const origClear = renderer.clear.bind(renderer);
renderer.clear = () => {
logFrame();
return origClear();
};
}
}
export function clearTerminal(): void {
if (process.env["CODEX_QUIET_MODE"] === "1") {
return;
}
// When using the alternate screen the content never scrolls, so we rarely
// need a full clear. Still expose the behaviour when explicitly requested
// (e.g. via Ctrl‑L) but avoid unnecessary clears on every render to minimise
// flicker.
if (inkRenderer) {
inkRenderer.clear();
}
}
export function onExit(): void {
// Ensure the clean‑up logic only runs once even if multiple exit signals
// (e.g. Ctrl‑C data handler *and* the process "exit" event) invoke this
// function. Re‑running the sequence is mostly harmless but can lead to
// duplicate log messages and increases the risk of confusing side‑effects
// should future clean‑up steps become non‑idempotent.
if (didRunOnExit) {
return;
}
didRunOnExit = true;
// First make sure Ink is properly unmounted so it can restore any terminal
// state it modified (e.g. raw‑mode on stdin). Failing to do so leaves the
// terminal in raw‑mode after the Node process has exited which looks like
// a “frozen” shell – no input is echoed and Ctrl‑C/Z no longer work. This
// regression was introduced when we switched from `inkRenderer.unmount()`
// to letting `process.exit` terminate the program a few commits ago. By
// explicitly unmounting here we ensure Ink performs its clean‑up logic
// *before* we restore the primary screen buffer.
if (inkRenderer) {
try {
inkRenderer.unmount();
} catch {
/* best‑effort – continue even if Ink throws */
}
}
}