From 337b02953ffc4aaefce8deb6d076eb249ccde061 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 17 Oct 2022 09:21:23 -0700 Subject: [PATCH] Move TernarySearchTree to own file (#163684) * Move TernarySearchTree to own file This class is taking up most of `map.ts`. I think it's time to split it out into its own file * Adding missing file change * Update changed file --- src/vs/base/common/map.ts | 727 ------------ src/vs/base/common/resourceTree.ts | 2 +- src/vs/base/common/ternarySearchTree.ts | 732 ++++++++++++ src/vs/base/node/id.ts | 2 +- src/vs/base/test/common/map.test.ts | 998 +--------------- .../test/common/ternarySearchtree.test.ts | 1007 +++++++++++++++++ .../contrib/suggest/browser/suggestMemory.ts | 3 +- .../test/common/testConfigurationService.ts | 2 +- .../contextkey/browser/contextKeyService.ts | 2 +- src/vs/platform/files/common/fileService.ts | 2 +- src/vs/platform/files/common/files.ts | 2 +- .../node/watcher/parcel/parcelWatcher.ts | 2 +- .../electron-main/protocolMainService.ts | 4 +- .../userData/common/fileUserDataProvider.ts | 2 +- src/vs/platform/workspace/common/workspace.ts | 2 +- .../api/common/extHostExtensionService.ts | 2 +- .../workbench/api/common/extHostWorkspace.ts | 2 +- .../extensionsAutoProfiler.ts | 2 +- .../files/browser/views/explorerViewer.ts | 3 +- .../markers/browser/markersFilterOptions.ts | 2 +- .../contrib/search/common/searchModel.ts | 3 +- .../decorations/browser/decorationsService.ts | 2 +- .../electron-sandbox/extensionHostProfiler.ts | 2 +- 23 files changed, 1763 insertions(+), 1744 deletions(-) create mode 100644 src/vs/base/common/ternarySearchTree.ts create mode 100644 src/vs/base/test/common/ternarySearchtree.test.ts diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 1c2e42bba59..e8b505142b0 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -3,9 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { shuffle } from 'vs/base/common/arrays'; -import { CharCode } from 'vs/base/common/charCode'; -import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; export function getOrSet(map: Map, key: K, value: V): V { @@ -36,730 +33,6 @@ export function setToString(set: Set): string { return `Set(${set.size}) {${entries.join(', ')}}`; } -export interface IKeyIterator { - reset(key: K): this; - next(): this; - - hasNext(): boolean; - cmp(a: string): number; - value(): string; -} - -export class StringIterator implements IKeyIterator { - - private _value: string = ''; - private _pos: number = 0; - - reset(key: string): this { - this._value = key; - this._pos = 0; - return this; - } - - next(): this { - this._pos += 1; - return this; - } - - hasNext(): boolean { - return this._pos < this._value.length - 1; - } - - cmp(a: string): number { - const aCode = a.charCodeAt(0); - const thisCode = this._value.charCodeAt(this._pos); - return aCode - thisCode; - } - - value(): string { - return this._value[this._pos]; - } -} - -export class ConfigKeysIterator implements IKeyIterator { - - private _value!: string; - private _from!: number; - private _to!: number; - - constructor( - private readonly _caseSensitive: boolean = true - ) { } - - reset(key: string): this { - this._value = key; - 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.Period) { - 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); - } -} - -export class PathIterator implements IKeyIterator { - - private _value!: string; - private _valueLen!: number; - private _from!: number; - private _to!: number; - - constructor( - private readonly _splitOnBackslash: boolean = true, - private readonly _caseSensitive: boolean = true - ) { } - - reset(key: string): this { - this._from = 0; - this._to = 0; - this._value = key; - this._valueLen = key.length; - for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) { - const ch = this._value.charCodeAt(pos); - if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) { - break; - } - } - - return this.next(); - } - - hasNext(): boolean { - return this._to < this._valueLen; - } - - next(): this { - // this._data = key.split(/[\\/]/).filter(s => !!s); - this._from = this._to; - let justSeps = true; - for (; this._to < this._valueLen; 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 { - - private _pathIterator!: PathIterator; - private _value!: URI; - private _states: UriIteratorState[] = []; - private _stateIdx: number = 0; - - constructor( - private readonly _ignorePathCasing: (uri: URI) => boolean, - private readonly _ignoreQueryAndFragment: (uri: URI) => 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._ignorePathCasing(key)); - this._pathIterator.reset(key.path); - if (this._pathIterator.value()) { - this._states.push(UriIteratorState.Path); - } - } - if (!this._ignoreQueryAndFragment(key)) { - 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 compareIgnoreCase(a, this._value.scheme); - } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { - return compareIgnoreCase(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 { - height: number = 1; - segment!: string; - value: V | undefined; - key: K | undefined; - left: TernarySearchTreeNode | undefined; - mid: TernarySearchTreeNode | undefined; - right: TernarySearchTreeNode | undefined; - - isEmpty(): boolean { - return !this.left && !this.mid && !this.right && !this.value; - } - - rotateLeft() { - const tmp = this.right!; - this.right = tmp.left; - tmp.left = this; - this.updateHeight(); - tmp.updateHeight(); - return tmp; - } - - rotateRight() { - const tmp = this.left!; - this.left = tmp.right; - tmp.right = this; - this.updateHeight(); - tmp.updateHeight(); - return tmp; - } - - updateHeight() { - this.height = 1 + Math.max(this.heightLeft, this.heightRight); - } - - balanceFactor() { - return this.heightRight - this.heightLeft; - } - - get heightLeft() { - return this.left?.height ?? 0; - } - - get heightRight() { - return this.right?.height ?? 0; - } -} - -const enum Dir { - Left = -1, - Mid = 0, - Right = 1, -} - -export class TernarySearchTree { - - static forUris(ignorePathCasing: (key: URI) => boolean = () => false, ignoreQueryAndFragment: (key: URI) => boolean = () => false): TernarySearchTree { - return new TernarySearchTree(new UriIterator(ignorePathCasing, ignoreQueryAndFragment)); - } - - static forPaths(ignorePathCasing = false): TernarySearchTree { - return new TernarySearchTree(new PathIterator(undefined, !ignorePathCasing)); - } - - static forStrings(): TernarySearchTree { - return new TernarySearchTree(new StringIterator()); - } - - static forConfigKeys(): TernarySearchTree { - return new TernarySearchTree(new ConfigKeysIterator()); - } - - private _iter: IKeyIterator; - private _root: TernarySearchTreeNode | undefined; - - constructor(segments: IKeyIterator) { - this._iter = segments; - } - - clear(): void { - this._root = undefined; - } - - /** - * Fill the tree with the same value of the given keys - */ - fill(element: V, keys: readonly K[]): void; - /** - * Fill the tree with given [key,value]-tuples - */ - fill(values: readonly [K, V][]): void; - fill(values: readonly [K, V][] | V, keys?: readonly K[]): void { - if (keys) { - const arr = keys.slice(0); - shuffle(arr); - for (const k of arr) { - this.set(k, (values)); - } - } else { - const arr = (<[K, V][]>values).slice(0); - shuffle(arr); - for (const entry of arr) { - this.set(entry[0], entry[1]); - } - } - } - - set(key: K, element: V): V | undefined { - const iter = this._iter.reset(key); - let node: TernarySearchTreeNode; - - if (!this._root) { - this._root = new TernarySearchTreeNode(); - this._root.segment = iter.value(); - } - const stack: [Dir, TernarySearchTreeNode][] = []; - - // find insert_node - node = this._root; - while (true) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - if (!node.left) { - node.left = new TernarySearchTreeNode(); - node.left.segment = iter.value(); - } - stack.push([Dir.Left, node]); - node = node.left; - - } else if (val < 0) { - // right - if (!node.right) { - node.right = new TernarySearchTreeNode(); - node.right.segment = iter.value(); - } - stack.push([Dir.Right, node]); - node = node.right; - - } else if (iter.hasNext()) { - // mid - iter.next(); - if (!node.mid) { - node.mid = new TernarySearchTreeNode(); - node.mid.segment = iter.value(); - } - stack.push([Dir.Mid, node]); - node = node.mid; - } else { - break; - } - } - - // set value - const oldElement = node.value; - node.value = element; - node.key = key; - - // balance - for (let i = stack.length - 1; i >= 0; i--) { - const node = stack[i][1]; - - node.updateHeight(); - const bf = node.balanceFactor(); - - if (bf < -1 || bf > 1) { - // needs rotate - const d1 = stack[i][0]; - const d2 = stack[i + 1][0]; - - if (d1 === Dir.Right && d2 === Dir.Right) { - //right, right -> rotate left - stack[i][1] = node.rotateLeft(); - - } else if (d1 === Dir.Left && d2 === Dir.Left) { - // left, left -> rotate right - stack[i][1] = node.rotateRight(); - - } else if (d1 === Dir.Right && d2 === Dir.Left) { - // right, left -> double rotate right, left - node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight(); - stack[i][1] = node.rotateLeft(); - - } else if (d1 === Dir.Left && d2 === Dir.Right) { - // left, right -> double rotate left, right - node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft(); - stack[i][1] = node.rotateRight(); - - } else { - throw new Error(); - } - - // patch path to parent - if (i > 0) { - switch (stack[i - 1][0]) { - case Dir.Left: - stack[i - 1][1].left = stack[i][1]; - break; - case Dir.Right: - stack[i - 1][1].right = stack[i][1]; - break; - case Dir.Mid: - stack[i - 1][1].mid = stack[i][1]; - break; - } - } else { - this._root = stack[0][1]; - } - } - } - - return oldElement; - } - - get(key: K): V | undefined { - return this._getNode(key)?.value; - } - - private _getNode(key: K) { - 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; - } - - has(key: K): boolean { - const node = this._getNode(key); - return !(node?.value === undefined && node?.mid === undefined); - } - - delete(key: K): void { - return this._delete(key, false); - } - - deleteSuperstr(key: K): void { - return this._delete(key, true); - } - - private _delete(key: K, superStr: boolean): void { - const iter = this._iter.reset(key); - const stack: [Dir, TernarySearchTreeNode][] = []; - let node = this._root; - - // find node - while (node) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - stack.push([Dir.Left, node]); - node = node.left; - } else if (val < 0) { - // right - stack.push([Dir.Right, node]); - node = node.right; - } else if (iter.hasNext()) { - // mid - iter.next(); - stack.push([Dir.Mid, node]); - node = node.mid; - } else { - break; - } - } - - if (!node) { - // node not found - return; - } - - if (superStr) { - // removing children, reset height - node.left = undefined; - node.mid = undefined; - node.right = undefined; - node.height = 1; - } else { - // removing element - node.key = undefined; - node.value = undefined; - } - - // BST node removal - if (!node.mid && !node.value) { - if (node.left && node.right) { - // full node - // replace deleted-node with the min-node of the right branch. - // If there is no true min-node leave things as they are - const min = this._min(node.right); - if (min.key) { - const { key, value, segment } = min; - this._delete(min.key!, false); - node.key = key; - node.value = value; - node.segment = segment; - } - - } else { - // empty or half empty - const newChild = node.left ?? node.right; - if (stack.length > 0) { - const [dir, parent] = stack[stack.length - 1]; - switch (dir) { - case Dir.Left: parent.left = newChild; break; - case Dir.Mid: parent.mid = newChild; break; - case Dir.Right: parent.right = newChild; break; - } - } else { - this._root = newChild; - } - } - } - - // AVL balance - for (let i = stack.length - 1; i >= 0; i--) { - const node = stack[i][1]; - - node.updateHeight(); - const bf = node.balanceFactor(); - if (bf > 1) { - // right heavy - if (node.right!.balanceFactor() >= 0) { - // right, right -> rotate left - stack[i][1] = node.rotateLeft(); - } else { - // right, left -> double rotate - node.right = node.right!.rotateRight(); - stack[i][1] = node.rotateLeft(); - } - - } else if (bf < -1) { - // left heavy - if (node.left!.balanceFactor() <= 0) { - // left, left -> rotate right - stack[i][1] = node.rotateRight(); - } else { - // left, right -> double rotate - node.left = node.left!.rotateLeft(); - stack[i][1] = node.rotateRight(); - } - } - - // patch path to parent - if (i > 0) { - switch (stack[i - 1][0]) { - case Dir.Left: - stack[i - 1][1].left = stack[i][1]; - break; - case Dir.Right: - stack[i - 1][1].right = stack[i][1]; - break; - case Dir.Mid: - stack[i - 1][1].mid = stack[i][1]; - break; - } - } else { - this._root = stack[0][1]; - } - } - } - - private _min(node: TernarySearchTreeNode): TernarySearchTreeNode { - while (node.left) { - node = node.left; - } - return node; - } - - 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): IterableIterator<[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 { - // collect - if (!node.mid) { - return undefined; - } else { - return this._entries(node.mid); - } - } - } - return undefined; - } - - forEach(callback: (value: V, index: K) => any): void { - for (const [key, value] of this) { - callback(value, key); - } - } - - *[Symbol.iterator](): IterableIterator<[K, V]> { - yield* this._entries(this._root); - } - - private _entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { - const result: [K, V][] = []; - this._dfsEntries(node, result); - return result[Symbol.iterator](); - } - - private _dfsEntries(node: TernarySearchTreeNode | undefined, bucket: [K, V][]) { - // DFS - if (!node) { - return; - } - if (node.left) { - this._dfsEntries(node.left, bucket); - } - if (node.value) { - bucket.push([node.key!, node.value]); - } - if (node.mid) { - this._dfsEntries(node.mid, bucket); - } - if (node.right) { - this._dfsEntries(node.right, bucket); - } - } - - // for debug/testing - _isBalanced(): boolean { - const nodeIsBalanced = (node: TernarySearchTreeNode | undefined): boolean => { - if (!node) { - return true; - } - const bf = node.balanceFactor(); - if (bf < -1 || bf > 1) { - return false; - } - return nodeIsBalanced(node.left) && nodeIsBalanced(node.right); - }; - return nodeIsBalanced(this._root); - } -} - interface ResourceMapKeyFn { (resource: URI): string; } diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index 56b1e00b91d..7d44171c1b1 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; -import { PathIterator } from 'vs/base/common/map'; +import { PathIterator } from 'vs/base/common/ternarySearchTree'; import * as paths from 'vs/base/common/path'; import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/base/common/ternarySearchTree.ts b/src/vs/base/common/ternarySearchTree.ts new file mode 100644 index 00000000000..91f4301ffa9 --- /dev/null +++ b/src/vs/base/common/ternarySearchTree.ts @@ -0,0 +1,732 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { shuffle } from 'vs/base/common/arrays'; +import { CharCode } from 'vs/base/common/charCode'; +import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; + +export interface IKeyIterator { + reset(key: K): this; + next(): this; + + hasNext(): boolean; + cmp(a: string): number; + value(): string; +} + +export class StringIterator implements IKeyIterator { + + private _value: string = ''; + private _pos: number = 0; + + reset(key: string): this { + this._value = key; + this._pos = 0; + return this; + } + + next(): this { + this._pos += 1; + return this; + } + + hasNext(): boolean { + return this._pos < this._value.length - 1; + } + + cmp(a: string): number { + const aCode = a.charCodeAt(0); + const thisCode = this._value.charCodeAt(this._pos); + return aCode - thisCode; + } + + value(): string { + return this._value[this._pos]; + } +} + +export class ConfigKeysIterator implements IKeyIterator { + + private _value!: string; + private _from!: number; + private _to!: number; + + constructor( + private readonly _caseSensitive: boolean = true + ) { } + + reset(key: string): this { + this._value = key; + 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.Period) { + 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); + } +} + +export class PathIterator implements IKeyIterator { + + private _value!: string; + private _valueLen!: number; + private _from!: number; + private _to!: number; + + constructor( + private readonly _splitOnBackslash: boolean = true, + private readonly _caseSensitive: boolean = true + ) { } + + reset(key: string): this { + this._from = 0; + this._to = 0; + this._value = key; + this._valueLen = key.length; + for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) { + const ch = this._value.charCodeAt(pos); + if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) { + break; + } + } + + return this.next(); + } + + hasNext(): boolean { + return this._to < this._valueLen; + } + + next(): this { + // this._data = key.split(/[\\/]/).filter(s => !!s); + this._from = this._to; + let justSeps = true; + for (; this._to < this._valueLen; 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 { + + private _pathIterator!: PathIterator; + private _value!: URI; + private _states: UriIteratorState[] = []; + private _stateIdx: number = 0; + + constructor( + private readonly _ignorePathCasing: (uri: URI) => boolean, + private readonly _ignoreQueryAndFragment: (uri: URI) => 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._ignorePathCasing(key)); + this._pathIterator.reset(key.path); + if (this._pathIterator.value()) { + this._states.push(UriIteratorState.Path); + } + } + if (!this._ignoreQueryAndFragment(key)) { + 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 compareIgnoreCase(a, this._value.scheme); + } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { + return compareIgnoreCase(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 { + height: number = 1; + segment!: string; + value: V | undefined; + key: K | undefined; + left: TernarySearchTreeNode | undefined; + mid: TernarySearchTreeNode | undefined; + right: TernarySearchTreeNode | undefined; + + isEmpty(): boolean { + return !this.left && !this.mid && !this.right && !this.value; + } + + rotateLeft() { + const tmp = this.right!; + this.right = tmp.left; + tmp.left = this; + this.updateHeight(); + tmp.updateHeight(); + return tmp; + } + + rotateRight() { + const tmp = this.left!; + this.left = tmp.right; + tmp.right = this; + this.updateHeight(); + tmp.updateHeight(); + return tmp; + } + + updateHeight() { + this.height = 1 + Math.max(this.heightLeft, this.heightRight); + } + + balanceFactor() { + return this.heightRight - this.heightLeft; + } + + get heightLeft() { + return this.left?.height ?? 0; + } + + get heightRight() { + return this.right?.height ?? 0; + } +} + +const enum Dir { + Left = -1, + Mid = 0, + Right = 1 +} + +export class TernarySearchTree { + + static forUris(ignorePathCasing: (key: URI) => boolean = () => false, ignoreQueryAndFragment: (key: URI) => boolean = () => false): TernarySearchTree { + return new TernarySearchTree(new UriIterator(ignorePathCasing, ignoreQueryAndFragment)); + } + + static forPaths(ignorePathCasing = false): TernarySearchTree { + return new TernarySearchTree(new PathIterator(undefined, !ignorePathCasing)); + } + + static forStrings(): TernarySearchTree { + return new TernarySearchTree(new StringIterator()); + } + + static forConfigKeys(): TernarySearchTree { + return new TernarySearchTree(new ConfigKeysIterator()); + } + + private _iter: IKeyIterator; + private _root: TernarySearchTreeNode | undefined; + + constructor(segments: IKeyIterator) { + this._iter = segments; + } + + clear(): void { + this._root = undefined; + } + + /** + * Fill the tree with the same value of the given keys + */ + fill(element: V, keys: readonly K[]): void; + /** + * Fill the tree with given [key,value]-tuples + */ + fill(values: readonly [K, V][]): void; + fill(values: readonly [K, V][] | V, keys?: readonly K[]): void { + if (keys) { + const arr = keys.slice(0); + shuffle(arr); + for (const k of arr) { + this.set(k, (values)); + } + } else { + const arr = (<[K, V][]>values).slice(0); + shuffle(arr); + for (const entry of arr) { + this.set(entry[0], entry[1]); + } + } + } + + set(key: K, element: V): V | undefined { + const iter = this._iter.reset(key); + let node: TernarySearchTreeNode; + + if (!this._root) { + this._root = new TernarySearchTreeNode(); + this._root.segment = iter.value(); + } + const stack: [Dir, TernarySearchTreeNode][] = []; + + // find insert_node + node = this._root; + while (true) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + if (!node.left) { + node.left = new TernarySearchTreeNode(); + node.left.segment = iter.value(); + } + stack.push([Dir.Left, node]); + node = node.left; + + } else if (val < 0) { + // right + if (!node.right) { + node.right = new TernarySearchTreeNode(); + node.right.segment = iter.value(); + } + stack.push([Dir.Right, node]); + node = node.right; + + } else if (iter.hasNext()) { + // mid + iter.next(); + if (!node.mid) { + node.mid = new TernarySearchTreeNode(); + node.mid.segment = iter.value(); + } + stack.push([Dir.Mid, node]); + node = node.mid; + } else { + break; + } + } + + // set value + const oldElement = node.value; + node.value = element; + node.key = key; + + // balance + for (let i = stack.length - 1; i >= 0; i--) { + const node = stack[i][1]; + + node.updateHeight(); + const bf = node.balanceFactor(); + + if (bf < -1 || bf > 1) { + // needs rotate + const d1 = stack[i][0]; + const d2 = stack[i + 1][0]; + + if (d1 === Dir.Right && d2 === Dir.Right) { + //right, right -> rotate left + stack[i][1] = node.rotateLeft(); + + } else if (d1 === Dir.Left && d2 === Dir.Left) { + // left, left -> rotate right + stack[i][1] = node.rotateRight(); + + } else if (d1 === Dir.Right && d2 === Dir.Left) { + // right, left -> double rotate right, left + node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight(); + stack[i][1] = node.rotateLeft(); + + } else if (d1 === Dir.Left && d2 === Dir.Right) { + // left, right -> double rotate left, right + node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft(); + stack[i][1] = node.rotateRight(); + + } else { + throw new Error(); + } + + // patch path to parent + if (i > 0) { + switch (stack[i - 1][0]) { + case Dir.Left: + stack[i - 1][1].left = stack[i][1]; + break; + case Dir.Right: + stack[i - 1][1].right = stack[i][1]; + break; + case Dir.Mid: + stack[i - 1][1].mid = stack[i][1]; + break; + } + } else { + this._root = stack[0][1]; + } + } + } + + return oldElement; + } + + get(key: K): V | undefined { + return this._getNode(key)?.value; + } + + private _getNode(key: K) { + 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; + } + + has(key: K): boolean { + const node = this._getNode(key); + return !(node?.value === undefined && node?.mid === undefined); + } + + delete(key: K): void { + return this._delete(key, false); + } + + deleteSuperstr(key: K): void { + return this._delete(key, true); + } + + private _delete(key: K, superStr: boolean): void { + const iter = this._iter.reset(key); + const stack: [Dir, TernarySearchTreeNode][] = []; + let node = this._root; + + // find node + while (node) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + stack.push([Dir.Left, node]); + node = node.left; + } else if (val < 0) { + // right + stack.push([Dir.Right, node]); + node = node.right; + } else if (iter.hasNext()) { + // mid + iter.next(); + stack.push([Dir.Mid, node]); + node = node.mid; + } else { + break; + } + } + + if (!node) { + // node not found + return; + } + + if (superStr) { + // removing children, reset height + node.left = undefined; + node.mid = undefined; + node.right = undefined; + node.height = 1; + } else { + // removing element + node.key = undefined; + node.value = undefined; + } + + // BST node removal + if (!node.mid && !node.value) { + if (node.left && node.right) { + // full node + // replace deleted-node with the min-node of the right branch. + // If there is no true min-node leave things as they are + const min = this._min(node.right); + if (min.key) { + const { key, value, segment } = min; + this._delete(min.key!, false); + node.key = key; + node.value = value; + node.segment = segment; + } + + } else { + // empty or half empty + const newChild = node.left ?? node.right; + if (stack.length > 0) { + const [dir, parent] = stack[stack.length - 1]; + switch (dir) { + case Dir.Left: parent.left = newChild; break; + case Dir.Mid: parent.mid = newChild; break; + case Dir.Right: parent.right = newChild; break; + } + } else { + this._root = newChild; + } + } + } + + // AVL balance + for (let i = stack.length - 1; i >= 0; i--) { + const node = stack[i][1]; + + node.updateHeight(); + const bf = node.balanceFactor(); + if (bf > 1) { + // right heavy + if (node.right!.balanceFactor() >= 0) { + // right, right -> rotate left + stack[i][1] = node.rotateLeft(); + } else { + // right, left -> double rotate + node.right = node.right!.rotateRight(); + stack[i][1] = node.rotateLeft(); + } + + } else if (bf < -1) { + // left heavy + if (node.left!.balanceFactor() <= 0) { + // left, left -> rotate right + stack[i][1] = node.rotateRight(); + } else { + // left, right -> double rotate + node.left = node.left!.rotateLeft(); + stack[i][1] = node.rotateRight(); + } + } + + // patch path to parent + if (i > 0) { + switch (stack[i - 1][0]) { + case Dir.Left: + stack[i - 1][1].left = stack[i][1]; + break; + case Dir.Right: + stack[i - 1][1].right = stack[i][1]; + break; + case Dir.Mid: + stack[i - 1][1].mid = stack[i][1]; + break; + } + } else { + this._root = stack[0][1]; + } + } + } + + private _min(node: TernarySearchTreeNode): TernarySearchTreeNode { + while (node.left) { + node = node.left; + } + return node; + } + + 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): IterableIterator<[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 { + // collect + if (!node.mid) { + return undefined; + } else { + return this._entries(node.mid); + } + } + } + return undefined; + } + + forEach(callback: (value: V, index: K) => any): void { + for (const [key, value] of this) { + callback(value, key); + } + } + + *[Symbol.iterator](): IterableIterator<[K, V]> { + yield* this._entries(this._root); + } + + private _entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + const result: [K, V][] = []; + this._dfsEntries(node, result); + return result[Symbol.iterator](); + } + + private _dfsEntries(node: TernarySearchTreeNode | undefined, bucket: [K, V][]) { + // DFS + if (!node) { + return; + } + if (node.left) { + this._dfsEntries(node.left, bucket); + } + if (node.value) { + bucket.push([node.key!, node.value]); + } + if (node.mid) { + this._dfsEntries(node.mid, bucket); + } + if (node.right) { + this._dfsEntries(node.right, bucket); + } + } + + // for debug/testing + _isBalanced(): boolean { + const nodeIsBalanced = (node: TernarySearchTreeNode | undefined): boolean => { + if (!node) { + return true; + } + const bf = node.balanceFactor(); + if (bf < -1 || bf > 1) { + return false; + } + return nodeIsBalanced(node.left) && nodeIsBalanced(node.right); + }; + return nodeIsBalanced(this._root); + } +} diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index 3b5e42449dd..65bff8cc522 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -5,7 +5,7 @@ import { networkInterfaces } from 'os'; import * as errors from 'vs/base/common/errors'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import * as uuid from 'vs/base/common/uuid'; import { getMac } from 'vs/base/node/macAddress'; diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 0d3f3dfe131..85234d90197 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -4,11 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { shuffle } from 'vs/base/common/arrays'; -import { randomPath } from 'vs/base/common/extpath'; -import { ConfigKeysIterator, LinkedMap, LRUCache, PathIterator, ResourceMap, StringIterator, TernarySearchTree, Touch, UriIterator } from 'vs/base/common/map'; +import { LinkedMap, LRUCache, ResourceMap, Touch } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; -import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; suite('Map', () => { @@ -328,897 +325,6 @@ suite('Map', () => { assert.strictEqual([...map.keys()][0], '1'); }); - - test('PathIterator', () => { - const iter = new PathIterator(); - iter.reset('file:///usr/bin/file.txt'); - - assert.strictEqual(iter.value(), 'file:'); - assert.strictEqual(iter.hasNext(), true); - assert.strictEqual(iter.cmp('file:'), 0); - assert.ok(iter.cmp('a') < 0); - assert.ok(iter.cmp('aile:') < 0); - assert.ok(iter.cmp('z') > 0); - assert.ok(iter.cmp('zile:') > 0); - - iter.next(); - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - iter.next(); - assert.strictEqual(iter.value(), ''); - assert.strictEqual(iter.hasNext(), false); - iter.next(); - assert.strictEqual(iter.value(), ''); - assert.strictEqual(iter.hasNext(), false); - - // - iter.reset('/foo/bar/'); - assert.strictEqual(iter.value(), 'foo'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'bar'); - assert.strictEqual(iter.hasNext(), false); - }); - - test('URIIterator', function () { - const iter = new UriIterator(() => false, () => false); - iter.reset(URI.parse('file:///usr/bin/file.txt')); - - assert.strictEqual(iter.value(), 'file'); - // assert.strictEqual(iter.cmp('FILE'), 0); - assert.strictEqual(iter.cmp('file'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - - iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); - - // scheme - assert.strictEqual(iter.value(), 'file'); - // assert.strictEqual(iter.cmp('FILE'), 0); - assert.strictEqual(iter.cmp('file'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // authority - assert.strictEqual(iter.value(), 'share'); - assert.strictEqual(iter.cmp('SHARe'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // query - assert.strictEqual(iter.value(), 'foo'); - assert.strictEqual(iter.cmp('z') > 0, true); - assert.strictEqual(iter.cmp('a') < 0, true); - assert.strictEqual(iter.hasNext(), false); - }); - - test('URIIterator - ignore query/fragment', function () { - const iter = new UriIterator(() => false, () => true); - iter.reset(URI.parse('file:///usr/bin/file.txt')); - - assert.strictEqual(iter.value(), 'file'); - // assert.strictEqual(iter.cmp('FILE'), 0); - assert.strictEqual(iter.cmp('file'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - - iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); - - // scheme - assert.strictEqual(iter.value(), 'file'); - // assert.strictEqual(iter.cmp('FILE'), 0); - assert.strictEqual(iter.cmp('file'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // authority - assert.strictEqual(iter.value(), 'share'); - assert.strictEqual(iter.cmp('SHARe'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - }); - - function assertTstDfs(trie: TernarySearchTree, ...elements: [string, E][]) { - - assert.ok(trie._isBalanced(), 'TST is not balanced'); - - let i = 0; - for (const [key, value] of trie) { - const expected = elements[i++]; - assert.ok(expected); - assert.strictEqual(key, expected[0]); - assert.strictEqual(value, expected[1]); - } - - assert.strictEqual(i, elements.length); - - const map = new Map(); - for (const [key, value] of elements) { - map.set(key, value); - } - map.forEach((value, key) => { - assert.strictEqual(trie.get(key), value); - }); - - // forEach - let forEachCount = 0; - trie.forEach((element, key) => { - assert.strictEqual(element, map.get(key)); - forEachCount++; - }); - assert.strictEqual(map.size, forEachCount); - - // iterator - let iterCount = 0; - for (const [key, value] of trie) { - assert.strictEqual(value, map.get(key)); - iterCount++; - } - assert.strictEqual(map.size, iterCount); - - } - - test('TernarySearchTree - set', function () { - - let trie = TernarySearchTree.forStrings(); - trie.set('foobar', 1); - trie.set('foobaz', 2); - - assertTstDfs(trie, ['foobar', 1], ['foobaz', 2]); // longer - - trie = TernarySearchTree.forStrings(); - trie.set('foobar', 1); - trie.set('fooba', 2); - assertTstDfs(trie, ['fooba', 2], ['foobar', 1]); // shorter - - trie = TernarySearchTree.forStrings(); - trie.set('foo', 1); - trie.set('foo', 2); - assertTstDfs(trie, ['foo', 2]); - - trie = TernarySearchTree.forStrings(); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foob', 4); - trie.set('bazz', 5); - - assertTstDfs(trie, - ['bar', 3], - ['bazz', 5], - ['foo', 1], - ['foob', 4], - ['foobar', 2], - ); - }); - - test('TernarySearchTree - findLongestMatch', function () { - - const trie = TernarySearchTree.forStrings(); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('foobaz', 3); - assertTstDfs(trie, ['foo', 1], ['foobar', 2], ['foobaz', 3]); - - assert.strictEqual(trie.findSubstr('f'), undefined); - assert.strictEqual(trie.findSubstr('z'), undefined); - assert.strictEqual(trie.findSubstr('foo'), 1); - assert.strictEqual(trie.findSubstr('fooö'), 1); - assert.strictEqual(trie.findSubstr('fooba'), 1); - assert.strictEqual(trie.findSubstr('foobarr'), 2); - assert.strictEqual(trie.findSubstr('foobazrr'), 3); - }); - - test('TernarySearchTree - basics', function () { - const trie = new TernarySearchTree(new StringIterator()); - - trie.set('foo', 1); - trie.set('bar', 2); - trie.set('foobar', 3); - assertTstDfs(trie, ['bar', 2], ['foo', 1], ['foobar', 3]); - - assert.strictEqual(trie.get('foo'), 1); - assert.strictEqual(trie.get('bar'), 2); - assert.strictEqual(trie.get('foobar'), 3); - assert.strictEqual(trie.get('foobaz'), undefined); - assert.strictEqual(trie.get('foobarr'), undefined); - - assert.strictEqual(trie.findSubstr('fo'), undefined); - assert.strictEqual(trie.findSubstr('foo'), 1); - assert.strictEqual(trie.findSubstr('foooo'), 1); - - - trie.delete('foobar'); - trie.delete('bar'); - assert.strictEqual(trie.get('foobar'), undefined); - assert.strictEqual(trie.get('bar'), undefined); - - trie.set('foobar', 17); - trie.set('barr', 18); - assert.strictEqual(trie.get('foobar'), 17); - assert.strictEqual(trie.get('barr'), 18); - assert.strictEqual(trie.get('bar'), undefined); - }); - - test('TernarySearchTree - delete & cleanup', function () { - // normal delete - let trie = new TernarySearchTree(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - assertTstDfs(trie, ['bar', 3], ['foo', 1], ['foobar', 2]); - trie.delete('foo'); - assertTstDfs(trie, ['bar', 3], ['foobar', 2]); - trie.delete('foobar'); - assertTstDfs(trie, ['bar', 3]); - - // superstr-delete - trie = new TernarySearchTree(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foobarbaz', 4); - trie.deleteSuperstr('foo'); - assertTstDfs(trie, ['bar', 3], ['foo', 1]); - - trie = new TernarySearchTree(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foobarbaz', 4); - trie.deleteSuperstr('fo'); - assertTstDfs(trie, ['bar', 3]); - - // trie = new TernarySearchTree(new StringIterator()); - // trie.set('foo', 1); - // trie.set('foobar', 2); - // trie.set('bar', 3); - // trie.deleteSuperStr('f'); - // assertTernarySearchTree(trie, ['bar', 3]); - }); - - test('TernarySearchTree (PathSegments) - basics', function () { - const trie = new TernarySearchTree(new PathIterator()); - - trie.set('/user/foo/bar', 1); - trie.set('/user/foo', 2); - trie.set('/user/foo/flip/flop', 3); - - assert.strictEqual(trie.get('/user/foo/bar'), 1); - assert.strictEqual(trie.get('/user/foo'), 2); - assert.strictEqual(trie.get('/user//foo'), 2); - assert.strictEqual(trie.get('/user\\foo'), 2); - assert.strictEqual(trie.get('/user/foo/flip/flop'), 3); - - assert.strictEqual(trie.findSubstr('/user/bar'), undefined); - assert.strictEqual(trie.findSubstr('/user/foo'), 2); - assert.strictEqual(trie.findSubstr('\\user\\foo'), 2); - assert.strictEqual(trie.findSubstr('/user//foo'), 2); - assert.strictEqual(trie.findSubstr('/user/foo/ba'), 2); - assert.strictEqual(trie.findSubstr('/user/foo/far/boo'), 2); - assert.strictEqual(trie.findSubstr('/user/foo/bar'), 1); - assert.strictEqual(trie.findSubstr('/user/foo/bar/far/boo'), 1); - }); - - test('TernarySearchTree - (AVL) set', function () { - { - // rotate left - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/fileA', 1); - trie.set('/fileB', 2); - trie.set('/fileC', 3); - assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); - } - - { - // rotate left (inside middle) - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/foo/fileA', 1); - trie.set('/foo/fileB', 2); - trie.set('/foo/fileC', 3); - assertTstDfs(trie, ['/foo/fileA', 1], ['/foo/fileB', 2], ['/foo/fileC', 3]); - } - - { - // rotate right - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/fileC', 3); - trie.set('/fileB', 2); - trie.set('/fileA', 1); - assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); - } - - { - // rotate right (inside middle) - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/mid/fileC', 3); - trie.set('/mid/fileB', 2); - trie.set('/mid/fileA', 1); - assertTstDfs(trie, ['/mid/fileA', 1], ['/mid/fileB', 2], ['/mid/fileC', 3]); - } - - { - // rotate right, left - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/fileD', 7); - trie.set('/fileB', 2); - trie.set('/fileG', 42); - trie.set('/fileF', 24); - trie.set('/fileZ', 73); - trie.set('/fileE', 15); - assertTstDfs(trie, ['/fileB', 2], ['/fileD', 7], ['/fileE', 15], ['/fileF', 24], ['/fileG', 42], ['/fileZ', 73]); - } - - { - // rotate left, right - const trie = new TernarySearchTree(new PathIterator()); - trie.set('/fileJ', 42); - trie.set('/fileZ', 73); - trie.set('/fileE', 15); - trie.set('/fileB', 2); - trie.set('/fileF', 7); - trie.set('/fileG', 1); - assertTstDfs(trie, ['/fileB', 2], ['/fileE', 15], ['/fileF', 7], ['/fileG', 1], ['/fileJ', 42], ['/fileZ', 73]); - } - }); - - test('TernarySearchTree - (BST) delete', function () { - - const trie = new TernarySearchTree(new StringIterator()); - - // delete root - trie.set('d', 1); - assertTstDfs(trie, ['d', 1]); - trie.delete('d'); - assertTstDfs(trie); - - // delete node with two element - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['f', 1]); - trie.delete('d'); - assertTstDfs(trie, ['b', 1], ['f', 1]); - - // single child node - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - trie.set('e', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1]); - trie.delete('f'); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1]); - }); - - test('TernarySearchTree - (AVL) delete', function () { - - const trie = new TernarySearchTree(new StringIterator()); - - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - trie.set('e', 1); - trie.set('z', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1], ['z', 1]); - - // right, right - trie.delete('b'); - assertTstDfs(trie, ['d', 1], ['e', 1], ['f', 1], ['z', 1]); - - trie.clear(); - trie.set('d', 1); - trie.set('c', 1); - trie.set('f', 1); - trie.set('a', 1); - trie.set('b', 1); - assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1], ['f', 1]); - - // left, left - trie.delete('f'); - assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1]); - - // mid - trie.clear(); - trie.set('a', 1); - trie.set('ad', 1); - trie.set('ab', 1); - trie.set('af', 1); - trie.set('ae', 1); - trie.set('az', 1); - assertTstDfs(trie, ['a', 1], ['ab', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - - trie.delete('ab'); - assertTstDfs(trie, ['a', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - - trie.delete('a'); - assertTstDfs(trie, ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284', function () { - - const keys = [ - URI.parse('fake-fs:/C'), - URI.parse('fake-fs:/A'), - URI.parse('fake-fs:/D'), - URI.parse('fake-fs:/B'), - ]; - - const tst = TernarySearchTree.forUris(); - - for (const item of keys) { - tst.set(item, true); - } - - assert.ok(tst._isBalanced()); - tst.delete(keys[0]); - assert.ok(tst._isBalanced()); - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (simple)', function () { - - const keys = ['C', 'A', 'D', 'B',]; - const tst = TernarySearchTree.forStrings(); - for (const item of keys) { - tst.set(item, true); - } - assertTstDfs(tst, ['A', true], ['B', true], ['C', true], ['D', true]); - - tst.delete(keys[0]); - assertTstDfs(tst, ['A', true], ['B', true], ['D', true]); - - { - const tst = TernarySearchTree.forStrings(); - tst.set('C', true); - tst.set('A', true); - tst.set('B', true); - assertTstDfs(tst, ['A', true], ['B', true], ['C', true]); - } - - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (random)', function () { - for (let round = 10; round >= 0; round--) { - const keys: URI[] = []; - for (let i = 0; i < 100; i++) { - keys.push(URI.from({ scheme: 'fake-fs', path: randomPath(undefined, undefined, 10) })); - } - const tst = TernarySearchTree.forUris(); - - for (const item of keys) { - tst.set(item, true); - assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`); - } - - for (const item of keys) { - tst.delete(item); - assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`); - } - } - }); - - test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (simple)', function () { - const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; - const array = raw.split(','); - const tuples: [string, string][] = []; - for (let i = 0; i < array.length; i += 2) { - tuples.push([array[i], array[i + 1]]); - } - - const map = TernarySearchTree.forConfigKeys(); - map.fill(tuples); - - assert.strictEqual([...map].join(), raw); - assert.ok(map.has('config.editor.renderWhitespace')); - - const len = [...map].length; - map.delete('config.editor.renderWhitespace'); - assert.ok(map._isBalanced()); - assert.strictEqual([...map].length, len - 1); - }); - - test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (random)', function () { - const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; - const array = raw.split(','); - const tuples: [string, string][] = []; - for (let i = 0; i < array.length; i += 2) { - tuples.push([array[i], array[i + 1]]); - } - - for (let round = 100; round >= 0; round--) { - shuffle(tuples); - const map = TernarySearchTree.forConfigKeys(); - map.fill(tuples); - - assert.strictEqual([...map].join(), raw); - assert.ok(map.has('config.editor.renderWhitespace')); - - const len = [...map].length; - map.delete('config.editor.renderWhitespace'); - assert.ok(map._isBalanced()); - assert.strictEqual([...map].length, len - 1); - } - }); - - test('TernarySearchTree (PathSegments) - lookup', function () { - - const map = new TernarySearchTree(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - - assert.strictEqual(map.get('/foo'), undefined); - assert.strictEqual(map.get('/user'), undefined); - assert.strictEqual(map.get('/user/foo'), 2); - assert.strictEqual(map.get('/user/foo/bar'), 1); - assert.strictEqual(map.get('/user/foo/bar/boo'), undefined); - }); - - test('TernarySearchTree (PathSegments) - superstr', function () { - - const map = new TernarySearchTree(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - - let item: IteratorResult<[string, number]>; - let iter = map.findSuperstr('/user'); - - item = iter!.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr('/usr'); - item = iter!.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - assert.strictEqual(map.findSuperstr('/not'), undefined); - assert.strictEqual(map.findSuperstr('/us'), undefined); - assert.strictEqual(map.findSuperstr('/usrr'), undefined); - assert.strictEqual(map.findSuperstr('/userr'), undefined); - }); - - - test('TernarySearchTree (PathSegments) - delete_superstr', function () { - - const map = new TernarySearchTree(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - - assertTstDfs(map, - ['/user/foo', 2], - ['/user/foo/bar', 1], - ['/user/foo/flip/flop', 3], - ['/usr/foo', 4], - ); - - // not a segment - map.deleteSuperstr('/user/fo'); - assertTstDfs(map, - ['/user/foo', 2], - ['/user/foo/bar', 1], - ['/user/foo/flip/flop', 3], - ['/usr/foo', 4], - ); - - // delete a segment - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - map.deleteSuperstr('/user/foo'); - assertTstDfs(map, - ['/user/foo', 2], - ['/usr/foo', 4], - ); - }); - - test('TernarySearchTree (URI) - basics', function () { - const trie = new TernarySearchTree(new UriIterator(() => false, () => false)); - - trie.set(URI.file('/user/foo/bar'), 1); - trie.set(URI.file('/user/foo'), 2); - trie.set(URI.file('/user/foo/flip/flop'), 3); - - assert.strictEqual(trie.get(URI.file('/user/foo/bar')), 1); - assert.strictEqual(trie.get(URI.file('/user/foo')), 2); - assert.strictEqual(trie.get(URI.file('/user/foo/flip/flop')), 3); - - assert.strictEqual(trie.findSubstr(URI.file('/user/bar')), undefined); - assert.strictEqual(trie.findSubstr(URI.file('/user/foo')), 2); - assert.strictEqual(trie.findSubstr(URI.file('/user/foo/ba')), 2); - assert.strictEqual(trie.findSubstr(URI.file('/user/foo/far/boo')), 2); - assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar')), 1); - assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar/far/boo')), 1); - }); - - test('TernarySearchTree (URI) - query parameters', function () { - const trie = new TernarySearchTree(new UriIterator(() => false, () => true)); - const root = URI.parse('memfs:/?param=1'); - trie.set(root, 1); - - assert.strictEqual(trie.get(URI.parse('memfs:/?param=1')), 1); - - assert.strictEqual(trie.findSubstr(URI.parse('memfs:/?param=1')), 1); - assert.strictEqual(trie.findSubstr(URI.parse('memfs:/aaa?param=1')), 1); - }); - - test('TernarySearchTree (URI) - lookup', function () { - - const map = new TernarySearchTree(new UriIterator(() => false, () => false)); - map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); - map.set(URI.parse('http://foo.bar/user/foo?query'), 2); - map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3); - map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); - - assert.strictEqual(map.get(URI.parse('http://foo.bar/foo')), undefined); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user')), undefined); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar')), 1); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?query')), 2); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?Query')), undefined); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?QUERY')), 3); - assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); - }); - - test('TernarySearchTree (URI) - lookup, casing', function () { - - const map = new TernarySearchTree(new UriIterator(uri => /^https?$/.test(uri.scheme), () => false)); - map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); - assert.strictEqual(map.get(URI.parse('http://foo.bar/USER/foo/bar')), 1); - - map.set(URI.parse('foo://foo.bar/user/foo/bar'), 1); - assert.strictEqual(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); - }); - - test('TernarySearchTree (URI) - superstr', function () { - - const map = new TernarySearchTree(new UriIterator(() => false, () => false)); - map.set(URI.file('/user/foo/bar'), 1); - map.set(URI.file('/user/foo'), 2); - map.set(URI.file('/user/foo/flip/flop'), 3); - map.set(URI.file('/usr/foo'), 4); - - let item: IteratorResult<[URI, number]>; - let iter = map.findSuperstr(URI.file('/user'))!; - - item = iter.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr(URI.file('/usr'))!; - item = iter.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr(URI.file('/'))!; - item = iter.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - assert.strictEqual(map.findSuperstr(URI.file('/not')), undefined); - assert.strictEqual(map.findSuperstr(URI.file('/us')), undefined); - assert.strictEqual(map.findSuperstr(URI.file('/usrr')), undefined); - assert.strictEqual(map.findSuperstr(URI.file('/userr')), undefined); - }); - - test('TernarySearchTree (ConfigKeySegments) - basics', function () { - const trie = new TernarySearchTree(new ConfigKeysIterator()); - - trie.set('config.foo.bar', 1); - trie.set('config.foo', 2); - trie.set('config.foo.flip.flop', 3); - - assert.strictEqual(trie.get('config.foo.bar'), 1); - assert.strictEqual(trie.get('config.foo'), 2); - assert.strictEqual(trie.get('config.foo.flip.flop'), 3); - - assert.strictEqual(trie.findSubstr('config.bar'), undefined); - assert.strictEqual(trie.findSubstr('config.foo'), 2); - assert.strictEqual(trie.findSubstr('config.foo.ba'), 2); - assert.strictEqual(trie.findSubstr('config.foo.far.boo'), 2); - assert.strictEqual(trie.findSubstr('config.foo.bar'), 1); - assert.strictEqual(trie.findSubstr('config.foo.bar.far.boo'), 1); - }); - - test('TernarySearchTree (ConfigKeySegments) - lookup', function () { - - const map = new TernarySearchTree(new ConfigKeysIterator()); - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - - assert.strictEqual(map.get('foo'), undefined); - assert.strictEqual(map.get('config'), undefined); - assert.strictEqual(map.get('config.foo'), 2); - assert.strictEqual(map.get('config.foo.bar'), 1); - assert.strictEqual(map.get('config.foo.bar.boo'), undefined); - }); - - test('TernarySearchTree (ConfigKeySegments) - superstr', function () { - - const map = new TernarySearchTree(new ConfigKeysIterator()); - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - map.set('boo', 4); - - let item: IteratorResult<[string, number]>; - const iter = map.findSuperstr('config'); - - item = iter!.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - assert.strictEqual(map.findSuperstr('foo'), undefined); - assert.strictEqual(map.findSuperstr('config.foo.no'), undefined); - assert.strictEqual(map.findSuperstr('config.foop'), undefined); - }); - - - test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () { - - const map = new TernarySearchTree(new ConfigKeysIterator()); - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - map.set('boo', 4); - - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ['config.foo.bar', 1], - ['config.foo.flip.flop', 3], - ); - - // not a segment - map.deleteSuperstr('config.fo'); - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ['config.foo.bar', 1], - ['config.foo.flip.flop', 3], - ); - - // delete a segment - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - map.set('config.boo', 4); - map.deleteSuperstr('config.foo'); - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ); - }); - - test('TST, fill', function () { - const tst = TernarySearchTree.forStrings(); - - const keys = ['foo', 'bar', 'bang', 'bazz']; - Object.freeze(keys); - tst.fill(true, keys); - - for (const key of keys) { - assert.ok(tst.get(key), key); - } - }); - test('ResourceMap - basics', function () { const map = new ResourceMap(); @@ -1379,105 +485,3 @@ suite('Map', () => { }); }); - -suite.skip('TST, perf', function () { - - function createRandomUris(n: number): URI[] { - const uris: URI[] = []; - function randomWord(): string { - let result = ''; - const length = 4 + Math.floor(Math.random() * 4); - for (let i = 0; i < length; i++) { - result += (Math.random() * 26 + 65).toString(36); - } - return result; - } - - // generate 10000 random words - const words: string[] = []; - for (let i = 0; i < 10000; i++) { - words.push(randomWord()); - } - - for (let i = 0; i < n; i++) { - - let len = 4 + Math.floor(Math.random() * 4); - - const segments: string[] = []; - for (; len >= 0; len--) { - segments.push(words[Math.floor(Math.random() * words.length)]); - } - - uris.push(URI.from({ scheme: 'file', path: segments.join('/') })); - } - - return uris; - } - - let tree: TernarySearchTree; - let sampleUris: URI[] = []; - let candidates: URI[] = []; - - suiteSetup(() => { - const len = 50_000; - sampleUris = createRandomUris(len); - candidates = [...sampleUris.slice(0, len / 2), ...createRandomUris(len / 2)]; - shuffle(candidates); - }); - - setup(() => { - tree = TernarySearchTree.forUris(); - for (const uri of sampleUris) { - tree.set(uri, true); - } - }); - - const _profile = false; - - function perfTest(name: string, callback: Function) { - test(name, function () { - if (_profile) { console.profile(name); } - const sw = new StopWatch(true); - callback(); - console.log(name, sw.elapsed()); - if (_profile) { console.profileEnd(); } - }); - } - - perfTest('TST, clear', function () { - tree.clear(); - }); - - perfTest('TST, insert', function () { - const insertTree = TernarySearchTree.forUris(); - for (const uri of sampleUris) { - insertTree.set(uri, true); - } - }); - - perfTest('TST, lookup', function () { - let match = 0; - for (const candidate of candidates) { - if (tree.has(candidate)) { - match += 1; - } - } - assert.strictEqual(match, sampleUris.length / 2); - }); - - perfTest('TST, substr', function () { - let match = 0; - for (const candidate of candidates) { - if (tree.findSubstr(candidate)) { - match += 1; - } - } - assert.strictEqual(match, sampleUris.length / 2); - }); - - perfTest('TST, superstr', function () { - for (const candidate of candidates) { - tree.findSuperstr(candidate); - } - }); -}); diff --git a/src/vs/base/test/common/ternarySearchtree.test.ts b/src/vs/base/test/common/ternarySearchtree.test.ts new file mode 100644 index 00000000000..4ad5d8bbebd --- /dev/null +++ b/src/vs/base/test/common/ternarySearchtree.test.ts @@ -0,0 +1,1007 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { shuffle } from 'vs/base/common/arrays'; +import { randomPath } from 'vs/base/common/extpath'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { ConfigKeysIterator, PathIterator, StringIterator, TernarySearchTree, UriIterator } from 'vs/base/common/ternarySearchTree'; +import { URI } from 'vs/base/common/uri'; + +suite('Ternary Search Tree', () => { + + test('PathIterator', () => { + const iter = new PathIterator(); + iter.reset('file:///usr/bin/file.txt'); + + assert.strictEqual(iter.value(), 'file:'); + assert.strictEqual(iter.hasNext(), true); + assert.strictEqual(iter.cmp('file:'), 0); + assert.ok(iter.cmp('a') < 0); + assert.ok(iter.cmp('aile:') < 0); + assert.ok(iter.cmp('z') > 0); + assert.ok(iter.cmp('zile:') > 0); + + iter.next(); + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + iter.next(); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); + iter.next(); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); + + // + iter.reset('/foo/bar/'); + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'bar'); + assert.strictEqual(iter.hasNext(), false); + }); + + test('URIIterator', function () { + const iter = new UriIterator(() => false, () => false); + iter.reset(URI.parse('file:///usr/bin/file.txt')); + + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + + iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); + + // scheme + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // authority + assert.strictEqual(iter.value(), 'share'); + assert.strictEqual(iter.cmp('SHARe'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // query + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.cmp('z') > 0, true); + assert.strictEqual(iter.cmp('a') < 0, true); + assert.strictEqual(iter.hasNext(), false); + }); + + test('URIIterator - ignore query/fragment', function () { + const iter = new UriIterator(() => false, () => true); + iter.reset(URI.parse('file:///usr/bin/file.txt')); + + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + + iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); + + // scheme + assert.strictEqual(iter.value(), 'file'); + // assert.strictEqual(iter.cmp('FILE'), 0); + assert.strictEqual(iter.cmp('file'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // authority + assert.strictEqual(iter.value(), 'share'); + assert.strictEqual(iter.cmp('SHARe'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + }); + + function assertTstDfs(trie: TernarySearchTree, ...elements: [string, E][]) { + + assert.ok(trie._isBalanced(), 'TST is not balanced'); + + let i = 0; + for (const [key, value] of trie) { + const expected = elements[i++]; + assert.ok(expected); + assert.strictEqual(key, expected[0]); + assert.strictEqual(value, expected[1]); + } + + assert.strictEqual(i, elements.length); + + const map = new Map(); + for (const [key, value] of elements) { + map.set(key, value); + } + map.forEach((value, key) => { + assert.strictEqual(trie.get(key), value); + }); + + // forEach + let forEachCount = 0; + trie.forEach((element, key) => { + assert.strictEqual(element, map.get(key)); + forEachCount++; + }); + assert.strictEqual(map.size, forEachCount); + + // iterator + let iterCount = 0; + for (const [key, value] of trie) { + assert.strictEqual(value, map.get(key)); + iterCount++; + } + assert.strictEqual(map.size, iterCount); + + } + + test('TernarySearchTree - set', function () { + + let trie = TernarySearchTree.forStrings(); + trie.set('foobar', 1); + trie.set('foobaz', 2); + + assertTstDfs(trie, ['foobar', 1], ['foobaz', 2]); // longer + + trie = TernarySearchTree.forStrings(); + trie.set('foobar', 1); + trie.set('fooba', 2); + assertTstDfs(trie, ['fooba', 2], ['foobar', 1]); // shorter + + trie = TernarySearchTree.forStrings(); + trie.set('foo', 1); + trie.set('foo', 2); + assertTstDfs(trie, ['foo', 2]); + + trie = TernarySearchTree.forStrings(); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foob', 4); + trie.set('bazz', 5); + + assertTstDfs(trie, + ['bar', 3], + ['bazz', 5], + ['foo', 1], + ['foob', 4], + ['foobar', 2], + ); + }); + + test('TernarySearchTree - findLongestMatch', function () { + + const trie = TernarySearchTree.forStrings(); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('foobaz', 3); + assertTstDfs(trie, ['foo', 1], ['foobar', 2], ['foobaz', 3]); + + assert.strictEqual(trie.findSubstr('f'), undefined); + assert.strictEqual(trie.findSubstr('z'), undefined); + assert.strictEqual(trie.findSubstr('foo'), 1); + assert.strictEqual(trie.findSubstr('fooö'), 1); + assert.strictEqual(trie.findSubstr('fooba'), 1); + assert.strictEqual(trie.findSubstr('foobarr'), 2); + assert.strictEqual(trie.findSubstr('foobazrr'), 3); + }); + + test('TernarySearchTree - basics', function () { + const trie = new TernarySearchTree(new StringIterator()); + + trie.set('foo', 1); + trie.set('bar', 2); + trie.set('foobar', 3); + assertTstDfs(trie, ['bar', 2], ['foo', 1], ['foobar', 3]); + + assert.strictEqual(trie.get('foo'), 1); + assert.strictEqual(trie.get('bar'), 2); + assert.strictEqual(trie.get('foobar'), 3); + assert.strictEqual(trie.get('foobaz'), undefined); + assert.strictEqual(trie.get('foobarr'), undefined); + + assert.strictEqual(trie.findSubstr('fo'), undefined); + assert.strictEqual(trie.findSubstr('foo'), 1); + assert.strictEqual(trie.findSubstr('foooo'), 1); + + + trie.delete('foobar'); + trie.delete('bar'); + assert.strictEqual(trie.get('foobar'), undefined); + assert.strictEqual(trie.get('bar'), undefined); + + trie.set('foobar', 17); + trie.set('barr', 18); + assert.strictEqual(trie.get('foobar'), 17); + assert.strictEqual(trie.get('barr'), 18); + assert.strictEqual(trie.get('bar'), undefined); + }); + + test('TernarySearchTree - delete & cleanup', function () { + // normal delete + let trie = new TernarySearchTree(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + assertTstDfs(trie, ['bar', 3], ['foo', 1], ['foobar', 2]); + trie.delete('foo'); + assertTstDfs(trie, ['bar', 3], ['foobar', 2]); + trie.delete('foobar'); + assertTstDfs(trie, ['bar', 3]); + + // superstr-delete + trie = new TernarySearchTree(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foobarbaz', 4); + trie.deleteSuperstr('foo'); + assertTstDfs(trie, ['bar', 3], ['foo', 1]); + + trie = new TernarySearchTree(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foobarbaz', 4); + trie.deleteSuperstr('fo'); + assertTstDfs(trie, ['bar', 3]); + + // trie = new TernarySearchTree(new StringIterator()); + // trie.set('foo', 1); + // trie.set('foobar', 2); + // trie.set('bar', 3); + // trie.deleteSuperStr('f'); + // assertTernarySearchTree(trie, ['bar', 3]); + }); + + test('TernarySearchTree (PathSegments) - basics', function () { + const trie = new TernarySearchTree(new PathIterator()); + + trie.set('/user/foo/bar', 1); + trie.set('/user/foo', 2); + trie.set('/user/foo/flip/flop', 3); + + assert.strictEqual(trie.get('/user/foo/bar'), 1); + assert.strictEqual(trie.get('/user/foo'), 2); + assert.strictEqual(trie.get('/user//foo'), 2); + assert.strictEqual(trie.get('/user\\foo'), 2); + assert.strictEqual(trie.get('/user/foo/flip/flop'), 3); + + assert.strictEqual(trie.findSubstr('/user/bar'), undefined); + assert.strictEqual(trie.findSubstr('/user/foo'), 2); + assert.strictEqual(trie.findSubstr('\\user\\foo'), 2); + assert.strictEqual(trie.findSubstr('/user//foo'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/ba'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/far/boo'), 2); + assert.strictEqual(trie.findSubstr('/user/foo/bar'), 1); + assert.strictEqual(trie.findSubstr('/user/foo/bar/far/boo'), 1); + }); + + test('TernarySearchTree - (AVL) set', function () { + { + // rotate left + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/fileA', 1); + trie.set('/fileB', 2); + trie.set('/fileC', 3); + assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); + } + + { + // rotate left (inside middle) + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/foo/fileA', 1); + trie.set('/foo/fileB', 2); + trie.set('/foo/fileC', 3); + assertTstDfs(trie, ['/foo/fileA', 1], ['/foo/fileB', 2], ['/foo/fileC', 3]); + } + + { + // rotate right + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/fileC', 3); + trie.set('/fileB', 2); + trie.set('/fileA', 1); + assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); + } + + { + // rotate right (inside middle) + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/mid/fileC', 3); + trie.set('/mid/fileB', 2); + trie.set('/mid/fileA', 1); + assertTstDfs(trie, ['/mid/fileA', 1], ['/mid/fileB', 2], ['/mid/fileC', 3]); + } + + { + // rotate right, left + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/fileD', 7); + trie.set('/fileB', 2); + trie.set('/fileG', 42); + trie.set('/fileF', 24); + trie.set('/fileZ', 73); + trie.set('/fileE', 15); + assertTstDfs(trie, ['/fileB', 2], ['/fileD', 7], ['/fileE', 15], ['/fileF', 24], ['/fileG', 42], ['/fileZ', 73]); + } + + { + // rotate left, right + const trie = new TernarySearchTree(new PathIterator()); + trie.set('/fileJ', 42); + trie.set('/fileZ', 73); + trie.set('/fileE', 15); + trie.set('/fileB', 2); + trie.set('/fileF', 7); + trie.set('/fileG', 1); + assertTstDfs(trie, ['/fileB', 2], ['/fileE', 15], ['/fileF', 7], ['/fileG', 1], ['/fileJ', 42], ['/fileZ', 73]); + } + }); + + test('TernarySearchTree - (BST) delete', function () { + + const trie = new TernarySearchTree(new StringIterator()); + + // delete root + trie.set('d', 1); + assertTstDfs(trie, ['d', 1]); + trie.delete('d'); + assertTstDfs(trie); + + // delete node with two element + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['f', 1]); + trie.delete('d'); + assertTstDfs(trie, ['b', 1], ['f', 1]); + + // single child node + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + trie.set('e', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1]); + trie.delete('f'); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1]); + }); + + test('TernarySearchTree - (AVL) delete', function () { + + const trie = new TernarySearchTree(new StringIterator()); + + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + trie.set('e', 1); + trie.set('z', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1], ['z', 1]); + + // right, right + trie.delete('b'); + assertTstDfs(trie, ['d', 1], ['e', 1], ['f', 1], ['z', 1]); + + trie.clear(); + trie.set('d', 1); + trie.set('c', 1); + trie.set('f', 1); + trie.set('a', 1); + trie.set('b', 1); + assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1], ['f', 1]); + + // left, left + trie.delete('f'); + assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1]); + + // mid + trie.clear(); + trie.set('a', 1); + trie.set('ad', 1); + trie.set('ab', 1); + trie.set('af', 1); + trie.set('ae', 1); + trie.set('az', 1); + assertTstDfs(trie, ['a', 1], ['ab', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + + trie.delete('ab'); + assertTstDfs(trie, ['a', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + + trie.delete('a'); + assertTstDfs(trie, ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284', function () { + + const keys = [ + URI.parse('fake-fs:/C'), + URI.parse('fake-fs:/A'), + URI.parse('fake-fs:/D'), + URI.parse('fake-fs:/B'), + ]; + + const tst = TernarySearchTree.forUris(); + + for (const item of keys) { + tst.set(item, true); + } + + assert.ok(tst._isBalanced()); + tst.delete(keys[0]); + assert.ok(tst._isBalanced()); + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (simple)', function () { + + const keys = ['C', 'A', 'D', 'B',]; + const tst = TernarySearchTree.forStrings(); + for (const item of keys) { + tst.set(item, true); + } + assertTstDfs(tst, ['A', true], ['B', true], ['C', true], ['D', true]); + + tst.delete(keys[0]); + assertTstDfs(tst, ['A', true], ['B', true], ['D', true]); + + { + const tst = TernarySearchTree.forStrings(); + tst.set('C', true); + tst.set('A', true); + tst.set('B', true); + assertTstDfs(tst, ['A', true], ['B', true], ['C', true]); + } + + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (random)', function () { + for (let round = 10; round >= 0; round--) { + const keys: URI[] = []; + for (let i = 0; i < 100; i++) { + keys.push(URI.from({ scheme: 'fake-fs', path: randomPath(undefined, undefined, 10) })); + } + const tst = TernarySearchTree.forUris(); + + for (const item of keys) { + tst.set(item, true); + assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`); + } + + for (const item of keys) { + tst.delete(item); + assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`); + } + } + }); + + test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (simple)', function () { + const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; + const array = raw.split(','); + const tuples: [string, string][] = []; + for (let i = 0; i < array.length; i += 2) { + tuples.push([array[i], array[i + 1]]); + } + + const map = TernarySearchTree.forConfigKeys(); + map.fill(tuples); + + assert.strictEqual([...map].join(), raw); + assert.ok(map.has('config.editor.renderWhitespace')); + + const len = [...map].length; + map.delete('config.editor.renderWhitespace'); + assert.ok(map._isBalanced()); + assert.strictEqual([...map].length, len - 1); + }); + + test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (random)', function () { + const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; + const array = raw.split(','); + const tuples: [string, string][] = []; + for (let i = 0; i < array.length; i += 2) { + tuples.push([array[i], array[i + 1]]); + } + + for (let round = 100; round >= 0; round--) { + shuffle(tuples); + const map = TernarySearchTree.forConfigKeys(); + map.fill(tuples); + + assert.strictEqual([...map].join(), raw); + assert.ok(map.has('config.editor.renderWhitespace')); + + const len = [...map].length; + map.delete('config.editor.renderWhitespace'); + assert.ok(map._isBalanced()); + assert.strictEqual([...map].length, len - 1); + } + }); + + test('TernarySearchTree (PathSegments) - lookup', function () { + + const map = new TernarySearchTree(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + + assert.strictEqual(map.get('/foo'), undefined); + assert.strictEqual(map.get('/user'), undefined); + assert.strictEqual(map.get('/user/foo'), 2); + assert.strictEqual(map.get('/user/foo/bar'), 1); + assert.strictEqual(map.get('/user/foo/bar/boo'), undefined); + }); + + test('TernarySearchTree (PathSegments) - superstr', function () { + + const map = new TernarySearchTree(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + + let item: IteratorResult<[string, number]>; + let iter = map.findSuperstr('/user'); + + item = iter!.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr('/usr'); + item = iter!.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + assert.strictEqual(map.findSuperstr('/not'), undefined); + assert.strictEqual(map.findSuperstr('/us'), undefined); + assert.strictEqual(map.findSuperstr('/usrr'), undefined); + assert.strictEqual(map.findSuperstr('/userr'), undefined); + }); + + + test('TernarySearchTree (PathSegments) - delete_superstr', function () { + + const map = new TernarySearchTree(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + + assertTstDfs(map, + ['/user/foo', 2], + ['/user/foo/bar', 1], + ['/user/foo/flip/flop', 3], + ['/usr/foo', 4], + ); + + // not a segment + map.deleteSuperstr('/user/fo'); + assertTstDfs(map, + ['/user/foo', 2], + ['/user/foo/bar', 1], + ['/user/foo/flip/flop', 3], + ['/usr/foo', 4], + ); + + // delete a segment + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + map.deleteSuperstr('/user/foo'); + assertTstDfs(map, + ['/user/foo', 2], + ['/usr/foo', 4], + ); + }); + + test('TernarySearchTree (URI) - basics', function () { + const trie = new TernarySearchTree(new UriIterator(() => false, () => false)); + + trie.set(URI.file('/user/foo/bar'), 1); + trie.set(URI.file('/user/foo'), 2); + trie.set(URI.file('/user/foo/flip/flop'), 3); + + assert.strictEqual(trie.get(URI.file('/user/foo/bar')), 1); + assert.strictEqual(trie.get(URI.file('/user/foo')), 2); + assert.strictEqual(trie.get(URI.file('/user/foo/flip/flop')), 3); + + assert.strictEqual(trie.findSubstr(URI.file('/user/bar')), undefined); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/ba')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/far/boo')), 2); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar')), 1); + assert.strictEqual(trie.findSubstr(URI.file('/user/foo/bar/far/boo')), 1); + }); + + test('TernarySearchTree (URI) - query parameters', function () { + const trie = new TernarySearchTree(new UriIterator(() => false, () => true)); + const root = URI.parse('memfs:/?param=1'); + trie.set(root, 1); + + assert.strictEqual(trie.get(URI.parse('memfs:/?param=1')), 1); + + assert.strictEqual(trie.findSubstr(URI.parse('memfs:/?param=1')), 1); + assert.strictEqual(trie.findSubstr(URI.parse('memfs:/aaa?param=1')), 1); + }); + + test('TernarySearchTree (URI) - lookup', function () { + + const map = new TernarySearchTree(new UriIterator(() => false, () => false)); + map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); + map.set(URI.parse('http://foo.bar/user/foo?query'), 2); + map.set(URI.parse('http://foo.bar/user/foo?QUERY'), 3); + map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); + + assert.strictEqual(map.get(URI.parse('http://foo.bar/foo')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar')), 1); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?query')), 2); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?Query')), undefined); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo?QUERY')), 3); + assert.strictEqual(map.get(URI.parse('http://foo.bar/user/foo/bar/boo')), undefined); + }); + + test('TernarySearchTree (URI) - lookup, casing', function () { + + const map = new TernarySearchTree(new UriIterator(uri => /^https?$/.test(uri.scheme), () => false)); + map.set(URI.parse('http://foo.bar/user/foo/bar'), 1); + assert.strictEqual(map.get(URI.parse('http://foo.bar/USER/foo/bar')), 1); + + map.set(URI.parse('foo://foo.bar/user/foo/bar'), 1); + assert.strictEqual(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); + }); + + test('TernarySearchTree (URI) - superstr', function () { + + const map = new TernarySearchTree(new UriIterator(() => false, () => false)); + map.set(URI.file('/user/foo/bar'), 1); + map.set(URI.file('/user/foo'), 2); + map.set(URI.file('/user/foo/flip/flop'), 3); + map.set(URI.file('/usr/foo'), 4); + + let item: IteratorResult<[URI, number]>; + let iter = map.findSuperstr(URI.file('/user'))!; + + item = iter.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr(URI.file('/usr'))!; + item = iter.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr(URI.file('/'))!; + item = iter.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + assert.strictEqual(map.findSuperstr(URI.file('/not')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/us')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/usrr')), undefined); + assert.strictEqual(map.findSuperstr(URI.file('/userr')), undefined); + }); + + test('TernarySearchTree (ConfigKeySegments) - basics', function () { + const trie = new TernarySearchTree(new ConfigKeysIterator()); + + trie.set('config.foo.bar', 1); + trie.set('config.foo', 2); + trie.set('config.foo.flip.flop', 3); + + assert.strictEqual(trie.get('config.foo.bar'), 1); + assert.strictEqual(trie.get('config.foo'), 2); + assert.strictEqual(trie.get('config.foo.flip.flop'), 3); + + assert.strictEqual(trie.findSubstr('config.bar'), undefined); + assert.strictEqual(trie.findSubstr('config.foo'), 2); + assert.strictEqual(trie.findSubstr('config.foo.ba'), 2); + assert.strictEqual(trie.findSubstr('config.foo.far.boo'), 2); + assert.strictEqual(trie.findSubstr('config.foo.bar'), 1); + assert.strictEqual(trie.findSubstr('config.foo.bar.far.boo'), 1); + }); + + test('TernarySearchTree (ConfigKeySegments) - lookup', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + + assert.strictEqual(map.get('foo'), undefined); + assert.strictEqual(map.get('config'), undefined); + assert.strictEqual(map.get('config.foo'), 2); + assert.strictEqual(map.get('config.foo.bar'), 1); + assert.strictEqual(map.get('config.foo.bar.boo'), undefined); + }); + + test('TernarySearchTree (ConfigKeySegments) - superstr', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('boo', 4); + + let item: IteratorResult<[string, number]>; + const iter = map.findSuperstr('config'); + + item = iter!.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + assert.strictEqual(map.findSuperstr('foo'), undefined); + assert.strictEqual(map.findSuperstr('config.foo.no'), undefined); + assert.strictEqual(map.findSuperstr('config.foop'), undefined); + }); + + + test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () { + + const map = new TernarySearchTree(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('boo', 4); + + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ['config.foo.bar', 1], + ['config.foo.flip.flop', 3], + ); + + // not a segment + map.deleteSuperstr('config.fo'); + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ['config.foo.bar', 1], + ['config.foo.flip.flop', 3], + ); + + // delete a segment + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('config.boo', 4); + map.deleteSuperstr('config.foo'); + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ); + }); + + test('TST, fill', function () { + const tst = TernarySearchTree.forStrings(); + + const keys = ['foo', 'bar', 'bang', 'bazz']; + Object.freeze(keys); + tst.fill(true, keys); + + for (const key of keys) { + assert.ok(tst.get(key), key); + } + }); +}); + + +suite.skip('TST, perf', function () { + + function createRandomUris(n: number): URI[] { + const uris: URI[] = []; + function randomWord(): string { + let result = ''; + const length = 4 + Math.floor(Math.random() * 4); + for (let i = 0; i < length; i++) { + result += (Math.random() * 26 + 65).toString(36); + } + return result; + } + + // generate 10000 random words + const words: string[] = []; + for (let i = 0; i < 10000; i++) { + words.push(randomWord()); + } + + for (let i = 0; i < n; i++) { + + let len = 4 + Math.floor(Math.random() * 4); + + const segments: string[] = []; + for (; len >= 0; len--) { + segments.push(words[Math.floor(Math.random() * words.length)]); + } + + uris.push(URI.from({ scheme: 'file', path: segments.join('/') })); + } + + return uris; + } + + let tree: TernarySearchTree; + let sampleUris: URI[] = []; + let candidates: URI[] = []; + + suiteSetup(() => { + const len = 50_000; + sampleUris = createRandomUris(len); + candidates = [...sampleUris.slice(0, len / 2), ...createRandomUris(len / 2)]; + shuffle(candidates); + }); + + setup(() => { + tree = TernarySearchTree.forUris(); + for (const uri of sampleUris) { + tree.set(uri, true); + } + }); + + const _profile = false; + + function perfTest(name: string, callback: Function) { + test(name, function () { + if (_profile) { console.profile(name); } + const sw = new StopWatch(true); + callback(); + console.log(name, sw.elapsed()); + if (_profile) { console.profileEnd(); } + }); + } + + perfTest('TST, clear', function () { + tree.clear(); + }); + + perfTest('TST, insert', function () { + const insertTree = TernarySearchTree.forUris(); + for (const uri of sampleUris) { + insertTree.set(uri, true); + } + }); + + perfTest('TST, lookup', function () { + let match = 0; + for (const candidate of candidates) { + if (tree.has(candidate)) { + match += 1; + } + } + assert.strictEqual(match, sampleUris.length / 2); + }); + + perfTest('TST, substr', function () { + let match = 0; + for (const candidate of candidates) { + if (tree.findSubstr(candidate)) { + match += 1; + } + } + assert.strictEqual(match, sampleUris.length / 2); + }); + + perfTest('TST, superstr', function () { + for (const candidate of candidates) { + tree.findSuperstr(candidate); + } + }); +}); diff --git a/src/vs/editor/contrib/suggest/browser/suggestMemory.ts b/src/vs/editor/contrib/suggest/browser/suggestMemory.ts index dce5326c6da..ff7df7cc7cb 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestMemory.ts @@ -6,7 +6,8 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { LRUCache, TernarySearchTree } from 'vs/base/common/map'; +import { LRUCache } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { IPosition } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { CompletionItemKind, CompletionItemKinds } from 'vs/editor/common/languages'; diff --git a/src/vs/platform/configuration/test/common/testConfigurationService.ts b/src/vs/platform/configuration/test/common/testConfigurationService.ts index 935454817ce..5a826f174b2 100644 --- a/src/vs/platform/configuration/test/common/testConfigurationService.ts +++ b/src/vs/platform/configuration/test/common/testConfigurationService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; import { getConfigurationValue, IConfigurationChangeEvent, IConfigurationOverrides, IConfigurationService, IConfigurationValue, isConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index 0ebe4e7c7ef..cb2e1e3faf9 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -6,11 +6,11 @@ import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; import { MarshalledObject } from 'vs/base/common/marshalling'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { cloneAndChange, distinct } from 'vs/base/common/objects'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index c05a9374965..bf27ee455f8 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -11,7 +11,7 @@ import { Emitter } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { Schemas } from 'vs/base/common/network'; import { mark } from 'vs/base/common/performance'; import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from 'vs/base/common/resources'; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 9b6968f03aa..37aacc25140 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { IExpression, IRelativePattern } from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { sep } from 'vs/base/common/path'; import { ReadableStreamEvents } from 'vs/base/common/stream'; import { startsWithIgnoreCase } from 'vs/base/common/strings'; diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 66b0bc116eb..6126ccaf8b5 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -13,7 +13,7 @@ import { Emitter } from 'vs/base/common/event'; import { isEqualOrParent, randomPath } from 'vs/base/common/extpath'; import { GLOBSTAR, ParsedPattern, patternsEquals } from 'vs/base/common/glob'; import { Disposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { normalizeNFC } from 'vs/base/common/normalization'; import { dirname, isAbsolute, join, normalize, sep } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; diff --git a/src/vs/platform/protocol/electron-main/protocolMainService.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts index 43ad2167684..b66fd1c1c0f 100644 --- a/src/vs/platform/protocol/electron-main/protocolMainService.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { session } from 'electron'; -import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; import { COI, FileAccess, Schemas } from 'vs/base/common/network'; import { basename, extname, normalize } from 'vs/base/common/path'; import { isLinux } from 'vs/base/common/platform'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; +import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IIPCObjectUrl, IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; diff --git a/src/vs/platform/userData/common/fileUserDataProvider.ts b/src/vs/platform/userData/common/fileUserDataProvider.ts index 6addcd0379c..539c8011f3e 100644 --- a/src/vs/platform/userData/common/fileUserDataProvider.ts +++ b/src/vs/platform/userData/common/fileUserDataProvider.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; import { ILogService } from 'vs/platform/log/common/log'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { VSBuffer } from 'vs/base/common/buffer'; /** diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index d4a071e4415..d8052dfb673 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { basename, extname } from 'vs/base/common/path'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { extname as resourceExtname, basenameOrAuthority, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 63f37f89625..ef5d9ed9246 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -9,7 +9,7 @@ import * as performance from 'vs/base/common/performance'; import { originalFSPath, joinPath, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { asPromise, Barrier, timeout } from 'vs/base/common/async'; import { dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostExtensionServiceShape, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from 'vs/workbench/api/common/extHost.protocol'; diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 75019bcd205..cd51e5c6c77 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -8,7 +8,7 @@ import { Barrier } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { Schemas } from 'vs/base/common/network'; import { Counter } from 'vs/base/common/numbers'; import { basename, basenameOrAuthority, dirname, ExtUri, relativePath } from 'vs/base/common/resources'; diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts index 79048b689b5..aa0d6ae5fe4 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsAutoProfiler.ts @@ -25,7 +25,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { timeout } from 'vs/base/common/async'; import { bottomUp, buildModel } from 'vs/platform/profiling/common/profilingModel'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { TelemetrySampleData, TelemetrySampleDataClassification } from 'vs/platform/profiling/common/profilingTelemetrySpec'; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index e1ded9a550c..6bf1604731d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -59,7 +59,8 @@ import { BrowserFileUpload, ExternalFileImport, getMultipleFilesOverwriteConfirm import { toErrorMessage } from 'vs/base/common/errorMessage'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; import { IgnoreFile } from 'vs/workbench/services/search/common/ignoreFile'; -import { ResourceSet, TernarySearchTree } from 'vs/base/common/map'; +import { ResourceSet } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; export class ExplorerDelegate implements IListVirtualDelegate { diff --git a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index e8e5ca44edf..1adde1f53eb 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -8,7 +8,7 @@ import { IExpression, splitGlobAware, getEmptyExpression, ParsedExpression, pars import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { relativePath } from 'vs/base/common/resources'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class ResourceGlobMatcher { diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index ab6b3513570..4676f65f66e 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -10,9 +10,10 @@ import { memoize } from 'vs/base/common/decorators'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { ResourceMap, TernarySearchTree } from 'vs/base/common/map'; +import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { lcut } from 'vs/base/common/strings'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index b8d5d9491ad..c1d150de039 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, DebounceEmitter, Event } from 'vs/base/common/event'; import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from '../common/decorations'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; diff --git a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts index 4b63dbd1d4e..12671cf7cce 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/extensionHostProfiler.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types';