Refactor raw mode handling

This commit is contained in:
Timothy Warren 2023-11-01 15:25:52 -04:00
parent b30c4d40d6
commit 4a8047a6d4
7 changed files with 93 additions and 168 deletions

View File

@ -37,4 +37,4 @@ try {
}
export const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols;
export { ptr };
export const getPointer = ptr;

View File

@ -2,10 +2,10 @@
* The main entrypoint when using Bun as the runtime
*/
import {Termios} from './termios'
import { getTermios } from "../common/termios";
export async function main(): Promise<number> {
const t = new Termios();
const t = await getTermios();
t.enableRawMode();
const decoder = new TextDecoder();

View File

@ -1,81 +0,0 @@
import { STDIN_FILENO, TCSANOW, ITermios, TERMIOS_SIZE } from "../common/termios";
import { cfmakeraw, tcgetattr, tcsetattr, ptr } from "./ffi";
/**
* Implementation to toggle raw mode with Bun runtime
*/
export 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
*/
#termios: Uint8Array;
/**
* The pointer to the termios struct
* @private // this.#ptr = ptr(this.#termios);
*/
#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 = ptr(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
tcgetattr(STDIN_FILENO, this.#ptr);
// 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 raw settings
cfmakeraw(this.#ptr);
// Actually set the new termios settings
tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
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 = ptr(this.#cookedTermios);
tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
this.#inRawMode = false;
}
}

View File

@ -30,7 +30,7 @@ export const getRuntime = (): RunTime => {
*/
export const importForRuntime = async (path: string) => {
const runtime = getRuntime();
const suffix = (runtime === RunTime.Deno) ? '.ts' : '';
const suffix = '.ts';
const base = `../${runtime}/`;
const pathParts = path.split('/')

View File

@ -1,3 +1,5 @@
import { importForRuntime } from "./index.ts";
export const STDIN_FILENO = 0;
export const STOUT_FILENO = 1;
export const TCSANOW = 0;
@ -23,4 +25,89 @@ export interface ITermios {
* Restores canonical mode
*/
disableRawMode(): void;
}
export const getTermios = async () => {
// 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
tcgetattr(STDIN_FILENO, this.#ptr);
// 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 raw settings
cfmakeraw(this.#ptr);
// Actually set the new termios settings
tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
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);
tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
this.#inRawMode = false;
}
}
return new Termios();
}

View File

@ -1,10 +1,10 @@
/**
* The main entrypoint when using Deno as the runtime
*/
import { Termios } from './termios.ts'
import {getTermios} from "../common/termios.ts";
export async function main(): Promise<number> {
const t = new Termios();
const t = await getTermios()
t.enableRawMode();
const decoder = new TextDecoder();

View File

@ -1,81 +0,0 @@
import { STDIN_FILENO, TCSANOW, ITermios, TERMIOS_SIZE } from "../common/termios.ts";
import { cfmakeraw, tcgetattr, tcsetattr, getPointer } from "./ffi.ts";
/**
* Implementation to toggle raw mode with Deno runtime
*/
export 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
*/
#termios: Uint8Array;
/**
* The pointer to the termios struct
* @private
*/
#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
tcgetattr(STDIN_FILENO, this.#ptr);
// 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 raw settings
cfmakeraw(this.#ptr);
// Actually set the new termios settings
tcsetattr(STDIN_FILENO, TCSANOW, this.#ptr);
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);
tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr);
this.#inRawMode = false;
}
}