diff --git a/src/bun/ffi.ts b/src/bun/ffi.ts index 2f7f07d..8cf24d1 100644 --- a/src/bun/ffi.ts +++ b/src/bun/ffi.ts @@ -1,8 +1,7 @@ -// Bun-specific ffi code -import { dlopen, ptr, FFIType, suffix, toArrayBuffer } from "bun:ffi"; -import {STDIN_FILENO, TCSANOW} from "../common/ffi"; - -const termiosArray = new Uint8Array(60); +/** + * This is all the nasty ffi setup for the bun runtime + */ +import { dlopen, ptr, suffix } from "bun:ffi"; const getLib = (name: string) => { return dlopen( @@ -29,26 +28,13 @@ let cStdLib = {symbols: {}}; try { cStdLib = getLib(`libc.${suffix}`); } catch { - cStdLib = getLib(`libc.${suffix}.6`); -} - -export const enableRawMode = () => { - // Get the current termios settings - const termiosPtr = ptr(termiosArray); - cStdLib.symbols.tcgetattr(STDIN_FILENO, termiosPtr); - - const oldTermios = new Uint8Array(toArrayBuffer(termiosPtr, 0, 60), 0, 60); - console.info(oldTermios); - - // Update termios struct with raw settings - cStdLib.symbols.cfmakeraw(termiosPtr); - - // Actually set the new termios settings - cStdLib.symbols.tcsetattr(STDIN_FILENO, TCSANOW, termiosPtr); - - return () => { - const oldTermiosPtr = ptr(oldTermios); - - cStdLib.symbols.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); + try { + cStdLib = getLib(`libc.${suffix}.6`); + } + catch { + throw new Error('Could not find c standard library'); } } + +export const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols; +export { ptr }; \ No newline at end of file diff --git a/src/bun/index.ts b/src/bun/index.ts index f7c14a8..35d6cf1 100644 --- a/src/bun/index.ts +++ b/src/bun/index.ts @@ -2,24 +2,25 @@ * The main entrypoint when using Bun as the runtime */ -import {enableRawMode} from './ffi' +import {Termios} from './termios' export async function main(): Promise { - const disableRawMode = enableRawMode(); + const t = new Termios(); + t.enableRawMode(); const decoder = new TextDecoder(); for await (const chunk of Bun.stdin.stream()) { const char = String(decoder.decode(chunk)).trim(); if (char === 'q') { - disableRawMode(); + t.disableRawMode(); return 0; } } process.on("exit", (code) => { console.log(`Process exited with code ${code}`); - disableRawMode(); + t.disableRawMode(); }); return -1; diff --git a/src/bun/termios.ts b/src/bun/termios.ts new file mode 100644 index 0000000..ab8a9cd --- /dev/null +++ b/src/bun/termios.ts @@ -0,0 +1,81 @@ +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; + } +} + diff --git a/src/common/ffi.ts b/src/common/ffi.ts deleted file mode 100644 index a0fe9c2..0000000 --- a/src/common/ffi.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const STDIN_FILENO = 0; -export const STOUT_FILENO = 1; -export const TCSANOW = 0; -export const TCSAFLUSH = 2; \ No newline at end of file diff --git a/src/common/termios.ts b/src/common/termios.ts new file mode 100644 index 0000000..d023d6c --- /dev/null +++ b/src/common/termios.ts @@ -0,0 +1,26 @@ +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; +} \ No newline at end of file diff --git a/src/deno/ffi.ts b/src/deno/termios.ts similarity index 92% rename from src/deno/ffi.ts rename to src/deno/termios.ts index 1eba0ec..9a28695 100644 --- a/src/deno/ffi.ts +++ b/src/deno/termios.ts @@ -2,6 +2,8 @@ // Determine library extension based on // your OS. +// import { termiosStruct } from "../common/termios.ts"; + let libSuffix = ''; switch (Deno.build.os) { case 'windows':