From d5ce04fe8b2c0fd8fae8fad8e4317d6cd85f6acf Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Tue, 14 Nov 2023 15:53:45 -0500 Subject: [PATCH] Basic file opening and display. Off-by-one bug skipping first line, though --- deno.jsonc | 2 +- justfile | 8 +++---- src/bun/file_io.ts | 2 +- src/bun/terminal_io.ts | 5 ++++- src/common/buffer.ts | 10 ++++----- src/common/document.ts | 32 ++++++++++++++++++++-------- src/common/editor.ts | 47 +++++++++++++++++++++++++++++++----------- src/scroll.ts | 8 ++++++- 8 files changed, 80 insertions(+), 34 deletions(-) diff --git a/deno.jsonc b/deno.jsonc index d9490e5..825aab0 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -3,7 +3,7 @@ "include": ["src/"], "rules": { "tags": ["recommended"], - "exclude": ["no-explicit-any"] + "exclude": ["no-explicit-any", "no-inferrable-types"] } }, "fmt": { diff --git a/justfile b/justfile index 429397a..8dbe4f0 100644 --- a/justfile +++ b/justfile @@ -34,8 +34,8 @@ bun-test: bun test --coverage # Run with bun -bun-run: - bun run ./src/scroll.ts +bun-run file="": + bun run ./src/scroll.ts {{file}} ######################################################################################################################## # Deno-specific commands @@ -56,5 +56,5 @@ deno-coverage: deno coverage --unstable-ffi .deno-cover # Run with deno -deno-run: - deno run --allow-all --allow-ffi --deny-net --deny-hrtime --unstable ./src/scroll.ts +deno-run file="": + deno run --allow-all --allow-ffi --deny-net --deny-hrtime --unstable ./src/scroll.ts {{file}} diff --git a/src/bun/file_io.ts b/src/bun/file_io.ts index 7b71c7e..61fc9cc 100644 --- a/src/bun/file_io.ts +++ b/src/bun/file_io.ts @@ -8,7 +8,7 @@ const BunFileIO: IFIO = { return await file.text(); }, openFileSync: (path: string): string => { - return readFileSync(path); + return readFileSync(path).toString(); }, }; diff --git a/src/bun/terminal_io.ts b/src/bun/terminal_io.ts index 330e56e..129bef5 100644 --- a/src/bun/terminal_io.ts +++ b/src/bun/terminal_io.ts @@ -5,7 +5,10 @@ import { ITerminal, ITerminalSize } from '../common/mod.ts'; import Ansi from '../common/ansi.ts'; const BunTerminalIO: ITerminal = { - argv: Bun.argv, + // Deno only returns arguments passed to the script, so + // remove the bun runtime executable, and entry script arguments + // to have consistent argument lists + argv: (Bun.argv.length > 2) ? Bun.argv.slice(2) : [], inputLoop: async function* inputLoop() { for await (const chunk of Bun.stdin.stream()) { yield chunk; diff --git a/src/common/buffer.ts b/src/common/buffer.ts index 6b9a18d..22f4ccf 100644 --- a/src/common/buffer.ts +++ b/src/common/buffer.ts @@ -1,4 +1,4 @@ -import { strlen } from './utils.ts'; +import { strlen, truncate } from './utils.ts'; import { getRuntime } from './runtime.ts'; class Buffer { @@ -7,12 +7,12 @@ class Buffer { constructor() { } - public append(s: string): void { - this.#b += s; + public append(s: string, maxLen?: number): void { + this.#b += (maxLen === undefined) ? s : truncate(s, maxLen); } - public appendLine(s: string): void { - this.#b += s + '\r\n'; + public appendLine(s = ''): void { + this.#b += (s ?? '') + '\r\n'; } public clear(): void { diff --git a/src/common/document.ts b/src/common/document.ts index dc7e793..5175fc9 100644 --- a/src/common/document.ts +++ b/src/common/document.ts @@ -1,4 +1,5 @@ import { chars } from './utils.ts'; +import { getRuntime } from './runtime.ts'; export class Row { chars: string[] = []; @@ -27,23 +28,36 @@ export class Document { return new Document(); } - public static open(_filename: string): Document { - const doc = new Document(); - const line = 'Hello, World!'; - const row = new Row(line); - - doc.#rows.push(row); - - return doc; + public isEmpty(): boolean { + return this.#rows.length === 0; } - public getRow(i: number): Row | null { + public async open(filename: string): Promise { + const { file } = await getRuntime(); + + // Clear any existing rows + if (!this.isEmpty()) { + this.#rows = []; + } + + const rawFile = await file.openFile(filename); + rawFile.split(/\r?\n/) + .forEach((row) => this.appendRow(row)); + + return this; + } + + public row(i: number): Row | null { if (this.#rows[i] !== undefined) { return this.#rows[i]; } return null; } + + protected appendRow(s: string): void { + this.#rows.push(new Row(s)); + } } export default Document; diff --git a/src/common/editor.ts b/src/common/editor.ts index efc9fc6..d76b462 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -1,23 +1,45 @@ import Ansi, { KeyCommand } from './ansi.ts'; import Buffer from './buffer.ts'; import Document from './document.ts'; -import { ctrl_key, IPoint, ITerminalSize, truncate, VERSION } from './mod.ts'; +import { IPoint, ITerminalSize, VERSION } from './mod.ts'; +import { ctrl_key } from './utils.ts'; export class Editor { + /** + * The output buffer for the terminal + * @private + */ #buffer: Buffer; + /** + * The size of the screen in rows/columns + * @private + */ #screen: ITerminalSize; + /** + * The current location of the mouse cursor + * @private + */ #cursor: IPoint; + /** + * The document being edited + * @private + */ #document: Document; - constructor(terminalSize: ITerminalSize, args: string[]) { + constructor(terminalSize: ITerminalSize) { this.#buffer = new Buffer(); this.#screen = terminalSize; this.#cursor = { x: 0, y: 0, }; - this.#document = (args.length >= 2) - ? Document.open(args[1]) - : Document.empty(); + + this.#document = Document.empty(); + } + + public async open(filename: string): Promise { + await this.#document.open(filename); + + return this; } // -------------------------------------------------------------------------- @@ -120,7 +142,7 @@ export class Editor { private drawRows(): void { for (let y = 0; y < this.#screen.rows; y++) { - if (y >= this.#document.numrows) { + if (this.#document.numrows < y) { this.drawPlaceholderRow(y); } else { this.drawFileRow(y); @@ -128,18 +150,19 @@ export class Editor { } } - private drawFileRow(_y: number): void { - const row = this.#document.getRow(0); + private drawFileRow(y: number): void { + const row = this.#document.row(y); let len = row?.chars.length ?? 0; if (len > this.#screen.cols) { len = this.#screen.cols; } - this.#buffer.appendLine(truncate(row!.toString(), len)); + this.#buffer.append(row!.toString(), len); + this.#buffer.appendLine(Ansi.ClearLine); } private drawPlaceholderRow(y: number): void { - if (y === Math.trunc(this.#screen.rows / 2)) { + if (y === Math.trunc(this.#screen.rows / 2) && this.#document.isEmpty()) { const message = `Kilo editor -- version ${VERSION}`; const messageLen = (message.length > this.#screen.cols) ? this.#screen.cols @@ -152,14 +175,14 @@ export class Editor { this.#buffer.append(' '.repeat(padding)); } - this.#buffer.append(truncate(message, messageLen)); + this.#buffer.append(message, messageLen); } else { this.#buffer.append('~'); } this.#buffer.append(Ansi.ClearLine); if (y < this.#screen.rows - 1) { - this.#buffer.appendLine(''); + this.#buffer.appendLine(); } } } diff --git a/src/scroll.ts b/src/scroll.ts index 9ca6dd4..a8f9139 100644 --- a/src/scroll.ts +++ b/src/scroll.ts @@ -47,7 +47,13 @@ export async function main() { const terminalSize = await term.getTerminalSize(); // Create the editor itself - const editor = new Editor(terminalSize, term.argv); + const editor = new Editor(terminalSize); + if (term.argv.length > 0) { + const filename = term.argv[0]; + if (filename.trim() !== '') { + await editor.open(filename); + } + } await editor.refreshScreen(); // The main event loop