import { die, IFFI, importForRuntime, ITerminalSize } from './mod.ts'; export const STDIN_FILENO = 0; export const TCSANOW = 0; export const TERMIOS_SIZE = 60; export const defaultTerminalSize: ITerminalSize = { rows: 24, cols: 80, }; /** * Implementation to toggle raw mode */ class Termios { /** * The ffi implementation for the current runtime * @private */ #ffi: IFFI; /** * Are we in raw mode? * @private */ #inRawMode: boolean; /** * The saved version of the termios struct for cooked/canonical mode * @private */ #cookedTermios: Uint8Array; /** * The data for the termios struct we are manipulating * @private */ #termios: Uint8Array; /** * The pointer to the termios struct * @private */ #ptr; constructor(ffi: IFFI) { this.#ffi = ffi; this.#inRawMode = false; // These are the TypedArrays linked to the raw pointer data this.#cookedTermios = new Uint8Array(TERMIOS_SIZE); this.#termios = new Uint8Array(TERMIOS_SIZE); // The current pointer for C this.#ptr = ffi.getPointer(this.#termios); } cleanup() { this.#ptr = null; this.#cookedTermios = new Uint8Array(0); this.#termios = new Uint8Array(0); this.#ffi.close(); } enableRawMode() { if (this.#inRawMode) { throw new Error('Can not enable raw mode when in raw mode'); } // Get the current termios settings let res = this.#ffi.tcgetattr(STDIN_FILENO, this.#ptr); if (res === -1) { die('Failed to get terminal settings'); } // The #ptr property is pointing to the #termios TypedArray. As the pointer // is manipulated, the TypedArray is as well. We will use this to save // the original canonical/cooked terminal settings for disabling raw mode later. // @ts-ignore: bad type definition this.#cookedTermios = new Uint8Array(this.#termios, 0, 60); // Update termios struct with (most of the) raw settings this.#ffi.cfmakeraw(this.#ptr); // Actually set the new termios settings res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr); if (res === -1) { die('Failed to update terminal settings. Can\'t enter raw mode'); } this.#inRawMode = true; } disableRawMode() { // Don't even bother throwing an error if we try to disable raw mode // and aren't in raw mode. It just doesn't really matter. if (!this.#inRawMode) { return; } const oldTermiosPtr = this.#ffi.getPointer(this.#cookedTermios); const res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); if (res === -1) { die('Failed to restore canonical mode.'); } this.#inRawMode = false; } } let termiosSingleton: Termios | null = null; export const getTermios = async () => { if (termiosSingleton !== null) { return termiosSingleton; } // Get the runtime-specific ffi wrappers const ffi = await importForRuntime('ffi'); termiosSingleton = new Termios(ffi); return termiosSingleton; };