From 576d8af2df0da76f4009dad4d5ef12a759545812 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Tue, 27 Oct 2020 15:36:50 -0400 Subject: [PATCH] Changes to use TernarySearchTree --- src/vs/base/common/map.ts | 88 ++++++++++-- src/vs/base/test/common/map.test.ts | 135 +++++++++++++++--- .../contextkey/browser/contextKeyService.ts | 31 ++-- .../decorations/browser/decorationsService.ts | 5 +- 4 files changed, 207 insertions(+), 52 deletions(-) diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 60df0a74ecb1b..8297b4678bba9 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -75,6 +75,57 @@ export class StringIterator implements IKeyIterator { } } +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; @@ -238,6 +289,10 @@ export class TernarySearchTree { return new TernarySearchTree(new StringIterator()); } + static forConfigKeys(): TernarySearchTree { + return new TernarySearchTree(new ConfigKeysIterator()); + } + private _iter: IKeyIterator; private _root: TernarySearchTreeNode | undefined; @@ -296,6 +351,10 @@ export class TernarySearchTree { } 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) { @@ -314,7 +373,11 @@ export class TernarySearchTree { break; } } - return node ? node.value : undefined; + return node; + } + + has(key: K): boolean { + return !!this.getNode(key); } delete(key: K): void { @@ -347,11 +410,18 @@ export class TernarySearchTree { stack.push([0, node]); node = node.mid; } else { - // remove element - node.value = undefined; + if (superStr) { + // remove children + node.left = undefined; + node.mid = undefined; + node.right = undefined; + } else { + // remove element + node.value = undefined; + } // clean up empty nodes - while (stack.length > 0 && (node.isEmpty() || superStr)) { + while (stack.length > 0 && node.isEmpty()) { let [dir, parent] = stack.pop()!; switch (dir) { case 1: parent.left = undefined; break; @@ -389,7 +459,7 @@ export class TernarySearchTree { return node && node.value || candidate; } - findSuperstr(key: K): Iterator | undefined { + findSuperstr(key: K): IterableIterator<[K, V]> | undefined { const iter = this._iter.reset(key); let node = this._root; while (node) { @@ -409,7 +479,7 @@ export class TernarySearchTree { if (!node.mid) { return undefined; } else { - return this._values(node.mid); + return this._entries(node.mid); } } } @@ -426,12 +496,6 @@ export class TernarySearchTree { yield* this._entries(this._root); } - private *_values(node: TernarySearchTreeNode): IterableIterator { - for (const [, value] of this._entries(node)) { - yield value; - } - } - private *_entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { if (node) { // left diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 90b435292045e..5781de52a7b7f 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator } from 'vs/base/common/map'; +import { ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache, UriIterator, ConfigKeysIterator } from 'vs/base/common/map'; import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; @@ -549,13 +549,15 @@ suite('Map', () => { trie.set('foo', 1); trie.set('foobar', 2); trie.set('bar', 3); + trie.set('foobarbaz', 4); trie.deleteSuperstr('foo'); - assertTernarySearchTree(trie, ['bar', 3]); + assertTernarySearchTree(trie, ['foo', 1], ['bar', 3]); trie = new TernarySearchTree(new StringIterator()); trie.set('foo', 1); trie.set('foobar', 2); trie.set('bar', 3); + trie.set('foobarbaz', 4); trie.deleteSuperstr('fo'); assertTernarySearchTree(trie, ['bar', 3]); @@ -612,17 +614,17 @@ suite('Map', () => { map.set('/user/foo/flip/flop', 3); map.set('/usr/foo', 4); - let item: IteratorResult; + let item: IteratorResult<[string, number]>; let iter = map.findSuperstr('/user'); item = iter!.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter!.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter!.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter!.next(); assert.equal(item.value, undefined); @@ -630,7 +632,7 @@ suite('Map', () => { iter = map.findSuperstr('/usr'); item = iter!.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter!.next(); @@ -675,7 +677,7 @@ suite('Map', () => { map.set('/usr/foo', 4); map.deleteSuperstr('/user/foo'); assertTernarySearchTree(map, - ['/usr/foo', 4], + ['/user/foo', 2], ['/usr/foo', 4], ); }); @@ -725,7 +727,7 @@ suite('Map', () => { assert.equal(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); }); - test('TernarySearchTree (PathSegments) - superstr', function () { + test('TernarySearchTree (URI) - superstr', function () { const map = new TernarySearchTree(new UriIterator(() => false)); map.set(URI.file('/user/foo/bar'), 1); @@ -733,17 +735,17 @@ suite('Map', () => { map.set(URI.file('/user/foo/flip/flop'), 3); map.set(URI.file('/usr/foo'), 4); - let item: IteratorResult; + let item: IteratorResult<[URI, number]>; let iter = map.findSuperstr(URI.file('/user'))!; item = iter.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter.next(); assert.equal(item.value, undefined); @@ -751,7 +753,7 @@ suite('Map', () => { iter = map.findSuperstr(URI.file('/usr'))!; item = iter.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter.next(); @@ -760,16 +762,16 @@ suite('Map', () => { iter = map.findSuperstr(URI.file('/'))!; item = iter.next(); - assert.equal(item.value, 2); + assert.equal(item.value[1], 2); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 1); + assert.equal(item.value[1], 1); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 3); + assert.equal(item.value[1], 3); assert.equal(item.done, false); item = iter.next(); - assert.equal(item.value, 4); + assert.equal(item.value[1], 4); assert.equal(item.done, false); item = iter.next(); assert.equal(item.value, undefined); @@ -781,6 +783,103 @@ suite('Map', () => { assert.equal(map.findSuperstr(URI.file('/userr')), undefined); }); + test('TernarySearchTree (ConfigKeySegments) - basics', function () { + let trie = new TernarySearchTree(new ConfigKeysIterator()); + + trie.set('config.foo.bar', 1); + trie.set('config.foo', 2); + trie.set('config.foo.flip.flop', 3); + + assert.equal(trie.get('config.foo.bar'), 1); + assert.equal(trie.get('config.foo'), 2); + assert.equal(trie.get('config.foo.flip.flop'), 3); + + assert.equal(trie.findSubstr('config.bar'), undefined); + assert.equal(trie.findSubstr('config.foo'), 2); + assert.equal(trie.findSubstr('config.foo.ba'), 2); + assert.equal(trie.findSubstr('config.foo.far.boo'), 2); + assert.equal(trie.findSubstr('config.foo.bar'), 1); + assert.equal(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.equal(map.get('foo'), undefined); + assert.equal(map.get('config'), undefined); + assert.equal(map.get('config.foo'), 2); + assert.equal(map.get('config.foo.bar'), 1); + assert.equal(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]>; + let iter = map.findSuperstr('config'); + + item = iter!.next(); + assert.equal(item.value[1], 2); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value[1], 1); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value[1], 3); + assert.equal(item.done, false); + item = iter!.next(); + assert.equal(item.value, undefined); + assert.equal(item.done, true); + + assert.equal(map.findSuperstr('foo'), undefined); + assert.equal(map.findSuperstr('config.foo.no'), undefined); + assert.equal(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); + + assertTernarySearchTree(map, + ['config.foo.bar', 1], + ['config.foo', 2], + ['config.foo.flip.flop', 3], + ['boo', 4], + ); + + // not a segment + map.deleteSuperstr('config.fo'); + assertTernarySearchTree(map, + ['config.foo.bar', 1], + ['config.foo', 2], + ['config.foo.flip.flop', 3], + ['boo', 4], + ); + + // 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'); + assertTernarySearchTree(map, + ['config.foo', 2], ['boo', 4], + ); + }); test('ResourceMap - basics', function () { const map = new ResourceMap(); diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index f3f2ce39223b5..e5b568c80901f 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; +import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; import { distinct } from 'vs/base/common/objects'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -93,7 +95,7 @@ class ConfigAwareContextValuesContainer extends Context { private static readonly _configContextObject = Object.freeze({}); private static readonly _keyPrefix = 'config.'; - private readonly _values = new Map(); + private readonly _values = TernarySearchTree.forConfigKeys(); private readonly _listener: IDisposable; constructor( @@ -106,34 +108,23 @@ class ConfigAwareContextValuesContainer extends Context { this._listener = this._configurationService.onDidChangeConfiguration(event => { if (event.source === ConfigurationTarget.DEFAULT) { // new setting, reset everything - const allKeys = Array.from(this._values.keys()); + const allKeys = Array.from(Iterable.map(this._values, ([k, v]) => k)); this._values.clear(); emitter.fire(new ArrayContextKeyChangeEvent(allKeys)); } else { const changedKeys: string[] = []; - const changedObjectKeys: string[] = []; - for (const configKey of event.affectedKeys) { const contextKey = `config.${configKey}`; - if (this._values.has(contextKey)) { - const value = this._values.get(contextKey); - - this._values.delete(contextKey); - changedKeys.push(contextKey); - if (value === ConfigAwareContextValuesContainer._configContextObject) { - changedObjectKeys.push(contextKey + '.'); - } + const cachedItems = this._values.findSuperstr(contextKey); + if (cachedItems !== undefined) { + changedKeys.push(...[...Iterable.map(cachedItems, ([key]) => key)]); + this._values.deleteSuperstr(contextKey); } - } - if (changedObjectKeys.length) { - const regex = new RegExp(`^(${changedObjectKeys.join('|').replace(/\./g, '\\.')})`); - for (const key of this._values.keys()) { - if (regex.test(key)) { - this._values.delete(key); - changedKeys.push(key); - } + if (this._values.has(contextKey)) { + changedKeys.push(contextKey); + this._values.delete(contextKey); } } diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 5205f3f768859..9bf7ab000be2e 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -260,8 +260,9 @@ class DecorationProviderWrapper { const iter = this.data.findSuperstr(uri); if (iter) { for (let item = iter.next(); !item.done; item = iter.next()) { - if (item.value && !(item.value instanceof DecorationDataRequest)) { - callback(item.value, true); + const value = item.value?.[1]; + if (value && !(value instanceof DecorationDataRequest)) { + callback(value, true); } } }