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 tsconfig.tsbuildinfo
########################################################################################################################
##########################################################################################
# Bun-specific commands
########################################################################################################################
##########################################################################################
# Check code with actual Typescript compiler
bun-check:
@ -46,9 +46,9 @@ bun-test:
bun-run file="":
bun run ./src/scroll.ts {{file}}
########################################################################################################################
##########################################################################################
# Deno-specific commands
########################################################################################################################
##########################################################################################
# Lint code and check types
deno-check:

View File

@ -1,5 +1,6 @@
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 { Position } from './types.ts';
import { Search } from './search.ts';
@ -74,7 +75,26 @@ export class Document {
q: string,
key: string,
): 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 {

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> {
const savedCursor = Position.from(this.#cursor);
@ -314,6 +315,7 @@ class Editor {
if (query !== null && query.length > 0) {
const pos = this.#document.find(query, key);
if (pos !== null) {
// We have a match here
this.#cursor = pos;
this.scroll();
} 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
*/
chars: string[] = [];
public chars: string[] = [];
/**
* The characters rendered for the current row
* (like replacing tabs with spaces)
*/
rchars: string[] = [];
public rchars: string[] = [];
/**
* The syntax highlighting map
*/
hl: HighlightType[] = [];
public hl: HighlightType[] = [];
private constructor(s: string | string[] = '') {
this.chars = Array.isArray(s) ? s : strChars(s);
@ -84,6 +84,10 @@ export class Row {
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 {
const thisStr = this.toString();
if (!this.toString().includes(s)) {

View File

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

View File

@ -1,5 +1,7 @@
import { RunTimeType } from './runtime.ts';
export { Position } from './position.ts';
/**
* The size of terminal in rows and columns
*/
@ -100,32 +102,6 @@ export type ITerminal = IRuntime['term'];
*/
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
// ----------------------------------------------------------------------------