diff --git a/src/common/ansi.ts b/src/common/ansi.ts index 9209096..fd14d09 100644 --- a/src/common/ansi.ts +++ b/src/common/ansi.ts @@ -1,2 +1,15 @@ -export enum Ansi { +function escape(suffix: string): string { + return `\x1b[${suffix}`; } + +function moveCursor(row: number, col: number): string { + return escape(`${row};${col}H`); +} + +export const Ansi = { + ClearScreen: escape('2J'), + ResetCursor: escape('H'), + moveCursor, +}; + +export default Ansi; diff --git a/src/common/editor.ts b/src/common/editor.ts index efab205..d1995ce 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -1,6 +1,60 @@ -export class Editor { +import { Ansi } from './ansi.ts'; +import { importForRuntime } from './runtime.ts'; +import { ctrl_key } from './strings.ts'; + +class Buffer { + #b = ''; + constructor() { } - public processKeyPress(): void { + + append(s: string): void { + this.#b += s; + } + + clear(): void { + this.#b = ''; + } + + getBuffer(): string { + return this.#b; + } +} + +export class Editor { + #buffer: Buffer; + constructor() { + this.#buffer = new Buffer(); + } + + /** + * Determine what to do based on input + * @param input - the decoded chunk of stdin + */ + public processKeyPress(input: string): boolean { + switch (input) { + case ctrl_key('q'): + this.clearScreen(); + return false; + + default: + return true; + } + } + + /** + * Clear the screen and write out the buffer + */ + public async refreshScreen(): Promise { + const { write } = await importForRuntime('terminal_io'); + + this.clearScreen(); + await write(this.#buffer.getBuffer()); + this.#buffer.clear(); + } + + private clearScreen(): void { + this.#buffer.append(Ansi.ClearScreen); + this.#buffer.append(Ansi.ResetCursor); } } diff --git a/src/common/mod.ts b/src/common/mod.ts index 90720ac..661957f 100644 --- a/src/common/mod.ts +++ b/src/common/mod.ts @@ -1,5 +1,5 @@ -import { importForRuntime } from "./runtime"; -import { ctrl_key, is_control } from "./strings"; +import { importForRuntime } from './runtime.ts'; +import { Editor } from './editor.ts'; export * from './runtime.ts'; export * from './strings.ts'; @@ -8,32 +8,29 @@ export type { ITestBase } from './test_base.ts'; const decoder = new TextDecoder(); -export function readKey(chunk): string { - const char = String(decoder.decode(chunk)) - - return char; -} - export async function main() { const { inputLoop, init } = await importForRuntime('mod.ts'); // Set up handlers to enable/disable raw mode for each runtime await init(); + // Create the editor itself + const editor = new Editor(); + // The main event loop for await (const chunk of inputLoop()) { - const char = readKey(chunk); + const char = String(decoder.decode(chunk)); - if (char === ctrl_key('q')) { + // Clear the screen for output + await editor.refreshScreen(); + + // Process input + const shouldLoop = editor.processKeyPress(char); + + if (!shouldLoop) { return 0; } - - if (is_control(char)) { - console.log(char.codePointAt(0) + '\r'); - } else { - console.log(`${char} ('${char.codePointAt(0)}')\r`); - } } return -1; -} \ No newline at end of file +} diff --git a/src/deno/mod.ts b/src/deno/mod.ts index 1f58990..2279d07 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 { getTermios } from "../common/mod.ts"; +import { getTermios } from '../common/mod.ts'; export * from './terminal_io.ts'; diff --git a/src/deno/terminal_io.ts b/src/deno/terminal_io.ts index 792862d..cab4cdb 100644 --- a/src/deno/terminal_io.ts +++ b/src/deno/terminal_io.ts @@ -6,3 +6,11 @@ export async function* inputLoop() { yield chunk; } } + +export async function write(s: string): Promise { + const buffer = new TextEncoder().encode(s); + + const stdout = Deno.stdout.writable.getWriter(); + await stdout.write(buffer); + stdout.releaseLock(); +}