diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 904321a93f76a5..e4759c631a0380 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -543,6 +543,39 @@ export class AsyncDataTree implements IDisposable protected readonly identityProvider?: IIdentityProvider; private readonly autoExpandSingleChildren: boolean; + private readonly nodeAccessHistory = new Set(); + private readonly MAX_RETAINED_NODES = 50000; + private gcInterval: ReturnType | undefined; + + private markNodeAccessed(element: T): void { + if (this.nodeAccessHistory.has(element)) { + this.nodeAccessHistory.delete(element); + } + this.nodeAccessHistory.add(element); + } + + private performGarbageCollection(): void { + if (this.nodeAccessHistory.size <= this.MAX_RETAINED_NODES) { + return; + } + const nodesToRemove = this.nodeAccessHistory.size - this.MAX_RETAINED_NODES; + let evictedCount = 0; + for (const element of this.nodeAccessHistory) { + if (evictedCount >= nodesToRemove) { + break; + } + try { + if (this.hasNode(element) && this.isExpanded(element)) { + this.collapse(element, true); + } + } catch (e) { + // Node might have already been removed + } + this.nodeAccessHistory.delete(element); + evictedCount++; + } + } + private readonly _onDidRender = new Emitter(); protected readonly _onDidChangeNodeSlowState = new Emitter>(); @@ -655,6 +688,8 @@ export class AsyncDataTree implements IDisposable this.onDidChangeFindMode = this.tree.onDidChangeFindMode; this.onDidChangeFindMatchType = this.tree.onDidChangeFindMatchType; } + + this.gcInterval = setInterval(() => this.performGarbageCollection(), 10000); } protected createTree( @@ -1203,6 +1238,10 @@ export class AsyncDataTree implements IDisposable return; } + if (!node.collapsed) { + this.markNodeAccessed(node.element.element as T); + } + if (!node.collapsed && node.element.stale) { if (deep) { this.collapse(node.element.element as T); @@ -1401,6 +1440,8 @@ export class AsyncDataTree implements IDisposable } dispose(): void { + clearInterval(this.gcInterval); + this.nodeAccessHistory.clear(); this._onDidRender.dispose(); this._onDidChangeNodeSlowState.dispose(); this.disposables.dispose();