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');
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');

View File

@ -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<Position> {
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<Row> {
@ -219,19 +226,20 @@ export class Document {
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;
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,
);
}
}
/**

View File

@ -58,6 +58,8 @@ export default class Editor {
*/
protected quitTimes: number = SCROLL_QUIT_TIMES;
protected highlightedWord: Option<string> = 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();

View File

@ -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?
*

View File

@ -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<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
*/
@ -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;
}