diff --git a/justfile b/justfile index d9a3630..98432e3 100644 --- a/justfile +++ b/justfile @@ -25,6 +25,7 @@ clean: rm -rf docs rm -f scroll.log rm -f scroll.err + rm -f tsconfig.tsbuildinfo ######################################################################################################################## # Bun-specific commands diff --git a/src/bun/file_io.ts b/src/bun/file_io.ts index d1e7ae8..20d8297 100644 --- a/src/bun/file_io.ts +++ b/src/bun/file_io.ts @@ -1,6 +1,4 @@ import { IFileIO } from '../common/runtime.ts'; - -import { appendFileSync, readFileSync } from 'node:fs'; import { appendFile } from 'node:fs/promises'; const BunFileIO: IFileIO = { @@ -8,15 +6,9 @@ const BunFileIO: IFileIO = { const file = await Bun.file(path); return await file.text(); }, - openFileSync: (path: string): string => { - return readFileSync(path).toString(); - }, appendFile: async function (path: string, contents: string): Promise { return await appendFile(path, contents); }, - appendFileSync: function (path: string, contents: string) { - return appendFileSync(path, contents); - }, saveFile: async function (path: string, contents: string): Promise { await Bun.write(path, contents); return; diff --git a/src/bun/mod.ts b/src/bun/mod.ts index 7ecd658..32db2dc 100644 --- a/src/bun/mod.ts +++ b/src/bun/mod.ts @@ -2,7 +2,7 @@ * The main entrypoint when using Bun as the runtime */ -import { IRuntime, RunTimeType } from '../common/mod.ts'; +import { IRuntime, RunTimeType } from '../common/runtime.ts'; import BunTerminalIO from './terminal_io.ts'; import BunFileIO from './file_io.ts'; diff --git a/src/bun/terminal_io.ts b/src/bun/terminal_io.ts index 8fbb3d6..53ac6e9 100644 --- a/src/bun/terminal_io.ts +++ b/src/bun/terminal_io.ts @@ -2,12 +2,9 @@ * Wrap the runtime-specific hook into stdin */ import Ansi from '../common/ansi.ts'; -import { - defaultTerminalSize, - ITerminal, - ITerminalSize, - readKey, -} from '../common/mod.ts'; +import { defaultTerminalSize } from '../common/config.ts'; +import { readKey } from '../common/fns.ts'; +import { ITerminal, ITerminalSize } from '../common/types.ts'; const BunTerminalIO: ITerminal = { // Deno only returns arguments passed to the script, so diff --git a/src/bun/test_base.ts b/src/bun/test_base.ts index eb49c0e..8fc1efb 100644 --- a/src/bun/test_base.ts +++ b/src/bun/test_base.ts @@ -2,7 +2,7 @@ * Adapt the bun test interface to the shared testing interface */ import { describe, expect, test } from 'bun:test'; -import { ITestBase } from '../common/mod.ts'; +import { ITestBase } from '../common/types.ts'; export function testSuite(testObj: any) { Object.keys(testObj).forEach((group) => { diff --git a/src/common/all_test.ts b/src/common/all_test.ts index 7555a4c..c8d137e 100644 --- a/src/common/all_test.ts +++ b/src/common/all_test.ts @@ -1,12 +1,12 @@ -import { Ansi, KeyCommand } from './ansi.ts'; import Buffer from './buffer.ts'; import Document from './document.ts'; import Editor from './editor.ts'; import Row from './row.ts'; -import { getTestRunner, readKey } from './runtime.ts'; -import { defaultTerminalSize } from './termios.ts'; +import { Ansi, KeyCommand } from './ansi.ts'; +import { defaultTerminalSize } from './config.ts'; +import { getTestRunner } from './runtime.ts'; import { Position } from './types.ts'; -import * as Util from './utils.ts'; +import * as Util from './fns.ts'; const { assertEquals, @@ -21,61 +21,29 @@ const { const encoder = new TextEncoder(); const testKeyMap = (codes: string[], expected: string) => { codes.forEach((code) => { - assertEquals(readKey(encoder.encode(code)), expected); + assertEquals(Util.readKey(encoder.encode(code)), expected); }); }; testSuite({ - 'ANSI::ANSI utils': { - 'Ansi.moveCursor': () => { + 'ANSI utils': { + 'moveCursor()': () => { assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H'); }, - 'Ansi.moveCursorForward': () => { + 'moveCursorForward()': () => { assertEquals(Ansi.moveCursorForward(2), '\x1b[2C'); }, - 'Ansi.moveCursorDown': () => { + 'moveCursorDown()': () => { assertEquals(Ansi.moveCursorDown(7), '\x1b[7B'); }, }, - 'ANSI::readKey()': { - 'readKey passthrough': () => { - // Ignore unhandled escape sequences - assertEquals(readKey(encoder.encode('\x1b[]')), '\x1b[]'); - - // Pass explicitly mapped values right through - assertEquals( - readKey(encoder.encode(KeyCommand.ArrowUp)), - KeyCommand.ArrowUp, - ); - assertEquals(readKey(encoder.encode(KeyCommand.Home)), KeyCommand.Home); - assertEquals( - readKey(encoder.encode(KeyCommand.Delete)), - KeyCommand.Delete, - ); - - // And pass through whatever else - assertEquals(readKey(encoder.encode('foobaz')), 'foobaz'); - }, - 'readKey Esc': () => - testKeyMap(['\x1b', Util.ctrlKey('l')], KeyCommand.Escape), - 'readKey Backspace': () => - testKeyMap( - [Util.ctrlKey('h'), '\x7f'], - KeyCommand.Backspace, - ), - 'readKey Home': () => - testKeyMap(['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'], KeyCommand.Home), - 'readKey End': () => - testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], KeyCommand.End), - 'readKey Enter': () => testKeyMap(['\n', '\r', '\v'], KeyCommand.Enter), - }, Buffer: { - 'Buffer exists': () => { + 'new Buffer': () => { const b = new Buffer(); assertInstanceOf(b, Buffer); assertEquals(b.strlen(), 0); }, - 'Buffer.appendLine': () => { + '.appendLine': () => { const b = new Buffer(); // Carriage return and line feed @@ -88,7 +56,7 @@ testSuite({ b.appendLine('foo'); assertEquals(b.strlen(), 5); }, - 'Buffer.append': () => { + '.append': () => { const b = new Buffer(); b.append('foobar'); @@ -98,7 +66,7 @@ testSuite({ b.append('foobar', 3); assertEquals(b.strlen(), 3); }, - 'Buffer.flush': async () => { + '.flush': async () => { const b = new Buffer(); b.appendLine('foobarbaz' + Ansi.ClearLine); assertEquals(b.strlen(), 14); @@ -109,20 +77,20 @@ testSuite({ }, }, Document: { - 'Document.empty': () => { + '.default': () => { const doc = Document.default(); assertEquals(doc.numRows, 0); assertTrue(doc.isEmpty()); assertEquals(doc.row(0), null); }, - 'Document.insertRow': () => { + '.insertRow': () => { const doc = Document.default(); doc.insertRow(undefined, 'foobar'); assertEquals(doc.numRows, 1); assertFalse(doc.isEmpty()); assertInstanceOf(doc.row(0), Row); }, - 'Document.insert': () => { + '.insert': () => { const doc = Document.default(); assertFalse(doc.dirty); doc.insert(Position.at(0, 0), 'foobar'); @@ -145,7 +113,7 @@ testSuite({ assertEquals(doc.numRows, 2); assertTrue(doc.dirty); }, - 'Document.delete': () => { + '.delete': () => { const doc = Document.default(); doc.insert(Position.default(), 'foobar'); doc.delete(Position.at(3, 0)); @@ -159,23 +127,23 @@ testSuite({ }, }, Position: { - 'default': () => { + '.default': () => { const p = Position.default(); assertEquals(p.x, 0); assertEquals(p.y, 0); }, - 'at': () => { + '.at': () => { const p = Position.at(5, 7); assertEquals(p.x, 5); assertEquals(p.y, 7); }, }, Row: { - 'Row.default': () => { + '.default': () => { const row = Row.default(); assertEquals(row.toString(), ''); }, - 'Row.from': () => { + '.from': () => { // From string const row = Row.from('xyz'); assertEquals(row.toString(), 'xyz'); @@ -187,7 +155,7 @@ testSuite({ assertEquals(Row.from(['๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น']).toString(), '๐Ÿ˜บ๐Ÿ˜ธ๐Ÿ˜น'); }, }, - 'Util misc fns': { + 'fns': { 'arrayInsert() strings': () => { const a = ['๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น']; const b = Util.arrayInsert(a, 1, 'x'); @@ -224,8 +192,6 @@ testSuite({ assertEquals(Util.maxAdd(99, 99, 75), 75); assertEquals(Util.maxAdd(25, 74, 101), 99); }, - }, - 'Util string fns': { 'ord()': () => { // Invalid output assertEquals(Util.ord(''), 256); @@ -236,7 +202,7 @@ testSuite({ 'chars() properly splits strings into unicode characters': () => { assertEquals(Util.chars('๐Ÿ˜บ๐Ÿ˜ธ๐Ÿ˜น'), ['๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜น']); }, - 'ctrl_key()': () => { + 'ctrlKey()': () => { const ctrl_a = Util.ctrlKey('a'); assertTrue(Util.isControl(ctrl_a)); assertEquals(ctrl_a, String.fromCodePoint(0x01)); @@ -245,12 +211,12 @@ testSuite({ assertFalse(Util.isControl(invalid)); assertEquals(invalid, '๐Ÿ˜บ'); }, - 'is_ascii()': () => { + 'isAscii()': () => { assertTrue(Util.isAscii('asjyverkjhsdf1928374')); assertFalse(Util.isAscii('๐Ÿ˜บacalskjsdf')); assertFalse(Util.isAscii('ab๐Ÿ˜บac')); }, - 'is_control()': () => { + 'isControl()': () => { assertFalse(Util.isControl('abc')); assertTrue(Util.isControl(String.fromCodePoint(0x01))); assertFalse(Util.isControl('๐Ÿ˜บ')); @@ -277,4 +243,42 @@ testSuite({ assertEquals(Util.truncate('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ', 5), '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง'); }, }, + 'readKey()': { + 'empty input': () => { + assertEquals(Util.readKey(new Uint8Array(0)), ''); + }, + 'passthrough': () => { + // Ignore unhandled escape sequences + assertEquals(Util.readKey(encoder.encode('\x1b[]')), '\x1b[]'); + + // Pass explicitly mapped values right through + assertEquals( + Util.readKey(encoder.encode(KeyCommand.ArrowUp)), + KeyCommand.ArrowUp, + ); + assertEquals( + Util.readKey(encoder.encode(KeyCommand.Home)), + KeyCommand.Home, + ); + assertEquals( + Util.readKey(encoder.encode(KeyCommand.Delete)), + KeyCommand.Delete, + ); + + // And pass through whatever else + assertEquals(Util.readKey(encoder.encode('foobaz')), 'foobaz'); + }, + + 'Esc': () => testKeyMap(['\x1b', Util.ctrlKey('l')], KeyCommand.Escape), + 'Backspace': () => + testKeyMap( + [Util.ctrlKey('h'), '\x7f'], + KeyCommand.Backspace, + ), + 'Home': () => + testKeyMap(['\x1b[1~', '\x1b[7~', '\x1b[H', '\x1bOH'], KeyCommand.Home), + 'End': () => + testKeyMap(['\x1b[4~', '\x1b[8~', '\x1b[F', '\x1bOF'], KeyCommand.End), + 'Enter': () => testKeyMap(['\n', '\r', '\v'], KeyCommand.Enter), + }, }); diff --git a/src/common/buffer.ts b/src/common/buffer.ts index 5ead78b..ec15810 100644 --- a/src/common/buffer.ts +++ b/src/common/buffer.ts @@ -1,4 +1,4 @@ -import { strlen, truncate } from './utils.ts'; +import { strlen, truncate } from './fns.ts'; import { getRuntime } from './runtime.ts'; class Buffer { diff --git a/src/common/config.ts b/src/common/config.ts new file mode 100644 index 0000000..8018d55 --- /dev/null +++ b/src/common/config.ts @@ -0,0 +1,13 @@ +import { ITerminalSize } from './types.ts'; + +export const SCROLL_VERSION = '0.0.1'; +export const SCROLL_QUIT_TIMES = 3; +export const SCROLL_TAB_SIZE = 4; + +export const SCROLL_LOG_FILE = './scroll.log'; +export const SCROLL_ERR_FILE = './scroll.err'; + +export const defaultTerminalSize: ITerminalSize = { + rows: 24, + cols: 80, +}; diff --git a/src/common/document.ts b/src/common/document.ts index 3cc80f1..26c9929 100644 --- a/src/common/document.ts +++ b/src/common/document.ts @@ -1,6 +1,7 @@ import Row from './row.ts'; +import { arrayInsert } from './fns.ts'; import { getRuntime } from './runtime.ts'; -import { arrayInsert, Position } from './mod.ts'; +import { Position } from './types.ts'; export class Document { #rows: Row[]; diff --git a/src/common/editor.ts b/src/common/editor.ts index ca0d563..d5bdbb1 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -1,18 +1,19 @@ import Ansi, { KeyCommand } from './ansi.ts'; import Buffer from './buffer.ts'; import Document from './document.ts'; -import { - getRuntime, - isControl, - ITerminalSize, - logToFile, - Position, - readKey, - SCROLL_QUIT_TIMES, - SCROLL_VERSION, -} from './mod.ts'; import Row from './row.ts'; -import { ctrlKey, maxAdd, posSub, truncate } from './utils.ts'; + +import { SCROLL_QUIT_TIMES, SCROLL_VERSION } from './config.ts'; +import { + ctrlKey, + isControl, + maxAdd, + posSub, + readKey, + truncate, +} from './fns.ts'; +import { getRuntime, logToFile } from './runtime.ts'; +import { ITerminalSize, Position } from './types.ts'; class Editor { /** diff --git a/src/common/utils.ts b/src/common/fns.ts similarity index 75% rename from src/common/utils.ts rename to src/common/fns.ts index c75f779..b49b412 100644 --- a/src/common/utils.ts +++ b/src/common/fns.ts @@ -1,7 +1,66 @@ +import { KeyCommand } from './ansi.ts'; + +const decoder = new TextDecoder(); + // ---------------------------------------------------------------------------- // Misc // ---------------------------------------------------------------------------- +/** + * An empty function + */ +export const noop = () => {}; + +/** + * 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; + } +} + +// ---------------------------------------------------------------------------- +// Array manipulation +// ---------------------------------------------------------------------------- + /** * Insert a value into an array at the specified index * @param arr - the array @@ -22,11 +81,6 @@ export function arrayInsert( return [...arr.slice(0, at), ...insert, ...arr.slice(at)]; } -/** - * An empty function - */ -export const noop = () => {}; - // ---------------------------------------------------------------------------- // Math // ---------------------------------------------------------------------------- diff --git a/src/common/main.ts b/src/common/main.ts index 83ddc38..71dc986 100644 --- a/src/common/main.ts +++ b/src/common/main.ts @@ -1,10 +1,11 @@ -import { getRuntime, readKey } from './runtime.ts'; +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() { const rt = await getRuntime(); - const { file, term } = rt; + const { term } = rt; // Setup raw mode, and tear down on error or normal exit const t = await getTermios(); @@ -17,7 +18,7 @@ export async function main() { // Setup error handler to log to file rt.onEvent('error', (error) => { t.disableRawMode(); - file.appendFileSync('./scroll.err', JSON.stringify(error, null, 2)); + logError(JSON.stringify(error, null, 2)); }); const terminalSize = await term.getTerminalSize(); diff --git a/src/common/mod.ts b/src/common/mod.ts deleted file mode 100644 index 47e5017..0000000 --- a/src/common/mod.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './editor.ts'; -export * from './runtime.ts'; -export * from './termios.ts'; -export * from './types.ts'; -export * from './utils.ts'; - -export type * from './types.ts'; - -export const SCROLL_VERSION = '0.0.1'; -export const SCROLL_QUIT_TIMES = 3; -export const SCROLL_TAB_SIZE = 4; diff --git a/src/common/row.ts b/src/common/row.ts index 2a1333c..6bf9aaa 100644 --- a/src/common/row.ts +++ b/src/common/row.ts @@ -1,5 +1,5 @@ -import { arrayInsert, chars, SCROLL_TAB_SIZE } from './mod.ts'; -import * as Util from './utils.ts'; +import { SCROLL_TAB_SIZE } from './config.ts'; +import * as Util from './fns.ts'; /** * One row of text in the current document @@ -46,7 +46,7 @@ export class Row { } public append(s: string): void { - this.chars = this.chars.concat(chars(s)); + this.chars = this.chars.concat(Util.chars(s)); this.updateRender(); } @@ -55,7 +55,7 @@ export class Row { if (at >= this.size) { this.chars = this.chars.concat(newSlice); } else { - this.chars = arrayInsert(this.chars, at + 1, newSlice); + this.chars = Util.arrayInsert(this.chars, at + 1, newSlice); } } @@ -98,7 +98,7 @@ export class Row { ' '.repeat(SCROLL_TAB_SIZE), ); - this.render = chars(newString); + this.render = Util.chars(newString); } } diff --git a/src/common/runtime.ts b/src/common/runtime.ts index d2d2e4f..0299104 100644 --- a/src/common/runtime.ts +++ b/src/common/runtime.ts @@ -1,9 +1,9 @@ +import { IRuntime, ITestBase } from './types.ts'; 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'; +import { noop } from './fns.ts'; +import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts'; + +export type { IFFI, IFileIO, IRuntime, ITerminal } from './types.ts'; /** * Which Typescript runtime is currently being used @@ -14,68 +14,33 @@ export enum RunTimeType { 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) => { +export function logToFile(s: unknown): void { + getRuntime().then(({ file }) => { const raw = typeof s === 'string' ? s : JSON.stringify(s, null, 2); const output = raw + '\n'; - f.appendFile('./scroll.log', output).then(noop); + file.appendFile(SCROLL_LOG_FILE, output).then(noop); + }); +} + +/** + * Append information to the scroll.err logfile + */ +export function logError(s: unknown): void { + getRuntime().then(({ file }) => { + const raw = typeof s === 'string' ? s : JSON.stringify(s, null, 2); + const output = raw + '\n'; + + file.appendFile(SCROLL_ERR_FILE, output).then(noop); }); } @@ -84,11 +49,11 @@ export function logToFile(s: unknown) { * @param s */ export function die(s: string | Error): void { + logError(s); getTermios().then((t) => { t.disableRawMode(); t.cleanup(); console.error(s); - getRuntime().then((r) => r.exit()); }); } diff --git a/src/common/runtime_types.ts b/src/common/runtime_types.ts deleted file mode 100644 index ce487df..0000000 --- a/src/common/runtime_types.ts +++ /dev/null @@ -1,134 +0,0 @@ -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 da3bd59..e6329cd 100644 --- a/src/common/termios.ts +++ b/src/common/termios.ts @@ -1,15 +1,10 @@ -import { die, IFFI, importForRuntime, ITerminalSize } from './mod.ts'; +import { die, IFFI, importForRuntime } from './runtime.ts'; export const STDIN_FILENO = 0; export const TCSANOW = 0; export const TERMIOS_SIZE = 60; -export const defaultTerminalSize: ITerminalSize = { - rows: 24, - cols: 80, -}; - /** * Implementation to toggle raw mode */ diff --git a/src/common/types.ts b/src/common/types.ts index 9bfa800..85ad8bc 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,3 +1,136 @@ +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; + appendFile(path: string, contents: string): Promise; + 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']; + // ---------------------------------------------------------------------------- // General types // ---------------------------------------------------------------------------- diff --git a/src/deno/file_io.ts b/src/deno/file_io.ts index f424e91..c81194d 100644 --- a/src/deno/file_io.ts +++ b/src/deno/file_io.ts @@ -6,11 +6,6 @@ const DenoFileIO: IFileIO = { const data = await Deno.readFile(path); return decoder.decode(data); }, - openFileSync: function (path: string): string { - const decoder = new TextDecoder('utf-8'); - const data = Deno.readFileSync(path); - return decoder.decode(data); - }, appendFile: async function (path: string, contents: string): Promise { const file = await Deno.open(path, { write: true, @@ -23,10 +18,6 @@ const DenoFileIO: IFileIO = { await writer.write(encoder.encode(contents)); file.close(); }, - appendFileSync: function (path: string, contents: string) { - const encoder = new TextEncoder(); - Deno.writeFileSync(path, encoder.encode(contents)); - }, saveFile: async function (path: string, contents: string): Promise { return await Deno.writeTextFile(path, contents); }, diff --git a/src/deno/mod.ts b/src/deno/mod.ts index d4e40a3..2ae59b4 100644 --- a/src/deno/mod.ts +++ b/src/deno/mod.ts @@ -1,7 +1,7 @@ /** * The main entrypoint when using Deno as the runtime */ -import { IRuntime, RunTimeType } from '../common/mod.ts'; +import { IRuntime, RunTimeType } from '../common/runtime.ts'; import DenoTerminalIO from './terminal_io.ts'; import DenoFileIO from './file_io.ts'; diff --git a/src/deno/terminal_io.ts b/src/deno/terminal_io.ts index f517eec..7032393 100644 --- a/src/deno/terminal_io.ts +++ b/src/deno/terminal_io.ts @@ -1,4 +1,5 @@ -import { ITerminal, ITerminalSize, readKey } from '../common/runtime.ts'; +import { readKey } from '../common/fns.ts'; +import { ITerminal, ITerminalSize } from '../common/types.ts'; const DenoTerminalIO: ITerminal = { argv: Deno.args, diff --git a/src/deno/test_base.ts b/src/deno/test_base.ts index 9cdabf8..b704638 100644 --- a/src/deno/test_base.ts +++ b/src/deno/test_base.ts @@ -1,4 +1,4 @@ -import { ITestBase } from '../common/mod.ts'; +import { ITestBase } from '../common/types.ts'; import { stdAssert } from './deps.ts'; const { assertEquals,