Basic number highlighting
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2024-02-29 15:48:11 -05:00
parent 55c9dc1c3d
commit 33f19ddec1
7 changed files with 145 additions and 86 deletions

View File

@ -220,8 +220,8 @@ testSuite({
}, },
'.cxToRx, .rxToCx': () => { '.cxToRx, .rxToCx': () => {
const row = Row.from('foo\tbar\tbaz'); const row = Row.from('foo\tbar\tbaz');
row.updateRender(); row.update();
assertNotEquals(row.chars, row.render); assertNotEquals(row.chars, row.rchars);
assertNotEquals(row.size, row.rsize); assertNotEquals(row.size, row.rsize);
assertEquals(row.size, 11); assertEquals(row.size, 11);
assertEquals(row.rsize, row.size + (SCROLL_TAB_SIZE * 2) - 2); assertEquals(row.rsize, row.size + (SCROLL_TAB_SIZE * 2) - 2);

View File

@ -1,49 +1,8 @@
import Row from './row.ts'; import Row from './row.ts';
import { arrayInsert } from './fns.ts'; import { arrayInsert } from './fns.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime } from './runtime.ts';
import { Position, SearchDirection } from './types.ts'; import { Position } from './types.ts';
import { KeyCommand } from './ansi.ts'; import { Search } from './search.ts';
class Search {
public lastMatch: number = -1;
public current: number = -1;
public direction: SearchDirection = SearchDirection.Forward;
public parseInput(key: string) {
switch (key) {
case KeyCommand.ArrowRight:
case KeyCommand.ArrowDown:
this.direction = SearchDirection.Forward;
break;
case KeyCommand.ArrowLeft:
case KeyCommand.ArrowUp:
this.direction = SearchDirection.Backward;
break;
default:
this.lastMatch = -1;
this.direction = SearchDirection.Forward;
}
if (this.lastMatch === -1) {
this.direction = SearchDirection.Forward;
}
this.current = this.lastMatch;
}
public getNextRow(rowCount: number): number {
this.current += this.direction;
if (this.current === -1) {
this.current = rowCount - 1;
} else if (this.current === rowCount) {
this.current = 0;
}
return this.current;
}
}
export class Document { export class Document {
#rows: Row[]; #rows: Row[];
@ -65,7 +24,10 @@ export class Document {
} }
public static default(): Document { public static default(): Document {
return new Document(); const self = new Document();
self.#search.parent = self;
return self;
} }
public isEmpty(): boolean { public isEmpty(): boolean {
@ -105,26 +67,14 @@ export class Document {
public resetFind() { public resetFind() {
this.#search = new Search(); this.#search = new Search();
this.#search.parent = this;
} }
public find( public find(
q: string, q: string,
key: string, key: string,
): Position | null { ): Position | null {
this.#search.parseInput(key); return this.#search.search(q, key);
let i = 0;
for (; i < this.numRows; i++) {
const current = this.#search.getNextRow(this.numRows);
const possible = this.#rows[current].find(q);
if (possible !== null) {
this.#search.lastMatch = current;
return Position.at(possible, current);
}
}
return null;
} }
public insert(at: Position, c: string): void { public insert(at: Position, c: string): void {
@ -132,7 +82,7 @@ export class Document {
this.insertRow(this.numRows, c); this.insertRow(this.numRows, c);
} else { } else {
this.#rows[at.y].insertChar(at.x, c); this.#rows[at.y].insertChar(at.x, c);
this.#rows[at.y].updateRender(); this.#rows[at.y].update();
} }
this.dirty = true; this.dirty = true;
@ -149,7 +99,7 @@ export class Document {
} }
const newRow = this.#rows[at.y].split(at.x); const newRow = this.#rows[at.y].split(at.x);
newRow.updateRender(); newRow.update();
this.#rows = arrayInsert(this.#rows, at.y + 1, newRow); this.#rows = arrayInsert(this.#rows, at.y + 1, newRow);
this.dirty = true; this.dirty = true;
@ -188,7 +138,7 @@ export class Document {
row.delete(at.x); row.delete(at.x);
} }
row.updateRender(); row.update();
this.dirty = true; this.dirty = true;
} }
@ -199,7 +149,7 @@ export class Document {
public insertRow(at: number = this.numRows, s: string = ''): void { public insertRow(at: number = this.numRows, s: string = ''): void {
this.#rows = arrayInsert(this.#rows, at, Row.from(s)); this.#rows = arrayInsert(this.#rows, at, Row.from(s));
this.#rows[at].updateRender(); this.#rows[at].update();
this.dirty = true; this.dirty = true;
} }

View File

@ -482,7 +482,7 @@ class Editor {
this.#screen.cols, this.#screen.cols,
); );
this.#buffer.append(row.rstring(this.#offset.x), len); this.#buffer.append(row.render(this.#offset.x, len));
} }
private drawPlaceholderRow(y: number): void { private drawPlaceholderRow(y: number): void {

16
src/common/highlight.ts Normal file
View File

@ -0,0 +1,16 @@
import Ansi, { AnsiColor } from './ansi.ts';
export enum HighlightType {
None,
Number,
}
export function highlightToColor(type: HighlightType): string {
switch (type) {
case HighlightType.Number:
return Ansi.color256(196);
default:
return Ansi.color(AnsiColor.FgDefault);
}
}

View File

@ -1,8 +1,12 @@
import { SCROLL_TAB_SIZE } from './config.ts'; import { SCROLL_TAB_SIZE } from './config.ts';
import { arrayInsert, strChars } from './fns.ts'; import { arrayInsert, isAsciiDigit, strChars } from './fns.ts';
import { highlightToColor, HighlightType } from './highlight.ts';
import Ansi from './ansi.ts';
/** /**
* One row of text in the current document * One row of text in the current document. In order to handle
* multi-byte graphemes, all operations are done on an
* array of 'character' strings.
*/ */
export class Row { export class Row {
/** /**
@ -14,16 +18,16 @@ export class Row {
* The characters rendered for the current row * The characters rendered for the current row
* (like replacing tabs with spaces) * (like replacing tabs with spaces)
*/ */
render: string[] = []; rchars: string[] = [];
/** /**
* The syntax highlighting map * The syntax highlighting map
*/ */
hl: string[] = []; hl: HighlightType[] = [];
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.render = []; this.rchars = [];
} }
public get size(): number { public get size(): number {
@ -31,11 +35,11 @@ export class Row {
} }
public get rsize(): number { public get rsize(): number {
return this.render.length; return this.rchars.length;
} }
public rstring(offset: number = 0): string { public rstring(offset: number = 0): string {
return this.render.slice(offset).join(''); return this.rchars.slice(offset).join('');
} }
public static default(): Row { public static default(): Row {
@ -52,7 +56,7 @@ export class Row {
public append(s: string): void { public append(s: string): void {
this.chars = this.chars.concat(strChars(s)); this.chars = this.chars.concat(strChars(s));
this.updateRender(); this.update();
} }
public insertChar(at: number, c: string): void { public insertChar(at: number, c: string): void {
@ -67,7 +71,7 @@ export class Row {
public split(at: number): Row { public split(at: number): Row {
const newRow = new Row(this.chars.slice(at)); const newRow = new Row(this.chars.slice(at));
this.chars = this.chars.slice(0, at); this.chars = this.chars.slice(0, at);
this.updateRender(); this.update();
return newRow; return newRow;
} }
@ -161,13 +165,45 @@ export class Row {
return this.chars.join(''); return this.chars.join('');
} }
public updateRender(): void { public update(): void {
const newString = this.chars.join('').replaceAll( const newString = this.chars.join('').replaceAll(
'\t', '\t',
' '.repeat(SCROLL_TAB_SIZE), ' '.repeat(SCROLL_TAB_SIZE),
); );
this.render = strChars(newString); this.rchars = strChars(newString);
this.highlight();
}
public render(offset: number, len: number): string {
const end = Math.min(len, this.rsize);
const start = Math.min(offset, len);
let result = '';
for (let i = start; i < end; i++) {
// if (this.chars[i] === '\t') {
// result += ' '.repeat(SCROLL_TAB_SIZE);
// } else {
result += highlightToColor(this.hl[i]);
result += this.rchars[i];
result += Ansi.ResetFormatting;
}
// }
return result;
}
private highlight(): void {
const highlighting = [];
for (const ch of this.rchars) {
if (isAsciiDigit(ch)) {
highlighting.push(HighlightType.Number);
} else {
highlighting.push(HighlightType.None);
}
}
this.hl = highlighting;
} }
} }

67
src/common/search.ts Normal file
View File

@ -0,0 +1,67 @@
import { Position } from './types.ts';
import { KeyCommand } from './ansi.ts';
import Document from './document.ts';
enum SearchDirection {
Forward = 1,
Backward = -1,
}
export class Search {
private lastMatch: number = -1;
private current: number = -1;
private direction: SearchDirection = SearchDirection.Forward;
public parent: Document | null = null;
private parseInput(key: string) {
switch (key) {
case KeyCommand.ArrowRight:
case KeyCommand.ArrowDown:
this.direction = SearchDirection.Forward;
break;
case KeyCommand.ArrowLeft:
case KeyCommand.ArrowUp:
this.direction = SearchDirection.Backward;
break;
default:
this.lastMatch = -1;
this.direction = SearchDirection.Forward;
}
if (this.lastMatch === -1) {
this.direction = SearchDirection.Forward;
}
this.current = this.lastMatch;
}
private getNextRow(rowCount: number): number {
this.current += this.direction;
if (this.current === -1) {
this.current = rowCount - 1;
} else if (this.current === rowCount) {
this.current = 0;
}
return this.current;
}
public search(q: string, key: string): Position | null {
this.parseInput(key);
let i = 0;
for (; i < this.parent!.numRows; i++) {
const current = this.getNextRow(this.parent!.numRows);
const possible = this.parent!.row(current)!.find(q);
if (possible !== null) {
this.lastMatch = current;
return Position.at(possible, current);
}
}
return null;
}
}

View File

@ -157,16 +157,6 @@ export class Position {
} }
} }
export enum SearchDirection {
Forward = 1,
Backward = -1,
}
export enum HighlightType {
None,
Number,
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Testing // Testing
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------