Skip to content

Commit c6d5242

Browse files
committed
scm: resource tree model
1 parent 6d09fb3 commit c6d5242

2 files changed

Lines changed: 152 additions & 0 deletions

File tree

src/vs/base/common/resourceTree.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { URI } from 'vs/base/common/uri';
7+
8+
export const enum NodeType {
9+
Branch,
10+
Leaf
11+
}
12+
13+
export interface LeafNode<T> {
14+
readonly type: NodeType.Leaf;
15+
readonly element: T;
16+
}
17+
18+
export interface BranchNode<T> {
19+
readonly type: NodeType.Branch;
20+
readonly children: Map<string, Node<T>>;
21+
}
22+
23+
export type Node<T> = BranchNode<T> | LeafNode<T>;
24+
25+
export class ResourceTree<T extends NonNullable<any>> {
26+
27+
readonly root: BranchNode<T> = { type: NodeType.Branch, children: new Map() };
28+
29+
constructor() { }
30+
31+
add(uri: URI, element: T): void {
32+
const parts = uri.fsPath.split(/[\\\/]/).filter(p => !!p);
33+
let node = this.root;
34+
35+
for (let i = 0; i < parts.length; i++) {
36+
const name = parts[i];
37+
let child = node.children.get(name);
38+
39+
if (!child) {
40+
if (i < parts.length - 1) {
41+
child = { type: NodeType.Branch, children: new Map() };
42+
node.children.set(name, child);
43+
} else {
44+
child = { type: NodeType.Leaf, element };
45+
node.children.set(name, child);
46+
return;
47+
}
48+
}
49+
50+
if (child.type === NodeType.Leaf) {
51+
if (i < parts.length - 1) {
52+
throw new Error('Inconsistent tree: can\'t override leaf with branch.');
53+
}
54+
55+
// replace
56+
node.children.set(name, { type: NodeType.Leaf, element });
57+
return;
58+
} else if (i === parts.length - 1) {
59+
throw new Error('Inconsistent tree: can\'t override branch with leaf.');
60+
}
61+
62+
node = child;
63+
}
64+
}
65+
66+
delete(uri: URI): T | undefined {
67+
const parts = uri.fsPath.split(/[\\\/]/).filter(p => !!p);
68+
return this._delete(this.root, parts, 0);
69+
}
70+
71+
private _delete(node: BranchNode<T>, parts: string[], index: number): T | undefined {
72+
const name = parts[index];
73+
const child = node.children.get(name);
74+
75+
if (!child) {
76+
return undefined;
77+
}
78+
79+
// not at end
80+
if (index < parts.length - 1) {
81+
if (child.type === NodeType.Leaf) {
82+
throw new Error('Inconsistent tree: Expected a branch, found a leaf instead.');
83+
} else {
84+
const result = this._delete(child, parts, index + 1);
85+
86+
if (typeof result !== 'undefined' && child.children.size === 0) {
87+
node.children.delete(name);
88+
}
89+
90+
return result;
91+
}
92+
}
93+
94+
//at end
95+
if (child.type === NodeType.Branch) {
96+
// TODO: maybe we can allow this
97+
throw new Error('Inconsistent tree: Expected a leaf, found a branch instead.');
98+
}
99+
100+
node.children.delete(name);
101+
return child.element;
102+
}
103+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import { ResourceTree, NodeType, BranchNode, LeafNode } from 'vs/base/common/resourceTree';
8+
import { URI } from 'vs/base/common/uri';
9+
10+
suite('ResourceTree', function () {
11+
test('ctor', function () {
12+
const tree = new ResourceTree<string>();
13+
assert.equal(tree.root.type, NodeType.Branch);
14+
assert.equal(tree.root.children.size, 0);
15+
});
16+
17+
test('simple', function () {
18+
const tree = new ResourceTree<string>();
19+
20+
tree.add(URI.file('/foo/bar.txt'), 'bar contents');
21+
assert.equal(tree.root.type, NodeType.Branch);
22+
assert.equal(tree.root.children.size, 1);
23+
24+
let foo = tree.root.children.get('foo') as BranchNode<string>;
25+
assert(foo);
26+
assert.equal(foo.type, NodeType.Branch);
27+
assert.equal(foo.children.size, 1);
28+
29+
let bar = foo.children.get('bar.txt') as LeafNode<string>;
30+
assert(bar);
31+
assert.equal(bar.type, NodeType.Leaf);
32+
assert.equal(bar.element, 'bar contents');
33+
34+
tree.add(URI.file('/hello.txt'), 'hello contents');
35+
assert.equal(tree.root.children.size, 2);
36+
37+
let hello = tree.root.children.get('hello.txt') as LeafNode<string>;
38+
assert(hello);
39+
assert.equal(hello.type, NodeType.Leaf);
40+
assert.equal(hello.element, 'hello contents');
41+
42+
tree.delete(URI.file('/foo/bar.txt'));
43+
assert.equal(tree.root.children.size, 1);
44+
hello = tree.root.children.get('hello.txt') as LeafNode<string>;
45+
assert(hello);
46+
assert.equal(hello.type, NodeType.Leaf);
47+
assert.equal(hello.element, 'hello contents');
48+
});
49+
});

0 commit comments

Comments
 (0)