Skip to content

Commit bede31d

Browse files
committed
resource tree: support data in branches
1 parent a698117 commit bede31d

3 files changed

Lines changed: 159 additions & 157 deletions

File tree

src/vs/base/common/resourceTree.ts

Lines changed: 68 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -11,58 +11,48 @@ import { URI } from 'vs/base/common/uri';
1111
import { mapValues } from 'vs/base/common/collections';
1212
import { PathIterator } from 'vs/base/common/map';
1313

14-
export interface ILeafNode<T, C = void> {
14+
export interface IResourceNode<T, C = void> {
1515
readonly uri: URI;
1616
readonly relativePath: string;
1717
readonly name: string;
18-
readonly element: T;
18+
readonly element: T | undefined;
19+
readonly children: Iterator<IResourceNode<T, C>>;
20+
readonly childrenCount: number;
21+
readonly parent: IResourceNode<T, C> | undefined;
1922
readonly context: C;
23+
get(childName: string): IResourceNode<T, C> | undefined;
2024
}
2125

22-
export interface IBranchNode<T, C = void> {
23-
readonly uri: URI;
24-
readonly relativePath: string;
25-
readonly name: string;
26-
readonly size: number;
27-
readonly children: Iterator<INode<T, C>>;
28-
readonly parent: IBranchNode<T, C> | undefined;
29-
readonly context: C;
30-
get(childName: string): INode<T, C> | undefined;
31-
}
32-
33-
export type INode<T, C> = IBranchNode<T, C> | ILeafNode<T, C>;
34-
35-
// Internals
36-
37-
class Node<C> {
38-
39-
@memoize
40-
get name(): string { return paths.posix.basename(this.relativePath); }
41-
42-
constructor(readonly uri: URI, readonly relativePath: string, readonly context: C) { }
43-
}
44-
45-
class BranchNode<T, C> extends Node<C> implements IBranchNode<T, C> {
26+
class Node<T, C> implements IResourceNode<T, C> {
4627

47-
private _children = new Map<string, BranchNode<T, C> | LeafNode<T, C>>();
28+
private _children = new Map<string, Node<T, C>>();
4829

49-
get size(): number {
30+
get childrenCount(): number {
5031
return this._children.size;
5132
}
5233

53-
get children(): Iterator<BranchNode<T, C> | LeafNode<T, C>> {
34+
get children(): Iterator<Node<T, C>> {
5435
return Iterator.fromArray(mapValues(this._children));
5536
}
5637

57-
constructor(uri: URI, relativePath: string, context: C, readonly parent: IBranchNode<T, C> | undefined = undefined) {
58-
super(uri, relativePath, context);
38+
@memoize
39+
get name(): string {
40+
return paths.posix.basename(this.relativePath);
5941
}
6042

61-
get(path: string): BranchNode<T, C> | LeafNode<T, C> | undefined {
43+
constructor(
44+
readonly uri: URI,
45+
readonly relativePath: string,
46+
readonly context: C,
47+
public element: T | undefined = undefined,
48+
readonly parent: IResourceNode<T, C> | undefined = undefined
49+
) { }
50+
51+
get(path: string): Node<T, C> | undefined {
6252
return this._children.get(path);
6353
}
6454

65-
set(path: string, child: BranchNode<T, C> | LeafNode<T, C>): void {
55+
set(path: string, child: Node<T, C>): void {
6656
this._children.set(path, child);
6757
}
6858

@@ -75,45 +65,38 @@ class BranchNode<T, C> extends Node<C> implements IBranchNode<T, C> {
7565
}
7666
}
7767

78-
class LeafNode<T, C> extends Node<C> implements ILeafNode<T, C> {
79-
80-
constructor(uri: URI, path: string, context: C, readonly element: T) {
81-
super(uri, path, context);
82-
}
83-
}
84-
85-
function collect<T, C>(node: INode<T, C>, result: T[]): T[] {
86-
if (ResourceTree.isBranchNode(node)) {
87-
Iterator.forEach(node.children, child => collect(child, result));
88-
} else {
68+
function collect<T, C>(node: IResourceNode<T, C>, result: T[]): T[] {
69+
if (typeof node.element !== 'undefined') {
8970
result.push(node.element);
9071
}
9172

73+
Iterator.forEach(node.children, child => collect(child, result));
74+
9275
return result;
9376
}
9477

9578
export class ResourceTree<T extends NonNullable<any>, C> {
9679

97-
readonly root: BranchNode<T, C>;
98-
99-
static isBranchNode<T, C>(obj: any): obj is IBranchNode<T, C> {
100-
return obj instanceof BranchNode;
101-
}
80+
readonly root: Node<T, C>;
10281

103-
static getRoot<T, C>(node: IBranchNode<T, C>): IBranchNode<T, C> {
82+
static getRoot<T, C>(node: IResourceNode<T, C>): IResourceNode<T, C> {
10483
while (node.parent) {
10584
node = node.parent;
10685
}
10786

10887
return node;
10988
}
11089

111-
static collect<T, C>(node: INode<T, C>): T[] {
90+
static collect<T, C>(node: IResourceNode<T, C>): T[] {
11291
return collect(node, []);
11392
}
11493

94+
static isResourceNode<T, C>(obj: any): obj is IResourceNode<T, C> {
95+
return obj instanceof Node;
96+
}
97+
11598
constructor(context: C, rootURI: URI = URI.file('/')) {
116-
this.root = new BranchNode(rootURI, '', context);
99+
this.root = new Node(rootURI, '', context);
117100
}
118101

119102
add(uri: URI, element: T): void {
@@ -129,26 +112,17 @@ export class ResourceTree<T extends NonNullable<any>, C> {
129112
let child = node.get(name);
130113

131114
if (!child) {
132-
if (iterator.hasNext()) {
133-
child = new BranchNode(joinPath(this.root.uri, path), path, this.root.context, node);
134-
node.set(name, child);
135-
} else {
136-
child = new LeafNode(uri, path, this.root.context, element);
137-
node.set(name, child);
138-
return;
139-
}
140-
}
141-
142-
if (!(child instanceof BranchNode)) {
143-
if (iterator.hasNext()) {
144-
throw new Error('Inconsistent tree: can\'t override leaf with branch.');
145-
}
146-
147-
// replace
148-
node.set(name, new LeafNode(uri, path, this.root.context, element));
149-
return;
115+
child = new Node(
116+
joinPath(this.root.uri, path),
117+
path,
118+
this.root.context,
119+
iterator.hasNext() ? undefined : element,
120+
node
121+
);
122+
123+
node.set(name, child);
150124
} else if (!iterator.hasNext()) {
151-
throw new Error('Inconsistent tree: can\'t override branch with leaf.');
125+
child.element = element;
152126
}
153127

154128
node = child;
@@ -167,33 +141,22 @@ export class ResourceTree<T extends NonNullable<any>, C> {
167141
return this._delete(this.root, iterator);
168142
}
169143

170-
private _delete(node: BranchNode<T, C>, iterator: PathIterator): T | undefined {
144+
private _delete(node: Node<T, C>, iterator: PathIterator): T | undefined {
171145
const name = iterator.value();
172146
const child = node.get(name);
173147

174148
if (!child) {
175149
return undefined;
176150
}
177151

178-
// not at end
179152
if (iterator.hasNext()) {
180-
if (child instanceof BranchNode) {
181-
const result = this._delete(child, iterator.next());
153+
const result = this._delete(child, iterator.next());
182154

183-
if (typeof result !== 'undefined' && child.size === 0) {
184-
node.delete(name);
185-
}
186-
187-
return result;
188-
} else {
189-
throw new Error('Inconsistent tree: Expected a branch, found a leaf instead.');
155+
if (typeof result !== 'undefined' && child.childrenCount === 0) {
156+
node.delete(name);
190157
}
191-
}
192158

193-
//at end
194-
if (child instanceof BranchNode) {
195-
// TODO: maybe we can allow this
196-
throw new Error('Inconsistent tree: Expected a leaf, found a branch instead.');
159+
return result;
197160
}
198161

199162
node.delete(name);
@@ -203,4 +166,22 @@ export class ResourceTree<T extends NonNullable<any>, C> {
203166
clear(): void {
204167
this.root.clear();
205168
}
169+
170+
getNode(uri: URI): IResourceNode<T, C> | undefined {
171+
const key = relativePath(this.root.uri, uri) || uri.fsPath;
172+
const iterator = new PathIterator(false).reset(key);
173+
let node = this.root;
174+
175+
while (true) {
176+
const name = iterator.value();
177+
const child = node.get(name);
178+
179+
if (!child || !iterator.hasNext()) {
180+
return child;
181+
}
182+
183+
node = child;
184+
iterator.next();
185+
}
186+
}
206187
}

src/vs/base/test/common/resourceTree.test.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,70 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as assert from 'assert';
7-
import { ResourceTree, IBranchNode, ILeafNode } from 'vs/base/common/resourceTree';
7+
import { ResourceTree } from 'vs/base/common/resourceTree';
88
import { URI } from 'vs/base/common/uri';
99

10-
suite('ResourceTree', function () {
10+
suite.only('ResourceTree', function () {
1111
test('ctor', function () {
1212
const tree = new ResourceTree<string, null>(null);
13-
assert(ResourceTree.isBranchNode(tree.root));
14-
assert.equal(tree.root.size, 0);
13+
assert.equal(tree.root.childrenCount, 0);
1514
});
1615

1716
test('simple', function () {
1817
const tree = new ResourceTree<string, null>(null);
1918

2019
tree.add(URI.file('/foo/bar.txt'), 'bar contents');
21-
assert(ResourceTree.isBranchNode(tree.root));
22-
assert.equal(tree.root.size, 1);
20+
assert.equal(tree.root.childrenCount, 1);
2321

24-
let foo = tree.root.get('foo') as IBranchNode<string, null>;
22+
let foo = tree.root.get('foo')!;
2523
assert(foo);
26-
assert(ResourceTree.isBranchNode(foo));
27-
assert.equal(foo.size, 1);
24+
assert.equal(foo.childrenCount, 1);
2825

29-
let bar = foo.get('bar.txt') as ILeafNode<string, null>;
26+
let bar = foo.get('bar.txt')!;
3027
assert(bar);
31-
assert(!ResourceTree.isBranchNode(bar));
3228
assert.equal(bar.element, 'bar contents');
3329

3430
tree.add(URI.file('/hello.txt'), 'hello contents');
35-
assert.equal(tree.root.size, 2);
31+
assert.equal(tree.root.childrenCount, 2);
3632

37-
let hello = tree.root.get('hello.txt') as ILeafNode<string, null>;
33+
let hello = tree.root.get('hello.txt')!;
3834
assert(hello);
39-
assert(!ResourceTree.isBranchNode(hello));
4035
assert.equal(hello.element, 'hello contents');
4136

4237
tree.delete(URI.file('/foo/bar.txt'));
43-
assert.equal(tree.root.size, 1);
44-
hello = tree.root.get('hello.txt') as ILeafNode<string, null>;
38+
assert.equal(tree.root.childrenCount, 1);
39+
hello = tree.root.get('hello.txt')!;
4540
assert(hello);
46-
assert(!ResourceTree.isBranchNode(hello));
4741
assert.equal(hello.element, 'hello contents');
4842
});
43+
44+
test('folders with data', function () {
45+
const tree = new ResourceTree<string, null>(null);
46+
47+
assert.equal(tree.root.childrenCount, 0);
48+
49+
tree.add(URI.file('/foo'), 'foo');
50+
assert.equal(tree.root.childrenCount, 1);
51+
assert.equal(tree.root.get('foo')!.element, 'foo');
52+
53+
tree.add(URI.file('/bar'), 'bar');
54+
assert.equal(tree.root.childrenCount, 2);
55+
assert.equal(tree.root.get('bar')!.element, 'bar');
56+
57+
tree.add(URI.file('/foo/file.txt'), 'file');
58+
assert.equal(tree.root.childrenCount, 2);
59+
assert.equal(tree.root.get('foo')!.element, 'foo');
60+
assert.equal(tree.root.get('bar')!.element, 'bar');
61+
assert.equal(tree.root.get('foo')!.get('file.txt')!.element, 'file');
62+
63+
tree.delete(URI.file('/foo'));
64+
assert.equal(tree.root.childrenCount, 1);
65+
assert(!tree.root.get('foo'));
66+
assert.equal(tree.root.get('bar')!.element, 'bar');
67+
68+
tree.delete(URI.file('/bar'));
69+
assert.equal(tree.root.childrenCount, 0);
70+
assert(!tree.root.get('foo'));
71+
assert(!tree.root.get('bar'));
72+
});
4973
});

0 commit comments

Comments
 (0)