Fix crash on macOS

This commit is contained in:
Timothy Warren 2023-11-29 14:55:57 -05:00
parent 8ee17f4eef
commit 9711202c3b
6 changed files with 155 additions and 141 deletions

View File

@ -2,151 +2,24 @@ import { getTermios } from './termios.ts';
import { ctrlKey, noop } from './utils.ts'; import { ctrlKey, noop } from './utils.ts';
import { ITestBase } from './types.ts'; import { ITestBase } from './types.ts';
import { KeyCommand } from './ansi.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 { export enum RunTimeType {
Bun = 'bun', Bun = 'bun',
Deno = 'deno', Deno = 'deno',
Unknown = 'common', Unknown = 'common',
} }
// ---------------------------------------------------------------------------- const decoder = new TextDecoder();
// Runtime adapter interfaces let scrollRuntime: IRuntime | null = null;
// ----------------------------------------------------------------------------
/**
* 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<Uint8Array, null>;
/**
* Get the size of the terminal
*/
getTerminalSize(): Promise<ITerminalSize>;
/**
* Get the current chunk of input, if it exists
*/
readStdin(): Promise<string | null>;
/**
* Get the raw chunk of input
*/
readStdinRaw(): Promise<Uint8Array | null>;
/**
* Pipe a string to stdout
*/
writeStdout(s: string): Promise<void>;
}
/**
* Runtime-specific file handling
*/
export interface IFileIO {
openFile(path: string): Promise<string>;
openFileSync(path: string): string;
appendFile(path: string, contents: string): Promise<void>;
appendFileSync(path: string, contents: string): void;
saveFile(path: string, contents: string): Promise<void>;
}
/**
* 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;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Misc runtime functions // Misc runtime functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
const decoder = new TextDecoder();
let scrollRuntime: IRuntime | null = null;
/** /**
* Convert input from ANSI escape sequences into a form * 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) { export function logToFile(s: unknown) {
importForRuntime('file_io').then((f) => { 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'; const output = raw + '\n';
f.appendFile('./scroll.log', output).then(noop); f.appendFile('./scroll.log', output).then(noop);
@ -210,6 +86,7 @@ export function logToFile(s: unknown) {
export function die(s: string | Error): void { export function die(s: string | Error): void {
getTermios().then((t) => { getTermios().then((t) => {
t.disableRawMode(); t.disableRawMode();
t.cleanup();
console.error(s); console.error(s);
getRuntime().then((r) => r.exit()); getRuntime().then((r) => r.exit());
@ -276,7 +153,8 @@ export const importForRuntime = async (path: string) => {
const suffix = '.ts'; const suffix = '.ts';
const base = `../${runtime}/`; const base = `../${runtime}/`;
const pathParts = path.split('/') const pathParts = path
.split('/')
.filter((part) => part !== '' && part !== '.' && part !== suffix) .filter((part) => part !== '' && part !== '.' && part !== suffix)
.map((part) => part.replace(suffix, '')); .map((part) => part.replace(suffix, ''));

134
src/common/runtime_types.ts Normal file
View File

@ -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<Uint8Array, null>;
/**
* Get the size of the terminal
*/
getTerminalSize(): Promise<ITerminalSize>;
/**
* Get the current chunk of input, if it exists
*/
readStdin(): Promise<string | null>;
/**
* Get the raw chunk of input
*/
readStdinRaw(): Promise<Uint8Array | null>;
/**
* Pipe a string to stdout
*/
writeStdout(s: string): Promise<void>;
};
/**
* Runtime-specific file system io
*/
file: {
openFile(path: string): Promise<string>;
openFileSync(path: string): string;
appendFile(path: string, contents: string): Promise<void>;
appendFileSync(path: string, contents: string): void;
saveFile(path: string, contents: string): Promise<void>;
};
/**
* 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'];

View File

@ -42,7 +42,7 @@ class Termios {
* The pointer to the termios struct * The pointer to the termios struct
* @private * @private
*/ */
#ptr; #ptr: unknown;
constructor(ffi: IFFI) { constructor(ffi: IFFI) {
this.#ffi = ffi; this.#ffi = ffi;
@ -80,7 +80,7 @@ class Termios {
// @ts-ignore: bad type definition // @ts-ignore: bad type definition
this.#cookedTermios = new Uint8Array(this.#termios, 0, 60); 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); this.#ffi.cfmakeraw(this.#ptr);
// Actually set the new termios settings // Actually set the new termios settings

View File

@ -22,6 +22,9 @@ export function arrayInsert<T>(
return [...arr.slice(0, at), ...insert, ...arr.slice(at)]; return [...arr.slice(0, at), ...insert, ...arr.slice(at)];
} }
/**
* An empty function
*/
export const noop = () => {}; export const noop = () => {};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -13,7 +13,6 @@ const DenoRuntime: IRuntime = {
globalThis.addEventListener(eventName, handler), globalThis.addEventListener(eventName, handler),
onExit: (cb: () => void): void => { onExit: (cb: () => void): void => {
globalThis.addEventListener('onbeforeunload', cb); globalThis.addEventListener('onbeforeunload', cb);
globalThis.onbeforeunload = cb;
}, },
exit: (code?: number) => Deno.exit(code), exit: (code?: number) => Deno.exit(code),
}; };

View File

@ -3,7 +3,7 @@ import { ITerminal, ITerminalSize, readKey } from '../common/runtime.ts';
const DenoTerminalIO: ITerminal = { const DenoTerminalIO: ITerminal = {
argv: Deno.args, argv: Deno.args,
inputLoop: async function* (): AsyncGenerator<Uint8Array, null> { inputLoop: async function* (): AsyncGenerator<Uint8Array, null> {
yield await DenoTerminalIO.readStdinRaw() ?? new Uint8Array(0); yield (await DenoTerminalIO.readStdinRaw()) ?? new Uint8Array(0);
return null; return null;
}, },
@ -26,7 +26,7 @@ const DenoTerminalIO: ITerminal = {
reader.releaseLock(); reader.releaseLock();
return res ?? null; return res ?? null;
}, },
writeStdout: async function write(s: string): Promise<void> { writeStdout: async function (s: string): Promise<void> {
const buffer: Uint8Array = new TextEncoder().encode(s); const buffer: Uint8Array = new TextEncoder().encode(s);
const stdout: WritableStream<Uint8Array> = Deno.stdout.writable; const stdout: WritableStream<Uint8Array> = Deno.stdout.writable;