From ea00f76a6203443c6c220a37bace95d774604fdb Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Thu, 18 Jul 2024 13:41:45 -0400 Subject: [PATCH] Add render optimizations from hecto, fix rendering of multiline comments --- src/common/all_test.ts | 4 ++ src/common/document.ts | 86 +++++++++++++++++++++++------------------- src/common/editor.ts | 10 ++++- src/common/fns.ts | 11 ++++++ src/common/row.ts | 55 +++++++++++++++------------ 5 files changed, 101 insertions(+), 65 deletions(-) diff --git a/src/common/all_test.ts b/src/common/all_test.ts index cb4376d..b7fc6e7 100644 --- a/src/common/all_test.ts +++ b/src/common/all_test.ts @@ -361,6 +361,10 @@ const DocumentTest = { doc.insert(Position.at(9, 0), 'buzz'); assertEquals(doc.numRows, 1); assertTrue(doc.dirty); + + // Update row + doc.highlight(None, None); + const row0 = doc.row(0).unwrap(); assertEquals(row0.toString(), 'foobazbarbuzz'); assertEquals(row0.rstring(), 'foobazbarbuzz'); diff --git a/src/common/document.ts b/src/common/document.ts index 77cf7af..4599855 100644 --- a/src/common/document.ts +++ b/src/common/document.ts @@ -58,9 +58,9 @@ export class Document { const rawFile = await file.openFile(filename); rawFile.split(/\r?\n/) - .forEach((row) => this.insertRow(this.numRows, row)); - - this.highlight(None); + .forEach((row) => { + this.#rows.push(Row.from(row)); + }); this.dirty = false; @@ -76,16 +76,20 @@ export class Document { await file.saveFile(filename, this.rowsToString()); this.type = FileType.from(filename); - // Re-highlight the file - this.highlight(None); - this.dirty = false; } + /** + * Find the cursor position of the query, if it exists + * + * @param q - the search query + * @param at - the point from which to start the search + * @param direction - which direction to search, backward or forward + */ public find( q: string, at: Position, - direction: SearchDirection = SearchDirection.Forward, + direction: SearchDirection, ): Option { if (at.y >= this.numRows) { logWarning('Trying to search beyond the end of the current file', { @@ -127,23 +131,6 @@ export class Document { return None; } - public insert(at: Position, c: string): void { - if (at.y > this.numRows) { - return; - } - - this.dirty = true; - - if (at.y === this.numRows) { - this.insertRow(this.numRows, c); - } else { - this.#rows[at.y].insertChar(at.x, c); - } - - // Re-highlight the file - this.highlight(None); - } - /** * Insert a new line, splitting and/or creating a new row as needed */ @@ -166,9 +153,30 @@ export class Document { const currentRow = this.#rows[at.y]; const newRow = currentRow.split(at.x, this.type); this.#rows = arrayInsert(this.#rows, at.y + 1, newRow); + } - // Re-highlight the file - this.highlight(None); + public insert(at: Position, c: string): void { + if (at.y > this.numRows) { + return; + } + + this.dirty = true; + + if (at.y === this.numRows) { + this.#rows.push(Row.from(c)); + } else { + this.#rows[at.y].insertChar(at.x, c); + } + + this.unHighlightRows(at.y); + } + + protected unHighlightRows(start: number): void { + if (this.numRows < start && start >= 1) { + for (let i = start - 1; i < this.numRows; i++) { + this.#rows[i].isHighlighted = false; + } + } } /** @@ -207,8 +215,7 @@ export class Document { row.delete(at.x); } - // Re-highlight the file - this.highlight(None); + this.unHighlightRows(at.y); } public row(i: number): Option { @@ -219,19 +226,20 @@ export class Document { return Option.from(this.#rows.at(i)); } - public highlight(searchMatch: Option): void { + public highlight(searchMatch: Option, limit: Option): void { let startWithComment = false; - this.#rows.forEach((row) => { - startWithComment = row.update(searchMatch, this.type, startWithComment); - }); - } + let until = this.numRows; + if (limit.isSome() && (limit.unwrap() + 1 < this.numRows)) { + until = limit.unwrap() + 1; + } - protected insertRow( - at: number = this.numRows, - s: string = '', - ): void { - this.#rows = arrayInsert(this.#rows, at, Row.from(s)); - this.dirty = true; + for (let i = 0; i < until; i++) { + startWithComment = this.#rows[i].update( + searchMatch, + this.type, + startWithComment, + ); + } } /** diff --git a/src/common/editor.ts b/src/common/editor.ts index 8544e6c..169cc19 100644 --- a/src/common/editor.ts +++ b/src/common/editor.ts @@ -58,6 +58,8 @@ export default class Editor { */ protected quitTimes: number = SCROLL_QUIT_TIMES; + protected highlightedWord: Option = None; + constructor(terminalSize: ITerminalSize) { this.buffer = new Buffer(); @@ -300,7 +302,7 @@ export default class Editor { this.moveCursor(KeyCommand.ArrowLeft); } - this.document.highlight(Some(query)); + this.highlightedWord = Some(query); } }, ); @@ -313,7 +315,7 @@ export default class Editor { this.scroll(); } - this.document.highlight(None); + this.highlightedWord = None; } /** @@ -438,6 +440,10 @@ export default class Editor { this.scroll(); this.buffer.append(Ansi.HideCursor); this.buffer.append(Ansi.ResetCursor); + this.document.highlight( + this.highlightedWord, + Some(this.offset.y + this.screen.rows), + ); this.drawRows(); this.drawStatusBar(); this.drawMessageBar(); diff --git a/src/common/fns.ts b/src/common/fns.ts index fb4add6..dcc6f1a 100644 --- a/src/common/fns.ts +++ b/src/common/fns.ts @@ -152,6 +152,17 @@ export function strlen(s: string): number { return strChars(s).length; } +/** + * Get a slice of a string + * + * @param s - the string + * @param from - the 'character' index of the start of the slice + * @param to - the 'character' index of the last character you want + */ +export function substr(s: string, from: number, to?: number): string { + return strChars(s).slice(from, to).join(''); +} + /** * Are all the characters in the string in ASCII range? * diff --git a/src/common/row.ts b/src/common/row.ts index e56293a..f3224ec 100644 --- a/src/common/row.ts +++ b/src/common/row.ts @@ -7,6 +7,7 @@ import { isSeparator, strChars, strlen, + substr, } from './fns.ts'; import { FileType } from './filetype/mod.ts'; import { highlightToColor, HighlightType } from './highlight.ts'; @@ -35,6 +36,11 @@ export class Row { */ public hl: HighlightType[] = []; + /** + * Has the current row been highlighted? + */ + public isHighlighted: boolean = false; + private constructor(s: string | string[] = '') { this.chars = Array.isArray(s) ? s : strChars(s); this.rchars = []; @@ -102,7 +108,7 @@ export class Row { /** * Search the current row for the specified string, and return - * the 'character' index of the start of that match + * the render 'character' index of the start of that match */ public find( s: string, @@ -142,13 +148,6 @@ export class Row { return (byteIndex >= 0) ? Some(this.byteIndexToCharIndex(byteIndex)) : None; } - public rLastIndexOf(s: string, offset: number = 0): Option { - const rstring = this.rchars.join(''); - const byteIndex = rstring.lastIndexOf(s, this.charIndexToByteIndex(offset)); - - return (byteIndex >= 0) ? Some(this.byteIndexToCharIndex(byteIndex)) : None; - } - /** * Convert the raw row offset to the equivalent offset for screen rendering */ @@ -247,19 +246,30 @@ export class Row { syntax: FileType, startWithComment: boolean, ): boolean { - this.hl = []; + // Check for the end of a multiline comment + // if we are currently in one + if (this.isHighlighted && word.isNone()) { + const lastHl = this.hl[this.hl.length - 1]; + return lastHl === HighlightType.MultiLineComment && + syntax.hasMultilineComments() && + this.size > 1 && + substr(this.toString(), this.size - 2) === + syntax.multiLineCommentEnd.unwrap(); + } + this.hl = []; let i = 0; let inMlComment = startWithComment; if (inMlComment && syntax.hasMultilineComments()) { - const maybEnd = this.rIndexOf(syntax.multiLineCommentEnd.unwrap(), i); - const closingIndex = (maybEnd.isSome()) - ? maybEnd.unwrap() + 1 + const maybeEnd = this.rIndexOf(syntax.multiLineCommentEnd.unwrap(), i); + const closingIndex = (maybeEnd.isSome()) + ? maybeEnd.unwrap() + 2 : this.rsize; for (; i < closingIndex; i++) { this.hl.push(HighlightType.MultiLineComment); } + i = closingIndex; } for (; i < this.rsize;) { @@ -282,12 +292,12 @@ export class Row { if (maybeNext.isSome()) { const next = maybeNext.unwrap(); - if (next < this.rsize) { - i = maybeNext.unwrap(); - continue; + if (next >= this.rsize) { + break; } - break; + i = next; + continue; } this.hl.push(HighlightType.None); i += 1; @@ -295,17 +305,14 @@ export class Row { this.highlightMatch(word); if (inMlComment && syntax.hasMultilineComments()) { - const commentEnd = syntax.multiLineCommentEnd.unwrap(); - const maybeIndex = this.rLastIndexOf(commentEnd); - - if (maybeIndex.isNone()) { + if ( + substr(this.toString(), this.size - 2) !== + syntax.multiLineCommentEnd.unwrap() + ) { return true; } - - const lastIndex = maybeIndex.unwrap(); - return lastIndex !== this.rsize - 2; } - + this.isHighlighted = true; return false; }