Do basic highlighting of search results, finish stop #151 of the kilo tutorial
Some checks failed
timw4mail/scroll/pipeline/head There was a failure building this commit

This commit is contained in:
Timothy Warren 2024-06-21 14:14:10 -04:00
parent faf59b4235
commit e6b53ef327
7 changed files with 68 additions and 39 deletions

View File

@ -30,9 +30,9 @@ clean:
rm -f scroll.err rm -f scroll.err
rm -f tsconfig.tsbuildinfo rm -f tsconfig.tsbuildinfo
######################################################################################################################## ##########################################################################################
# Bun-specific commands # Bun-specific commands
######################################################################################################################## ##########################################################################################
# Check code with actual Typescript compiler # Check code with actual Typescript compiler
bun-check: bun-check:
@ -46,9 +46,9 @@ bun-test:
bun-run file="": bun-run file="":
bun run ./src/scroll.ts {{file}} bun run ./src/scroll.ts {{file}}
######################################################################################################################## ##########################################################################################
# Deno-specific commands # Deno-specific commands
######################################################################################################################## ##########################################################################################
# Lint code and check types # Lint code and check types
deno-check: deno-check:

View File

@ -1,5 +1,6 @@
import Row from './row.ts'; import Row from './row.ts';
import { arrayInsert } from './fns.ts'; import { arrayInsert, strlen } from './fns.ts';
import { HighlightType } from './highlight.ts';
import { getRuntime } from './runtime.ts'; import { getRuntime } from './runtime.ts';
import { Position } from './types.ts'; import { Position } from './types.ts';
import { Search } from './search.ts'; import { Search } from './search.ts';
@ -74,7 +75,26 @@ export class Document {
q: string, q: string,
key: string, key: string,
): Position | null { ): Position | null {
return this.#search.search(q, key); const potential = this.#search.search(q, key);
if (potential !== null) {
// Update highlight of search match
const row = this.#rows[potential.y];
// Okay, we have to take the Javascript string index (potential.x), convert
// it to the Row character index, and then convert that to the Row render index
// so that the highlighted color starts in the right place.
const start = row.cxToRx(row.byteIndexToCharIndex(potential.x));
// Just to be safe with unicode searches, take the number of 'characters'
// as the search query length, not the JS string length.
const end = start + strlen(q);
for (let i = start; i < end; i++) {
row.hl[i] = HighlightType.Match;
}
}
return potential;
} }
public insert(at: Position, c: string): void { public insert(at: Position, c: string): void {

View File

@ -295,7 +295,8 @@ class Editor {
} }
/** /**
* Find text within the document * Find text within the document. This is roughly equivalent to the
* `editorFindCallback` function in the kilo tutorial.
*/ */
public async find(): Promise<void> { public async find(): Promise<void> {
const savedCursor = Position.from(this.#cursor); const savedCursor = Position.from(this.#cursor);
@ -314,6 +315,7 @@ class Editor {
if (query !== null && query.length > 0) { if (query !== null && query.length > 0) {
const pos = this.#document.find(query, key); const pos = this.#document.find(query, key);
if (pos !== null) { if (pos !== null) {
// We have a match here
this.#cursor = pos; this.#cursor = pos;
this.scroll(); this.scroll();
} else { } else {

18
src/common/position.ts Normal file
View File

@ -0,0 +1,18 @@
/**
* Convenience type for (x,y) coordinate values
*/
export class Position {
private constructor(public x: number = 0, public y: number = 0) {}
public static at(x: number, y: number): Position {
return new Position(x, y);
}
public static from(p: Position): Position {
return new Position(p.x, p.y);
}
public static default(): Position {
return new Position();
}
}

View File

@ -12,18 +12,18 @@ export class Row {
/** /**
* The actual characters in the current row * The actual characters in the current row
*/ */
chars: string[] = []; public chars: string[] = [];
/** /**
* The characters rendered for the current row * The characters rendered for the current row
* (like replacing tabs with spaces) * (like replacing tabs with spaces)
*/ */
rchars: string[] = []; public rchars: string[] = [];
/** /**
* The syntax highlighting map * The syntax highlighting map
*/ */
hl: HighlightType[] = []; public 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);
@ -84,6 +84,10 @@ export class Row {
this.chars.splice(at, 1); this.chars.splice(at, 1);
} }
/**
* Search the current row for the specified string, and return
* the index of the start of that match
*/
public find(s: string, offset: number = 0): number | null { public find(s: string, offset: number = 0): number | null {
const thisStr = this.toString(); const thisStr = this.toString();
if (!this.toString().includes(s)) { if (!this.toString().includes(s)) {

View File

@ -49,13 +49,22 @@ export class Search {
} }
public search(q: string, key: string): Position | null { public search(q: string, key: string): Position | null {
if (this.parent === null) {
return null;
}
this.parseInput(key); this.parseInput(key);
let i = 0; let i = 0;
for (; i < this.parent!.numRows; i++) { for (; i < this.parent.numRows; i++) {
const current = this.getNextRow(this.parent!.numRows); const current = this.getNextRow(this.parent.numRows);
const row = this.parent.row(current);
const possible = this.parent!.row(current)!.find(q); if (row === null) {
continue;
}
const possible = row.find(q);
if (possible !== null) { if (possible !== null) {
this.lastMatch = current; this.lastMatch = current;
return Position.at(possible, current); return Position.at(possible, current);

View File

@ -1,5 +1,7 @@
import { RunTimeType } from './runtime.ts'; import { RunTimeType } from './runtime.ts';
export { Position } from './position.ts';
/** /**
* The size of terminal in rows and columns * The size of terminal in rows and columns
*/ */
@ -100,32 +102,6 @@ export type ITerminal = IRuntime['term'];
*/ */
export type IFileIO = IRuntime['file']; export type IFileIO = IRuntime['file'];
// ----------------------------------------------------------------------------
// General types
// ----------------------------------------------------------------------------
export class Position {
public x: number;
public y: number;
private constructor(x: number = 0, y: number = 0) {
this.x = x;
this.y = y;
}
public static at(x: number, y: number): Position {
return new Position(x, y);
}
public static from(p: Position): Position {
return new Position(p.x, p.y);
}
public static default(): Position {
return new Position();
}
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Testing // Testing
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------