Basic number highlighting
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
55c9dc1c3d
commit
33f19ddec1
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
16
src/common/highlight.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
67
src/common/search.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -157,16 +157,6 @@ export class Position {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SearchDirection {
|
|
||||||
Forward = 1,
|
|
||||||
Backward = -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum HighlightType {
|
|
||||||
None,
|
|
||||||
Number,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Testing
|
// Testing
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user