scroll/src/common/termios.ts

138 lines
3.0 KiB
JavaScript

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 with Bun runtime
*/
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;
};