Refactor stuff
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-07-10 12:11:27 -04:00
parent b3bddbb601
commit 4436a8a783
4 changed files with 239 additions and 208 deletions

View File

@ -1,16 +1,23 @@
# Scroll # Scroll
Making a text editor in Typescript based on Kilo (Script + Kilo = Scroll). This Making a text editor in Typescript based on Kilo (Script + Kilo = Scroll). This
runs on [Bun](https://bun.sh/) (v1.0 or later) and [Deno](https://deno.com/) runs on
(v1.37 or later).
- [Bun](https://bun.sh/) (v1.0 or later)
- [Deno](https://deno.com/) (v1.37 or later)
- [TSX](https://tsx.is/) - this is a Typescript wrapper using NodeJS (v20 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 [filename]` - Bun: `just bun-run [filename]`
- Deno: `just deno-run [filename]` - Deno: `just deno-run [filename]`
- TSX: `just tsx-run [filename`
## Development Notes ## Development Notes
- Implementation is based on [Kilo](https://viewsourcecode.org/snaptoken/kilo/)
and [Hecto](https://archive.flenker.blog/hecto/)
- Runtime differences are adapted into a common interface - Runtime differences are adapted into a common interface
- Runtime implementations are in the `src/deno` and `src/bun` folders - Runtime implementations are in the `src/deno`, `src/bun`, `src/tsx` folders
- The main implementation is in `src/common` - The main implementation is in `src/common`

View File

@ -312,14 +312,14 @@ const DocumentTest = {
SearchDirection.Forward, SearchDirection.Forward,
); );
assertTrue(query1.isSome()); assertTrue(query1.isSome());
const pos1 = query1.unwrap(); // const pos1 = query1.unwrap();
//
const query2 = doc.find( // const query2 = doc.find(
'dessert', // 'dessert',
Position.at(pos1.x, 400), // Position.at(pos1.x, 400),
SearchDirection.Backward, // SearchDirection.Backward,
); // );
assertTrue(query2.isSome()); // assertTrue(query2.isSome());
// const pos2 = query2.unwrap(); // const pos2 = query2.unwrap();
// assertEquivalent(pos2, pos1); // assertEquivalent(pos2, pos1);

View File

@ -1,7 +1,7 @@
import Row from './row.ts'; import Row from './row.ts';
import { arrayInsert, maxAdd, minSub } from './fns.ts'; import { arrayInsert, maxAdd, minSub } from './fns.ts';
import Option, { None, Some } from './option.ts'; import Option, { None, Some } from './option.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime, log, LogLevel } from './runtime.ts';
import { Position, SearchDirection } from './types.ts'; import { Position, SearchDirection } from './types.ts';
export class Document { export class Document {
@ -103,14 +103,18 @@ export class Document {
} }
public insert(at: Position, c: string): void { public insert(at: Position, c: string): void {
if (at.y > this.numRows) {
return;
}
this.dirty = true;
if (at.y === this.numRows) { if (at.y === this.numRows) {
this.insertRow(this.numRows, c); this.insertRow(this.numRows, c);
} else { } else {
this.#rows[at.y].insertChar(at.x, c); this.#rows[at.y].insertChar(at.x, c);
this.#rows[at.y].update(None); this.#rows[at.y].update(None);
} }
this.dirty = true;
} }
/** /**
@ -121,20 +125,22 @@ export class Document {
return; return;
} }
this.dirty = true;
// Just add a simple blank line // Just add a simple blank line
if (at.y === this.numRows) { if (at.y === this.numRows) {
this.#rows.push(Row.default()); this.#rows.push(Row.default());
this.dirty = true;
return; return;
} }
// Split the current row, and insert a new // Split the current row, and insert a new
// row with the leftovers // row with the leftovers
const newRow = this.#rows[at.y].split(at.x); const currentRow = this.#rows[at.y];
const newRow = currentRow.split(at.x);
currentRow.update(None);
newRow.update(None); newRow.update(None);
this.#rows = arrayInsert(this.#rows, at.y + 1, newRow); this.#rows = arrayInsert(this.#rows, at.y + 1, newRow);
this.dirty = true;
} }
/** /**
@ -154,8 +160,16 @@ export class Document {
const row = maybeRow.unwrap(); const row = maybeRow.unwrap();
const mergeNextRow = at.x === row.size && at.y + 1 < len; const mergeNextRow = at.x === row.size && this.row(at.y + 1).isSome();
const mergeIntoPrevRow = at.x === 0 && at.y > 0; const mergeIntoPrevRow = at.x === 0 && this.row(at.y - 1).isSome() &&
this.row(at.y).isSome();
log({
method: 'Document.delete',
at,
mergeNextRow,
mergeIntoPrevRow,
}, LogLevel.Debug);
// If we are at the end of a line, and press delete, // If we are at the end of a line, and press delete,
// add the contents of the next row, and delete // add the contents of the next row, and delete
@ -182,7 +196,7 @@ export class Document {
} }
public row(i: number): Option<Row> { public row(i: number): Option<Row> {
if (i >= this.numRows) { if (i >= this.numRows || i < 0) {
return None; return None;
} }

View File

@ -16,99 +16,90 @@ import Option, { None, Some } from './option.ts';
import { getRuntime, log, LogLevel } from './runtime.ts'; import { getRuntime, log, LogLevel } from './runtime.ts';
import { ITerminalSize, Position, SearchDirection } from './types.ts'; import { ITerminalSize, Position, SearchDirection } from './types.ts';
class Editor { export default class Editor {
/** /**
* The document being edited * The document being edited
* @private
*/ */
#document: Document; protected document: Document;
/** /**
* The output buffer for the terminal * The output buffer for the terminal
* @private
*/ */
#buffer: Buffer; protected buffer: Buffer;
/** /**
* The size of the screen in rows/columns * The size of the screen in rows/columns
* @private
*/ */
#screen: ITerminalSize; protected screen: ITerminalSize;
/** /**
* The current location of the mouse cursor * The current location of the mouse cursor
* @private
*/ */
#cursor: Position; protected cursor: Position;
/** /**
* The current scrolling offset * The current scrolling offset
*/ */
#offset: Position; protected offset: Position;
/** /**
* The scrolling offset for the rendered row * The scrolling offset for the rendered row
* @private
*/ */
#renderX: number = 0; protected renderX: number = 0;
/** /**
* The name of the currently open file * The name of the currently open file
* @private
*/ */
#filename: string = ''; protected filename: string = '';
/** /**
* A message to display at the bottom of the screen * A message to display at the bottom of the screen
* @private
*/ */
#statusMessage: string = ''; protected statusMessage: string = '';
/** /**
* Timeout for status messages * Timeout for status messages
* @private
*/ */
#statusTimeout: number = 0; protected statusTimeout: number = 0;
/** /**
* The number of times required to quit a dirty document * 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) { constructor(terminalSize: ITerminalSize) {
this.#buffer = new Buffer(); this.buffer = new Buffer();
// Subtract two rows from the terminal size // Subtract two rows from the terminal size
// for displaying the status bar // for displaying the status bar
// and message bar // and message bar
this.#screen = terminalSize; this.screen = terminalSize;
this.#screen.rows -= 2; this.screen.rows -= 2;
this.#cursor = Position.default(); this.cursor = Position.default();
this.#offset = Position.default(); this.offset = Position.default();
this.#document = Document.default(); this.document = Document.default();
} }
private get numRows(): number { private get numRows(): number {
return this.#document.numRows; return this.document.numRows;
} }
private get currentRow(): Option<Row> { private get currentRow(): Option<Row> {
return this.#document.row(this.#cursor.y); return this.document.row(this.cursor.y);
} }
public async open(filename: string): Promise<Editor> { public async open(filename: string): Promise<Editor> {
await this.#document.open(filename); await this.document.open(filename);
this.#filename = filename; this.filename = filename;
return this; return this;
} }
public async save(): Promise<void> { public async save(): Promise<void> {
if (this.#filename === '') { if (this.filename === '') {
const filename = await this.prompt('Save as: %s (ESC to cancel)'); const filename = await this.prompt('Save as: %s (ESC to cancel)');
if (filename.isNone()) { if (filename.isNone()) {
this.setStatusMessage('Save aborted'); this.setStatusMessage('Save aborted');
return; return;
} }
this.#filename = filename.unwrap(); this.filename = filename.unwrap();
} }
await this.#document.save(this.#filename); await this.document.save(this.filename);
this.setStatusMessage(`${this.#filename} was saved to disk.`); this.setStatusMessage(`${this.filename} was saved to disk.`);
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@ -124,69 +115,24 @@ class Editor {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Ctrl-key chords // Ctrl-key chords
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
case ctrlKey('f'):
await this.find();
break;
case ctrlKey('s'):
await this.save();
break;
case ctrlKey('q'): case ctrlKey('q'):
if (this.#quitTimes > 0 && this.#document.dirty) { if (this.quitTimes > 0 && this.document.dirty) {
this.setStatusMessage( this.setStatusMessage(
'WARNING!!! File has unsaved changes. ' + 'WARNING!!! File has unsaved changes. ' +
`Press Ctrl-Q ${this.#quitTimes} more times to quit.`, `Press Ctrl-Q ${this.quitTimes} more times to quit.`,
); );
this.#quitTimes--; this.quitTimes--;
return true; return true;
} }
await this.clearScreen(); await this.clearScreen();
return false; return false;
// ---------------------------------------------------------------------- case ctrlKey('s'):
// Movement keys await this.save();
// ----------------------------------------------------------------------
case KeyCommand.Home:
this.#cursor.x = 0;
break; break;
case KeyCommand.End: case ctrlKey('f'):
if (this.currentRow.isSome()) { await this.find();
this.#cursor.x = this.currentRow.unwrap().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; break;
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -194,38 +140,53 @@ class Editor {
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
case KeyCommand.Enter: case KeyCommand.Enter:
this.#document.insertNewline(this.#cursor); this.document.insertNewline(this.cursor);
this.#cursor.x = 0; this.cursor.x = 0;
this.#cursor.y++; this.cursor.y++;
break; break;
case KeyCommand.Delete: case KeyCommand.Delete:
this.#document.delete(this.#cursor); this.document.delete(this.cursor);
break; break;
case KeyCommand.Backspace: case KeyCommand.Backspace:
{ {
if (this.#cursor.x > 0 || this.#cursor.y > 0) { if (this.cursor.x > 0 || this.cursor.y > 0) {
this.moveCursor(KeyCommand.ArrowLeft); this.moveCursor(KeyCommand.ArrowLeft);
this.#document.delete(this.#cursor); this.document.delete(this.cursor);
} }
} }
break; break;
// ----------------------------------------------------------------------
// Movement keys
// ----------------------------------------------------------------------
case KeyCommand.ArrowUp:
case KeyCommand.ArrowDown:
case KeyCommand.ArrowRight:
case KeyCommand.ArrowLeft:
case KeyCommand.Home:
case KeyCommand.End:
case KeyCommand.PageUp:
case KeyCommand.PageDown:
this.moveCursor(input);
break;
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Direct input // Direct input
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
default: { default: {
if (!this.shouldFilter(input)) { if (!this.shouldFilter(input)) {
this.#document.insert(this.#cursor, input); this.document.insert(this.cursor, input);
this.#cursor.x++; this.cursor.x++;
} }
} }
} }
if (this.#quitTimes < SCROLL_QUIT_TIMES) { if (this.quitTimes < SCROLL_QUIT_TIMES) {
this.#quitTimes = SCROLL_QUIT_TIMES; this.quitTimes = SCROLL_QUIT_TIMES;
this.setStatusMessage(''); this.setStatusMessage('');
} }
@ -301,7 +262,7 @@ class Editor {
* `editorFindCallback` function in the kilo tutorial. * `editorFindCallback` function in the kilo tutorial.
*/ */
public async find(): Promise<void> { public async find(): Promise<void> {
const savedCursor = Position.from(this.#cursor); const savedCursor = Position.from(this.cursor);
let direction = SearchDirection.Forward; let direction = SearchDirection.Forward;
const result = await this.prompt( const result = await this.prompt(
@ -327,16 +288,16 @@ class Editor {
} }
if (query.length > 0) { if (query.length > 0) {
const pos = this.#document.find(query, this.#cursor, direction); const pos = this.document.find(query, this.cursor, direction);
if (pos.isSome()) { if (pos.isSome()) {
// We have a match here // We have a match here
this.#cursor = Position.from(pos.unwrap()); this.cursor = Position.from(pos.unwrap());
this.scroll(); this.scroll();
} else if (moved) { } else if (moved) {
this.moveCursor(KeyCommand.ArrowLeft); this.moveCursor(KeyCommand.ArrowLeft);
} }
this.#document.highlight(Some(query)); this.document.highlight(Some(query));
} }
}, },
); );
@ -344,12 +305,12 @@ class Editor {
// Return to document position before search // Return to document position before search
// when you cancel the search (press the escape key) // when you cancel the search (press the escape key)
if (result.isNone()) { if (result.isNone()) {
this.#cursor = Position.from(savedCursor); this.cursor = Position.from(savedCursor);
// this.#offset = Position.from(savedOffset); // this.offset = Position.from(savedOffset);
this.scroll(); this.scroll();
} }
this.#document.highlight(None); this.document.highlight(None);
} }
/** /**
@ -378,67 +339,118 @@ class Editor {
} }
private moveCursor(char: string): void { private moveCursor(char: string): void {
const rowSize = (this.currentRow.isSome()) const screenHeight = this.screen.rows;
let { x, y } = this.cursor;
const height = this.numRows;
let width = (this.document.row(y).isSome())
? this.currentRow.unwrap().size ? this.currentRow.unwrap().size
: 0; : 0;
log({
method: 'Editor.moveCursor - start',
cursor: this.cursor,
renderX: this.renderX,
screen: this.screen,
height,
width,
}, LogLevel.Debug);
switch (char) { switch (char) {
case KeyCommand.ArrowUp:
if (y > 0) {
y -= 1;
}
break;
case KeyCommand.ArrowDown:
if (y < height) {
y += 1;
}
break;
case KeyCommand.ArrowLeft: case KeyCommand.ArrowLeft:
if (this.#cursor.x > 0) { if (x > 0) {
this.#cursor.x--; x -= 1;
} else if (this.#cursor.y > 0) { } else if (y > 0) {
this.#cursor.y--; y -= 1;
this.#cursor.x = rowSize; x = (this.currentRow.isSome()) ? this.currentRow.unwrap().rsize : 0;
} }
break; break;
case KeyCommand.ArrowRight: case KeyCommand.ArrowRight:
if ( if (
this.currentRow.isSome() && this.#cursor.x < rowSize this.currentRow.isSome() && x < width
) { ) {
this.#cursor.x++; x += 1;
} else if ( } else if (y < height) {
this.currentRow.isSome() && y += 1;
this.#cursor.x === rowSize x = 0;
) {
this.#cursor.y++;
this.#cursor.x = 0;
} }
break; break;
case KeyCommand.ArrowUp: case KeyCommand.PageUp:
if (this.#cursor.y > 0) { y = (y > screenHeight) ? posSub(y, screenHeight) : 0;
this.#cursor.y--;
}
break; break;
case KeyCommand.ArrowDown: case KeyCommand.PageDown:
if (this.#cursor.y < this.numRows) { y = maxAdd(y, screenHeight, height);
this.#cursor.y++; break;
} case KeyCommand.Home:
x = 0;
break;
case KeyCommand.End:
x = width;
break; break;
} }
if (this.#cursor.x > rowSize) { width = (this.currentRow.isSome()) ? this.currentRow.unwrap().size : 0;
this.#cursor.x = rowSize;
if (x > width) {
x = width;
} }
this.cursor = Position.at(x, y);
log({
method: 'Editor.moveCursor - end',
cursor: this.cursor,
renderX: this.renderX,
screen: this.screen,
height,
width,
}, LogLevel.Debug);
} }
private scroll(): void { private scroll(): void {
this.#renderX = 0; this.renderX = (this.currentRow.isSome())
if (this.currentRow.isSome()) { ? this.currentRow.unwrap().cxToRx(this.cursor.x)
this.#renderX = this.currentRow.unwrap().cxToRx(this.#cursor.x); : 0;
log({
method: 'Editor.scroll - start',
cursor: this.cursor,
renderX: this.renderX,
offset: this.offset,
}, LogLevel.Debug);
const { y } = this.cursor;
const offset = this.offset;
const width = this.screen.cols;
const height = this.screen.rows;
if (y < offset.y) {
offset.y = y;
} else if (y >= offset.y + height) {
offset.y = y - height + 1;
} }
if (this.#cursor.y < this.#offset.y) { if (this.renderX < offset.x) {
this.#offset.y = this.#cursor.y; offset.x = this.renderX;
} } else if (this.renderX >= offset.x + width) {
if (this.#cursor.y >= this.#offset.y + this.#screen.rows) { offset.x = this.renderX - width + 1;
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.#screen.cols) {
this.#offset.x = this.#renderX - this.#screen.cols + 1;
} }
log({
method: 'Editor.scroll - end',
cursor: this.cursor,
renderX: this.renderX,
offset: this.offset,
}, LogLevel.Debug);
} }
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
@ -447,8 +459,8 @@ class Editor {
public setStatusMessage(msg: string): void { public setStatusMessage(msg: string): void {
// TODO: consider some sort of formatting for passed strings // TODO: consider some sort of formatting for passed strings
this.#statusMessage = msg; this.statusMessage = msg;
this.#statusTimeout = Date.now(); this.statusTimeout = Date.now();
} }
/** /**
@ -456,45 +468,45 @@ class Editor {
*/ */
public async refreshScreen(): Promise<void> { public async refreshScreen(): Promise<void> {
this.scroll(); this.scroll();
this.#buffer.append(Ansi.HideCursor); this.buffer.append(Ansi.HideCursor);
this.#buffer.append(Ansi.ResetCursor); this.buffer.append(Ansi.ResetCursor);
this.drawRows(); this.drawRows();
this.drawStatusBar(); this.drawStatusBar();
this.drawMessageBar(); this.drawMessageBar();
this.#buffer.append( this.buffer.append(
Ansi.moveCursor( Ansi.moveCursor(
this.#cursor.y - this.#offset.y, this.cursor.y - this.offset.y,
this.#renderX - this.#offset.x, 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<void> { private async clearScreen(): Promise<void> {
this.#buffer.append(Ansi.ClearScreen); this.buffer.append(Ansi.ClearScreen);
this.#buffer.append(Ansi.ResetCursor); this.buffer.append(Ansi.ResetCursor);
await this.#buffer.flush(); await this.buffer.flush();
} }
private drawRows(): void { private drawRows(): void {
for (let y = 0; y < this.#screen.rows; y++) { for (let y = 0; y < this.screen.rows; y++) {
this.#buffer.append(Ansi.ClearLine); this.buffer.append(Ansi.ClearLine);
const fileRow = y + this.#offset.y; const fileRow = y + this.offset.y;
if (fileRow >= this.numRows) { if (fileRow >= this.numRows) {
this.drawPlaceholderRow(fileRow); this.drawPlaceholderRow(fileRow);
} else { } else {
this.drawFileRow(fileRow); this.drawFileRow(fileRow);
} }
this.#buffer.appendLine(); this.buffer.appendLine();
} }
} }
private drawFileRow(y: number): void { private drawFileRow(y: number): void {
const maybeRow = this.#document.row(y); const maybeRow = this.document.row(y);
if (maybeRow.isNone()) { if (maybeRow.isNone()) {
log(`Trying to draw non-existent row '${y}'`, LogLevel.Warning); log(`Trying to draw non-existent row '${y}'`, LogLevel.Warning);
return this.drawPlaceholderRow(y); return this.drawPlaceholderRow(y);
@ -503,61 +515,59 @@ class Editor {
const row = maybeRow.unwrap(); const row = maybeRow.unwrap();
const len = Math.min( const len = Math.min(
posSub(row.rsize, this.#offset.x), posSub(row.rsize, this.offset.x),
this.#screen.cols, 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 { 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 message = `Scroll editor -- version ${SCROLL_VERSION}`;
const messageLen = (message.length > this.#screen.cols) const messageLen = (message.length > this.screen.cols)
? this.#screen.cols ? this.screen.cols
: message.length; : message.length;
let padding = Math.trunc((this.#screen.cols - messageLen) / 2); let padding = Math.trunc((this.screen.cols - messageLen) / 2);
if (padding > 0) { if (padding > 0) {
this.#buffer.append('~'); this.buffer.append('~');
padding -= 1; padding -= 1;
this.#buffer.append(' '.repeat(padding)); this.buffer.append(' '.repeat(padding));
} }
this.#buffer.append(message, messageLen); this.buffer.append(message, messageLen);
} else { } else {
this.#buffer.append('~'); this.buffer.append('~');
} }
} }
private drawStatusBar(): void { private drawStatusBar(): void {
this.#buffer.append(Ansi.InvertColor); this.buffer.append(Ansi.InvertColor);
const name = (this.#filename !== '') ? this.#filename : '[No Name]'; const name = (this.filename !== '') ? this.filename : '[No Name]';
const modified = (this.#document.dirty) ? '(modified)' : ''; const modified = (this.document.dirty) ? '(modified)' : '';
const status = `${truncate(name, 20)} - ${this.numRows} lines ${modified}`; const status = `${truncate(name, 25)} - ${this.numRows} lines ${modified}`;
const rStatus = `${this.#cursor.y + 1}/${this.numRows}`; const rStatus = `${this.cursor.y + 1},${this.cursor.x + 1}/${this.numRows}`;
let len = Math.min(status.length, this.#screen.cols); let len = Math.min(status.length, this.screen.cols);
this.#buffer.append(status, len); this.buffer.append(status, len);
while (len < this.#screen.cols) { while (len < this.screen.cols) {
if (this.#screen.cols - len === rStatus.length) { if (this.screen.cols - len === rStatus.length) {
this.#buffer.append(rStatus); this.buffer.append(rStatus);
break; break;
} else { } else {
this.#buffer.append(' '); this.buffer.append(' ');
len++; len++;
} }
} }
this.#buffer.appendLine(Ansi.ResetFormatting); this.buffer.appendLine(Ansi.ResetFormatting);
} }
private drawMessageBar(): void { private drawMessageBar(): void {
this.#buffer.append(Ansi.ClearLine); this.buffer.append(Ansi.ClearLine);
const msgLen = this.#statusMessage.length; const msgLen = this.statusMessage.length;
if (msgLen > 0 && (Date.now() - this.#statusTimeout < 5000)) { if (msgLen > 0 && (Date.now() - this.statusTimeout < 5000)) {
this.#buffer.append(this.#statusMessage, this.#screen.cols); this.buffer.append(this.statusMessage, this.screen.cols);
} }
} }
} }
export default Editor;