zz_

app/tui/fullscreen.ts

zzmq.dev@gmail.com/zz / app / tui / fullscreen.ts @ 5e5720fb 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
}