Move some things around to more logical places, attempt to set up an error log file

This commit is contained in:
Timothy Warren 2023-11-16 11:10:33 -05:00
parent a4ef630c7b
commit f966ebf4ac
15 changed files with 203 additions and 154 deletions

View File

@ -1,12 +1,13 @@
# Scroll # Scroll
Making a text editor in Typescript based on Kilo (Script + Kilo = Scroll). This Making a text editor in Typescript based on Kilo (Script + Kilo = Scroll). This
runs on [Bun](https://bun.sh/) and [Deno](https://deno.com/). runs on [Bun](https://bun.sh/) (v1.0 or later) and [Deno](https://deno.com/)
(v1.37 or later).
To simplify running, I'm using [Just](https://github.com/casey/just) To simplify running, I'm using [Just](https://github.com/casey/just).
- Bun: `just bun-run` - Bun: `just bun-run [filename]`
- Deno: `just deno-run` - Deno: `just deno-run [filename]`
## Development Notes ## Development Notes

View File

@ -2,7 +2,7 @@
* This is all the nasty ffi setup for the bun runtime * This is all the nasty ffi setup for the bun runtime
*/ */
import { dlopen, ptr, suffix } from 'bun:ffi'; import { dlopen, ptr, suffix } from 'bun:ffi';
import { IFFI } from '../common/types.ts'; import { IFFI } from '../common/runtime.ts';
const getLib = (name: string) => { const getLib = (name: string) => {
return dlopen( return dlopen(

View File

@ -1,6 +1,7 @@
import { IFIO } from '../common/types.ts'; import { IFIO } from '../common/runtime.ts';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { appendFile } from 'node:fs/promises';
const BunFileIO: IFIO = { const BunFileIO: IFIO = {
openFile: async (path: string): Promise<string> => { openFile: async (path: string): Promise<string> => {
@ -10,6 +11,9 @@ const BunFileIO: IFIO = {
openFileSync: (path: string): string => { openFileSync: (path: string): string => {
return readFileSync(path).toString(); return readFileSync(path).toString();
}, },
appendFile: async function (path: string, contents: string): Promise<void> {
await appendFile(path, contents);
},
}; };
export default BunFileIO; export default BunFileIO;

View File

@ -2,22 +2,18 @@
* The main entrypoint when using Bun as the runtime * The main entrypoint when using Bun as the runtime
*/ */
import { getTermios, IRuntime, RunTimeType } from '../common/mod.ts'; import { IRuntime, RunTimeType } from '../common/mod.ts';
import BunFFI from './ffi.ts'; import BunFFI from './ffi.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';
process.on('error', async (e) => {
(await getTermios()).disableRawMode();
console.error(e);
process.exit();
});
const BunRuntime: IRuntime = { const BunRuntime: IRuntime = {
name: RunTimeType.Bun, name: RunTimeType.Bun,
file: BunFileIO, file: BunFileIO,
ffi: BunFFI, ffi: BunFFI,
term: BunTerminalIO, term: BunTerminalIO,
onEvent: (eventName: string, handler: (e: Event) => void) =>
process.on(eventName, handler),
onExit: (cb: () => void): void => { onExit: (cb: () => void): void => {
process.on('beforeExit', cb); process.on('beforeExit', cb);
process.on('exit', cb); process.on('exit', cb);

View File

@ -1,3 +1,7 @@
/**
* ANSI/VT terminal escape code handling
*/
export const ANSI_PREFIX = '\x1b['; export const ANSI_PREFIX = '\x1b[';
function esc(pieces: TemplateStringsArray): string { function esc(pieces: TemplateStringsArray): string {
@ -39,4 +43,33 @@ export const Ansi = {
moveCursorDown: (row: number): string => ANSI_PREFIX + `${row}B`, moveCursorDown: (row: number): string => ANSI_PREFIX + `${row}B`,
}; };
const decoder = new TextDecoder();
export function readKey(raw: Uint8Array): string {
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 '\x1bOH':
case '\x1b[7~':
case '\x1b[1~':
case '\x1b[H':
return KeyCommand.Home;
case '\x1bOF':
case '\x1b[8~':
case '\x1b[4~':
case '\x1b[F':
return KeyCommand.End;
default:
return parsed;
}
}
export default Ansi; export default Ansi;

View File

@ -20,7 +20,7 @@ export class Document {
this.#rows = []; this.#rows = [];
} }
get numrows(): number { get numRows(): number {
return this.#rows.length; return this.#rows.length;
} }

View File

@ -142,7 +142,7 @@ export class Editor {
private drawRows(): void { private drawRows(): void {
for (let y = 0; y < this.#screen.rows; y++) { for (let y = 0; y < this.#screen.rows; y++) {
if (this.#document.numrows < y) { if (this.#document.numRows < y) {
this.drawPlaceholderRow(y); this.drawPlaceholderRow(y);
} else { } else {
this.drawFileRow(y); this.drawFileRow(y);

View File

@ -1,40 +1,11 @@
import { KeyCommand } from './ansi.ts'; import { readKey } from './ansi.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime } from './runtime.ts';
import { getTermios } from './termios.ts'; import { getTermios } from './termios.ts';
import { Editor } from './editor.ts'; import { Editor } from './editor.ts';
const decoder = new TextDecoder();
function readKey(raw: Uint8Array): string {
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 '\x1bOH':
case '\x1b[7~':
case '\x1b[1~':
case '\x1b[H':
return KeyCommand.Home;
case '\x1bOF':
case '\x1b[8~':
case '\x1b[4~':
case '\x1b[F':
return KeyCommand.End;
default:
return parsed;
}
}
export async function main() { export async function main() {
const runTime = await getRuntime(); const runTime = await getRuntime();
const { term, onExit } = runTime; const { term, file, onExit, onEvent } = runTime;
// 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();
@ -43,6 +14,16 @@ export async function main() {
t.disableRawMode(); t.disableRawMode();
}); });
// Setup error handler to log to file
onEvent('error', (error: Event) => {
t.disableRawMode();
error.preventDefault();
error.stopPropagation();
file.appendFile('scroll.err', JSON.stringify(error, null, 2)).then(
() => {},
);
});
const terminalSize = await term.getTerminalSize(); const terminalSize = await term.getTerminalSize();
// Create the editor itself // Create the editor itself
@ -53,6 +34,8 @@ export async function main() {
await editor.open(filename); await editor.open(filename);
} }
} }
// Clear the screen
await editor.refreshScreen(); await editor.refreshScreen();
// The main event loop // The main event loop

View File

@ -2,7 +2,6 @@ export * from './editor.ts';
export * from './runtime.ts'; export * from './runtime.ts';
export * from './termios.ts'; export * from './termios.ts';
export * from './utils.ts'; export * from './utils.ts';
export * from './types.ts';
export type * from './types.ts'; export type * from './types.ts';
export const VERSION = '0.0.1'; export const VERSION = '0.0.1';

View File

@ -1,5 +1,127 @@
import { getTermios } from './termios.ts'; import { getTermios } from './termios.ts';
import { IRuntime, RunTimeType } from './types.ts';
// ----------------------------------------------------------------------------
// Runtime adapter interfaces
// ----------------------------------------------------------------------------
export enum RunTimeType {
Bun = 'bun',
Deno = 'deno',
Unknown = 'common',
}
/**
* 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;
}
export interface ITerminalSize {
rows: number;
cols: number;
}
/**
* 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, void, unknown>;
/**
* Get the size of the terminal
*/
getTerminalSize(): Promise<ITerminalSize>;
/**
* Pipe a string to stdout
*/
writeStdout(s: string): Promise<void>;
}
/**
* Runtime-specific file handling
*/
export interface IFIO {
openFile(path: string): Promise<string>;
openFileSync(path: string): string;
appendFile(path: string, contents: string): Promise<void>;
}
/**
* The common interface for runtime adapters
*/
export interface IRuntime {
/**
* The name of the runtime
*/
name: RunTimeType;
/**
* Runtime-specific FFI
*/
ffi: IFFI;
/**
* Runtime-specific terminal functionality
*/
term: ITerminal;
/**
* Runtime-specific file system io
*/
file: IFIO;
/**
* Set up an event handler
*
* @param eventName - The event to listen for
* @param handler - The event handler
*/
onEvent: (eventName: string, handler: (e: Event) => 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
// ----------------------------------------------------------------------------
let scrollRuntime: IRuntime | null = null; let scrollRuntime: IRuntime | null = null;

View File

@ -7,109 +7,6 @@ export interface IPoint {
y: number; y: number;
} }
// ----------------------------------------------------------------------------
// Runtime adapter interfaces
// ----------------------------------------------------------------------------
export enum RunTimeType {
Bun = 'bun',
Deno = 'deno',
Unknown = 'common',
}
/**
* The native functions for 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
*/
getPointer(ta: any): unknown;
}
export interface ITerminalSize {
rows: number;
cols: number;
}
/**
* 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, void, unknown>;
/**
* Get the size of the terminal
*/
getTerminalSize(): Promise<ITerminalSize>;
/**
* Pipe a string to stdout
*/
writeStdout(s: string): Promise<void>;
}
/**
* Runtime-specific file handling
*/
export interface IFIO {
openFile(path: string): Promise<string>;
openFileSync(path: string): string;
}
export interface IRuntime {
/**
* The name of the runtime
*/
name: RunTimeType;
/**
* Runtime-specific FFI
*/
ffi: IFFI;
/**
* Runtime-specific terminal functionality
*/
term: ITerminal;
/**
* Runtime-specific file system io
*/
file: IFIO;
/**
* 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;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Testing // Testing
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
// Deno-specific ffi code // Deno-specific ffi code
import { IFFI } from '../common/types.ts'; import { IFFI } from '../common/runtime.ts';
let libSuffix = ''; let libSuffix = '';
switch (Deno.build.os) { switch (Deno.build.os) {

View File

@ -1,4 +1,4 @@
import { IFIO } from '../common/types.ts'; import { IFIO } from '../common/runtime.ts';
const DenoFileIO: IFIO = { const DenoFileIO: IFIO = {
openFile: async function (path: string): Promise<string> { openFile: async function (path: string): Promise<string> {
@ -11,6 +11,18 @@ const DenoFileIO: IFIO = {
const data = Deno.readFileSync(path); const data = Deno.readFileSync(path);
return decoder.decode(data); return decoder.decode(data);
}, },
appendFile: async function (path: string, contents: string): Promise<void> {
const file = await Deno.open(path, {
write: true,
append: true,
create: true,
});
const encoder = new TextEncoder();
const writer = file.writable.getWriter();
await writer.write(encoder.encode(contents));
file.close();
},
}; };
export default DenoFileIO; export default DenoFileIO;

View File

@ -11,6 +11,8 @@ const DenoRuntime: IRuntime = {
file: DenoFileIO, file: DenoFileIO,
ffi: DenoFFI, ffi: DenoFFI,
term: DenoTerminalIO, term: DenoTerminalIO,
onEvent: (eventName: string, handler: (e: Event) => void) =>
globalThis.addEventListener(eventName, handler),
onExit: (cb: () => void): void => { onExit: (cb: () => void): void => {
globalThis.addEventListener('onbeforeunload', cb); globalThis.addEventListener('onbeforeunload', cb);
globalThis.onbeforeunload = cb; globalThis.onbeforeunload = cb;

View File

@ -1,4 +1,4 @@
import { ITerminal, ITerminalSize } from '../common/types.ts'; import { ITerminal, ITerminalSize } from '../common/runtime.ts';
const DenoTerminalIO: ITerminal = { const DenoTerminalIO: ITerminal = {
argv: Deno.args, argv: Deno.args,