From 9afeed41cd85d196a0032a84899ce2a1360b9db7 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Fri, 28 Jun 2024 16:45:55 -0400 Subject: [PATCH] Mostly refactor prompt input (search/file saving) to use input loop shared with normal functionality --- src/bun/file_io.ts | 6 +- src/bun/mod.ts | 1 + src/bun/terminal_io.ts | 1 + src/bun/test_base.ts | 1 + src/common/editor.ts | 688 +++++++++++++++++++++++----------------- src/common/main.ts | 3 - src/common/runtime.ts | 13 +- src/deno/file_io.ts | 4 + src/deno/mod.ts | 3 + src/deno/terminal_io.ts | 3 + src/deno/test_base.ts | 3 + 11 files changed, 432 insertions(+), 294 deletions(-) diff --git a/src/bun/file_io.ts b/src/bun/file_io.ts index 20d8297..a48a9b3 100644 --- a/src/bun/file_io.ts +++ b/src/bun/file_io.ts @@ -1,16 +1,18 @@ +if (!('Bun' in globalThis)) throw new Error('This module requires Bun'); + import { IFileIO } from '../common/runtime.ts'; import { appendFile } from 'node:fs/promises'; const BunFileIO: IFileIO = { openFile: async (path: string): Promise => { - const file = await Bun.file(path); + const file = await globalThis.Bun.file(path); return await file.text(); }, appendFile: async function (path: string, contents: string): Promise { return await appendFile(path, contents); }, saveFile: async function (path: string, contents: string): Promise { - await Bun.write(path, contents); + await globalThis.Bun.write(path, contents); return; }, }; diff --git a/src/bun/mod.ts b/src/bun/mod.ts index 743d89d..e2363b6 100644 --- a/src/bun/mod.ts +++ b/src/bun/mod.ts @@ -1,3 +1,4 @@ +if (!('Bun' in globalThis)) throw new Error('This module requires Bun'); /** * The main entrypoint when using Bun as the runtime */ diff --git a/src/bun/terminal_io.ts b/src/bun/terminal_io.ts index b7971af..1792fe6 100644 --- a/src/bun/terminal_io.ts +++ b/src/bun/terminal_io.ts @@ -1,3 +1,4 @@ +if (!('Bun' in globalThis)) throw new Error('This module requires Bun'); /** * Wrap the runtime-specific hook into stdin */ diff --git a/src/bun/test_base.ts b/src/bun/test_base.ts index 312acde..b9d7f3d 100644 --- a/src/bun/test_base.ts +++ b/src/bun/test_base.ts @@ -1,3 +1,4 @@ +if (!('Bun' in globalThis)) throw new Error('This module requires Bun'); /** * Adapt the bun test interface to the shared testing interface */ diff --git a/src/common/editor.ts b/src/common/editor.ts index 9058c46..020157c 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -8,108 +8,170 @@ import { ctrlKey, isControl, maxAdd, - none, posSub, - readKey, some, + strlen, truncate, } from './fns.ts'; -import { getRuntime, log, LogLevel } from './runtime.ts'; +import { log, LogLevel } from './runtime.ts'; import { ITerminalSize, Position } from './types.ts'; +export enum EditorMode { + Normal = 'Normal', + Find = 'Find', + Save = 'Save', + Prompt = 'Prompt', +} + +class Prompt { + private constructor( + public basePrompt: string, + private callback: (query: string, char: string) => void, + public key: string = '', + public answer: string = '', + ) {} + + public static from( + basePrompt: string, + callback: (query: string, char: string) => void = ( + query: string, + key: string, + ) => { + log('Default prompt callback', LogError.Warning); + }, + ): Prompt { + return new Prompt(basePrompt, callback); + } + + public backspace(): void { + this.answer = truncate(this.answer, strlen(this.answer) - 1); + } + + public append(char: string): void { + this.answer += char; + } + + public cb(): void { + this.callback(this.answer, this.key); + } + + public render(): string { + if (this.basePrompt.includes('%s')) { + return this.basePrompt.replace('%s', this.answer); + } else { + return `${this.basePrompt}${this.answer}`; + } + } +} + class Editor { + /** + * How to handle the stdin stream + * @private + */ + public mode: EditorMode = EditorMode.Normal; /** * The document being edited * @private */ - #document: Document; + public document: Document; /** * The output buffer for the terminal * @private */ - #buffer: Buffer; + protected buffer: Buffer; /** * The size of the screen in rows/columns * @private */ - #screen: ITerminalSize; + protected screen: ITerminalSize; /** * The current location of the mouse cursor * @private */ - #cursor: Position; + protected cursor: Position; /** * The current scrolling offset */ - #offset: Position; + protected offset: Position; /** * The scrolling offset for the rendered row * @private */ - #renderX: number = 0; + protected renderX: number = 0; /** * The name of the currently open file * @private */ - #filename: string = ''; + protected filename: string = ''; + /** + * Current input prompt state + */ + public _prompt: Prompt | null = null; /** * A message to display at the bottom of the screen * @private */ - #statusMessage: string = ''; + public statusMessage: string = ''; /** * Timeout for status messages * @private */ - #statusTimeout: number = 0; + protected statusTimeout: number = 0; /** * The number of times required to quit a dirty document * @private */ - #quitTimes: number = SCROLL_QUIT_TIMES; + protected quitTimes: number = SCROLL_QUIT_TIMES; constructor(terminalSize: ITerminalSize) { - this.#buffer = new Buffer(); + this.buffer = new Buffer(); // Subtract two rows from the terminal size // for displaying the status bar // and message bar - this.#screen = terminalSize; - this.#screen.rows -= 2; + this.screen = terminalSize; + this.screen.rows -= 2; - this.#cursor = Position.default(); - this.#offset = Position.default(); - this.#document = Document.default(); + this.cursor = Position.default(); + this.offset = Position.default(); + this.document = Document.default(); } private get numRows(): number { - return this.#document.numRows; + return this.document.numRows; } private get currentRow(): Row | null { - return this.#document.row(this.#cursor.y); + return this.document.row(this.cursor.y); } public async open(filename: string): Promise { - await this.#document.open(filename); - this.#filename = filename; + await this.document.open(filename); + this.filename = filename; return this; } public async save(): Promise { - if (this.#filename === '') { - const filename = await this.prompt('Save as: %s (ESC to cancel)'); - if (filename === null) { - this.setStatusMessage('Save aborted'); - return; - } - - this.#filename = filename; + if (this.filename !== '') { + await this.document.save(this.filename); + this.setStatusMessage(`${this.filename} was saved to disk.`); + return; } - await this.#document.save(this.#filename); - this.setStatusMessage(`${this.#filename} was saved to disk.`); + this.prompt('Save as: %s (ESC to cancel)', (name: string, key: string) => { + if (key === KeyCommand.Enter) { + this.mode = EditorMode.Normal; + this.filename = name; + return this.save(); + } + + if (name === null || key === KeyCommand.Escape) { + this.mode = EditorMode.Normal; + this.setStatusMessage('Save aborted'); + } + }); } // -------------------------------------------------------------------------- @@ -121,204 +183,96 @@ class Editor { * @param input - the decoded chunk of stdin */ public async processKeyPress(input: string): Promise { - switch (input) { - // ---------------------------------------------------------------------- - // Ctrl-key chords - // ---------------------------------------------------------------------- - case ctrlKey('f'): - await this.find(); - break; + switch (this.mode) { + case EditorMode.Find: + log(this, LogLevel.Debug); + // this._prompt = Prompt.from('Search: %s (Use ESC/Arrows/Enter)'); + this.find(); + this.processPromptKeyPress(input); + // this.find(); + return true; - case ctrlKey('s'): - await this.save(); - break; + case EditorMode.Save: + log(this, LogLevel.Debug); + this.save(); + this.processPromptKeyPress(input); + // this.save(); + return true; - case ctrlKey('q'): - if (this.#quitTimes > 0 && this.#document.dirty) { - this.setStatusMessage( - 'WARNING!!! File has unsaved changes. ' + - `Press Ctrl-Q ${this.#quitTimes} more times to quit.`, - ); - this.#quitTimes--; - return true; - } - await this.clearScreen(); - return false; + case EditorMode.Prompt: + return this.processPromptKeyPress(input); - // ---------------------------------------------------------------------- - // Movement keys - // ---------------------------------------------------------------------- - - case KeyCommand.Home: - this.#cursor.x = 0; - break; - - case KeyCommand.End: - if (this.currentRow !== null) { - this.#cursor.x = this.currentRow.size; - } - break; - - case KeyCommand.PageUp: - case KeyCommand.PageDown: - { - if (input === KeyCommand.PageUp) { - this.#cursor.y = this.#offset.y; - } else if (input === KeyCommand.PageDown) { - this.#cursor.y = maxAdd( - this.#offset.y, - this.#screen.rows - 1, - this.numRows, - ); - } - - let times = this.#screen.rows; - while (times--) { - this.moveCursor( - input === KeyCommand.PageUp - ? KeyCommand.ArrowUp - : KeyCommand.ArrowDown, - ); - } - } - break; - - case KeyCommand.ArrowUp: - case KeyCommand.ArrowDown: - case KeyCommand.ArrowRight: - case KeyCommand.ArrowLeft: - this.moveCursor(input); - break; - - // ---------------------------------------------------------------------- - // Text manipulation keys - // ---------------------------------------------------------------------- - - case KeyCommand.Enter: - this.#document.insertNewline(this.#cursor); - this.#cursor.x = 0; - this.#cursor.y++; - break; - - case KeyCommand.Delete: - this.#document.delete(this.#cursor); - break; - - case KeyCommand.Backspace: - { - if (this.#cursor.x > 0 || this.#cursor.y > 0) { - this.moveCursor(KeyCommand.ArrowLeft); - this.#document.delete(this.#cursor); - } - } - break; - - // ---------------------------------------------------------------------- - // Direct input - // ---------------------------------------------------------------------- - - default: { - if (!this.shouldFilter(input)) { - this.#document.insert(this.#cursor, input); - this.#cursor.x++; - } - } + case EditorMode.Normal: // fall through + default: + return this.processNormalKeyPress(input); } - if (this.#quitTimes < SCROLL_QUIT_TIMES) { - this.#quitTimes = SCROLL_QUIT_TIMES; - this.setStatusMessage(''); - } - - return true; + await this.refreshScreen(); } - public async prompt( + public prompt( p: string, - callback?: (query: string, char: string) => void, - ): Promise { - const { term } = await getRuntime(); - - let res = ''; - const maybeCallback = (query: string, char: string) => { - if (callback !== undefined) { - callback(query, char); - } - }; - - outer: while (true) { - if (p.includes('%s')) { - this.setStatusMessage(p.replace('%s', res)); - } else { - this.setStatusMessage(`${p}${res}`); - } - - await this.refreshScreen(); - for await (const chunk of term.inputLoop()) { - const char = readKey(chunk); - if (none(char)) { - continue; - } - - switch (char) { - // Remove the last character from the prompt input - case KeyCommand.Backspace: - case KeyCommand.Delete: - res = truncate(res, res.length - 1); - maybeCallback(res, char); - continue outer; - - // End the prompt - case KeyCommand.Escape: - this.setStatusMessage(''); - maybeCallback(res, char); - - return null; - - // Return the input and end the prompt - case KeyCommand.Enter: - if (res.length > 0) { - this.setStatusMessage(''); - maybeCallback(res, char); - return res; - } - break; - - // Add to the prompt result - default: - if (!isControl(char)) { - res += char; - } - } - - maybeCallback(res, char); - } + callback: (query: string, char: string) => void, + ): string | null { + if (this._prompt === null) { + this._prompt = Prompt.from(p, callback); } + + switch (this._prompt.key) { + // End the prompt + case KeyCommand.Escape: + this.mode = EditorMode.Normal; + + this.setStatusMessage(''); + return null; + + // Return the input and end the prompt + case KeyCommand.Enter: + this.mode = EditorMode.Normal; + + if (this._prompt.answer.length > 0) { + this.setStatusMessage(''); + return this._prompt.answer; + } + break; + + default: + // Nothing to do here + } + + return this._prompt.answer ?? null; } /** * Find text within the document. This is roughly equivalent to the * `editorFindCallback` function in the kilo tutorial. */ - public async find(): Promise { - const savedCursor = Position.from(this.#cursor); - const savedOffset = Position.from(this.#offset); + public find(): void { + const savedCursor = Position.from(this.cursor); + const savedOffset = Position.from(this.offset); - const query = await this.prompt( + this.prompt( 'Search: %s (Use ESC/Arrows/Enter)', (query: string, key: string) => { if (key === KeyCommand.Enter || key === KeyCommand.Escape) { + this.mode = EditorMode.Normal; if (key === KeyCommand.Escape) { - this.#document.resetFind(); + this.document.resetFind(); + // Return to document position before search + // when you cancel the search (press the escape key) + if (query === null) { + this.cursor = Position.from(savedCursor); + this.offset = Position.from(savedOffset); + } } return null; } - if (some(query) && query.length > 0) { - const pos = this.#document.find(query, key); + if (query.length > 0) { + const pos = this.document.find(query, key); if (pos !== null) { // We have a match here - this.#cursor = pos; + this.cursor = pos; this.scroll(); } else { this.setStatusMessage('Not found'); @@ -326,13 +280,6 @@ class Editor { } }, ); - - // Return to document position before search - // when you cancel the search (press the escape key) - if (query === null) { - this.#cursor = Position.from(savedCursor); - this.#offset = Position.from(savedOffset); - } } /** @@ -362,63 +309,61 @@ class Editor { private moveCursor(char: string): void { switch (char) { case KeyCommand.ArrowLeft: - if (this.#cursor.x > 0) { - this.#cursor.x--; - } else if (this.#cursor.y > 0) { - this.#cursor.y--; - this.#cursor.x = (this.currentRow !== null) - ? this.currentRow.size - : 0; + if (this.cursor.x > 0) { + this.cursor.x--; + } else if (this.cursor.y > 0) { + this.cursor.y--; + this.cursor.x = (this.currentRow !== null) ? this.currentRow.size : 0; } break; case KeyCommand.ArrowRight: if ( - this.currentRow !== null && this.#cursor.x < this.currentRow.size + this.currentRow !== null && this.cursor.x < this.currentRow.size ) { - this.#cursor.x++; + this.cursor.x++; } else if ( this.currentRow !== null && - this.#cursor.x === this.currentRow.size + this.cursor.x === this.currentRow.size ) { - this.#cursor.y++; - this.#cursor.x = 0; + this.cursor.y++; + this.cursor.x = 0; } break; case KeyCommand.ArrowUp: - if (this.#cursor.y > 0) { - this.#cursor.y--; + if (this.cursor.y > 0) { + this.cursor.y--; } break; case KeyCommand.ArrowDown: - if (this.#cursor.y < this.numRows) { - this.#cursor.y++; + if (this.cursor.y < this.numRows) { + this.cursor.y++; } break; } const rowLen = this.currentRow?.size ?? 0; - if (this.#cursor.x > rowLen) { - this.#cursor.x = rowLen; + if (this.cursor.x > rowLen) { + this.cursor.x = rowLen; } } private scroll(): void { - this.#renderX = 0; + this.renderX = 0; if (this.currentRow !== null) { - this.#renderX = this.currentRow.cxToRx(this.#cursor.x); + this.renderX = this.currentRow.cxToRx(this.cursor.x); } - if (this.#cursor.y < this.#offset.y) { - this.#offset.y = this.#cursor.y; + if (this.cursor.y < this.offset.y) { + this.offset.y = this.cursor.y; } - if (this.#cursor.y >= this.#offset.y + this.#screen.rows) { - this.#offset.y = this.#cursor.y - this.#screen.rows + 1; + if (this.cursor.y >= this.offset.y + this.screen.rows) { + this.offset.y = this.cursor.y - this.screen.rows + 1; } - if (this.#renderX < this.#offset.x) { - this.#offset.x = this.#renderX; + if (this.renderX < this.offset.x) { + this.offset.x = this.renderX; } - if (this.#renderX >= this.#offset.x + this.#screen.cols) { - this.#offset.x = this.#renderX - this.#screen.cols + 1; + if (this.renderX >= this.offset.x + this.screen.cols) { + this.offset.x = this.renderX - this.screen.cols + 1; } } @@ -428,8 +373,8 @@ class Editor { public setStatusMessage(msg: string): void { // TODO: consider some sort of formatting for passed strings - this.#statusMessage = msg; - this.#statusTimeout = Date.now(); + this.statusMessage = msg; + this.statusTimeout = Date.now(); } /** @@ -437,106 +382,281 @@ class Editor { */ public async refreshScreen(): Promise { this.scroll(); - this.#buffer.append(Ansi.HideCursor); - this.#buffer.append(Ansi.ResetCursor); + this.buffer.append(Ansi.HideCursor); + this.buffer.append(Ansi.ResetCursor); this.drawRows(); this.drawStatusBar(); this.drawMessageBar(); - this.#buffer.append( + this.buffer.append( Ansi.moveCursor( - this.#cursor.y - this.#offset.y, - this.#renderX - this.#offset.x, + this.cursor.y - this.offset.y, + this.renderX - this.offset.x, ), ); - this.#buffer.append(Ansi.ShowCursor); + this.buffer.append(Ansi.ShowCursor); - await this.#buffer.flush(); + await this.buffer.flush(); } - private async clearScreen(): Promise { - this.#buffer.append(Ansi.ClearScreen); - this.#buffer.append(Ansi.ResetCursor); + public async clearScreen(): Promise { + this.buffer.append(Ansi.ClearScreen); + this.buffer.append(Ansi.ResetCursor); - await this.#buffer.flush(); + await this.buffer.flush(); } private drawRows(): void { - for (let y = 0; y < this.#screen.rows; y++) { - this.#buffer.append(Ansi.ClearLine); - const fileRow = y + this.#offset.y; + for (let y = 0; y < this.screen.rows; y++) { + this.buffer.append(Ansi.ClearLine); + const fileRow = y + this.offset.y; if (fileRow >= this.numRows) { this.drawPlaceholderRow(fileRow); } else { this.drawFileRow(fileRow); } - this.#buffer.appendLine(); + this.buffer.appendLine(); } } private drawFileRow(y: number): void { - const row = this.#document.row(y); + const row = this.document.row(y); if (row === null) { log(`Trying to draw non-existent row '${y}'`, LogLevel.Warning); return this.drawPlaceholderRow(y); } const len = Math.min( - posSub(row.rsize, this.#offset.x), - this.#screen.cols, + posSub(row.rsize, this.offset.x), + this.screen.cols, ); - this.#buffer.append(row.render(this.#offset.x, len)); + this.buffer.append(row.render(this.offset.x, len)); } private drawPlaceholderRow(y: number): void { - if (y === Math.trunc(this.#screen.rows / 2) && this.#document.isEmpty()) { + if (y === Math.trunc(this.screen.rows / 2) && this.document.isEmpty()) { const message = `Scroll editor -- version ${SCROLL_VERSION}`; - const messageLen = (message.length > this.#screen.cols) - ? this.#screen.cols + const messageLen = (message.length > this.screen.cols) + ? this.screen.cols : message.length; - let padding = Math.trunc((this.#screen.cols - messageLen) / 2); + let padding = Math.trunc((this.screen.cols - messageLen) / 2); if (padding > 0) { - this.#buffer.append('~'); + this.buffer.append('~'); padding -= 1; - this.#buffer.append(' '.repeat(padding)); + this.buffer.append(' '.repeat(padding)); } - this.#buffer.append(message, messageLen); + this.buffer.append(message, messageLen); } else { - this.#buffer.append('~'); + this.buffer.append('~'); } } private drawStatusBar(): void { - this.#buffer.append(Ansi.InvertColor); - const name = (this.#filename !== '') ? this.#filename : '[No Name]'; - const modified = (this.#document.dirty) ? '(modified)' : ''; + this.buffer.append(Ansi.InvertColor); + const name = (this.filename !== '') ? this.filename : '[No Name]'; + const modified = (this.document.dirty) ? '(modified)' : ''; const status = `${truncate(name, 20)} - ${this.numRows} lines ${modified}`; - const rStatus = `${this.#cursor.y + 1}/${this.numRows}`; - let len = Math.min(status.length, this.#screen.cols); - this.#buffer.append(status, len); + const rStatus = `${this.cursor.y + 1}/${this.numRows}`; + let len = Math.min(status.length, this.screen.cols); + this.buffer.append(status, len); - while (len < this.#screen.cols) { - if (this.#screen.cols - len === rStatus.length) { - this.#buffer.append(rStatus); + while (len < this.screen.cols) { + if (this.screen.cols - len === rStatus.length) { + this.buffer.append(rStatus); break; } else { - this.#buffer.append(' '); + this.buffer.append(' '); len++; } } - this.#buffer.appendLine(Ansi.ResetFormatting); + this.buffer.appendLine(Ansi.ResetFormatting); } private drawMessageBar(): void { - this.#buffer.append(Ansi.ClearLine); - const msgLen = this.#statusMessage.length; - if (msgLen > 0 && (Date.now() - this.#statusTimeout < 5000)) { - this.#buffer.append(this.#statusMessage, this.#screen.cols); + this.buffer.append(Ansi.ClearLine); + const msgLen = this.statusMessage.length; + if (msgLen > 0 && (Date.now() - this.statusTimeout < 5000)) { + this.buffer.append(this.statusMessage, this.screen.cols); } } + + // -------------------------------------------------------------------------- + // Terminal input parsing + // -------------------------------------------------------------------------- + + private async processNormalKeyPress(input: string): Promise { + switch (input) { + // ---------------------------------------------------------------------- + // Ctrl-key chords + // ---------------------------------------------------------------------- + case ctrlKey('f'): + this.mode = EditorMode.Find; + // break; + return this.processKeyPress(input); + + case ctrlKey('s'): + this.mode = EditorMode.Save; + return this.processKeyPress(input); + + // await this.save(); + // break; + + case ctrlKey('q'): + if (this.quitTimes > 0 && this.document.dirty) { + this.setStatusMessage( + 'WARNING!!! File has unsaved changes. ' + + `Press Ctrl-Q ${this.quitTimes} more times to quit.`, + ); + this.quitTimes--; + return true; + } + await this.clearScreen(); + return false; + + // ---------------------------------------------------------------------- + // Movement keys + // ---------------------------------------------------------------------- + + case KeyCommand.Home: + this.cursor.x = 0; + break; + + case KeyCommand.End: + if (this.currentRow !== null) { + this.cursor.x = this.currentRow.size; + } + break; + + case KeyCommand.PageUp: + case KeyCommand.PageDown: + { + if (input === KeyCommand.PageUp) { + this.cursor.y = this.offset.y; + } else if (input === KeyCommand.PageDown) { + this.cursor.y = maxAdd( + this.offset.y, + this.screen.rows - 1, + this.numRows, + ); + } + + let times = this.screen.rows; + while (times--) { + this.moveCursor( + input === KeyCommand.PageUp + ? KeyCommand.ArrowUp + : KeyCommand.ArrowDown, + ); + } + } + break; + + case KeyCommand.ArrowUp: + case KeyCommand.ArrowDown: + case KeyCommand.ArrowRight: + case KeyCommand.ArrowLeft: + this.moveCursor(input); + break; + + // ---------------------------------------------------------------------- + // Text manipulation keys + // ---------------------------------------------------------------------- + + case KeyCommand.Enter: + this.document.insertNewline(this.cursor); + this.cursor.x = 0; + this.cursor.y++; + break; + + case KeyCommand.Delete: + this.document.delete(this.cursor); + break; + + case KeyCommand.Backspace: + { + if (this.cursor.x > 0 || this.cursor.y > 0) { + this.moveCursor(KeyCommand.ArrowLeft); + this.document.delete(this.cursor); + } + } + break; + + // ---------------------------------------------------------------------- + // Direct input + // ---------------------------------------------------------------------- + + default: { + if (!this.shouldFilter(input)) { + this.document.insert(this.cursor, input); + this.cursor.x++; + } + } + } + + if (this.quitTimes < SCROLL_QUIT_TIMES) { + this.quitTimes = SCROLL_QUIT_TIMES; + this.setStatusMessage(''); + } + + return true; + } + + private async processPromptKeyPress(char: string): Promise { + log(char, LogLevel.Debug); + log(this, LogLevel.Debug); + + if (this._prompt === null) { + log('Prompt should not be null here', LogLevel.Warning); + this.mode = EditorMode.Normal; + + return true; + } + + this.setStatusMessage(this._prompt.render()); + await this.refreshScreen(); + + this._prompt.key = char; + switch (char) { + // Remove the last character from the prompt input + case KeyCommand.Backspace: + case KeyCommand.Delete: + this._prompt.backspace(); + this._prompt.cb(); + break; + + // End the prompt + case KeyCommand.Escape: + this.mode = EditorMode.Normal; + + this.setStatusMessage(''); + this._prompt.cb(); + break; + + // Return the input and end the prompt + case KeyCommand.Enter: + this.mode = EditorMode.Normal; + + if (this._prompt.answer.length > 0) { + this.setStatusMessage(''); + this._prompt.cb(); + } + break; + + // Add to the prompt result + default: + if (!isControl(char)) { + this._prompt.append(char); + } + } + + // this.setStatusMessage(this._prompt.render()); + // await this.refreshScreen(); + + return true; + } } export default Editor; diff --git a/src/common/main.ts b/src/common/main.ts index 8b616db..98228ec 100644 --- a/src/common/main.ts +++ b/src/common/main.ts @@ -51,9 +51,6 @@ export async function main() { if (!shouldLoop) { return; } - - // Render output - await editor.refreshScreen(); } } } diff --git a/src/common/runtime.ts b/src/common/runtime.ts index 96dcb2d..2e7fae6 100644 --- a/src/common/runtime.ts +++ b/src/common/runtime.ts @@ -30,7 +30,7 @@ let scrollRuntime: IRuntime | null = null; export function log(s: unknown, level: LogLevel = LogLevel.Notice): void { getRuntime().then(({ file }) => { - const raw = typeof s === 'string' ? s : JSON.stringify(s, null, 2); + const raw = JSON.stringify(s, null, 2); const output = `${level}: ${raw}\n`; const outputFile = (level === LogLevel.Error) @@ -85,13 +85,16 @@ export async function getRuntime(): Promise { const pkg = await import(path); if ('default' in pkg) { scrollRuntime = pkg.default; - if (scrollRuntime !== null) { - return Promise.resolve(scrollRuntime); - } } + + if (scrollRuntime !== null) { + return Promise.resolve(scrollRuntime); + } + + return Promise.reject('Missing default import'); } - return Promise.reject('Missing default import'); + return Promise.resolve(scrollRuntime); } /** diff --git a/src/deno/file_io.ts b/src/deno/file_io.ts index c81194d..ab71f86 100644 --- a/src/deno/file_io.ts +++ b/src/deno/file_io.ts @@ -1,3 +1,7 @@ +if (!('Deno' in globalThis)) { + throw new Error('This module requires Deno to run'); +} + import { IFileIO } from '../common/runtime.ts'; const DenoFileIO: IFileIO = { diff --git a/src/deno/mod.ts b/src/deno/mod.ts index c5a48f8..09fea74 100644 --- a/src/deno/mod.ts +++ b/src/deno/mod.ts @@ -1,3 +1,6 @@ +if (!('Deno' in globalThis)) { + throw new Error('This module requires Deno to run'); +} /** * The main entrypoint when using Deno as the runtime */ diff --git a/src/deno/terminal_io.ts b/src/deno/terminal_io.ts index 7032393..f22e070 100644 --- a/src/deno/terminal_io.ts +++ b/src/deno/terminal_io.ts @@ -1,3 +1,6 @@ +if (!('Deno' in globalThis)) { + throw new Error('This module requires Deno to run'); +} import { readKey } from '../common/fns.ts'; import { ITerminal, ITerminalSize } from '../common/types.ts'; diff --git a/src/deno/test_base.ts b/src/deno/test_base.ts index 0ebaeac..57a7be3 100644 --- a/src/deno/test_base.ts +++ b/src/deno/test_base.ts @@ -1,3 +1,6 @@ +if (!('Deno' in globalThis)) { + throw new Error('This module requires Deno to run'); +} import { ITestBase } from '../common/types.ts'; import { stdAssert } from './deps.ts'; const {