Add incremental (character by character) search functionality
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
b3177cbd48
commit
e21944b4a4
6
justfile
6
justfile
@ -50,11 +50,11 @@ bun-run file="":
|
||||
# Lint code and check types
|
||||
deno-check:
|
||||
deno lint
|
||||
deno check --unstable --all -c deno.jsonc ./src/deno/*.ts ./src/common/*.ts
|
||||
deno check --unstable-ffi --all -c deno.jsonc ./src/deno/*.ts ./src/common/*.ts
|
||||
|
||||
# Test with deno
|
||||
deno-test:
|
||||
deno test --allow-all --unstable
|
||||
deno test --allow-all --unstable-ffi
|
||||
|
||||
# Create test coverage report with deno
|
||||
deno-coverage:
|
||||
@ -62,4 +62,4 @@ deno-coverage:
|
||||
|
||||
# Run with deno
|
||||
deno-run file="":
|
||||
deno run --allow-all --allow-ffi --deny-hrtime --unstable ./src/scroll.ts {{file}}
|
||||
deno run --allow-all --allow-ffi --deny-hrtime --unstable-ffi ./src/scroll.ts {{file}}
|
||||
|
@ -138,6 +138,19 @@ testSuite({
|
||||
assertEquals(p.x, 5);
|
||||
assertEquals(p.y, 7);
|
||||
},
|
||||
'.from': () => {
|
||||
const p1 = Position.at(1, 2);
|
||||
const p2 = Position.from(p1);
|
||||
|
||||
p1.x = 2;
|
||||
p1.y = 4;
|
||||
|
||||
assertEquals(p1.x, 2);
|
||||
assertEquals(p1.y, 4);
|
||||
|
||||
assertEquals(p2.x, 1);
|
||||
assertEquals(p2.y, 2);
|
||||
},
|
||||
},
|
||||
Row: {
|
||||
'.default': () => {
|
||||
|
@ -231,10 +231,18 @@ class Editor {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async prompt(p: string): Promise<string | null> {
|
||||
public async prompt(
|
||||
p: string,
|
||||
callback?: (query: string, char: string) => void,
|
||||
): Promise<string | null> {
|
||||
const { term } = await getRuntime();
|
||||
|
||||
let res = '';
|
||||
const maybeCallback = (query: string, char: string) => {
|
||||
if (callback !== undefined) {
|
||||
callback(query, char);
|
||||
}
|
||||
};
|
||||
|
||||
outer: while (true) {
|
||||
if (p.includes('%s')) {
|
||||
@ -250,26 +258,38 @@ class Editor {
|
||||
continue;
|
||||
}
|
||||
|
||||
// End the prompt
|
||||
if (char === KeyCommand.Enter) {
|
||||
this.setStatusMessage('');
|
||||
if (res.length === 0) {
|
||||
switch (char) {
|
||||
// Remove the last character from the prompt input
|
||||
case KeyCommand.Backspace:
|
||||
case KeyCommand.Delete:
|
||||
res = truncate(res, res.length - 1);
|
||||
maybeCallback(res, char);
|
||||
continue outer;
|
||||
|
||||
// End the prompt
|
||||
case KeyCommand.Escape:
|
||||
this.setStatusMessage('');
|
||||
maybeCallback(res, char);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return res;
|
||||
// Return the input and end the prompt
|
||||
case KeyCommand.Enter:
|
||||
if (res.length > 0) {
|
||||
this.setStatusMessage('');
|
||||
maybeCallback(res, char);
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
|
||||
// Add to the prompt result
|
||||
default:
|
||||
if (!isControl(char)) {
|
||||
res += char;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow backspacing
|
||||
if (char === KeyCommand.Backspace || char === KeyCommand.Delete) {
|
||||
res = truncate(res, res.length - 1);
|
||||
continue outer;
|
||||
}
|
||||
|
||||
// Add to the prompt result
|
||||
if (!isControl(char!)) {
|
||||
res += char;
|
||||
}
|
||||
maybeCallback(res, char);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -278,14 +298,32 @@ class Editor {
|
||||
* Find text within the document
|
||||
*/
|
||||
public async find(): Promise<void> {
|
||||
const res = await this.prompt('Search: %s (ESC to cancel)');
|
||||
if (res !== null && res.length > 0) {
|
||||
const pos = this.#document.find(res);
|
||||
if (pos !== null) {
|
||||
this.#cursor = pos;
|
||||
} else {
|
||||
this.setStatusMessage('Not found');
|
||||
}
|
||||
const savedCursor = Position.from(this.#cursor);
|
||||
const savedOffset = Position.from(this.#offset);
|
||||
|
||||
const query = await this.prompt(
|
||||
'Search: %s (ESC to cancel)',
|
||||
(query: string, key: string) => {
|
||||
if (key === KeyCommand.Enter || key === KeyCommand.Escape) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (query !== null && query.length > 0) {
|
||||
const pos = this.#document.find(query);
|
||||
if (pos !== null) {
|
||||
this.#cursor = pos;
|
||||
} else {
|
||||
this.setStatusMessage('Not found');
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Return to document position before search
|
||||
// when you cancel the search (press the escape key)
|
||||
if (query !== null) {
|
||||
this.#cursor = Position.from(savedCursor);
|
||||
this.#offset = Position.from(savedOffset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ export class Position {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
private constructor(x: number, y: number) {
|
||||
private constructor(x: number = 0, y: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
@ -148,9 +148,18 @@ export class Position {
|
||||
return new Position(x, y);
|
||||
}
|
||||
|
||||
public static default(): Position {
|
||||
return new Position(0, 0);
|
||||
public static from(p: Position): Position {
|
||||
return new Position(p.x, p.y);
|
||||
}
|
||||
|
||||
public static default(): Position {
|
||||
return new Position();
|
||||
}
|
||||
}
|
||||
|
||||
export enum SearchDirection {
|
||||
Forward = 1,
|
||||
Backward = -1,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
Loading…
Reference in New Issue
Block a user