Add Option type to remove the need to use null/undefined
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
76eacd835f
commit
1b3e9d9796
@ -3,6 +3,7 @@ import Buffer from './buffer.ts';
|
||||
import Document from './document.ts';
|
||||
import Editor from './editor.ts';
|
||||
import { highlightToColor, HighlightType } from './highlight.ts';
|
||||
import _Option, { None, Some } from './option.ts';
|
||||
import Position from './position.ts';
|
||||
import Row from './row.ts';
|
||||
|
||||
@ -12,11 +13,10 @@ import { getTestRunner } from './runtime.ts';
|
||||
|
||||
const {
|
||||
assertStrictEquals: assertEquals,
|
||||
assertEquals: assertLooseEquals,
|
||||
assertEquals: assertEquivalent,
|
||||
assertExists,
|
||||
assertInstanceOf,
|
||||
assertNotEquals,
|
||||
assertNull,
|
||||
assertFalse,
|
||||
assertTrue,
|
||||
testSuite,
|
||||
@ -30,8 +30,6 @@ const THIS_FILE = './src/common/all_test.ts';
|
||||
|
||||
const fnTest = () => {
|
||||
const {
|
||||
some,
|
||||
none,
|
||||
arrayInsert,
|
||||
noop,
|
||||
posSub,
|
||||
@ -48,39 +46,25 @@ const fnTest = () => {
|
||||
} = Fn;
|
||||
|
||||
return {
|
||||
'some()': () => {
|
||||
assertFalse(some(null));
|
||||
assertFalse(some(void 0));
|
||||
assertFalse(some(undefined));
|
||||
assertTrue(some(0));
|
||||
assertTrue(some(false));
|
||||
},
|
||||
'none()': () => {
|
||||
assertTrue(none(null));
|
||||
assertTrue(none(void 0));
|
||||
assertTrue(none(undefined));
|
||||
assertFalse(none(0));
|
||||
assertFalse(none(false));
|
||||
},
|
||||
'arrayInsert() strings': () => {
|
||||
const a = ['😺', '😸', '😹'];
|
||||
const b = arrayInsert(a, 1, 'x');
|
||||
const c = ['😺', 'x', '😸', '😹'];
|
||||
assertLooseEquals(b, c);
|
||||
assertEquivalent(b, c);
|
||||
|
||||
const d = arrayInsert(c, 17, 'y');
|
||||
const e = ['😺', 'x', '😸', '😹', 'y'];
|
||||
assertLooseEquals(d, e);
|
||||
assertEquivalent(d, e);
|
||||
|
||||
assertLooseEquals(arrayInsert([], 0, 'foo'), ['foo']);
|
||||
assertEquivalent(arrayInsert([], 0, 'foo'), ['foo']);
|
||||
},
|
||||
'arrayInsert() numbers': () => {
|
||||
const a = [1, 3, 5];
|
||||
const b = [1, 3, 4, 5];
|
||||
assertLooseEquals(arrayInsert(a, 2, 4), b);
|
||||
assertEquivalent(arrayInsert(a, 2, 4), b);
|
||||
|
||||
const c = [1, 2, 3, 4, 5];
|
||||
assertLooseEquals(arrayInsert(b, 1, 2), c);
|
||||
assertEquivalent(arrayInsert(b, 1, 2), c);
|
||||
},
|
||||
'noop fn': () => {
|
||||
assertExists(noop);
|
||||
@ -106,7 +90,7 @@ const fnTest = () => {
|
||||
assertEquals(ord('a'), 97);
|
||||
},
|
||||
'strChars() properly splits strings into unicode characters': () => {
|
||||
assertLooseEquals(strChars('😺😸😹'), ['😺', '😸', '😹']);
|
||||
assertEquivalent(strChars('😺😸😹'), ['😺', '😸', '😹']);
|
||||
},
|
||||
'ctrlKey()': () => {
|
||||
const ctrl_a = ctrlKey('a');
|
||||
@ -432,6 +416,12 @@ const EditorTest = {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const OptionTest = {
|
||||
// @TODO implement Option tests
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const PositionTest = {
|
||||
'.default': () => {
|
||||
const p = Position.default();
|
||||
@ -498,12 +488,12 @@ const RowTest = {
|
||||
},
|
||||
'.find': () => {
|
||||
const normalRow = Row.from('For whom the bell tolls');
|
||||
assertEquals(normalRow.find('who'), 4);
|
||||
assertNull(normalRow.find('foo'));
|
||||
assertEquivalent(normalRow.find('who'), Some(4));
|
||||
assertEquals(normalRow.find('foo'), None);
|
||||
|
||||
const emojiRow = Row.from('😺😸😹');
|
||||
assertEquals(emojiRow.find('😹'), 2);
|
||||
assertNull(emojiRow.find('🤰🏼'));
|
||||
assertEquivalent(emojiRow.find('😹'), Some(2));
|
||||
assertEquals(emojiRow.find('🤰🏼'), None);
|
||||
},
|
||||
'.byteIndexToCharIndex': () => {
|
||||
// Each 'character' is two bytes
|
||||
@ -528,7 +518,7 @@ const RowTest = {
|
||||
},
|
||||
'.cxToRx, .rxToCx': () => {
|
||||
const row = Row.from('foo\tbar\tbaz');
|
||||
row.update();
|
||||
row.update(None);
|
||||
assertNotEquals(row.chars, row.rchars);
|
||||
assertNotEquals(row.size, row.rsize);
|
||||
assertEquals(row.size, 11);
|
||||
@ -545,7 +535,9 @@ const RowTest = {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const SearchTest = {};
|
||||
const SearchTest = {
|
||||
// @TODO implement Search tests
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test Suite Setup
|
||||
@ -559,6 +551,7 @@ testSuite({
|
||||
Buffer: BufferTest,
|
||||
Document: DocumentTest,
|
||||
Editor: EditorTest,
|
||||
Option: OptionTest,
|
||||
Position: PositionTest,
|
||||
Row: RowTest,
|
||||
Search: SearchTest,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Row from './row.ts';
|
||||
import { arrayInsert, some, strlen } from './fns.ts';
|
||||
import { arrayInsert, strlen } from './fns.ts';
|
||||
import { HighlightType } from './highlight.ts';
|
||||
import Option, { None, Some } from './option.ts';
|
||||
import { getRuntime } from './runtime.ts';
|
||||
import { Position } from './types.ts';
|
||||
import { Search } from './search.ts';
|
||||
@ -26,7 +27,7 @@ export class Document {
|
||||
|
||||
public static default(): Document {
|
||||
const self = new Document();
|
||||
self.#search.parent = self;
|
||||
self.#search.parent = Some(self);
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -68,15 +69,17 @@ export class Document {
|
||||
|
||||
public resetFind(): void {
|
||||
this.#search = new Search();
|
||||
this.#search.parent = this;
|
||||
this.#search.parent = Some(this);
|
||||
}
|
||||
|
||||
public find(
|
||||
q: string,
|
||||
key: string,
|
||||
): Position | null {
|
||||
const potential = this.#search.search(q, key);
|
||||
if (some(potential) && potential instanceof Position) {
|
||||
): Option<Position> {
|
||||
const possible = this.#search.search(q, key);
|
||||
if (possible.isSome()) {
|
||||
const potential = possible.unwrap();
|
||||
|
||||
// Update highlight of search match
|
||||
const row = this.#rows[potential.y];
|
||||
|
||||
@ -94,7 +97,7 @@ export class Document {
|
||||
}
|
||||
}
|
||||
|
||||
return potential;
|
||||
return possible;
|
||||
}
|
||||
|
||||
public insert(at: Position, c: string): void {
|
||||
@ -102,7 +105,7 @@ export class Document {
|
||||
this.insertRow(this.numRows, c);
|
||||
} else {
|
||||
this.#rows[at.y].insertChar(at.x, c);
|
||||
this.#rows[at.y].update();
|
||||
this.#rows[at.y].update(None);
|
||||
}
|
||||
|
||||
this.dirty = true;
|
||||
@ -126,7 +129,7 @@ export class Document {
|
||||
// Split the current row, and insert a new
|
||||
// row with the leftovers
|
||||
const newRow = this.#rows[at.y].split(at.x);
|
||||
newRow.update();
|
||||
newRow.update(None);
|
||||
this.#rows = arrayInsert(this.#rows, at.y + 1, newRow);
|
||||
|
||||
this.dirty = true;
|
||||
@ -165,7 +168,7 @@ export class Document {
|
||||
row.delete(at.x);
|
||||
}
|
||||
|
||||
row.update();
|
||||
row.update(None);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
@ -176,12 +179,12 @@ export class Document {
|
||||
|
||||
public insertRow(at: number = this.numRows, s: string = ''): void {
|
||||
this.#rows = arrayInsert(this.#rows, at, Row.from(s));
|
||||
this.#rows[at].update();
|
||||
this.#rows[at].update(None);
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
public highlight(searchMatch?: string): void {
|
||||
public highlight(searchMatch: Option<string>): void {
|
||||
this.#rows.forEach((row) => {
|
||||
row.update(searchMatch);
|
||||
});
|
||||
|
@ -8,12 +8,11 @@ import {
|
||||
ctrlKey,
|
||||
isControl,
|
||||
maxAdd,
|
||||
none,
|
||||
posSub,
|
||||
readKey,
|
||||
some,
|
||||
truncate,
|
||||
} from './fns.ts';
|
||||
import Option, { None, Some } from './option.ts';
|
||||
import { getRuntime, log, LogLevel } from './runtime.ts';
|
||||
import { ITerminalSize, Position } from './types.ts';
|
||||
|
||||
@ -100,12 +99,12 @@ class Editor {
|
||||
public async save(): Promise<void> {
|
||||
if (this.#filename === '') {
|
||||
const filename = await this.prompt('Save as: %s (ESC to cancel)');
|
||||
if (filename === null) {
|
||||
if (filename.isNone()) {
|
||||
this.setStatusMessage('Save aborted');
|
||||
return;
|
||||
}
|
||||
|
||||
this.#filename = filename;
|
||||
this.#filename = filename.unwrap();
|
||||
}
|
||||
|
||||
await this.#document.save(this.#filename);
|
||||
@ -236,7 +235,7 @@ class Editor {
|
||||
public async prompt(
|
||||
p: string,
|
||||
callback?: (query: string, char: string) => void,
|
||||
): Promise<string | null> {
|
||||
): Promise<Option<string>> {
|
||||
const { term } = await getRuntime();
|
||||
|
||||
let res = '';
|
||||
@ -256,7 +255,7 @@ class Editor {
|
||||
await this.refreshScreen();
|
||||
for await (const chunk of term.inputLoop()) {
|
||||
const char = readKey(chunk);
|
||||
if (none(char)) {
|
||||
if (char.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -273,14 +272,14 @@ class Editor {
|
||||
this.setStatusMessage('');
|
||||
maybeCallback(res, char);
|
||||
|
||||
return null;
|
||||
return None;
|
||||
|
||||
// Return the input and end the prompt
|
||||
case KeyCommand.Enter:
|
||||
if (res.length > 0) {
|
||||
this.setStatusMessage('');
|
||||
maybeCallback(res, char);
|
||||
return res;
|
||||
return Some(res);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -314,11 +313,11 @@ class Editor {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (some(query) && query.length > 0) {
|
||||
if (query.length > 0) {
|
||||
const pos = this.#document.find(query, key);
|
||||
if (pos !== null) {
|
||||
if (pos.isSome()) {
|
||||
// We have a match here
|
||||
this.#cursor = pos;
|
||||
this.#cursor = pos.unwrap();
|
||||
this.scroll();
|
||||
} else {
|
||||
this.setStatusMessage('Not found');
|
||||
|
@ -11,20 +11,6 @@ const decoder = new TextDecoder();
|
||||
*/
|
||||
export const noop = () => {};
|
||||
|
||||
/**
|
||||
* Does a value exist? (not null or undefined)
|
||||
*/
|
||||
export function some(v: unknown): boolean {
|
||||
return v !== null && typeof v !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the value null or undefined?
|
||||
*/
|
||||
export function none(v: unknown): boolean {
|
||||
return v === null || typeof v === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert input from ANSI escape sequences into a form
|
||||
* that can be more easily mapped to editor commands
|
||||
@ -149,10 +135,10 @@ export function ord(s: string): number {
|
||||
/**
|
||||
* Split a string by graphemes, not just bytes
|
||||
*
|
||||
* @param s - the string to split into 'characters'
|
||||
* @param s - the string to split into unicode code points
|
||||
*/
|
||||
export function strChars(s: string): string[] {
|
||||
return s.split(/(?:)/u);
|
||||
return [...s];
|
||||
}
|
||||
|
||||
/**
|
||||
|
175
src/common/option.ts
Normal file
175
src/common/option.ts
Normal file
@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Rust-style optional type
|
||||
*
|
||||
* Based on https://gist.github.com/s-panferov/575da5a7131c285c0539
|
||||
*/
|
||||
export default interface Option<T> {
|
||||
isSome(): boolean;
|
||||
isNone(): boolean;
|
||||
isSomeAnd(fn: (a: T) => boolean): boolean;
|
||||
isNoneAnd(fn: () => boolean): boolean;
|
||||
unwrap(): T | never;
|
||||
unwrapOr(def: T): T;
|
||||
unwrapOrElse(f: () => T): T;
|
||||
map<U>(f: (a: T) => U): Option<U>;
|
||||
mapOr<U>(def: U, f: (a: T) => U): U;
|
||||
mapOrElse<U>(def: () => U, f: (a: T) => U): U;
|
||||
and<U>(optb: Option<U>): Option<U>;
|
||||
andThen<U>(f: (a: T) => Option<U>): Option<U>;
|
||||
or(optb: Option<T>): Option<T>;
|
||||
orElse(f: () => Option<T>): Option<T>;
|
||||
}
|
||||
|
||||
class _Some<T> implements Option<T> {
|
||||
private value: T;
|
||||
|
||||
constructor(v: T) {
|
||||
this.value = v;
|
||||
}
|
||||
|
||||
static wrapNull<T>(value: T): Option<T> {
|
||||
if (value == null) {
|
||||
return None;
|
||||
} else {
|
||||
return new _Some<T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
map<U>(fn: (a: T) => U): Option<U> {
|
||||
return new _Some(fn(this.value));
|
||||
}
|
||||
|
||||
mapOr<U>(_def: U, f: (a: T) => U): U {
|
||||
return f(this.value);
|
||||
}
|
||||
|
||||
mapOrElse<U>(_def: () => U, f: (a: T) => U): U {
|
||||
return f(this.value);
|
||||
}
|
||||
|
||||
isSome(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isNone(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isSomeAnd(fn: (a: T) => boolean): boolean {
|
||||
return fn(this.value);
|
||||
}
|
||||
|
||||
isNoneAnd(_fn: () => boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
unwrap(): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapOr(_def: T): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
unwrapOrElse(_f: () => T): T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
and<U>(optb: Option<U>): Option<U> {
|
||||
return optb;
|
||||
}
|
||||
|
||||
andThen<U>(f: (a: T) => Option<U>): Option<U> {
|
||||
return f(this.value);
|
||||
}
|
||||
|
||||
or(_optb: Option<T>): Option<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
orElse(_f: () => Option<T>): Option<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return 'Some ' + this.value;
|
||||
}
|
||||
}
|
||||
|
||||
class _None<T> implements Option<T> {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
map<U>(_fn: (a: T) => U): Option<U> {
|
||||
return <Option<U>> _None._instance;
|
||||
}
|
||||
|
||||
isSome(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isNone(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isSomeAnd(_fn: (a: T) => boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
isNoneAnd(fn: () => boolean): boolean {
|
||||
return fn();
|
||||
}
|
||||
|
||||
unwrap(): never {
|
||||
console.error('None.unwrap()');
|
||||
throw 'None.get';
|
||||
}
|
||||
|
||||
unwrapOr(def: T): T {
|
||||
return def;
|
||||
}
|
||||
|
||||
unwrapOrElse(f: () => T): T {
|
||||
return f();
|
||||
}
|
||||
|
||||
mapOr<U>(def: U, _f: (a: T) => U): U {
|
||||
return def;
|
||||
}
|
||||
|
||||
mapOrElse<U>(def: () => U, _f: (a: T) => U): U {
|
||||
return def();
|
||||
}
|
||||
|
||||
and<U>(_optb: Option<U>): Option<U> {
|
||||
return _None.instance<U>();
|
||||
}
|
||||
|
||||
andThen<U>(_f: (a: T) => Option<U>): Option<U> {
|
||||
return _None.instance<U>();
|
||||
}
|
||||
|
||||
or(optb: Option<T>): Option<T> {
|
||||
return optb;
|
||||
}
|
||||
|
||||
orElse(f: () => Option<T>): Option<T> {
|
||||
return f();
|
||||
}
|
||||
|
||||
private static _instance: Option<any> = new _None();
|
||||
|
||||
public static instance<X>(): Option<X> {
|
||||
return <Option<X>> _None._instance;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return 'None';
|
||||
}
|
||||
}
|
||||
|
||||
export const None: Option<any> = _None.instance();
|
||||
|
||||
export function Some<T>(value: T): Option<T> {
|
||||
return _Some.wrapNull(value);
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import { SCROLL_TAB_SIZE } from './config.ts';
|
||||
import { arrayInsert, isAsciiDigit, some, strChars } from './fns.ts';
|
||||
import { highlightToColor, HighlightType } from './highlight.ts';
|
||||
import Ansi from './ansi.ts';
|
||||
|
||||
import { SCROLL_TAB_SIZE } from './config.ts';
|
||||
import { arrayInsert, isAsciiDigit, strChars } from './fns.ts';
|
||||
import { highlightToColor, HighlightType } from './highlight.ts';
|
||||
import Option, { None, Some } from './option.ts';
|
||||
|
||||
/**
|
||||
* One row of text in the current document. In order to handle
|
||||
* multi-byte graphemes, all operations are done on an
|
||||
@ -56,7 +58,7 @@ export class Row {
|
||||
|
||||
public append(s: string): void {
|
||||
this.chars = this.chars.concat(strChars(s));
|
||||
this.update();
|
||||
this.update(None);
|
||||
}
|
||||
|
||||
public insertChar(at: number, c: string): void {
|
||||
@ -74,7 +76,7 @@ export class Row {
|
||||
public split(at: number): Row {
|
||||
const newRow = new Row(this.chars.slice(at));
|
||||
this.chars = this.chars.slice(0, at);
|
||||
this.update();
|
||||
this.update(None);
|
||||
|
||||
return newRow;
|
||||
}
|
||||
@ -92,25 +94,31 @@ export class Row {
|
||||
|
||||
/**
|
||||
* Search the current row for the specified string, and return
|
||||
* the index of the start of that match
|
||||
* the 'character' index of the start of that match
|
||||
*/
|
||||
public find(s: string, offset: number = 0): number | null {
|
||||
public find(s: string, offset: number = 0): Option<number> {
|
||||
const thisStr = this.toString();
|
||||
if (!this.toString().includes(s)) {
|
||||
return null;
|
||||
return None;
|
||||
}
|
||||
|
||||
const byteCount = thisStr.indexOf(s, this.charIndexToByteIndex(offset));
|
||||
// Look for the search query `s`, starting from the 'character' `offset`
|
||||
const byteIndex = thisStr.indexOf(s, this.charIndexToByteIndex(offset));
|
||||
|
||||
// No match after the specified offset
|
||||
if (byteIndex < 0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// In many cases, the string length will
|
||||
// equal the number of characters. So
|
||||
// searching is fairly easy
|
||||
if (thisStr.length === this.chars.length) {
|
||||
return byteCount;
|
||||
return Some(byteIndex);
|
||||
}
|
||||
|
||||
// Emoji/Extended Unicode-friendly search
|
||||
return this.byteIndexToCharIndex(byteCount);
|
||||
return Some(this.byteIndexToCharIndex(byteIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,6 +187,9 @@ export class Row {
|
||||
return charIndex;
|
||||
}
|
||||
|
||||
// The char index will be the same size or smaller than
|
||||
// the JS string index, as a 'character' can consist
|
||||
// of multiple JS string indicies
|
||||
return this.chars.slice(0, charIndex).reduce(
|
||||
(prev, current) => prev += current.length,
|
||||
0,
|
||||
@ -189,21 +200,22 @@ export class Row {
|
||||
return this.chars.join('');
|
||||
}
|
||||
|
||||
public update(searchMatch?: string): void {
|
||||
public update(word: Option<string>): void {
|
||||
const newString = this.chars.join('').replaceAll(
|
||||
'\t',
|
||||
' '.repeat(SCROLL_TAB_SIZE),
|
||||
);
|
||||
|
||||
this.rchars = strChars(newString);
|
||||
this.highlight(searchMatch);
|
||||
this.highlight(word);
|
||||
}
|
||||
|
||||
public highlight(searchMatch?: string): void {
|
||||
public highlight(word: Option<string>): void {
|
||||
const highlighting = [];
|
||||
// let searchIndex = 0;
|
||||
|
||||
if (some(searchMatch)) {
|
||||
// TODO: highlight search here
|
||||
if (word.isSome()) {
|
||||
// const searchMatch = this.find(word.unwrap(), searchIndex);
|
||||
}
|
||||
|
||||
for (const ch of this.rchars) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Position } from './types.ts';
|
||||
import { KeyCommand } from './ansi.ts';
|
||||
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,
|
||||
@ -11,7 +13,7 @@ export class Search {
|
||||
private lastMatch: number = -1;
|
||||
private current: number = -1;
|
||||
private direction: SearchDirection = SearchDirection.Forward;
|
||||
public parent: Document | null = null;
|
||||
public parent: Option<Document> = None;
|
||||
|
||||
private parseInput(key: string) {
|
||||
switch (key) {
|
||||
@ -48,29 +50,31 @@ export class Search {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
public search(q: string, key: string): Position | null {
|
||||
if (this.parent === null) {
|
||||
return null;
|
||||
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 < this.parent.numRows; i++) {
|
||||
const current = this.getNextRow(this.parent.numRows);
|
||||
const row = this.parent.row(current);
|
||||
for (; i < parent.numRows; i++) {
|
||||
const current = this.getNextRow(parent.numRows);
|
||||
const row = parent.row(current);
|
||||
|
||||
if (row === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const possible = row.find(q);
|
||||
if (possible !== null) {
|
||||
if (possible.isSome()) {
|
||||
this.lastMatch = current;
|
||||
return Position.at(possible, current);
|
||||
return possible.map((p: number) => Position.at(p, current));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user