app/tui/fullscreen.ts
zzmq.dev@gmail.com/zz / app / tui / fullscreen.ts
@ 0caf808f
Download
Ladder Summary
- file The module provides two exported functions, `enterFullscreen()` and `exitFullscreen()`, that toggle the terminal into and out of an alternate‑screen “full‑screen” mode: on entry it switches to the alternate buffer, clears the screen, hides the cursor, and enables mouse‑click/wheel reporting (SGR encoding), while also installing one‑time process‑exit/SIGINT/SIGTERM handlers that invoke `exitFullscreen()`. `exitFullscreen()` reverses those changes—disabling mouse reporting, showing the cursor, and restoring the normal screen buffer—ensuring the shell’s display is left clean whenever the app quits or is interrupted.
// Puts the terminal into its alternate screen buffer for the life of
// the process, the way a real full-screen app (less, vim, Textual's own
// apps) does -- so the TUI occupies a stable viewport instead of
// printing into (and growing) the shell's normal scrollback. Restores
// the original screen on any exit path so the shell is left clean.
//
// Also enables mouse reporting (click + wheel, SGR encoding) so the
// scroll wheel can drive the same scroll actions as the arrow keys (see
// useMouseWheel.ts) -- the one real tradeoff this introduces is that a
// plain click-drag no longer starts the terminal's own text selection,
// since button presses now go to the app instead. Hold Option (macOS)
// or Shift (most other terminals) while clicking/dragging to force the
// terminal to select text natively anyway -- a standard convention, not
// a workaround specific to this app.
let active = false;
// Separate from `active` because enterFullscreen can now be called more
// than once per process (AssistApp's ctrl+t shell toggle leaves and
// re-enters it) -- the exit/SIGINT/SIGTERM handlers only need
// registering the first time, not once per re-entry, or they'd pile up
// across however many times someone hops in and out of a shell.
let listenersAttached = false;
export function enterFullscreen(): void {
if (active) return;
active = true;
process.stdout.write("\x1B[?1049h"); // switch to alternate screen buffer
process.stdout.write("\x1B[2J\x1B[H"); // clear it, cursor to home
process.stdout.write("\x1B[?25l"); // hide cursor
process.stdout.write("\x1B[?1000h"); // enable mouse reporting (click + wheel)
process.stdout.write("\x1B[?1006h"); // SGR extended mouse encoding
if (listenersAttached) return;
listenersAttached = true;
const restore = () => exitFullscreen();
process.on("exit", restore);
process.on("SIGINT", () => {
restore();
process.exit(0);
});
process.on("SIGTERM", () => {
restore();
process.exit(0);
});
}
export function exitFullscreen(): void {
if (!active) return;
active = false;
process.stdout.write("\x1B[?1006l");
process.stdout.write("\x1B[?1000l");
process.stdout.write("\x1B[?25h"); // show cursor
process.stdout.write("\x1B[?1049l"); // restore the normal screen buffer
}