@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
418 lines (369 loc) • 13.4 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2020 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// based on https://github.com/microsoft/vscode/blob/04c36be045a94fee58e5f8992d3e3fd980294a84/src/vs/base/common/map.ts#L251
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/tslint/config */
import URI from './uri';
import { CharCode } from './char-code';
import { compareSubstringIgnoreCase, compare, compareSubstring } from './strings';
export interface IKeyIterator<K> {
reset(key: K): this;
next(): this;
hasNext(): boolean;
cmp(a: string): number;
value(): string;
}
export class PathIterator implements IKeyIterator<string> {
private _value!: string;
private _from!: number;
private _to!: number;
constructor(
private readonly _splitOnBackslash: boolean = true,
private readonly _caseSensitive: boolean = true
) { }
reset(key: string): this {
this._value = key.replace(/\\$|\/$/, '');
this._from = 0;
this._to = 0;
return this.next();
}
hasNext(): boolean {
return this._to < this._value.length;
}
next(): this {
// this._data = key.split(/[\\/]/).filter(s => !!s);
this._from = this._to;
let justSeps = true;
for (; this._to < this._value.length; this._to++) {
const ch = this._value.charCodeAt(this._to);
if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) {
if (justSeps) {
this._from++;
} else {
break;
}
} else {
justSeps = false;
}
}
return this;
}
cmp(a: string): number {
return this._caseSensitive
? compareSubstring(a, this._value, 0, a.length, this._from, this._to)
: compareSubstringIgnoreCase(a, this._value, 0, a.length, this._from, this._to);
}
value(): string {
return this._value.substring(this._from, this._to);
}
}
const enum UriIteratorState {
Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5
}
export class UriIterator implements IKeyIterator<URI> {
private _pathIterator!: PathIterator;
private _value!: URI;
private _states: UriIteratorState[] = [];
private _stateIdx: number = 0;
constructor(
protected readonly caseSensitive: boolean
) { }
reset(key: URI): this {
this._value = key;
this._states = [];
if (this._value.scheme) {
this._states.push(UriIteratorState.Scheme);
}
if (this._value.authority) {
this._states.push(UriIteratorState.Authority);
}
if (this._value.path) {
this._pathIterator = new PathIterator(false, this.caseSensitive);
this._pathIterator.reset(key.path.toString());
if (this._pathIterator.value()) {
this._states.push(UriIteratorState.Path);
}
}
if (this._value.query) {
this._states.push(UriIteratorState.Query);
}
if (this._value.fragment) {
this._states.push(UriIteratorState.Fragment);
}
this._stateIdx = 0;
return this;
}
next(): this {
if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) {
this._pathIterator.next();
} else {
this._stateIdx += 1;
}
return this;
}
hasNext(): boolean {
return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext())
|| this._stateIdx < this._states.length - 1;
}
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compareSubstringIgnoreCase(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareSubstringIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.cmp(a);
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return compare(a, this._value.query);
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return compare(a, this._value.fragment);
}
throw new Error();
}
value(): string {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return this._value.scheme;
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return this._value.authority;
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
return this._pathIterator.value();
} else if (this._states[this._stateIdx] === UriIteratorState.Query) {
return this._value.query;
} else if (this._states[this._stateIdx] === UriIteratorState.Fragment) {
return this._value.fragment;
}
throw new Error();
}
}
class TernarySearchTreeNode<K, V> {
segment!: string;
value: V | undefined;
key!: K;
left: TernarySearchTreeNode<K, V> | undefined;
mid: TernarySearchTreeNode<K, V> | undefined;
right: TernarySearchTreeNode<K, V> | undefined;
isEmpty(): boolean {
return !this.left && !this.mid && !this.right && !this.value;
}
}
export class TernarySearchTree<K, V> {
static forUris<E>(caseSensitive: boolean): TernarySearchTree<URI, E> {
return new TernarySearchTree<URI, E>(new UriIterator(caseSensitive));
}
static forPaths<E>(): TernarySearchTree<string, E> {
return new TernarySearchTree<string, E>(new PathIterator());
}
private _iter: IKeyIterator<K>;
private _root: TernarySearchTreeNode<K, V> | undefined;
constructor(segments: IKeyIterator<K>) {
this._iter = segments;
}
clear(): void {
this._root = undefined;
}
set(key: K, element: V): V | undefined {
const iter = this._iter.reset(key);
let node: TernarySearchTreeNode<K, V>;
if (!this._root) {
this._root = new TernarySearchTreeNode<K, V>();
this._root.segment = iter.value();
}
node = this._root;
while (true) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
if (!node.left) {
node.left = new TernarySearchTreeNode<K, V>();
node.left.segment = iter.value();
}
node = node.left;
} else if (val < 0) {
// right
if (!node.right) {
node.right = new TernarySearchTreeNode<K, V>();
node.right.segment = iter.value();
}
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
if (!node.mid) {
node.mid = new TernarySearchTreeNode<K, V>();
node.mid.segment = iter.value();
}
node = node.mid;
} else {
break;
}
}
const oldElement = node.value;
node.value = element;
node.key = key;
return oldElement;
}
get(key: K): V | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
node = node.mid;
} else {
break;
}
}
return node ? node.value : undefined;
}
delete(key: K): void {
const iter = this._iter.reset(key);
const stack: [-1 | 0 | 1, TernarySearchTreeNode<K, V>][] = [];
let node = this._root;
// find and unset node
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
stack.push([1, node]);
node = node.left;
} else if (val < 0) {
// right
stack.push([-1, node]);
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
stack.push([0, node]);
node = node.mid;
} else {
// remove element
node.value = undefined;
// clean up empty nodes
while (stack.length > 0 && node.isEmpty()) {
const [dir, parent] = stack.pop()!;
switch (dir) {
case 1: parent.left = undefined; break;
case 0: parent.mid = undefined; break;
case -1: parent.right = undefined; break;
}
node = parent;
}
break;
}
}
}
findSubstr(key: K): V | undefined {
const iter = this._iter.reset(key);
let node = this._root;
let candidate: V | undefined = undefined;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
candidate = node.value || candidate;
node = node.mid;
} else {
break;
}
}
return node && node.value || candidate;
}
findSuperstr(key: K): Iterator<V> | undefined {
const iter = this._iter.reset(key);
let node = this._root;
while (node) {
const val = iter.cmp(node.segment);
if (val > 0) {
// left
node = node.left;
} else if (val < 0) {
// right
node = node.right;
} else if (iter.hasNext()) {
// mid
iter.next();
node = node.mid;
} else {
// collect
if (!node.mid) {
return undefined;
} else {
return this._nodeIterator(node.mid);
}
}
}
return undefined;
}
private _nodeIterator(node: TernarySearchTreeNode<K, V>): Iterator<V> {
let res: { done: false; value: V; };
let idx: number;
let data: V[];
const next = (): IteratorResult<V> => {
if (!data) {
// lazy till first invocation
data = [];
idx = 0;
this._forEach(node, value => data.push(value));
}
if (idx >= data.length) {
return { done: true, value: undefined };
}
if (!res) {
res = { done: false, value: data[idx++] };
} else {
res.value = data[idx++];
}
return res;
};
return { next };
}
forEach(callback: (value: V, index: K) => any) {
this._forEach(this._root, callback);
}
private _forEach(node: TernarySearchTreeNode<K, V> | undefined, callback: (value: V, index: K) => any) {
if (node) {
// left
this._forEach(node.left, callback);
// node
if (node.value) {
// callback(node.value, this._iter.join(parts));
callback(node.value, node.key);
}
// mid
this._forEach(node.mid, callback);
// right
this._forEach(node.right, callback);
}
}
}