diff --git a/src/common/runtime.ts b/src/common/runtime.ts index a316ef0..d2d2e4f 100644 --- a/src/common/runtime.ts +++ b/src/common/runtime.ts @@ -2,151 +2,24 @@ import { getTermios } from './termios.ts'; import { ctrlKey, noop } from './utils.ts'; import { ITestBase } from './types.ts'; import { KeyCommand } from './ansi.ts'; +import { IRuntime } from './runtime_types.ts'; +export type * from './runtime_types.ts'; +/** + * Which Typescript runtime is currently being used + */ export enum RunTimeType { Bun = 'bun', Deno = 'deno', Unknown = 'common', } -// ---------------------------------------------------------------------------- -// Runtime adapter interfaces -// ---------------------------------------------------------------------------- - -/** - * The size of terminal in rows and columns - */ -export interface ITerminalSize { - rows: number; - 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-specific terminal functionality - */ -export interface ITerminal { - /** - * The arguments passed to the program on launch - */ - argv: string[]; - - /** - * The generator function returning chunks of input from the stdin stream - */ - inputLoop(): AsyncGenerator; - - /** - * Get the size of the terminal - */ - getTerminalSize(): Promise; - - /** - * Get the current chunk of input, if it exists - */ - readStdin(): Promise; - - /** - * Get the raw chunk of input - */ - readStdinRaw(): Promise; - - /** - * Pipe a string to stdout - */ - writeStdout(s: string): Promise; -} - -/** - * Runtime-specific file handling - */ -export interface IFileIO { - openFile(path: string): Promise; - openFileSync(path: string): string; - appendFile(path: string, contents: string): Promise; - appendFileSync(path: string, contents: string): void; - saveFile(path: string, contents: string): Promise; -} - -/** - * The common interface for runtime adapters - */ -export interface IRuntime { - /** - * The name of the runtime - */ - name: RunTimeType; - - /** - * Runtime-specific terminal functionality - */ - term: ITerminal; - - /** - * Runtime-specific file system io - */ - file: IFileIO; - - /** - * Set up an event handler - * - * @param eventName - The event to listen for - * @param handler - The event handler - */ - onEvent: ( - eventName: string, - handler: (e: Event | ErrorEvent) => void, - ) => void; - - /** - * Set a beforeExit/beforeUnload event handler for the runtime - * @param cb - The event handler - */ - onExit(cb: () => void): void; - - /** - * Stop execution - * - * @param code - */ - exit(code?: number): void; -} +const decoder = new TextDecoder(); +let scrollRuntime: IRuntime | null = null; // ---------------------------------------------------------------------------- // Misc runtime functions // ---------------------------------------------------------------------------- -const decoder = new TextDecoder(); -let scrollRuntime: IRuntime | null = null; /** * Convert input from ANSI escape sequences into a form @@ -194,9 +67,12 @@ export function readKey(raw: Uint8Array): string { } } +/** + * Append information to the scroll.log logfile + */ export function logToFile(s: unknown) { importForRuntime('file_io').then((f) => { - const raw = (typeof s === 'string') ? s : JSON.stringify(s, null, 2); + const raw = typeof s === 'string' ? s : JSON.stringify(s, null, 2); const output = raw + '\n'; f.appendFile('./scroll.log', output).then(noop); @@ -210,6 +86,7 @@ export function logToFile(s: unknown) { export function die(s: string | Error): void { getTermios().then((t) => { t.disableRawMode(); + t.cleanup(); console.error(s); getRuntime().then((r) => r.exit()); @@ -276,7 +153,8 @@ export const importForRuntime = async (path: string) => { const suffix = '.ts'; const base = `../${runtime}/`; - const pathParts = path.split('/') + const pathParts = path + .split('/') .filter((part) => part !== '' && part !== '.' && part !== suffix) .map((part) => part.replace(suffix, '')); diff --git a/src/common/runtime_types.ts b/src/common/runtime_types.ts new file mode 100644 index 0000000..ce487df --- /dev/null +++ b/src/common/runtime_types.ts @@ -0,0 +1,134 @@ +import { RunTimeType } from './runtime.ts'; + +// ---------------------------------------------------------------------------- +// Runtime adapter interfaces +// ---------------------------------------------------------------------------- + +/** + * The size of terminal in rows and columns + */ +export interface ITerminalSize { + rows: number; + 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; +} + +/** + * The common interface for runtime adapters + */ +export interface IRuntime { + /** + * The name of the runtime + */ + name: RunTimeType; + + /** + * Runtime-specific terminal functionality + */ + term: { + /** + * The arguments passed to the program on launch + */ + argv: string[]; + + /** + * The generator function returning chunks of input from the stdin stream + */ + inputLoop(): AsyncGenerator; + + /** + * Get the size of the terminal + */ + getTerminalSize(): Promise; + + /** + * Get the current chunk of input, if it exists + */ + readStdin(): Promise; + + /** + * Get the raw chunk of input + */ + readStdinRaw(): Promise; + + /** + * Pipe a string to stdout + */ + writeStdout(s: string): Promise; + }; + + /** + * Runtime-specific file system io + */ + file: { + openFile(path: string): Promise; + openFileSync(path: string): string; + appendFile(path: string, contents: string): Promise; + appendFileSync(path: string, contents: string): void; + saveFile(path: string, contents: string): Promise; + }; + + /** + * Set up an event handler + * + * @param eventName - The event to listen for + * @param handler - The event handler + */ + onEvent: ( + eventName: string, + handler: (e: Event | ErrorEvent) => void, + ) => void; + + /** + * Set a beforeExit/beforeUnload event handler for the runtime + * @param cb - The event handler + */ + onExit(cb: () => void): void; + + /** + * Stop execution + * + * @param code + */ + exit(code?: number): void; +} + +/** + * Runtime-specific terminal functionality + */ +export type ITerminal = IRuntime['term']; + +/** + * Runtime-specific file handling + */ +export type IFileIO = IRuntime['file']; diff --git a/src/common/termios.ts b/src/common/termios.ts index 873ed39..da3bd59 100644 --- a/src/common/termios.ts +++ b/src/common/termios.ts @@ -42,7 +42,7 @@ class Termios { * The pointer to the termios struct * @private */ - #ptr; + #ptr: unknown; constructor(ffi: IFFI) { this.#ffi = ffi; @@ -80,7 +80,7 @@ class Termios { // @ts-ignore: bad type definition this.#cookedTermios = new Uint8Array(this.#termios, 0, 60); - // Update termios struct with (most of the) raw settings + // Update termios struct with the raw settings this.#ffi.cfmakeraw(this.#ptr); // Actually set the new termios settings diff --git a/src/common/utils.ts b/src/common/utils.ts index ce02b63..c75f779 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -22,6 +22,9 @@ export function arrayInsert( return [...arr.slice(0, at), ...insert, ...arr.slice(at)]; } +/** + * An empty function + */ export const noop = () => {}; // ---------------------------------------------------------------------------- diff --git a/src/deno/mod.ts b/src/deno/mod.ts index 48d33f4..d4e40a3 100644 --- a/src/deno/mod.ts +++ b/src/deno/mod.ts @@ -13,7 +13,6 @@ const DenoRuntime: IRuntime = { globalThis.addEventListener(eventName, handler), onExit: (cb: () => void): void => { globalThis.addEventListener('onbeforeunload', cb); - globalThis.onbeforeunload = cb; }, exit: (code?: number) => Deno.exit(code), }; diff --git a/src/deno/terminal_io.ts b/src/deno/terminal_io.ts index 5e73245..f517eec 100644 --- a/src/deno/terminal_io.ts +++ b/src/deno/terminal_io.ts @@ -3,7 +3,7 @@ import { ITerminal, ITerminalSize, readKey } from '../common/runtime.ts'; const DenoTerminalIO: ITerminal = { argv: Deno.args, inputLoop: async function* (): AsyncGenerator { - yield await DenoTerminalIO.readStdinRaw() ?? new Uint8Array(0); + yield (await DenoTerminalIO.readStdinRaw()) ?? new Uint8Array(0); return null; }, @@ -26,7 +26,7 @@ const DenoTerminalIO: ITerminal = { reader.releaseLock(); return res ?? null; }, - writeStdout: async function write(s: string): Promise { + writeStdout: async function (s: string): Promise { const buffer: Uint8Array = new TextEncoder().encode(s); const stdout: WritableStream = Deno.stdout.writable;