core-agent-ide/codex-cli/src/utils/terminal.ts
Ilan Bigio 59a180ddec Initial commit
Signed-off-by: Ilan Bigio <ilan@openai.com>
2025-04-16 12:56:08 -04:00

82 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 cleanup routine has already executed so repeat calls are
// silently ignored. This can happen when different exit paths (e.g. the raw
// CtrlC 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;
};
// Monkeypatch the public rerender/unmount methods so we know when Ink
// flushes a new frame. Reacts 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 CtrlL) but avoid unnecessary clears on every render to minimise
// flicker.
if (inkRenderer) {
inkRenderer.clear();
}
}
export function onExit(): void {
// Ensure the cleanup logic only runs once even if multiple exit signals
// (e.g. CtrlC data handler *and* the process "exit" event) invoke this
// function. Rerunning the sequence is mostly harmless but can lead to
// duplicate log messages and increases the risk of confusing sideeffects
// should future cleanup steps become nonidempotent.
if (didRunOnExit) {
return;
}
didRunOnExit = true;
// First make sure Ink is properly unmounted so it can restore any terminal
// state it modified (e.g. rawmode on stdin). Failing to do so leaves the
// terminal in rawmode after the Node process has exited which looks like
// a “frozen” shell no input is echoed and CtrlC/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 cleanup logic
// *before* we restore the primary screen buffer.
if (inkRenderer) {
try {
inkRenderer.unmount();
} catch {
/* besteffort continue even if Ink throws */
}
}
}