diff --git a/src/common/document.ts b/src/common/document.ts index ec3f86a..c0b97f7 100644 --- a/src/common/document.ts +++ b/src/common/document.ts @@ -8,6 +8,10 @@ export class Row { this.chars = chars(s); } + public get size(): number { + return this.chars.length; + } + public toString(): string { return this.chars.join(''); } @@ -48,11 +52,7 @@ export class Document { } public row(i: number): Row | null { - if (this.#rows[i] !== undefined) { - return this.#rows[i]; - } - - return null; + return this.#rows[i] ?? null; } public appendRow(s: string): void { diff --git a/src/common/editor.ts b/src/common/editor.ts index 0c428be..790628e 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -1,8 +1,8 @@ import Ansi, { KeyCommand } from './ansi.ts'; import Buffer from './buffer.ts'; -import Document from './document.ts'; +import Document, { Row } from './document.ts'; import { IPoint, ITerminalSize, logToFile, VERSION } from './mod.ts'; -import { ctrlKey } from './utils.ts'; +import { ctrlKey, posSub } from './utils.ts'; export class Editor { /** @@ -29,6 +29,11 @@ export class Editor { * @private */ #document: Document; + + private get currentRow(): Row | null { + return this.#document.row(this.#cursor.y); + } + constructor(terminalSize: ITerminalSize) { this.#buffer = new Buffer(); this.#screen = terminalSize; @@ -102,11 +107,24 @@ export class Editor { 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 - 1 + : 0; } break; case KeyCommand.ArrowRight: - if (this.#cursor.x < this.#screen.cols) { + if ( + this.currentRow !== null && this.#cursor.x < this.currentRow.size - 1 + ) { this.#cursor.x++; + } else if ( + this.currentRow !== null && + this.#cursor.x === this.currentRow.size - 1 + ) { + this.#cursor.y++; + this.#cursor.x = 0; } break; case KeyCommand.ArrowUp: @@ -115,11 +133,16 @@ export class Editor { } break; case KeyCommand.ArrowDown: - if (this.#cursor.y < this.#screen.rows) { + if (this.#cursor.y < this.#document.numRows) { this.#cursor.y++; } break; } + + const rowLen = this.currentRow?.size ?? 0; + if (this.#cursor.x > rowLen) { + this.#cursor.x = rowLen; + } } private scroll(): void { @@ -129,6 +152,12 @@ export class Editor { if (this.#cursor.y >= this.#offset.y + this.#screen.rows) { this.#offset.y = this.#cursor.y - this.#screen.rows + 1; } + if (this.#cursor.x < this.#offset.x) { + this.#offset.x = this.#cursor.x; + } + if (this.#cursor.x >= this.#offset.x + this.#screen.cols) { + this.#offset.x = this.#cursor.x - this.#screen.cols + 1; + } } // -------------------------------------------------------------------------- @@ -144,7 +173,10 @@ export class Editor { this.#buffer.append(Ansi.ResetCursor); this.drawRows(); this.#buffer.append( - Ansi.moveCursor(this.#cursor.y, this.#cursor.x), + Ansi.moveCursor( + this.#cursor.y - this.#offset.y, + this.#cursor.x - this.#offset.x, + ), ); this.#buffer.append(Ansi.ShowCursor); @@ -160,11 +192,16 @@ export class Editor { private drawRows(): void { for (let y = 0; y < this.#screen.rows; y++) { - let filerow = y + this.#offset.y; + const filerow = y + this.#offset.y; if (filerow >= this.#document.numRows) { - this.drawPlaceholderRow(y); + this.drawPlaceholderRow(filerow); } else { - this.drawFileRow(y); + this.drawFileRow(filerow); + } + + this.#buffer.append(Ansi.ClearLine); + if (y < this.#screen.rows - 1) { + this.#buffer.appendLine(); } } } @@ -176,13 +213,12 @@ export class Editor { return this.drawPlaceholderRow(y); } - let len = row.chars.length ?? 0; - if (len > this.#screen.cols) { - len = this.#screen.cols; - } + const len = Math.min( + posSub(row.chars.length, this.#offset.x), + this.#screen.cols, + ); this.#buffer.append(row.toString(), len); - this.#buffer.appendLine(Ansi.ClearLine); } private drawPlaceholderRow(y: number): void { @@ -203,10 +239,5 @@ export class Editor { } else { this.#buffer.append('~'); } - - this.#buffer.append(Ansi.ClearLine); - if (y < this.#screen.rows - 1) { - this.#buffer.appendLine(); - } } } diff --git a/src/common/utils.ts b/src/common/utils.ts index 0f29ed5..da7c1f4 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -4,6 +4,35 @@ export const noop = () => {}; +/** + * Subtract two numbers, returning a zero if the result is negative + * @param l + * @param s + */ +export function posSub(l: number, s: number): number { + return minSub(l, s, 0); +} + +/** + * Subtract two numbers, returning at least the minimum specified + * @param l + * @param s + * @param min + */ +export function minSub(l: number, s: number, min: number): number { + return Math.max(l - s, min); +} + +/** + * Add two numbers, up to a max value + * @param n1 + * @param n2 + * @param max + */ +export function maxAdd(n1: number, n2: number, max: number): number { + return Math.min(n1 + n2, max); +} + // ---------------------------------------------------------------------------- // Strings // ----------------------------------------------------------------------------