Skip to content

Commit 73357b8

Browse files
committed
ResourceTree 💄
1 parent 33deade commit 73357b8

3 files changed

Lines changed: 46 additions & 42 deletions

File tree

src/vs/base/common/resourceTree.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import { memoize } from 'vs/base/common/decorators';
77
import * as paths from 'vs/base/common/path';
88
import { Iterator } from 'vs/base/common/iterator';
9+
import { relativePath } from 'vs/base/common/resources';
10+
import { URI } from 'vs/base/common/uri';
911

1012
export interface ILeafNode<T> {
1113
readonly path: string;
@@ -23,10 +25,6 @@ export interface IBranchNode<T> {
2325

2426
export type INode<T> = IBranchNode<T> | ILeafNode<T>;
2527

26-
export function isBranchNode<T>(obj: any): obj is IBranchNode<T> {
27-
return obj instanceof BranchNode;
28-
}
29-
3028
// Internals
3129

3230
class Node {
@@ -73,7 +71,14 @@ export class ResourceTree<T extends NonNullable<any>> {
7371

7472
readonly root = new BranchNode<T>('');
7573

76-
add(key: string, element: T): void {
74+
static isBranchNode<T>(obj: any): obj is IBranchNode<T> {
75+
return obj instanceof BranchNode;
76+
}
77+
78+
constructor(private rootURI: URI = URI.file('/')) { }
79+
80+
add(uri: URI, element: T): void {
81+
const key = relativePath(this.rootURI, uri) || uri.fsPath;
7782
const parts = key.split(/[\\\/]/).filter(p => !!p);
7883
let node = this.root;
7984
let path = this.root.path;
@@ -111,7 +116,8 @@ export class ResourceTree<T extends NonNullable<any>> {
111116
}
112117
}
113118

114-
delete(key: string): T | undefined {
119+
delete(uri: URI): T | undefined {
120+
const key = relativePath(this.rootURI, uri) || uri.fsPath;
115121
const parts = key.split(/[\\\/]/).filter(p => !!p);
116122
return this._delete(this.root, parts, 0);
117123
}

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,46 @@
44
*--------------------------------------------------------------------------------------------*/
55

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

910
suite('ResourceTree', function () {
1011
test('ctor', function () {
1112
const tree = new ResourceTree<string>();
12-
assert(isBranchNode(tree.root));
13+
assert(ResourceTree.isBranchNode(tree.root));
1314
assert.equal(tree.root.size, 0);
1415
});
1516

1617
test('simple', function () {
1718
const tree = new ResourceTree<string>();
1819

19-
tree.add('/foo/bar.txt', 'bar contents');
20-
assert(isBranchNode(tree.root));
20+
tree.add(URI.file('/foo/bar.txt'), 'bar contents');
21+
assert(ResourceTree.isBranchNode(tree.root));
2122
assert.equal(tree.root.size, 1);
2223

2324
let foo = tree.root.get('foo') as IBranchNode<string>;
2425
assert(foo);
25-
assert(isBranchNode(foo));
26+
assert(ResourceTree.isBranchNode(foo));
2627
assert.equal(foo.size, 1);
2728

2829
let bar = foo.get('bar.txt') as ILeafNode<string>;
2930
assert(bar);
30-
assert(!isBranchNode(bar));
31+
assert(!ResourceTree.isBranchNode(bar));
3132
assert.equal(bar.element, 'bar contents');
3233

33-
tree.add('/hello.txt', 'hello contents');
34+
tree.add(URI.file('/hello.txt'), 'hello contents');
3435
assert.equal(tree.root.size, 2);
3536

3637
let hello = tree.root.get('hello.txt') as ILeafNode<string>;
3738
assert(hello);
38-
assert(!isBranchNode(hello));
39+
assert(!ResourceTree.isBranchNode(hello));
3940
assert.equal(hello.element, 'hello contents');
4041

41-
tree.delete('/foo/bar.txt');
42+
tree.delete(URI.file('/foo/bar.txt'));
4243
assert.equal(tree.root.size, 1);
4344
hello = tree.root.get('hello.txt') as ILeafNode<string>;
4445
assert(hello);
45-
assert(!isBranchNode(hello));
46+
assert(!ResourceTree.isBranchNode(hello));
4647
assert.equal(hello.element, 'hello contents');
4748
});
4849
});

src/vs/workbench/contrib/scm/browser/repositoryPanel.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import 'vs/css!./media/scmViewlet';
77
import { Event } from 'vs/base/common/event';
88
import { domEvent } from 'vs/base/browser/event';
9-
import { basename, relativePath } from 'vs/base/common/resources';
9+
import { basename } from 'vs/base/common/resources';
1010
import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle';
1111
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
1212
import { append, $, addClass, toggleClass, trackFocus, removeClass } from 'vs/base/browser/dom';
@@ -37,7 +37,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
3737
import * as platform from 'vs/base/common/platform';
3838
import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
3939
import { ISequence, ISplice } from 'vs/base/common/sequence';
40-
import { ResourceTree, IBranchNode, isBranchNode, INode } from 'vs/base/common/resourceTree';
40+
import { ResourceTree, IBranchNode, INode } from 'vs/base/common/resourceTree';
4141
import { ObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
4242
import { Iterator } from 'vs/base/common/iterator';
4343
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
@@ -187,10 +187,10 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
187187

188188
const resource = node.element;
189189
const theme = this.themeService.getTheme();
190-
const icon = isBranchNode(resource) ? undefined : (theme.type === LIGHT ? resource.decorations.icon : resource.decorations.iconDark);
190+
const icon = ResourceTree.isBranchNode(resource) ? undefined : (theme.type === LIGHT ? resource.decorations.icon : resource.decorations.iconDark);
191191

192-
const uri = isBranchNode(resource) ? URI.file(resource.path) : resource.sourceUri;
193-
const fileKind = isBranchNode(resource) ? FileKind.FOLDER : FileKind.FILE;
192+
const uri = ResourceTree.isBranchNode(resource) ? URI.file(resource.path) : resource.sourceUri;
193+
const fileKind = ResourceTree.isBranchNode(resource) ? FileKind.FOLDER : FileKind.FILE;
194194
template.fileLabel.setFile(uri, {
195195
fileDecorations: { colors: false, badges: !icon },
196196
hidePath: true,
@@ -202,13 +202,13 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IBran
202202

203203
const disposables = new DisposableStore();
204204

205-
if (!isBranchNode(resource)) {
205+
if (!ResourceTree.isBranchNode(resource)) {
206206
disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resource.resourceGroup), template.actionBar));
207207
toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough);
208208
toggleClass(template.element, 'faded', resource.decorations.faded);
209209
}
210210

211-
const tooltip = (isBranchNode(resource) ? resource.path : resource.decorations.tooltip) || '';
211+
const tooltip = (ResourceTree.isBranchNode(resource) ? resource.path : resource.decorations.tooltip) || '';
212212

213213
if (icon) {
214214
template.decorationIcon.style.display = '';
@@ -265,7 +265,7 @@ class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {
265265
getHeight() { return 22; }
266266

267267
getTemplateId(element: TreeElement) {
268-
if (isBranchNode(element) || isSCMResource(element)) {
268+
if (ResourceTree.isBranchNode(element) || isSCMResource(element)) {
269269
return ResourceRenderer.TEMPLATE_ID;
270270
} else {
271271
return ResourceGroupRenderer.TEMPLATE_ID;
@@ -276,7 +276,7 @@ class ProviderListDelegate implements IListVirtualDelegate<TreeElement> {
276276
class SCMTreeFilter implements ITreeFilter<TreeElement> {
277277

278278
filter(element: TreeElement): boolean {
279-
if (isBranchNode(element)) {
279+
if (ResourceTree.isBranchNode(element)) {
280280
return true;
281281
} else if (isSCMResourceGroup(element)) {
282282
return element.elements.length > 0 || !element.hideWhenEmpty;
@@ -293,15 +293,15 @@ export class SCMTreeSorter implements ITreeSorter<TreeElement> {
293293
return 0;
294294
}
295295

296-
const oneIsDirectory = isBranchNode(one);
297-
const otherIsDirectory = isBranchNode(other);
296+
const oneIsDirectory = ResourceTree.isBranchNode(one);
297+
const otherIsDirectory = ResourceTree.isBranchNode(other);
298298

299299
if (oneIsDirectory !== otherIsDirectory) {
300300
return oneIsDirectory ? -1 : 1;
301301
}
302302

303-
const oneName = isBranchNode(one) ? one.name : basename((one as ISCMResource).sourceUri);
304-
const otherName = isBranchNode(other) ? other.name : basename((other as ISCMResource).sourceUri);
303+
const oneName = ResourceTree.isBranchNode(one) ? one.name : basename((one as ISCMResource).sourceUri);
304+
const otherName = ResourceTree.isBranchNode(other) ? other.name : basename((other as ISCMResource).sourceUri);
305305

306306
return compareFileNames(oneName, otherName);
307307
}
@@ -324,7 +324,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements IKeyboardNavigati
324324

325325
const scmResourceIdentityProvider = new class implements IIdentityProvider<TreeElement> {
326326
getId(e: TreeElement): string {
327-
if (isBranchNode(e)) {
327+
if (ResourceTree.isBranchNode(e)) {
328328
return e.path;
329329
} else if (isSCMResource(e)) {
330330
const group = e.resourceGroup;
@@ -353,7 +353,7 @@ function groupItemAsTreeElement(item: IGroupItem, flat: boolean): ICompressedTre
353353
}
354354

355355
function asTreeElement(node: INode<ISCMResource>, incompressible: boolean): ICompressedTreeElement<TreeElement> {
356-
if (isBranchNode(node)) {
356+
if (ResourceTree.isBranchNode(node)) {
357357
return {
358358
element: node,
359359
children: Iterator.map(node.children, node => asTreeElement(node, false)),
@@ -382,18 +382,17 @@ class ViewModel {
382382
const itemsToInsert: IGroupItem[] = [];
383383

384384
for (const group of toInsert) {
385-
const tree = new ResourceTree<ISCMResource>();
385+
const tree = new ResourceTree<ISCMResource>(group.provider.rootUri || URI.file('/'));
386386
const resources: ISCMResource[] = [...group.elements];
387387
const disposable = combinedDisposable(
388388
group.onDidChange(() => this.tree.refilter()),
389389
group.onDidSplice(splice => this.onDidSpliceGroup(item, splice))
390390
);
391391

392392
const item = { group, resources, tree, disposable };
393-
const root = item.group.provider.rootUri || URI.file('/');
394393

395394
for (const resource of resources) {
396-
item.tree.add(relativePath(root, resource.sourceUri) || resource.sourceUri.fsPath, resource);
395+
item.tree.add(resource.sourceUri, resource);
397396
}
398397

399398
itemsToInsert.push(item);
@@ -409,16 +408,14 @@ class ViewModel {
409408
}
410409

411410
private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice<ISCMResource>): void {
412-
const root = item.group.provider.rootUri || URI.file('/');
413-
414411
for (const resource of toInsert) {
415-
item.tree.add(relativePath(root, resource.sourceUri) || resource.sourceUri.fsPath, resource);
412+
item.tree.add(resource.sourceUri, resource);
416413
}
417414

418415
const deleted = item.resources.splice(start, deleteCount, ...toInsert);
419416

420417
for (const resource of deleted) {
421-
item.tree.delete(relativePath(root, resource.sourceUri) || resource.sourceUri.fsPath);
418+
item.tree.delete(resource.sourceUri);
422419
}
423420

424421
this.refresh(item);
@@ -618,12 +615,12 @@ export class RepositoryPanel extends ViewletPanel {
618615

619616
this._register(Event.chain(this.tree.onDidOpen)
620617
.map(e => e.elements[0])
621-
.filter(e => !!e && !isBranchNode(e) && isSCMResource(e))
618+
.filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e))
622619
.on(this.open, this));
623620

624621
this._register(Event.chain(this.tree.onDidPin)
625622
.map(e => e.elements[0])
626-
.filter(e => !!e && !isBranchNode(e) && isSCMResource(e))
623+
.filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e))
627624
.on(this.pin, this));
628625

629626
this._register(this.tree.onContextMenu(this.onListContextMenu, this));
@@ -717,7 +714,7 @@ export class RepositoryPanel extends ViewletPanel {
717714
const element = e.element;
718715
let actions: IAction[];
719716

720-
if (isBranchNode(element)) {
717+
if (ResourceTree.isBranchNode(element)) {
721718
actions = [];
722719
} else if (isSCMResource(element)) {
723720
actions = this.menus.getResourceContextActions(element);
@@ -735,7 +732,7 @@ export class RepositoryPanel extends ViewletPanel {
735732

736733
private getSelectedResources(): ISCMResource[] {
737734
return this.tree.getSelection()
738-
.filter(r => !!r && !isBranchNode(r) && isSCMResource(r)) as ISCMResource[];
735+
.filter(r => !!r && !ResourceTree.isBranchNode(r) && isSCMResource(r)) as ISCMResource[];
739736
}
740737

741738
private updateInputBox(): void {

0 commit comments

Comments
 (0)