diff --git a/src/bun/ffi.ts b/src/bun/ffi.ts deleted file mode 100644 index 0e81fb8..0000000 --- a/src/bun/ffi.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * This is all the nasty ffi setup for the bun runtime - */ -import { dlopen, ptr, suffix } from 'bun:ffi'; -import { IFFI } from '../common/runtime.ts'; - -const getLib = (name: string) => { - return dlopen( - name, - { - tcgetattr: { - args: ['i32', 'pointer'], - returns: 'i32', - }, - tcsetattr: { - args: ['i32', 'i32', 'pointer'], - returns: 'i32', - }, - cfmakeraw: { - args: ['pointer'], - returns: 'void', - }, - }, - ); -}; - -let cStdLib: any = { symbols: {} }; - -try { - cStdLib = getLib(`libc.${suffix}`); -} catch { - try { - cStdLib = getLib(`libc.${suffix}.6`); - } catch { - throw new Error('Could not find c standard library'); - } -} - -const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols; -let closed = false; -const BunFFI: IFFI = { - tcgetattr, - tcsetattr, - cfmakeraw, - getPointer: ptr, - close: () => { - if (!closed) { - cStdLib.close(); - closed = true; - } - - // Do nothing if FFI library was already closed - }, -}; - -export default BunFFI; diff --git a/src/bun/terminal_io.ts b/src/bun/terminal_io.ts index e4027eb..18dfbe8 100644 --- a/src/bun/terminal_io.ts +++ b/src/bun/terminal_io.ts @@ -1,11 +1,45 @@ /** * Wrap the runtime-specific hook into stdin */ +import process from 'node:process'; import Ansi from '../common/ansi.ts'; import { defaultTerminalSize } from '../common/config.ts'; import { readKey } from '../common/fns.ts'; import { ITerminal, ITerminalSize } from '../common/types.ts'; +async function _getTerminalSizeFromAnsi(): Promise { + // Tell the cursor to move to Row 999 and Column 999 + // Since this command specifically doesn't go off the screen + // When we ask where the cursor is, we should get the size of the screen + await BunTerminalIO.writeStdout( + Ansi.moveCursorForward(999) + Ansi.moveCursorDown(999), + ); + + // Ask where the cursor is + await BunTerminalIO.writeStdout(Ansi.GetCursorLocation); + + // Get the first chunk from stdin + // The response is \x1b[(rows);(cols)R.. + const chunk = await BunTerminalIO.readStdinRaw(); + if (chunk === null) { + return defaultTerminalSize; + } + + const rawCode = (new TextDecoder()).decode(chunk); + const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1'); + const [srows, scols] = res.split(';'); + const rows = parseInt(srows, 10) ?? 24; + const cols = parseInt(scols, 10) ?? 80; + + // Clear the screen + await BunTerminalIO.writeStdout(Ansi.ClearScreen + Ansi.ResetCursor); + + return { + rows, + cols, + }; +} + const BunTerminalIO: ITerminal = { // Deno only returns arguments passed to the script, so // remove the bun runtime executable, and entry script arguments @@ -22,37 +56,13 @@ const BunTerminalIO: ITerminal = { * Get the size of the terminal window via ANSI codes * @see https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html#window-size-the-hard-way */ - getTerminalSize: async function getTerminalSize(): Promise { - // Tell the cursor to move to Row 999 and Column 999 - // Since this command specifically doesn't go off the screen - // When we ask where the cursor is, we should get the size of the screen - await BunTerminalIO.writeStdout( - Ansi.moveCursorForward(999) + Ansi.moveCursorDown(999), - ); + getTerminalSize: function getTerminalSize(): Promise { + const [cols, rows] = process.stdout.getWindowSize(); - // Ask where the cursor is - await BunTerminalIO.writeStdout(Ansi.GetCursorLocation); - - // Get the first chunk from stdin - // The response is \x1b[(rows);(cols)R.. - const chunk = await BunTerminalIO.readStdinRaw(); - if (chunk === null) { - return defaultTerminalSize; - } - - const rawCode = (new TextDecoder()).decode(chunk); - const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1'); - const [srows, scols] = res.split(';'); - const rows = parseInt(srows, 10) ?? 24; - const cols = parseInt(scols, 10) ?? 80; - - // Clear the screen - await BunTerminalIO.writeStdout(Ansi.ClearScreen + Ansi.ResetCursor); - - return { + return Promise.resolve({ rows, cols, - }; + }); }, readStdin: async function (): Promise { const raw = await BunTerminalIO.readStdinRaw(); diff --git a/src/common/main.ts b/src/common/main.ts index b37f604..8b616db 100644 --- a/src/common/main.ts +++ b/src/common/main.ts @@ -1,6 +1,6 @@ +import process from 'node:process'; import { readKey } from './fns.ts'; import { getRuntime, logError } from './runtime.ts'; -import { getTermios } from './termios.ts'; import Editor from './editor.ts'; export async function main() { @@ -8,16 +8,14 @@ export async function main() { const { term } = rt; // Setup raw mode, and tear down on error or normal exit - const t = await getTermios(); - t.enableRawMode(); + process.stdin.setRawMode(true); rt.onExit(() => { - t.disableRawMode(); - t.cleanup(); + process.stdin.setRawMode(false); }); // Setup error handler to log to file rt.onEvent('error', (error) => { - t.disableRawMode(); + process.stdin.setRawMode(false); logError(JSON.stringify(error, null, 2)); }); diff --git a/src/common/runtime.ts b/src/common/runtime.ts index 172ac74..9f94cea 100644 --- a/src/common/runtime.ts +++ b/src/common/runtime.ts @@ -1,9 +1,9 @@ +import process from 'node:process'; import { IRuntime, ITestBase } from './types.ts'; -import { getTermios } from './termios.ts'; import { noop } from './fns.ts'; import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts'; -export type { IFFI, IFileIO, IRuntime, ITerminal } from './types.ts'; +export type { IFileIO, IRuntime, ITerminal } from './types.ts'; /** * Which Typescript runtime is currently being used @@ -53,12 +53,9 @@ export function logError(s: unknown): void { */ export function die(s: string | Error): void { logError(s); - getTermios().then((t) => { - t.disableRawMode(); - t.cleanup(); - console.error(s); - getRuntime().then((r) => r.exit()); - }); + process.stdin.setRawMode(false); + console.error(s); + getRuntime().then((r) => r.exit()); } /** diff --git a/src/common/termios.ts b/src/common/termios.ts deleted file mode 100644 index fc7f464..0000000 --- a/src/common/termios.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { die, IFFI, importForRuntime, log, LogLevel } from './runtime.ts'; - -export const STDIN_FILENO = 0; -export const TCSANOW = 0; - -export const TERMIOS_SIZE = 60; - -/** - * Implementation to toggle raw mode - */ -class Termios { - /** - * The ffi implementation for the current runtime - * @private - */ - #ffi: IFFI; - - /** - * 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; - - /** - * Has the nasty ffi stuff been cleaned up? - * @private - */ - #cleaned: boolean = false; - - /** - * The pointer to the termios struct - * @private - */ - readonly #ptr: unknown; - - constructor(ffi: IFFI) { - this.#ffi = ffi; - 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 = ffi.getPointer(this.#termios); - } - - cleanup() { - if (!this.#cleaned) { - this.#ffi.close(); - - this.#cleaned = true; - } - - log('Attempting to cleanup Termios class again', LogLevel.Warning); - } - - enableRawMode() { - if (this.#inRawMode) { - throw new Error('Can not enable raw mode when in raw mode'); - } - - // Get the current termios settings - let res = this.#ffi.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. - // @ts-ignore: bad type definition - this.#cookedTermios = new Uint8Array(this.#termios, 0, 60); - - // Update termios struct with the raw settings - this.#ffi.cfmakeraw(this.#ptr); - - // Actually set the new termios settings - res = this.#ffi.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) { - log( - 'Attempting to disable raw mode when not in raw mode', - LogLevel.Warning, - ); - return; - } - - const oldTermiosPtr = this.#ffi.getPointer(this.#cookedTermios); - const res = this.#ffi.tcsetattr(STDIN_FILENO, TCSANOW, oldTermiosPtr); - if (res === -1) { - die('Failed to restore canonical mode.'); - } - - this.#inRawMode = false; - } -} - -let termiosSingleton: Termios | null = null; - -export const getTermios = async () => { - if (termiosSingleton !== null) { - return termiosSingleton; - } - - // Get the runtime-specific ffi wrappers - const ffi = await importForRuntime('ffi'); - termiosSingleton = new Termios(ffi); - - return termiosSingleton; -}; diff --git a/src/common/types.ts b/src/common/types.ts index 8d8aa44..835861e 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -8,37 +8,6 @@ export interface ITerminalSize { cols: number; } -/** - * The native functions for getting/setting terminal settings - */ -export interface IFFI { - /** - * Get the existing termios settings (for canonical mode) - */ - tcgetattr(fd: number, termiosPtr: unknown): number; - - /** - * Update the termios settings - */ - tcsetattr(fd: number, act: number, termiosPtr: unknown): number; - - /** - * Update the termios pointer with raw mode settings - */ - cfmakeraw(termiosPtr: unknown): void; - - /** - * Convert a TypedArray to an opaque pointer for ffi calls - */ - // deno-lint-ignore no-explicit-any - getPointer(ta: any): unknown; - - /** - * Closes the FFI handle - */ - close(): void; -} - // ---------------------------------------------------------------------------- // Runtime adapter interfaces // ---------------------------------------------------------------------------- diff --git a/src/deno/ffi.ts b/src/deno/ffi.ts deleted file mode 100644 index bc2b818..0000000 --- a/src/deno/ffi.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Deno-specific ffi code -import { IFFI } from '../common/runtime.ts'; - -let suffix = ''; -switch (Deno.build.os) { - case 'windows': - suffix = 'dll'; - break; - case 'darwin': - suffix = 'dylib'; - break; - default: - suffix = 'so.6'; - break; -} - -const cSharedLib = `libc.${suffix}`; -const cStdLib = Deno.dlopen( - cSharedLib, - { - tcgetattr: { - parameters: ['i32', 'pointer'], - result: 'i32', - }, - tcsetattr: { - parameters: ['i32', 'i32', 'pointer'], - result: 'i32', - }, - cfmakeraw: { - parameters: ['pointer'], - result: 'void', - }, - } as const, -); - -const { tcgetattr, tcsetattr, cfmakeraw } = cStdLib.symbols; -let closed = false; -const DenoFFI: IFFI = { - tcgetattr, - tcsetattr, - cfmakeraw, - getPointer: Deno.UnsafePointer.of, - close: () => { - if (!closed) { - cStdLib.close(); - closed = true; - } - - // Do nothing if FFI library was already closed - }, -}; - -export default DenoFFI;