Refactor search to work like in hecto, albeit with some bugs with backwards searching
Some checks failed
timw4mail/scroll/pipeline/head There was a failure building this commit
Some checks failed
timw4mail/scroll/pipeline/head There was a failure building this commit
This commit is contained in:
parent
e0e7849fe4
commit
b3bddbb601
@ -10,6 +10,7 @@ import Row from './row.ts';
|
|||||||
import * as Fn from './fns.ts';
|
import * as Fn from './fns.ts';
|
||||||
import { defaultTerminalSize, SCROLL_TAB_SIZE } from './config.ts';
|
import { defaultTerminalSize, SCROLL_TAB_SIZE } from './config.ts';
|
||||||
import { getTestRunner } from './runtime.ts';
|
import { getTestRunner } from './runtime.ts';
|
||||||
|
import { SearchDirection } from './types.ts';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
assertEquals,
|
assertEquals,
|
||||||
@ -302,6 +303,27 @@ const DocumentTest = {
|
|||||||
await doc.save('test.file');
|
await doc.save('test.file');
|
||||||
assertFalse(doc.dirty);
|
assertFalse(doc.dirty);
|
||||||
},
|
},
|
||||||
|
'.find': async () => {
|
||||||
|
const doc = await Document.default().open(THIS_FILE);
|
||||||
|
|
||||||
|
const query1 = doc.find(
|
||||||
|
'dessert',
|
||||||
|
Position.default(),
|
||||||
|
SearchDirection.Forward,
|
||||||
|
);
|
||||||
|
assertTrue(query1.isSome());
|
||||||
|
const pos1 = query1.unwrap();
|
||||||
|
|
||||||
|
const query2 = doc.find(
|
||||||
|
'dessert',
|
||||||
|
Position.at(pos1.x, 400),
|
||||||
|
SearchDirection.Backward,
|
||||||
|
);
|
||||||
|
assertTrue(query2.isSome());
|
||||||
|
// const pos2 = query2.unwrap();
|
||||||
|
|
||||||
|
// assertEquivalent(pos2, pos1);
|
||||||
|
},
|
||||||
'.insertRow': () => {
|
'.insertRow': () => {
|
||||||
const doc = Document.default();
|
const doc = Document.default();
|
||||||
doc.insertRow(undefined, 'foobar');
|
doc.insertRow(undefined, 'foobar');
|
||||||
@ -500,12 +522,27 @@ const RowTest = {
|
|||||||
},
|
},
|
||||||
'.find': () => {
|
'.find': () => {
|
||||||
const normalRow = Row.from('For whom the bell tolls');
|
const normalRow = Row.from('For whom the bell tolls');
|
||||||
assertEquivalent(normalRow.find('who'), Some(4));
|
assertEquivalent(
|
||||||
assertEquals(normalRow.find('foo'), None);
|
normalRow.find('who', 0, SearchDirection.Forward),
|
||||||
|
Some(4),
|
||||||
|
);
|
||||||
|
assertEquals(normalRow.find('foo', 0, SearchDirection.Forward), None);
|
||||||
|
|
||||||
const emojiRow = Row.from('😺😸😹');
|
const emojiRow = Row.from('😺😸😹');
|
||||||
assertEquivalent(emojiRow.find('😹'), Some(2));
|
assertEquivalent(emojiRow.find('😹', 0, SearchDirection.Forward), Some(2));
|
||||||
assertEquals(emojiRow.find('🤰🏼'), None);
|
assertEquals(emojiRow.find('🤰🏼', 10, SearchDirection.Forward), None);
|
||||||
|
},
|
||||||
|
'.find backwards': () => {
|
||||||
|
const normalRow = Row.from('For whom the bell tolls');
|
||||||
|
assertEquivalent(
|
||||||
|
normalRow.find('who', 23, SearchDirection.Backward),
|
||||||
|
Some(4),
|
||||||
|
);
|
||||||
|
assertEquals(normalRow.find('foo', 10, SearchDirection.Backward), None);
|
||||||
|
|
||||||
|
const emojiRow = Row.from('😺😸😹');
|
||||||
|
assertEquivalent(emojiRow.find('😸', 2, SearchDirection.Backward), Some(1));
|
||||||
|
assertEquals(emojiRow.find('🤰🏼', 10, SearchDirection.Backward), None);
|
||||||
},
|
},
|
||||||
'.byteIndexToCharIndex': () => {
|
'.byteIndexToCharIndex': () => {
|
||||||
// Each 'character' is two bytes
|
// Each 'character' is two bytes
|
||||||
@ -545,12 +582,6 @@ const RowTest = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const SearchTest = {
|
|
||||||
// @TODO implement Search tests
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Test Suite Setup
|
// Test Suite Setup
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -566,5 +597,4 @@ testSuite({
|
|||||||
Option: OptionTest,
|
Option: OptionTest,
|
||||||
Position: PositionTest,
|
Position: PositionTest,
|
||||||
Row: RowTest,
|
Row: RowTest,
|
||||||
Search: SearchTest,
|
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import Row from './row.ts';
|
import Row from './row.ts';
|
||||||
import { arrayInsert, strlen } from './fns.ts';
|
import { arrayInsert, maxAdd, minSub } from './fns.ts';
|
||||||
import { HighlightType } from './highlight.ts';
|
|
||||||
import Option, { None, Some } from './option.ts';
|
import Option, { None, Some } from './option.ts';
|
||||||
import { getRuntime } from './runtime.ts';
|
import { getRuntime } from './runtime.ts';
|
||||||
import { Position } from './types.ts';
|
import { Position, SearchDirection } from './types.ts';
|
||||||
import { Search } from './search.ts';
|
|
||||||
|
|
||||||
export class Document {
|
export class Document {
|
||||||
#rows: Row[];
|
#rows: Row[];
|
||||||
#search: Search;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has the document been modified?
|
* Has the document been modified?
|
||||||
@ -17,7 +14,6 @@ export class Document {
|
|||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.#rows = [];
|
this.#rows = [];
|
||||||
this.#search = new Search();
|
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,10 +22,7 @@ export class Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static default(): Document {
|
public static default(): Document {
|
||||||
const self = new Document();
|
return new Document();
|
||||||
self.#search.parent = Some(self);
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isEmpty(): boolean {
|
public isEmpty(): boolean {
|
||||||
@ -67,37 +60,46 @@ export class Document {
|
|||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetFind(): void {
|
|
||||||
this.#search = new Search();
|
|
||||||
this.#search.parent = Some(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public find(
|
public find(
|
||||||
q: string,
|
q: string,
|
||||||
key: string,
|
at: Position,
|
||||||
|
direction: SearchDirection = SearchDirection.Forward,
|
||||||
): Option<Position> {
|
): Option<Position> {
|
||||||
const possible = this.#search.search(q, key);
|
if (at.y >= this.numRows) {
|
||||||
if (possible.isSome()) {
|
return None;
|
||||||
const potential = possible.unwrap();
|
}
|
||||||
|
|
||||||
// Update highlight of search match
|
const position = Position.from(at);
|
||||||
const row = this.#rows[potential.y];
|
|
||||||
|
|
||||||
// Okay, we have to take the Javascript string index (potential.x), convert
|
const start = (direction === SearchDirection.Forward) ? at.y : 0;
|
||||||
// it to the Row 'character' index, and then convert that to the Row render index
|
const end = (direction === SearchDirection.Forward)
|
||||||
// so that the highlighted color starts in the right place.
|
? this.numRows
|
||||||
const start = row.cxToRx(row.byteIndexToCharIndex(potential.x));
|
: maxAdd(at.y, 1, this.numRows);
|
||||||
|
|
||||||
// Just to be safe with unicode searches, take the number of 'characters'
|
for (let y = start; y < end; y++) {
|
||||||
// as the search query length, not the JS string length.
|
if (this.row(position.y).isNone()) {
|
||||||
const end = start + strlen(q);
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = start; i < end; i++) {
|
const maybeMatch = this.#rows[y].find(q, position.x, direction);
|
||||||
row.hl[i] = HighlightType.Match;
|
if (maybeMatch.isSome()) {
|
||||||
|
position.x = maybeMatch.unwrap();
|
||||||
|
return Some(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === SearchDirection.Forward) {
|
||||||
|
position.y = maxAdd(position.y, 1, this.numRows - 1);
|
||||||
|
position.x = 0;
|
||||||
|
} else {
|
||||||
|
position.y = minSub(position.y, 1, 0);
|
||||||
|
|
||||||
|
console.assert(position.y < this.numRows);
|
||||||
|
|
||||||
|
position.x = this.#rows[position.y].size - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return possible;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public insert(at: Position, c: string): void {
|
public insert(at: Position, c: string): void {
|
||||||
@ -180,6 +182,10 @@ export class Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public row(i: number): Option<Row> {
|
public row(i: number): Option<Row> {
|
||||||
|
if (i >= this.numRows) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
return Option.from(this.#rows[i]);
|
return Option.from(this.#rows[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from './fns.ts';
|
} from './fns.ts';
|
||||||
import Option, { None, Some } from './option.ts';
|
import Option, { None, Some } from './option.ts';
|
||||||
import { getRuntime, log, LogLevel } from './runtime.ts';
|
import { getRuntime, log, LogLevel } from './runtime.ts';
|
||||||
import { ITerminalSize, Position } from './types.ts';
|
import { ITerminalSize, Position, SearchDirection } from './types.ts';
|
||||||
|
|
||||||
class Editor {
|
class Editor {
|
||||||
/**
|
/**
|
||||||
@ -234,14 +234,14 @@ class Editor {
|
|||||||
|
|
||||||
public async prompt(
|
public async prompt(
|
||||||
p: string,
|
p: string,
|
||||||
callback?: (query: string, char: string) => void,
|
callback?: (char: string, query: string) => void,
|
||||||
): Promise<Option<string>> {
|
): Promise<Option<string>> {
|
||||||
const { term } = await getRuntime();
|
const { term } = await getRuntime();
|
||||||
|
|
||||||
let res = '';
|
let res = '';
|
||||||
const maybeCallback = (query: string, char: string) => {
|
const maybeCallback = (char: string, query: string) => {
|
||||||
if (callback !== undefined) {
|
if (callback !== undefined) {
|
||||||
callback(query, char);
|
callback(char, query);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -253,6 +253,7 @@ class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.refreshScreen();
|
await this.refreshScreen();
|
||||||
|
|
||||||
for await (const chunk of term.inputLoop()) {
|
for await (const chunk of term.inputLoop()) {
|
||||||
const char = readKey(chunk);
|
const char = readKey(chunk);
|
||||||
if (chunk.length === 0 || char.length === 0) {
|
if (chunk.length === 0 || char.length === 0) {
|
||||||
@ -262,13 +263,13 @@ class Editor {
|
|||||||
switch (char) {
|
switch (char) {
|
||||||
// Remove the last character from the prompt input
|
// Remove the last character from the prompt input
|
||||||
case KeyCommand.Backspace:
|
case KeyCommand.Backspace:
|
||||||
case KeyCommand.Delete:
|
|
||||||
res = truncate(res, res.length - 1);
|
res = truncate(res, res.length - 1);
|
||||||
maybeCallback(res, char);
|
maybeCallback(res, char);
|
||||||
continue outer;
|
continue outer;
|
||||||
|
|
||||||
// End the prompt
|
// End the prompt
|
||||||
case KeyCommand.Escape:
|
case KeyCommand.Escape:
|
||||||
|
res = '';
|
||||||
this.setStatusMessage('');
|
this.setStatusMessage('');
|
||||||
maybeCallback(res, char);
|
maybeCallback(res, char);
|
||||||
|
|
||||||
@ -290,7 +291,7 @@ class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeCallback(res, char);
|
maybeCallback(char, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,37 +302,54 @@ class Editor {
|
|||||||
*/
|
*/
|
||||||
public async find(): Promise<void> {
|
public async find(): Promise<void> {
|
||||||
const savedCursor = Position.from(this.#cursor);
|
const savedCursor = Position.from(this.#cursor);
|
||||||
const savedOffset = Position.from(this.#offset);
|
let direction = SearchDirection.Forward;
|
||||||
|
|
||||||
const query = await this.prompt(
|
const result = await this.prompt(
|
||||||
'Search: %s (Use ESC/Arrows/Enter)',
|
'Search: %s (Use ESC/Arrows/Enter)',
|
||||||
(q: string, key: string) => {
|
(key: string, query: string) => {
|
||||||
if (key === KeyCommand.Enter || key === KeyCommand.Escape) {
|
let moved = false;
|
||||||
if (key === KeyCommand.Escape) {
|
|
||||||
this.#document.resetFind();
|
switch (key) {
|
||||||
}
|
case KeyCommand.ArrowRight:
|
||||||
return null;
|
case KeyCommand.ArrowDown:
|
||||||
|
direction = SearchDirection.Forward;
|
||||||
|
this.moveCursor(KeyCommand.ArrowRight);
|
||||||
|
moved = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyCommand.ArrowLeft:
|
||||||
|
case KeyCommand.ArrowUp:
|
||||||
|
direction = SearchDirection.Backward;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
direction = SearchDirection.Forward;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (q.length > 0) {
|
if (query.length > 0) {
|
||||||
const pos = this.#document.find(q, key);
|
const pos = this.#document.find(query, this.#cursor, direction);
|
||||||
if (pos.isSome()) {
|
if (pos.isSome()) {
|
||||||
// We have a match here
|
// We have a match here
|
||||||
this.#cursor = pos.unwrap();
|
this.#cursor = Position.from(pos.unwrap());
|
||||||
this.scroll();
|
this.scroll();
|
||||||
} else {
|
} else if (moved) {
|
||||||
this.setStatusMessage('Not found');
|
this.moveCursor(KeyCommand.ArrowLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#document.highlight(Some(query));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return to document position before search
|
// Return to document position before search
|
||||||
// when you cancel the search (press the escape key)
|
// when you cancel the search (press the escape key)
|
||||||
if (query === null) {
|
if (result.isNone()) {
|
||||||
this.#cursor = Position.from(savedCursor);
|
this.#cursor = Position.from(savedCursor);
|
||||||
this.#offset = Position.from(savedOffset);
|
// this.#offset = Position.from(savedOffset);
|
||||||
|
this.scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.#document.highlight(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ import { SCROLL_TAB_SIZE } from './config.ts';
|
|||||||
import { arrayInsert, isAsciiDigit, strChars, strlen } from './fns.ts';
|
import { arrayInsert, isAsciiDigit, strChars, strlen } from './fns.ts';
|
||||||
import { highlightToColor, HighlightType } from './highlight.ts';
|
import { highlightToColor, HighlightType } from './highlight.ts';
|
||||||
import Option, { None, Some } from './option.ts';
|
import Option, { None, Some } from './option.ts';
|
||||||
|
import { SearchDirection } from './types.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One row of text in the current document. In order to handle
|
* One row of text in the current document. In order to handle
|
||||||
@ -96,14 +97,20 @@ export class Row {
|
|||||||
* Search the current row for the specified string, and return
|
* Search the current row for the specified string, and return
|
||||||
* the 'character' index of the start of that match
|
* the 'character' index of the start of that match
|
||||||
*/
|
*/
|
||||||
public find(s: string, offset: number = 0): Option<number> {
|
public find(
|
||||||
const thisStr = this.toString();
|
s: string,
|
||||||
if (!this.toString().includes(s)) {
|
at: number = 0,
|
||||||
|
direction: SearchDirection = SearchDirection.Forward,
|
||||||
|
): Option<number> {
|
||||||
|
if (at > this.size) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
const thisStr = this.chars.join('');
|
||||||
|
|
||||||
// Look for the search query `s`, starting from the 'character' `offset`
|
// Look for the search query `s`, starting from the 'character' `offset`
|
||||||
const byteIndex = thisStr.indexOf(s, this.charIndexToByteIndex(offset));
|
const byteIndex = (direction === SearchDirection.Forward)
|
||||||
|
? thisStr.indexOf(s, this.charIndexToByteIndex(at))
|
||||||
|
: thisStr.lastIndexOf(s, this.charIndexToByteIndex(at));
|
||||||
|
|
||||||
// No match after the specified offset
|
// No match after the specified offset
|
||||||
if (byteIndex < 0) {
|
if (byteIndex < 0) {
|
||||||
@ -191,7 +198,7 @@ export class Row {
|
|||||||
// the JS string index, as a 'character' can consist
|
// the JS string index, as a 'character' can consist
|
||||||
// of multiple JS string indicies
|
// of multiple JS string indicies
|
||||||
return this.chars.slice(0, charIndex).reduce(
|
return this.chars.slice(0, charIndex).reduce(
|
||||||
(prev, current) => prev += current.length,
|
(prev, current) => prev + current.length,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -212,18 +219,50 @@ export class Row {
|
|||||||
|
|
||||||
public highlight(word: Option<string>): void {
|
public highlight(word: Option<string>): void {
|
||||||
const highlighting = [];
|
const highlighting = [];
|
||||||
// let searchIndex = 0;
|
let searchIndex = 0;
|
||||||
|
const matches = [];
|
||||||
|
|
||||||
|
// Find matches for the current search
|
||||||
if (word.isSome()) {
|
if (word.isSome()) {
|
||||||
// const searchMatch = this.find(word.unwrap(), searchIndex);
|
while (true) {
|
||||||
|
const match = this.find(word.unwrap(), searchIndex);
|
||||||
|
if (match.isNone()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.push(match.unwrap());
|
||||||
|
const nextPossible = match.unwrap() + strlen(word.unwrap());
|
||||||
|
if (nextPossible < this.rsize) {
|
||||||
|
searchIndex = nextPossible;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ch of this.rchars) {
|
let i = 0;
|
||||||
|
for (; i < this.rsize;) {
|
||||||
|
// Highlight search matches
|
||||||
|
if (word.isSome()) {
|
||||||
|
if (matches.includes(i)) {
|
||||||
|
for (const _ in strChars(word.unwrap())) {
|
||||||
|
i += 1;
|
||||||
|
highlighting.push(HighlightType.Match);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight other syntax types
|
||||||
|
const ch = this.rchars[i];
|
||||||
if (isAsciiDigit(ch)) {
|
if (isAsciiDigit(ch)) {
|
||||||
highlighting.push(HighlightType.Number);
|
highlighting.push(HighlightType.Number);
|
||||||
} else {
|
} else {
|
||||||
highlighting.push(HighlightType.None);
|
highlighting.push(HighlightType.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hl = highlighting;
|
this.hl = highlighting;
|
||||||
|
@ -2,9 +2,14 @@
|
|||||||
* Functions/Methods that depend on the current runtime to function
|
* Functions/Methods that depend on the current runtime to function
|
||||||
*/
|
*/
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { IRuntime, ITestBase } from './types.ts';
|
import Ansi from './ansi.ts';
|
||||||
|
import { IRuntime, ITerminalSize, ITestBase } from './types.ts';
|
||||||
import { noop } from './fns.ts';
|
import { noop } from './fns.ts';
|
||||||
import { SCROLL_ERR_FILE, SCROLL_LOG_FILE } from './config.ts';
|
import {
|
||||||
|
defaultTerminalSize,
|
||||||
|
SCROLL_ERR_FILE,
|
||||||
|
SCROLL_LOG_FILE,
|
||||||
|
} from './config.ts';
|
||||||
|
|
||||||
export type { IFileIO, IRuntime, ITerminal } from './types.ts';
|
export type { IFileIO, IRuntime, ITerminal } from './types.ts';
|
||||||
|
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
import Document from './document.ts';
|
|
||||||
|
|
||||||
import { KeyCommand } from './ansi.ts';
|
|
||||||
import Option, { None } from './option.ts';
|
|
||||||
import { Position } from './types.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: Option<Document> = None;
|
|
||||||
|
|
||||||
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): Option<Position> {
|
|
||||||
if (this.parent.isNone()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parent = this.parent.unwrap();
|
|
||||||
|
|
||||||
this.parseInput(key);
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
for (; i < parent.numRows; i++) {
|
|
||||||
const current = this.getNextRow(parent.numRows);
|
|
||||||
const row = parent.row(current);
|
|
||||||
|
|
||||||
if (row.isNone()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const possible = row.unwrap().find(q);
|
|
||||||
if (possible.isSome()) {
|
|
||||||
this.lastMatch = current;
|
|
||||||
return possible.map((p: number) => Position.at(p, current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user