Add render optimizations from hecto, fix rendering of multiline comments
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-07-18 13:41:45 -04:00
parent 2b3be61933
commit ea00f76a62
5 changed files with 101 additions and 65 deletions

View File

@ -361,6 +361,10 @@ const DocumentTest = {
doc.insert(Position.at(9, 0), 'buzz'); doc.insert(Position.at(9, 0), 'buzz');
assertEquals(doc.numRows, 1); assertEquals(doc.numRows, 1);
assertTrue(doc.dirty); assertTrue(doc.dirty);
// Update row
doc.highlight(None, None);
const row0 = doc.row(0).unwrap(); const row0 = doc.row(0).unwrap();
assertEquals(row0.toString(), 'foobazbarbuzz'); assertEquals(row0.toString(), 'foobazbarbuzz');
assertEquals(row0.rstring(), 'foobazbarbuzz'); assertEquals(row0.rstring(), 'foobazbarbuzz');

View File

@ -58,9 +58,9 @@ export class Document {
const rawFile = await file.openFile(filename); const rawFile = await file.openFile(filename);
rawFile.split(/\r?\n/) rawFile.split(/\r?\n/)
.forEach((row) => this.insertRow(this.numRows, row)); .forEach((row) => {
this.#rows.push(Row.from(row));
this.highlight(None); });
this.dirty = false; this.dirty = false;
@ -76,16 +76,20 @@ export class Document {
await file.saveFile(filename, this.rowsToString()); await file.saveFile(filename, this.rowsToString());
this.type = FileType.from(filename); this.type = FileType.from(filename);
// Re-highlight the file
this.highlight(None);
this.dirty = false; 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( public find(
q: string, q: string,
at: Position, at: Position,
direction: SearchDirection = SearchDirection.Forward, direction: SearchDirection,
): Option<Position> { ): Option<Position> {
if (at.y >= this.numRows) { if (at.y >= this.numRows) {
logWarning('Trying to search beyond the end of the current file', { logWarning('Trying to search beyond the end of the current file', {
@ -127,23 +131,6 @@ export class Document {
return None; 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 * 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 currentRow = this.#rows[at.y];
const newRow = currentRow.split(at.x, this.type); const newRow = currentRow.split(at.x, this.type);
this.#rows = arrayInsert(this.#rows, at.y + 1, newRow); this.#rows = arrayInsert(this.#rows, at.y + 1, newRow);
}
// Re-highlight the file public insert(at: Position, c: string): void {
this.highlight(None); 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); row.delete(at.x);
} }
// Re-highlight the file this.unHighlightRows(at.y);
this.highlight(None);
} }
public row(i: number): Option<Row> { public row(i: number): Option<Row> {
@ -219,19 +226,20 @@ export class Document {
return Option.from(this.#rows.at(i)); return Option.from(this.#rows.at(i));
} }
public highlight(searchMatch: Option<string>): void { public highlight(searchMatch: Option<string>, limit: Option<number>): void {
let startWithComment = false; let startWithComment = false;
this.#rows.forEach((row) => { let until = this.numRows;
startWithComment = row.update(searchMatch, this.type, startWithComment); if (limit.isSome() && (limit.unwrap() + 1 < this.numRows)) {
}); until = limit.unwrap() + 1;
} }
protected insertRow( for (let i = 0; i < until; i++) {
at: number = this.numRows, startWithComment = this.#rows[i].update(
s: string = '', searchMatch,
): void { this.type,
this.#rows = arrayInsert(this.#rows, at, Row.from(s)); startWithComment,
this.dirty = true; );
}
} }
/** /**

View File

@ -58,6 +58,8 @@ export default class Editor {
*/ */
protected quitTimes: number = SCROLL_QUIT_TIMES; protected quitTimes: number = SCROLL_QUIT_TIMES;
protected highlightedWord: Option<string> = None;
constructor(terminalSize: ITerminalSize) { constructor(terminalSize: ITerminalSize) {
this.buffer = new Buffer(); this.buffer = new Buffer();
@ -300,7 +302,7 @@ export default class Editor {
this.moveCursor(KeyCommand.ArrowLeft); this.moveCursor(KeyCommand.ArrowLeft);
} }
this.document.highlight(Some(query)); this.highlightedWord = Some(query);
} }
}, },
); );
@ -313,7 +315,7 @@ export default class Editor {
this.scroll(); this.scroll();
} }
this.document.highlight(None); this.highlightedWord = None;
} }
/** /**
@ -438,6 +440,10 @@ export default class Editor {
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.document.highlight(
this.highlightedWord,
Some(this.offset.y + this.screen.rows),
);
this.drawRows(); this.drawRows();
this.drawStatusBar(); this.drawStatusBar();
this.drawMessageBar(); this.drawMessageBar();

View File

@ -152,6 +152,17 @@ export function strlen(s: string): number {
return strChars(s).length; 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? * Are all the characters in the string in ASCII range?
* *

View File

@ -7,6 +7,7 @@ import {
isSeparator, isSeparator,
strChars, strChars,
strlen, strlen,
substr,
} from './fns.ts'; } from './fns.ts';
import { FileType } from './filetype/mod.ts'; import { FileType } from './filetype/mod.ts';
import { highlightToColor, HighlightType } from './highlight.ts'; import { highlightToColor, HighlightType } from './highlight.ts';
@ -35,6 +36,11 @@ export class Row {
*/ */
public hl: HighlightType[] = []; public hl: HighlightType[] = [];
/**
* Has the current row been highlighted?
*/
public isHighlighted: boolean = false;
private constructor(s: string | string[] = '') { private constructor(s: string | string[] = '') {
this.chars = Array.isArray(s) ? s : strChars(s); this.chars = Array.isArray(s) ? s : strChars(s);
this.rchars = []; this.rchars = [];
@ -102,7 +108,7 @@ export class Row {
/** /**
* Search the current row for the specified string, and return * 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( public find(
s: string, s: string,
@ -142,13 +148,6 @@ export class Row {
return (byteIndex >= 0) ? Some(this.byteIndexToCharIndex(byteIndex)) : None; return (byteIndex >= 0) ? Some(this.byteIndexToCharIndex(byteIndex)) : None;
} }
public rLastIndexOf(s: string, offset: number = 0): Option<number> {
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 * Convert the raw row offset to the equivalent offset for screen rendering
*/ */
@ -247,19 +246,30 @@ export class Row {
syntax: FileType, syntax: FileType,
startWithComment: boolean, startWithComment: boolean,
): 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 i = 0;
let inMlComment = startWithComment; let inMlComment = startWithComment;
if (inMlComment && syntax.hasMultilineComments()) { if (inMlComment && syntax.hasMultilineComments()) {
const maybEnd = this.rIndexOf(syntax.multiLineCommentEnd.unwrap(), i); const maybeEnd = this.rIndexOf(syntax.multiLineCommentEnd.unwrap(), i);
const closingIndex = (maybEnd.isSome()) const closingIndex = (maybeEnd.isSome())
? maybEnd.unwrap() + 1 ? maybeEnd.unwrap() + 2
: this.rsize; : this.rsize;
for (; i < closingIndex; i++) { for (; i < closingIndex; i++) {
this.hl.push(HighlightType.MultiLineComment); this.hl.push(HighlightType.MultiLineComment);
} }
i = closingIndex;
} }
for (; i < this.rsize;) { for (; i < this.rsize;) {
@ -282,12 +292,12 @@ export class Row {
if (maybeNext.isSome()) { if (maybeNext.isSome()) {
const next = maybeNext.unwrap(); const next = maybeNext.unwrap();
if (next < this.rsize) { if (next >= this.rsize) {
i = maybeNext.unwrap(); break;
continue;
} }
break; i = next;
continue;
} }
this.hl.push(HighlightType.None); this.hl.push(HighlightType.None);
i += 1; i += 1;
@ -295,17 +305,14 @@ export class Row {
this.highlightMatch(word); this.highlightMatch(word);
if (inMlComment && syntax.hasMultilineComments()) { if (inMlComment && syntax.hasMultilineComments()) {
const commentEnd = syntax.multiLineCommentEnd.unwrap(); if (
const maybeIndex = this.rLastIndexOf(commentEnd); substr(this.toString(), this.size - 2) !==
syntax.multiLineCommentEnd.unwrap()
if (maybeIndex.isNone()) { ) {
return true; return true;
} }
const lastIndex = maybeIndex.unwrap();
return lastIndex !== this.rsize - 2;
} }
this.isHighlighted = true;
return false; return false;
} }