Refactor everything again

This commit is contained in:
Timothy Warren 2023-11-29 16:09:58 -05:00
parent e9ce780c1d
commit 02be6a133c
22 changed files with 324 additions and 320 deletions

View File

@ -25,6 +25,7 @@ clean:
rm -rf docs rm -rf docs
rm -f scroll.log rm -f scroll.log
rm -f scroll.err rm -f scroll.err
rm -f tsconfig.tsbuildinfo
######################################################################################################################## ########################################################################################################################
# Bun-specific commands # Bun-specific commands

View File

@ -1,6 +1,4 @@
import { IFileIO } from '../common/runtime.ts'; import { IFileIO } from '../common/runtime.ts';
import { appendFileSync, readFileSync } from 'node:fs';
import { appendFile } from 'node:fs/promises'; import { appendFile } from 'node:fs/promises';
const BunFileIO: IFileIO = { const BunFileIO: IFileIO = {
@ -8,15 +6,9 @@ const BunFileIO: IFileIO = {
const file = await Bun.file(path); const file = await Bun.file(path);
return await file.text(); return await file.text();
}, },
openFileSync: (path: string): string => {
return readFileSync(path).toString();
},
appendFile: async function (path: string, contents: string): Promise<void> { appendFile: async function (path: string, contents: string): Promise<void> {
return await appendFile(path, contents); return await appendFile(path, contents);
}, },
appendFileSync: function (path: string, contents: string) {
return appendFileSync(path, contents);
},
saveFile: async function (path: string, contents: string): Promise<void> { saveFile: async function (path: string, contents: string): Promise<void> {
await Bun.write(path, contents); await Bun.write(path, contents);
return; return;

View File

@ -2,7 +2,7 @@
* The main entrypoint when using Bun as the runtime * 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 BunTerminalIO from './terminal_io.ts';
import BunFileIO from './file_io.ts'; import BunFileIO from './file_io.ts';

View File

@ -2,12 +2,9 @@
* Wrap the runtime-specific hook into stdin * Wrap the runtime-specific hook into stdin
*/ */
import Ansi from '../common/ansi.ts'; import Ansi from '../common/ansi.ts';
import { import { defaultTerminalSize } from '../common/config.ts';
defaultTerminalSize, import { readKey } from '../common/fns.ts';
ITerminal, import { ITerminal, ITerminalSize } from '../common/types.ts';
ITerminalSize,
readKey,
} from '../common/mod.ts';
const BunTerminalIO: ITerminal = { const BunTerminalIO: ITerminal = {
// Deno only returns arguments passed to the script, so // Deno only returns arguments passed to the script, so

View File

@ -2,7 +2,7 @@
* Adapt the bun test interface to the shared testing interface * Adapt the bun test interface to the shared testing interface
*/ */
import { describe, expect, test } from 'bun:test'; import { describe, expect, test } from 'bun:test';
import { ITestBase } from '../common/mod.ts'; import { ITestBase } from '../common/types.ts';
export function testSuite(testObj: any) { export function testSuite(testObj: any) {
Object.keys(testObj).forEach((group) => { Object.keys(testObj).forEach((group) => {

View File

@ -1,12 +1,12 @@
import { Ansi, KeyCommand } from './ansi.ts';
import Buffer from './buffer.ts'; import Buffer from './buffer.ts';
import Document from './document.ts'; import Document from './document.ts';
import Editor from './editor.ts'; import Editor from './editor.ts';
import Row from './row.ts'; import Row from './row.ts';
import { getTestRunner, readKey } from './runtime.ts'; import { Ansi, KeyCommand } from './ansi.ts';
import { defaultTerminalSize } from './termios.ts'; import { defaultTerminalSize } from './config.ts';
import { getTestRunner } from './runtime.ts';
import { Position } from './types.ts'; import { Position } from './types.ts';
import * as Util from './utils.ts'; import * as Util from './fns.ts';
const { const {
assertEquals, assertEquals,
@ -21,61 +21,29 @@ const {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const testKeyMap = (codes: string[], expected: string) => { const testKeyMap = (codes: string[], expected: string) => {
codes.forEach((code) => { codes.forEach((code) => {
assertEquals(readKey(encoder.encode(code)), expected); assertEquals(Util.readKey(encoder.encode(code)), expected);
}); });
}; };
testSuite({ testSuite({
'ANSI::ANSI utils': { 'ANSI utils': {
'Ansi.moveCursor': () => { 'moveCursor()': () => {
assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H'); assertEquals(Ansi.moveCursor(1, 2), '\x1b[2;3H');
}, },
'Ansi.moveCursorForward': () => { 'moveCursorForward()': () => {
assertEquals(Ansi.moveCursorForward(2), '\x1b[2C'); assertEquals(Ansi.moveCursorForward(2), '\x1b[2C');
}, },
'Ansi.moveCursorDown': () => { 'moveCursorDown()': () => {
assertEquals(Ansi.moveCursorDown(7), '\x1b[7B'); 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: {
'Buffer exists': () => { 'new Buffer': () => {
const b = new Buffer(); const b = new Buffer();
assertInstanceOf(b, Buffer); assertInstanceOf(b, Buffer);
assertEquals(b.strlen(), 0); assertEquals(b.strlen(), 0);
}, },
'Buffer.appendLine': () => { '.appendLine': () => {
const b = new Buffer(); const b = new Buffer();
// Carriage return and line feed // Carriage return and line feed
@ -88,7 +56,7 @@ testSuite({
b.appendLine('foo'); b.appendLine('foo');
assertEquals(b.strlen(), 5); assertEquals(b.strlen(), 5);
}, },
'Buffer.append': () => { '.append': () => {
const b = new Buffer(); const b = new Buffer();
b.append('foobar'); b.append('foobar');
@ -98,7 +66,7 @@ testSuite({
b.append('foobar', 3); b.append('foobar', 3);
assertEquals(b.strlen(), 3); assertEquals(b.strlen(), 3);
}, },
'Buffer.flush': async () => { '.flush': async () => {
const b = new Buffer(); const b = new Buffer();
b.appendLine('foobarbaz' + Ansi.ClearLine); b.appendLine('foobarbaz' + Ansi.ClearLine);
assertEquals(b.strlen(), 14); assertEquals(b.strlen(), 14);
@ -109,20 +77,20 @@ testSuite({
}, },
}, },
Document: { Document: {
'Document.empty': () => { '.default': () => {
const doc = Document.default(); const doc = Document.default();
assertEquals(doc.numRows, 0); assertEquals(doc.numRows, 0);
assertTrue(doc.isEmpty()); assertTrue(doc.isEmpty());
assertEquals(doc.row(0), null); assertEquals(doc.row(0), null);
}, },
'Document.insertRow': () => { '.insertRow': () => {
const doc = Document.default(); const doc = Document.default();
doc.insertRow(undefined, 'foobar'); doc.insertRow(undefined, 'foobar');
assertEquals(doc.numRows, 1); assertEquals(doc.numRows, 1);
assertFalse(doc.isEmpty()); assertFalse(doc.isEmpty());
assertInstanceOf(doc.row(0), Row); assertInstanceOf(doc.row(0), Row);
}, },
'Document.insert': () => { '.insert': () => {
const doc = Document.default(); const doc = Document.default();
assertFalse(doc.dirty); assertFalse(doc.dirty);
doc.insert(Position.at(0, 0), 'foobar'); doc.insert(Position.at(0, 0), 'foobar');
@ -145,7 +113,7 @@ testSuite({
assertEquals(doc.numRows, 2); assertEquals(doc.numRows, 2);
assertTrue(doc.dirty); assertTrue(doc.dirty);
}, },
'Document.delete': () => { '.delete': () => {
const doc = Document.default(); const doc = Document.default();
doc.insert(Position.default(), 'foobar'); doc.insert(Position.default(), 'foobar');
doc.delete(Position.at(3, 0)); doc.delete(Position.at(3, 0));
@ -159,23 +127,23 @@ testSuite({
}, },
}, },
Position: { Position: {
'default': () => { '.default': () => {
const p = Position.default(); const p = Position.default();
assertEquals(p.x, 0); assertEquals(p.x, 0);
assertEquals(p.y, 0); assertEquals(p.y, 0);
}, },
'at': () => { '.at': () => {
const p = Position.at(5, 7); const p = Position.at(5, 7);
assertEquals(p.x, 5); assertEquals(p.x, 5);
assertEquals(p.y, 7); assertEquals(p.y, 7);
}, },
}, },
Row: { Row: {
'Row.default': () => { '.default': () => {
const row = Row.default(); const row = Row.default();
assertEquals(row.toString(), ''); assertEquals(row.toString(), '');
}, },
'Row.from': () => { '.from': () => {
// From string // From string
const row = Row.from('xyz'); const row = Row.from('xyz');
assertEquals(row.toString(), 'xyz'); assertEquals(row.toString(), 'xyz');
@ -187,7 +155,7 @@ testSuite({
assertEquals(Row.from(['😺', '😸', '😹']).toString(), '😺😸😹'); assertEquals(Row.from(['😺', '😸', '😹']).toString(), '😺😸😹');
}, },
}, },
'Util misc fns': { 'fns': {
'arrayInsert() strings': () => { 'arrayInsert() strings': () => {
const a = ['😺', '😸', '😹']; const a = ['😺', '😸', '😹'];
const b = Util.arrayInsert(a, 1, 'x'); const b = Util.arrayInsert(a, 1, 'x');
@ -224,8 +192,6 @@ testSuite({
assertEquals(Util.maxAdd(99, 99, 75), 75); assertEquals(Util.maxAdd(99, 99, 75), 75);
assertEquals(Util.maxAdd(25, 74, 101), 99); assertEquals(Util.maxAdd(25, 74, 101), 99);
}, },
},
'Util string fns': {
'ord()': () => { 'ord()': () => {
// Invalid output // Invalid output
assertEquals(Util.ord(''), 256); assertEquals(Util.ord(''), 256);
@ -236,7 +202,7 @@ testSuite({
'chars() properly splits strings into unicode characters': () => { 'chars() properly splits strings into unicode characters': () => {
assertEquals(Util.chars('😺😸😹'), ['😺', '😸', '😹']); assertEquals(Util.chars('😺😸😹'), ['😺', '😸', '😹']);
}, },
'ctrl_key()': () => { 'ctrlKey()': () => {
const ctrl_a = Util.ctrlKey('a'); const ctrl_a = Util.ctrlKey('a');
assertTrue(Util.isControl(ctrl_a)); assertTrue(Util.isControl(ctrl_a));
assertEquals(ctrl_a, String.fromCodePoint(0x01)); assertEquals(ctrl_a, String.fromCodePoint(0x01));
@ -245,12 +211,12 @@ testSuite({
assertFalse(Util.isControl(invalid)); assertFalse(Util.isControl(invalid));
assertEquals(invalid, '😺'); assertEquals(invalid, '😺');
}, },
'is_ascii()': () => { 'isAscii()': () => {
assertTrue(Util.isAscii('asjyverkjhsdf1928374')); assertTrue(Util.isAscii('asjyverkjhsdf1928374'));
assertFalse(Util.isAscii('😺acalskjsdf')); assertFalse(Util.isAscii('😺acalskjsdf'));
assertFalse(Util.isAscii('ab😺ac')); assertFalse(Util.isAscii('ab😺ac'));
}, },
'is_control()': () => { 'isControl()': () => {
assertFalse(Util.isControl('abc')); assertFalse(Util.isControl('abc'));
assertTrue(Util.isControl(String.fromCodePoint(0x01))); assertTrue(Util.isControl(String.fromCodePoint(0x01)));
assertFalse(Util.isControl('😺')); assertFalse(Util.isControl('😺'));
@ -277,4 +243,42 @@ testSuite({
assertEquals(Util.truncate('👨‍👩‍👧‍👦', 5), '👨‍👩‍👧'); 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),
},
}); });

View File

@ -1,4 +1,4 @@
import { strlen, truncate } from './utils.ts'; import { strlen, truncate } from './fns.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime } from './runtime.ts';
class Buffer { class Buffer {

13
src/common/config.ts Normal file
View File

@ -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,
};

View File

@ -1,6 +1,7 @@
import Row from './row.ts'; import Row from './row.ts';
import { arrayInsert } from './fns.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime } from './runtime.ts';
import { arrayInsert, Position } from './mod.ts'; import { Position } from './types.ts';
export class Document { export class Document {
#rows: Row[]; #rows: Row[];

View File

@ -1,18 +1,19 @@
import Ansi, { KeyCommand } from './ansi.ts'; import Ansi, { KeyCommand } from './ansi.ts';
import Buffer from './buffer.ts'; import Buffer from './buffer.ts';
import Document from './document.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 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 { class Editor {
/** /**

View File

@ -1,7 +1,66 @@
import { KeyCommand } from './ansi.ts';
const decoder = new TextDecoder();
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Misc // 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 * Insert a value into an array at the specified index
* @param arr - the array * @param arr - the array
@ -22,11 +81,6 @@ 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 = () => {};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Math // Math
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -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 { getTermios } from './termios.ts';
import Editor from './editor.ts'; import Editor from './editor.ts';
export async function main() { export async function main() {
const rt = await getRuntime(); const rt = await getRuntime();
const { file, term } = rt; const { term } = rt;
// Setup raw mode, and tear down on error or normal exit // Setup raw mode, and tear down on error or normal exit
const t = await getTermios(); const t = await getTermios();
@ -17,7 +18,7 @@ export async function main() {
// Setup error handler to log to file // Setup error handler to log to file
rt.onEvent('error', (error) => { rt.onEvent('error', (error) => {
t.disableRawMode(); t.disableRawMode();
file.appendFileSync('./scroll.err', JSON.stringify(error, null, 2)); logError(JSON.stringify(error, null, 2));
}); });
const terminalSize = await term.getTerminalSize(); const terminalSize = await term.getTerminalSize();

View File

@ -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;

View File

@ -1,5 +1,5 @@
import { arrayInsert, chars, SCROLL_TAB_SIZE } from './mod.ts'; import { SCROLL_TAB_SIZE } from './config.ts';
import * as Util from './utils.ts'; import * as Util from './fns.ts';
/** /**
* One row of text in the current document * One row of text in the current document
@ -46,7 +46,7 @@ export class Row {
} }
public append(s: string): void { public append(s: string): void {
this.chars = this.chars.concat(chars(s)); this.chars = this.chars.concat(Util.chars(s));
this.updateRender(); this.updateRender();
} }
@ -55,7 +55,7 @@ export class Row {
if (at >= this.size) { if (at >= this.size) {
this.chars = this.chars.concat(newSlice); this.chars = this.chars.concat(newSlice);
} else { } 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), ' '.repeat(SCROLL_TAB_SIZE),
); );
this.render = chars(newString); this.render = Util.chars(newString);
} }
} }

View File

@ -1,9 +1,9 @@
import { IRuntime, ITestBase } from './types.ts';
import { getTermios } from './termios.ts'; import { getTermios } from './termios.ts';
import { ctrlKey, noop } from './utils.ts'; import { noop } from './fns.ts';
import { ITestBase } from './types.ts'; import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts';
import { KeyCommand } from './ansi.ts';
import { IRuntime } from './runtime_types.ts'; export type { IFFI, IFileIO, IRuntime, ITerminal } from './types.ts';
export type * from './runtime_types.ts';
/** /**
* Which Typescript runtime is currently being used * Which Typescript runtime is currently being used
@ -14,68 +14,33 @@ export enum RunTimeType {
Unknown = 'common', Unknown = 'common',
} }
const decoder = new TextDecoder();
let scrollRuntime: IRuntime | null = null; let scrollRuntime: IRuntime | null = null;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Misc runtime functions // 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 * Append information to the scroll.log logfile
*/ */
export function logToFile(s: unknown) { export function logToFile(s: unknown): void {
importForRuntime('file_io').then((f) => { getRuntime().then(({ file }) => {
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); 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 * @param s
*/ */
export function die(s: string | Error): void { export function die(s: string | Error): void {
logError(s);
getTermios().then((t) => { getTermios().then((t) => {
t.disableRawMode(); t.disableRawMode();
t.cleanup(); t.cleanup();
console.error(s); console.error(s);
getRuntime().then((r) => r.exit()); getRuntime().then((r) => r.exit());
}); });
} }

View File

@ -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<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

@ -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 STDIN_FILENO = 0;
export const TCSANOW = 0; export const TCSANOW = 0;
export const TERMIOS_SIZE = 60; export const TERMIOS_SIZE = 60;
export const defaultTerminalSize: ITerminalSize = {
rows: 24,
cols: 80,
};
/** /**
* Implementation to toggle raw mode * Implementation to toggle raw mode
*/ */

View File

@ -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<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>;
appendFile(path: string, contents: string): Promise<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'];
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// General types // General types
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -6,11 +6,6 @@ const DenoFileIO: IFileIO = {
const data = await Deno.readFile(path); const data = await Deno.readFile(path);
return decoder.decode(data); 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<void> { appendFile: async function (path: string, contents: string): Promise<void> {
const file = await Deno.open(path, { const file = await Deno.open(path, {
write: true, write: true,
@ -23,10 +18,6 @@ const DenoFileIO: IFileIO = {
await writer.write(encoder.encode(contents)); await writer.write(encoder.encode(contents));
file.close(); 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<void> { saveFile: async function (path: string, contents: string): Promise<void> {
return await Deno.writeTextFile(path, contents); return await Deno.writeTextFile(path, contents);
}, },

View File

@ -1,7 +1,7 @@
/** /**
* The main entrypoint when using Deno as the runtime * 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 DenoTerminalIO from './terminal_io.ts';
import DenoFileIO from './file_io.ts'; import DenoFileIO from './file_io.ts';

View File

@ -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 = { const DenoTerminalIO: ITerminal = {
argv: Deno.args, argv: Deno.args,

View File

@ -1,4 +1,4 @@
import { ITestBase } from '../common/mod.ts'; import { ITestBase } from '../common/types.ts';
import { stdAssert } from './deps.ts'; import { stdAssert } from './deps.ts';
const { const {
assertEquals, assertEquals,