Basic file opening and display. Off-by-one bug skipping first line, though

This commit is contained in:
Timothy Warren 2023-11-14 15:53:45 -05:00
parent 816295ff9c
commit d5ce04fe8b
8 changed files with 80 additions and 34 deletions

View File

@ -3,7 +3,7 @@
"include": ["src/"],
"rules": {
"tags": ["recommended"],
"exclude": ["no-explicit-any"]
"exclude": ["no-explicit-any", "no-inferrable-types"]
}
},
"fmt": {

View File

@ -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}}

View File

@ -8,7 +8,7 @@ const BunFileIO: IFIO = {
return await file.text();
},
openFileSync: (path: string): string => {
return readFileSync(path);
return readFileSync(path).toString();
},
};

View File

@ -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;

View File

@ -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 {

View File

@ -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<Document> {
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;

View File

@ -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<Editor> {
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();
}
}
}

View File

@ -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