2023-11-02 13:06:48 -04:00
|
|
|
import { die, importForRuntime } from './index.ts';
|
2023-11-01 15:25:52 -04:00
|
|
|
|
2023-11-01 14:00:40 -04:00
|
|
|
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;
|
2023-11-01 15:25:52 -04:00
|
|
|
}
|
|
|
|
|
2023-11-02 13:06:48 -04:00
|
|
|
let termiosSingleton: ITermios | null = null;
|
|
|
|
|
2023-11-01 15:25:52 -04:00
|
|
|
export const getTermios = async () => {
|
2023-11-02 13:06:48 -04:00
|
|
|
if (termiosSingleton !== null) {
|
|
|
|
return termiosSingleton;
|
|
|
|
}
|
|
|
|
|
2023-11-01 15:25:52 -04:00
|
|
|
// Get the runtime-specific ffi wrappers
|
2023-11-01 15:27:31 -04:00
|
|
|
const { tcgetattr, tcsetattr, cfmakeraw, getPointer } =
|
|
|
|
await importForRuntime('ffi');
|
2023-11-01 15:25:52 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Implementation to toggle raw mode with Bun runtime
|
|
|
|
*/
|
|
|
|
class Termios implements ITermios {
|
|
|
|
/**
|
|
|
|
* Are we in raw mode?
|
|
|
|
* @private
|
|
|
|
*/
|
2023-11-01 15:27:31 -04:00
|
|
|
#inRawMode: boolean;
|
2023-11-01 15:25:52 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2023-11-02 13:06:48 -04:00
|
|
|
let res = tcgetattr(STDIN_FILENO, this.#ptr);
|
|
|
|
if (res === -1) {
|
|
|
|
die('Failed to get terminal settings');
|
|
|
|
}
|
2023-11-01 15:25:52 -04:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
2023-11-02 13:06:48 -04:00
|
|
|
// 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
|
2023-11-01 15:25:52 -04:00
|
|
|
|
|
|
|
// Actually set the new termios settings
|
2023-11-02 13:06:48 -04:00
|
|
|
res = tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
|
|
|
|
if (res === -1) {
|
|
|
|
die('Failed to update terminal settings. Can\'t enter raw mode');
|
|
|
|
}
|
|
|
|
|
2023-11-01 15:25:52 -04:00
|
|
|
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);
|
2023-11-02 13:06:48 -04:00
|
|
|
let res = tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
|
|
|
|
if (res === -1) {
|
|
|
|
die('Failed to restore canonical mode.');
|
|
|
|
}
|
2023-11-01 15:25:52 -04:00
|
|
|
|
|
|
|
this.#inRawMode = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-02 13:06:48 -04:00
|
|
|
termiosSingleton = new Termios();
|
|
|
|
|
|
|
|
return termiosSingleton;
|
2023-11-01 15:27:31 -04:00
|
|
|
};
|