import { die, importForRuntime } from './mod.ts'; export const STDIN_FILENO = 0; export const STOUT_FILENO = 1; export const TCSANOW = 0; export const TCSAFLUSH = 2; export const TERMIOS_SIZE = 60; /** * Common interface for setting Termios properties */ export interface ITermios { /** * Are we currently in raw mode? */ inRawMode: boolean; /** * Toggles on raw mode */ enableRawMode(): void; /** * Restores canonical mode */ disableRawMode(): void; } let termiosSingleton: ITermios | null = null; export const getTermios = async () => { if (termiosSingleton !== null) { return termiosSingleton; } // Get the runtime-specific ffi wrappers const { tcgetattr, tcsetattr, cfmakeraw, getPointer } = await importForRuntime('ffi'); /** * Implementation to toggle raw mode */ class Termios implements ITermios { /** * 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 */ readonly #termios: Uint8Array; /** * The pointer to the termios struct * @private */ readonly #ptr: any; constructor() { 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 = getPointer(this.#termios); } get inRawMode() { return this.#inRawMode; } enableRawMode() { if (this.#inRawMode) { throw new Error('Can not enable raw mode when in raw mode'); } // Get the current termios settings let res = 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. this.#cookedTermios = new Uint8Array(this.#termios, 0, 60); // Update termios struct with (most of the) raw settings res = cfmakeraw(this.#ptr); if (res === -1) { die('Failed to call cfmakeraw'); } // @TODO: Tweak a few more terminal settings // Actually set the new termios settings res = 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 = getPointer(this.#cookedTermios); const res = tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); if (res === -1) { die('Failed to restore canonical mode.'); } this.#inRawMode = false; } } termiosSingleton = new Termios(); return termiosSingleton; };