/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; import * as paths from 'vs/base/common/path'; import { Iterator } from 'vs/base/common/iterator'; import { relativePath, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { mapValues } from 'vs/base/common/collections'; import { PathIterator } from 'vs/base/common/map'; export interface IResourceNode { readonly uri: URI; readonly relativePath: string; readonly name: string; readonly element: T | undefined; readonly children: Iterator>; readonly childrenCount: number; readonly parent: IResourceNode | undefined; readonly context: C; get(childName: string): IResourceNode | undefined; } class Node implements IResourceNode { private _children = new Map>(); get childrenCount(): number { return this._children.size; } get children(): Iterator> { return Iterator.fromArray(mapValues(this._children)); } @memoize get name(): string { return paths.posix.basename(this.relativePath); } constructor( readonly uri: URI, readonly relativePath: string, readonly context: C, public element: T | undefined = undefined, readonly parent: IResourceNode | undefined = undefined ) { } get(path: string): Node | undefined { return this._children.get(path); } set(path: string, child: Node): void { this._children.set(path, child); } delete(path: string): void { this._children.delete(path); } clear(): void { this._children.clear(); } } function collect(node: IResourceNode, result: T[]): T[] { if (typeof node.element !== 'undefined') { result.push(node.element); } Iterator.forEach(node.children, child => collect(child, result)); return result; } export class ResourceTree, C> { readonly root: Node; static getRoot(node: IResourceNode): IResourceNode { while (node.parent) { node = node.parent; } return node; } static collect(node: IResourceNode): T[] { return collect(node, []); } static isResourceNode(obj: any): obj is IResourceNode { return obj instanceof Node; } constructor(context: C, rootURI: URI = URI.file('/')) { this.root = new Node(rootURI, '', context); } add(uri: URI, element: T): void { const key = relativePath(this.root.uri, uri) || uri.fsPath; const iterator = new PathIterator(false).reset(key); let node = this.root; let path = ''; while (true) { const name = iterator.value(); path = path + '/' + name; let child = node.get(name); if (!child) { child = new Node( joinPath(this.root.uri, path), path, this.root.context, iterator.hasNext() ? undefined : element, node ); node.set(name, child); } else if (!iterator.hasNext()) { child.element = element; } node = child; if (!iterator.hasNext()) { return; } iterator.next(); } } delete(uri: URI): T | undefined { const key = relativePath(this.root.uri, uri) || uri.fsPath; const iterator = new PathIterator(false).reset(key); return this._delete(this.root, iterator); } private _delete(node: Node, iterator: PathIterator): T | undefined { const name = iterator.value(); const child = node.get(name); if (!child) { return undefined; } if (iterator.hasNext()) { const result = this._delete(child, iterator.next()); if (typeof result !== 'undefined' && child.childrenCount === 0) { node.delete(name); } return result; } node.delete(name); return child.element; } clear(): void { this.root.clear(); } getNode(uri: URI): IResourceNode | undefined { const key = relativePath(this.root.uri, uri) || uri.fsPath; const iterator = new PathIterator(false).reset(key); let node = this.root; while (true) { const name = iterator.value(); const child = node.get(name); if (!child || !iterator.hasNext()) { return child; } node = child; iterator.next(); } } }