Move some things around to more logical places, attempt to set up an error log file
This commit is contained in:
parent
a4ef630c7b
commit
f966ebf4ac
@ -1,12 +1,13 @@
|
||||
# Scroll
|
||||
|
||||
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`
|
||||
- Deno: `just deno-run`
|
||||
- Bun: `just bun-run [filename]`
|
||||
- Deno: `just deno-run [filename]`
|
||||
|
||||
## Development Notes
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
* This is all the nasty ffi setup for the bun runtime
|
||||
*/
|
||||
import { dlopen, ptr, suffix } from 'bun:ffi';
|
||||
import { IFFI } from '../common/types.ts';
|
||||
import { IFFI } from '../common/runtime.ts';
|
||||
|
||||
const getLib = (name: string) => {
|
||||
return dlopen(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { IFIO } from '../common/types.ts';
|
||||
import { IFIO } from '../common/runtime.ts';
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { appendFile } from 'node:fs/promises';
|
||||
|
||||
const BunFileIO: IFIO = {
|
||||
openFile: async (path: string): Promise<string> => {
|
||||
@ -10,6 +11,9 @@ const BunFileIO: IFIO = {
|
||||
openFileSync: (path: string): string => {
|
||||
return readFileSync(path).toString();
|
||||
},
|
||||
appendFile: async function (path: string, contents: string): Promise<void> {
|
||||
await appendFile(path, contents);
|
||||
},
|
||||
};
|
||||
|
||||
export default BunFileIO;
|
||||
|
@ -2,22 +2,18 @@
|
||||
* 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 BunTerminalIO from './terminal_io.ts';
|
||||
import BunFileIO from './file_io.ts';
|
||||
|
||||
process.on('error', async (e) => {
|
||||
(await getTermios()).disableRawMode();
|
||||
console.error(e);
|
||||
process.exit();
|
||||
});
|
||||
|
||||
const BunRuntime: IRuntime = {
|
||||
name: RunTimeType.Bun,
|
||||
file: BunFileIO,
|
||||
ffi: BunFFI,
|
||||
term: BunTerminalIO,
|
||||
onEvent: (eventName: string, handler: (e: Event) => void) =>
|
||||
process.on(eventName, handler),
|
||||
onExit: (cb: () => void): void => {
|
||||
process.on('beforeExit', cb);
|
||||
process.on('exit', cb);
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* ANSI/VT terminal escape code handling
|
||||
*/
|
||||
|
||||
export const ANSI_PREFIX = '\x1b[';
|
||||
|
||||
function esc(pieces: TemplateStringsArray): string {
|
||||
@ -39,4 +43,33 @@ export const Ansi = {
|
||||
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;
|
||||
|
@ -20,7 +20,7 @@ export class Document {
|
||||
this.#rows = [];
|
||||
}
|
||||
|
||||
get numrows(): number {
|
||||
get numRows(): number {
|
||||
return this.#rows.length;
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ export class Editor {
|
||||
|
||||
private drawRows(): void {
|
||||
for (let y = 0; y < this.#screen.rows; y++) {
|
||||
if (this.#document.numrows < y) {
|
||||
if (this.#document.numRows < y) {
|
||||
this.drawPlaceholderRow(y);
|
||||
} else {
|
||||
this.drawFileRow(y);
|
||||
|
@ -1,40 +1,11 @@
|
||||
import { KeyCommand } from './ansi.ts';
|
||||
import { readKey } from './ansi.ts';
|
||||
import { getRuntime } from './runtime.ts';
|
||||
import { getTermios } from './termios.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() {
|
||||
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
|
||||
const t = await getTermios();
|
||||
@ -43,6 +14,16 @@ export async function main() {
|
||||
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();
|
||||
|
||||
// Create the editor itself
|
||||
@ -53,6 +34,8 @@ export async function main() {
|
||||
await editor.open(filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the screen
|
||||
await editor.refreshScreen();
|
||||
|
||||
// The main event loop
|
||||
|
@ -2,7 +2,6 @@ export * from './editor.ts';
|
||||
export * from './runtime.ts';
|
||||
export * from './termios.ts';
|
||||
export * from './utils.ts';
|
||||
export * from './types.ts';
|
||||
export type * from './types.ts';
|
||||
|
||||
export const VERSION = '0.0.1';
|
||||
|
@ -1,5 +1,127 @@
|
||||
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;
|
||||
|
||||
|
@ -7,109 +7,6 @@ export interface IPoint {
|
||||
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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Deno-specific ffi code
|
||||
import { IFFI } from '../common/types.ts';
|
||||
import { IFFI } from '../common/runtime.ts';
|
||||
|
||||
let libSuffix = '';
|
||||
switch (Deno.build.os) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IFIO } from '../common/types.ts';
|
||||
import { IFIO } from '../common/runtime.ts';
|
||||
|
||||
const DenoFileIO: IFIO = {
|
||||
openFile: async function (path: string): Promise<string> {
|
||||
@ -11,6 +11,18 @@ const DenoFileIO: IFIO = {
|
||||
const data = Deno.readFileSync(path);
|
||||
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;
|
||||
|
@ -11,6 +11,8 @@ const DenoRuntime: IRuntime = {
|
||||
file: DenoFileIO,
|
||||
ffi: DenoFFI,
|
||||
term: DenoTerminalIO,
|
||||
onEvent: (eventName: string, handler: (e: Event) => void) =>
|
||||
globalThis.addEventListener(eventName, handler),
|
||||
onExit: (cb: () => void): void => {
|
||||
globalThis.addEventListener('onbeforeunload', cb);
|
||||
globalThis.onbeforeunload = cb;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ITerminal, ITerminalSize } from '../common/types.ts';
|
||||
import { ITerminal, ITerminalSize } from '../common/runtime.ts';
|
||||
|
||||
const DenoTerminalIO: ITerminal = {
|
||||
argv: Deno.args,
|
||||
|
Loading…
Reference in New Issue
Block a user