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', } const decoder = new TextDecoder(); let scrollRuntime: IRuntime | null = null; // ---------------------------------------------------------------------------- // Misc runtime functions // ---------------------------------------------------------------------------- /** * Convert input from ANSI escape sequences into a form * that can be more easily mapped to editor commands * * @param raw - the raw chunk of input */ export function readKey(raw: Uint8Array): string { if (raw.length === 0) { return ''; } const parsed = decoder.decode(raw); // Return the input if it's unambiguous if (parsed in KeyCommand) { return parsed; } // Some keycodes have multiple potential inputs switch (parsed) { case '\x1b[1~': case '\x1b[7~': case '\x1bOH': case '\x1b[H': return KeyCommand.Home; case '\x1b[4~': case '\x1b[8~': case '\x1bOF': case '\x1b[F': return KeyCommand.End; case '\n': case '\v': return KeyCommand.Enter; case ctrlKey('l'): return KeyCommand.Escape; case ctrlKey('h'): return KeyCommand.Backspace; default: return parsed; } } /** * 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 output = raw + '\n'; f.appendFile('./scroll.log', output).then(noop); }); } /** * Kill program, displaying an error message * @param s */ export function die(s: string | Error): void { getTermios().then((t) => { t.disableRawMode(); t.cleanup(); console.error(s); getRuntime().then((r) => r.exit()); }); } /** * Determine which Typescript runtime we are operating under */ export function runtimeType(): RunTimeType { let runtime = RunTimeType.Unknown; if ('Deno' in globalThis) { runtime = RunTimeType.Deno; } if ('Bun' in globalThis) { runtime = RunTimeType.Bun; } return runtime; } /** * Get the adapter object for the current Runtime */ export async function getRuntime(): Promise { if (scrollRuntime === null) { const runtime = runtimeType(); const path = `../${runtime}/mod.ts`; const pkg = await import(path); if ('default' in pkg) { scrollRuntime = pkg.default; } } return Promise.resolve(scrollRuntime!); } /** * Get the common test interface object */ export async function getTestRunner(): Promise { const runtime = runtimeType(); const path = `../${runtime}/test_base.ts`; const pkg = await import(path); if ('default' in pkg) { return pkg.default; } return pkg; } /** * Import a runtime-specific module * * e.g. to load "src/bun/mod.ts", if the runtime is bun, * you can use like so `await importForRuntime('index')`; * * @param path - the path within the runtime module */ export const importForRuntime = async (path: string) => { const runtime = runtimeType(); const suffix = '.ts'; const base = `../${runtime}/`; const pathParts = path .split('/') .filter((part) => part !== '' && part !== '.' && part !== suffix) .map((part) => part.replace(suffix, '')); const cleanedPath = pathParts.join('/'); const importPath = base + cleanedPath + suffix; const pkg = await import(importPath); if ('default' in pkg) { return pkg.default; } return pkg; };