/** * Input handling: connects OpenTUI's StdinParser to the solid-tui screen. * * Vendored from OpenTUI (MIT): https://github.com/miunau/opentui * packages/core/src/lib/stdin-parser.ts + parse.keypress.ts + parse.keypress-kitty.ts * + parse.mouse.ts + clock.ts + paste.ts * * This module provides: * - startInput() / stopInput() to manage raw mode and stdin parsing * - onKeypress / onMouse / onPaste callbacks for the screen to wire up */ export type { KeyEventType, ParsedKey } from "./parse-keypress.ts"; export type { MouseEventType, RawMouseEvent, ScrollInfo, } from "./parse-mouse.ts"; export type { PasteMetadata } from "./paste.ts"; export { decodePasteBytes } from "./paste.ts"; export { type StdinEvent, StdinParser, type StdinParserOptions, } from "./stdin-parser.ts"; import type { ParsedKey } from "./parse-keypress.ts"; import type { RawMouseEvent } from "./parse-mouse.ts"; import { StdinParser, type StdinParserOptions } from "./stdin-parser.ts"; export interface InputCallbacks { onKey?: (key: ParsedKey) => void; onMouse?: (event: RawMouseEvent) => void; onPaste?: (bytes: Uint8Array) => void; onResponse?: (protocol: string, sequence: string) => void; } export interface InputHandle { /** The underlying parser — for protocol context updates. */ parser: StdinParser; /** Stop listening and restore terminal. */ stop(): void; } /** * Start reading raw stdin and dispatching parsed events. * Enables raw mode, bracketed paste, and optionally SGR mouse. */ export function startInput( callbacks: InputCallbacks, options: { mouse?: boolean; kittyKeyboard?: boolean; bracketedPaste?: boolean; } = {}, ): InputHandle { const mouse = options.mouse ?? false; const kittyKeyboard = options.kittyKeyboard ?? false; const bracketedPaste = options.bracketedPaste ?? true; const parserOpts: StdinParserOptions = { armTimeouts: true, useKittyKeyboard: kittyKeyboard, onTimeoutFlush: () => drain(), }; const parser = new StdinParser(parserOpts); function drain() { // A callback may tear the screen down mid-drain (e.g. an input handler // that calls screen.unmount() to return to a menu), which destroys the // parser. Stop quietly rather than reading from a destroyed parser. try { let event = parser.read(); while (event !== null) { switch (event.type) { case "key": callbacks.onKey?.(event.key); break; case "mouse": callbacks.onMouse?.(event.event); break; case "paste": callbacks.onPaste?.(event.bytes); break; case "response": callbacks.onResponse?.(event.protocol, event.sequence); break; } event = parser.read(); } } catch { // Parser destroyed by a callback — nothing left to drain. } } function onData(chunk: Buffer | string) { const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string); parser.push(data); drain(); } // Enable raw mode if (process.stdin.isTTY) { process.stdin.setRawMode(true); } process.stdin.resume(); process.stdin.on("data", onData); // Enable terminal protocols if (bracketedPaste) { process.stdout.write("\x1b[?2004h"); // Enable bracketed paste } if (mouse) { process.stdout.write("\x1b[?1006h"); // Enable SGR mouse process.stdout.write("\x1b[?1003h"); // Enable all mouse tracking } if (kittyKeyboard) { process.stdout.write("\x1b[>1u"); // Enable kitty keyboard (disambiguate) } return { parser, stop() { process.stdin.off("data", onData); process.stdin.pause(); if (process.stdin.isTTY) { process.stdin.setRawMode(false); } // Disable terminal protocols if (kittyKeyboard) { process.stdout.write("\x1b[