From 4df0c70c32ec62f50c0a36a0acd6a8917ba56432 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Mon, 20 Nov 2023 15:14:36 -0500 Subject: [PATCH] Add PageUp/PageDown and Home/End scrolling --- src/common/document.ts | 32 ++++++++++++++++++++++++- src/common/editor.ts | 54 +++++++++++++++++++++++++++++++----------- src/common/mod.ts | 1 + src/deno/ffi.ts | 2 +- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/common/document.ts b/src/common/document.ts index c0b97f7..c8aaa51 100644 --- a/src/common/document.ts +++ b/src/common/document.ts @@ -1,17 +1,41 @@ import { chars } from './utils.ts'; import { getRuntime } from './runtime.ts'; +import { TAB_SIZE } from './mod.ts'; export class Row { chars: string[] = []; + render: string[] = []; constructor(s: string = '') { this.chars = chars(s); + this.render = []; } public get size(): number { return this.chars.length; } + public get rsize(): number { + return this.render.length; + } + + public rstring(offset: number = 0): string { + return this.render.slice(offset).join(''); + } + + public cxToRx(cx: number): number { + let rx = 0; + let j = 0; + for (; j < cx; j++) { + if (this.chars[j] === '\t') { + rx += (TAB_SIZE - 1) - (rx % TAB_SIZE); + } + rx++; + } + + return rx; + } + public toString(): string { return this.chars.join(''); } @@ -56,7 +80,13 @@ export class Document { } public appendRow(s: string): void { - this.#rows.push(new Row(s)); + const at = this.numRows; + this.#rows[at] = new Row(s); + this.updateRow(this.#rows[at]); + } + + private updateRow(r: Row): void { + r.render = chars(r.toString().replace('\t', ' '.repeat(TAB_SIZE))); } } diff --git a/src/common/editor.ts b/src/common/editor.ts index 790628e..51b1bba 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -1,7 +1,7 @@ import Ansi, { KeyCommand } from './ansi.ts'; import Buffer from './buffer.ts'; import Document, { Row } from './document.ts'; -import { IPoint, ITerminalSize, logToFile, VERSION } from './mod.ts'; +import { IPoint, ITerminalSize, logToFile, maxAdd, VERSION } from './mod.ts'; import { ctrlKey, posSub } from './utils.ts'; export class Editor { @@ -29,10 +29,11 @@ export class Editor { * @private */ #document: Document; - - private get currentRow(): Row | null { - return this.#document.row(this.#cursor.y); - } + /** + * The scrolling offset for the rendered row + * @private + */ + #render: IPoint; constructor(terminalSize: ITerminalSize) { this.#buffer = new Buffer(); @@ -45,10 +46,18 @@ export class Editor { x: 0, y: 0, }; + this.#render = { + x: 0, + y: 0, + }; this.#document = Document.empty(); } + private get currentRow(): Row | null { + return this.#document.row(this.#cursor.y); + } + public async open(filename: string): Promise { await this.#document.open(filename); @@ -74,12 +83,24 @@ export class Editor { break; case KeyCommand.End: - this.#cursor.x = this.#screen.cols - 1; + if (this.currentRow !== null) { + this.#cursor.x = this.currentRow.size - 1; + } 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.#document.numRows, + ); + } + let times = this.#screen.rows; while (times--) { this.moveCursor( @@ -146,17 +167,22 @@ export class Editor { } private scroll(): void { + this.#render.x = 0; + if (this.currentRow !== null) { + this.#render.x = 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.#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.#render.x < this.#offset.x) { + this.#offset.x = this.#render.x; } - if (this.#cursor.x >= this.#offset.x + this.#screen.cols) { - this.#offset.x = this.#cursor.x - this.#screen.cols + 1; + if (this.#render.x >= this.#offset.x + this.#screen.cols) { + this.#offset.x = this.#render.x - this.#screen.cols + 1; } } @@ -175,7 +201,7 @@ export class Editor { this.#buffer.append( Ansi.moveCursor( this.#cursor.y - this.#offset.y, - this.#cursor.x - this.#offset.x, + this.#render.x - this.#offset.x, ), ); this.#buffer.append(Ansi.ShowCursor); @@ -192,6 +218,7 @@ export class Editor { private drawRows(): void { for (let y = 0; y < this.#screen.rows; y++) { + this.#buffer.append(Ansi.ClearLine); const filerow = y + this.#offset.y; if (filerow >= this.#document.numRows) { this.drawPlaceholderRow(filerow); @@ -199,7 +226,6 @@ export class Editor { this.drawFileRow(filerow); } - this.#buffer.append(Ansi.ClearLine); if (y < this.#screen.rows - 1) { this.#buffer.appendLine(); } @@ -214,11 +240,11 @@ export class Editor { } const len = Math.min( - posSub(row.chars.length, this.#offset.x), + posSub(row.rsize, this.#offset.x), this.#screen.cols, ); - this.#buffer.append(row.toString(), len); + this.#buffer.append(row.rstring(this.#offset.x), len); } private drawPlaceholderRow(y: number): void { diff --git a/src/common/mod.ts b/src/common/mod.ts index 91e7f84..3619bbd 100644 --- a/src/common/mod.ts +++ b/src/common/mod.ts @@ -5,3 +5,4 @@ export * from './utils.ts'; export type * from './types.ts'; export const VERSION = '0.0.1'; +export const TAB_SIZE = 4; diff --git a/src/deno/ffi.ts b/src/deno/ffi.ts index 01c8786..ab3012c 100644 --- a/src/deno/ffi.ts +++ b/src/deno/ffi.ts @@ -39,7 +39,7 @@ const DenoFFI: IFFI = { tcsetattr, cfmakeraw, getPointer: Deno.UnsafePointer.of, - close: cStdLib.close, + close: () => {}, }; export default DenoFFI;