diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index f274d1a72d67db5ec27dd5b18c2417566e909aef..a2f048b67edf25f84d05bd330103c2a19ae5ddfa 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -296,3 +296,75 @@ export class LRUCache extends BoundedLinkedMap { return null; } } + +// --- trie'ish datastructure + +interface Node { + element?: E; + children: { [key: string]: Node }; +} + +/** + * A trie map that allows for fast look up when keys are substrings + * to the actual search keys (dir/subdir-problem). + */ +export class TrieMap { + + static PathSplitter = s => s.split(/[\\/]/); + + private _splitter: (s: string) => string[]; + private _root: Node = { children: Object.create(null) }; + + constructor(splitter: (s: string) => string[]) { + this._splitter = splitter; + } + + insert(path: string, element: E): void { + const parts = this._splitter(path); + let i = 0; + + // find insertion node + let node = this._root; + for (; i < parts.length; i++) { + let child = node.children[parts[i]]; + if (child) { + node = child; + continue; + } + break; + } + + // create new nodes + let newNode: Node; + for (; i < parts.length; i++) { + newNode = { children: Object.create(null) }; + node.children[parts[i]] = newNode; + node = newNode; + } + + node.element = element; + } + + findSubstr(path: string): E { + const parts = this._splitter(path); + + let lastNode: Node; + let {children} = this._root; + for (const part of parts) { + const node = children[part]; + if (!node) { + break; + } + if (node.element) { + lastNode = node; + } + children = node.children; + } + + // return the last matching node + // that had an element + if (lastNode) { + return lastNode.element; + } + } +} diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 64d92dcf0ef38a82930084315744ccbf5a81ca5e..39545b12627f92592bd08d5afbbbb8e0e78ef9b6 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -5,7 +5,7 @@ 'use strict'; -import { BoundedLinkedMap, LRUCache, LinkedMap } from 'vs/base/common/map'; +import { BoundedLinkedMap, LRUCache, LinkedMap, TrieMap } from 'vs/base/common/map'; import * as assert from 'assert'; suite('Map', () => { @@ -271,4 +271,23 @@ suite('Map', () => { assert.ok(!cache.has('3')); assert.ok(!cache.has('4')); }); + + + test('TrieMap - basics', function () { + + const map = new TrieMap(TrieMap.PathSplitter); + + map.insert('/user/foo/bar', 1); + map.insert('/user/foo', 2); + map.insert('/user/foo/flip/flop', 3); + + assert.equal(map.findSubstr('/user/bar'), undefined); + assert.equal(map.findSubstr('/user/foo'), 2); + assert.equal(map.findSubstr('\\user\\foo'), 2); + assert.equal(map.findSubstr('/user/foo/ba'), 2); + assert.equal(map.findSubstr('/user/foo/far/boo'), 2); + assert.equal(map.findSubstr('/user/foo/bar'), 1); + assert.equal(map.findSubstr('/user/foo/bar/far/boo'), 1); + + }); }); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index ff787b6ee678dcdb1685dcb337ce7dc63d902982..85097ec0ad5f3776cd612dc5af33326361ccad71 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -5,6 +5,7 @@ 'use strict'; import { Emitter } from 'vs/base/common/event'; +import { TrieMap } from 'vs/base/common/map'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as Platform from 'vs/base/common/platform'; import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; @@ -425,7 +426,7 @@ export function defineAPI(factory: IExtensionApiFactory, extensions: IExtensionD let defaultApiImpl: typeof vscode; // create trie to enable fast 'filename -> extension id' look up - const trie = new TrieMap(); + const trie = new TrieMap(TrieMap.PathSplitter); for (const ext of extensions) { if (ext.name) { const path = realpathSync(ext.extensionFolderPath); @@ -457,68 +458,3 @@ export function defineAPI(factory: IExtensionApiFactory, extensions: IExtensionD return defaultApiImpl; }; } - -// --- trie'ish datastructure - -interface Node { - element?: E; - children: { [key: string]: Node }; -} - -class TrieMap { - - private static _sep = /[\\/]/; - - private _root: Node = { - children: Object.create(null) - }; - - insert(path: string, element: E): void { - const parts = path.split(TrieMap._sep); - let i = 0; - - // find insertion node - let node = this._root; - for (; i < parts.length; i++) { - let child = node.children[parts[i]]; - if (child) { - node = child; - continue; - } - break; - } - - // create new nodes - let newNode: Node; - for (; i < parts.length; i++) { - newNode = { children: Object.create(null) }; - node.children[parts[i]] = newNode; - node = newNode; - } - - newNode.element = element; - } - - findSubstr(path: string): E { - const parts = path.split(TrieMap._sep); - - let lastNode: Node; - let {children} = this._root; - for (const part of parts) { - const node = children[part]; - if (!node) { - break; - } - if (node.element) { - lastNode = node; - } - children = node.children; - } - - // return the last matching node - // that had an element - if (lastNode) { - return lastNode.element; - } - } -} \ No newline at end of file