treeView.ts 36.2 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/views';
import { Event, Emitter } from 'vs/base/common/event';
8
import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
9
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
10
import { IAction, ActionRunner, Action } from 'vs/base/common/actions';
11 12 13
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
14
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
15
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
16
import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
17
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
18 19
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
20
import { IProgressService } from 'vs/platform/progress/common/progress';
21 22 23
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import * as DOM from 'vs/base/browser/dom';
B
Benjamin Pasero 已提交
24
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
25
import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
26
import { URI } from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
27
import { dirname, basename } from 'vs/base/common/resources';
28
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService';
29
import { FileKind } from 'vs/platform/files/common/files';
30
import { WorkbenchAsyncDataTree, ResourceNavigator } from 'vs/platform/list/browser/listService';
S
SteVen Batten 已提交
31
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
S
Sandeep Somavarapu 已提交
32
import { localize } from 'vs/nls';
33
import { timeout } from 'vs/base/common/async';
J
Joao Moreno 已提交
34
import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
35
import { isString } from 'vs/base/common/types';
36
import { ILabelService } from 'vs/platform/label/common/label';
37
import { Registry } from 'vs/platform/registry/common/platform';
38
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
39
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
40 41
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
J
Johannes Rieken 已提交
42
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
S
SteVen Batten 已提交
43
import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
44
import { IOpenerService } from 'vs/platform/opener/common/opener';
45
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
46

47
export class TreeViewPane extends ViewPane {
48

S
rename  
Sandeep Somavarapu 已提交
49
	private treeView: ITreeView;
50 51 52 53 54 55

	constructor(
		options: IViewletViewOptions,
		@IKeybindingService keybindingService: IKeybindingService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IConfigurationService configurationService: IConfigurationService,
S
Sandeep Somavarapu 已提交
56
		@IContextKeyService contextKeyService: IContextKeyService,
57
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
58
		@IInstantiationService instantiationService: IInstantiationService,
59 60
		@IOpenerService openerService: IOpenerService,
		@IThemeService themeService: IThemeService,
61
		@ITelemetryService telemetryService: ITelemetryService,
62
	) {
63
		super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
64
		const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
S
rename  
Sandeep Somavarapu 已提交
65
		this.treeView = treeView;
M
Matt Bierner 已提交
66
		this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
67
		this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle)));
M
Matt Bierner 已提交
68 69
		this._register(toDisposable(() => this.treeView.setVisibility(false)));
		this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility()));
70
		this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire()));
71
		this.updateTreeVisibility();
72 73 74 75
	}

	focus(): void {
		super.focus();
S
rename  
Sandeep Somavarapu 已提交
76
		this.treeView.focus();
77 78 79
	}

	renderBody(container: HTMLElement): void {
J
Joao Moreno 已提交
80 81
		super.renderBody(container);

82
		if (this.treeView instanceof TreeView) {
S
Sandeep Somavarapu 已提交
83 84
			this.treeView.show(container);
		}
85 86
	}

87 88 89 90
	shouldShowWelcome(): boolean {
		return (this.treeView.dataProvider === undefined) && (this.treeView.message === undefined);
	}

91 92
	layoutBody(height: number, width: number): void {
		this.treeView.layout(height, width);
93 94 95
	}

	getActions(): IAction[] {
96
		return [...super.getActions(), ...this.treeView.getPrimaryActions()];
97 98 99
	}

	getSecondaryActions(): IAction[] {
100
		return [...super.getSecondaryActions(), ...this.treeView.getSecondaryActions()];
101 102 103
	}

	getOptimalWidth(): number {
S
rename  
Sandeep Somavarapu 已提交
104
		return this.treeView.getOptimalWidth();
105 106 107
	}

	private updateTreeVisibility(): void {
108
		this.treeView.setVisibility(this.isBodyVisible());
109 110 111 112
	}
}

class Root implements ITreeItem {
S
Sandeep Somavarapu 已提交
113
	label = { label: 'root' };
114
	handle = '0';
S
Sandeep Somavarapu 已提交
115
	parentHandle: string | undefined = undefined;
116
	collapsibleState = TreeItemCollapsibleState.Expanded;
117
	children: ITreeItem[] | undefined = undefined;
118 119
}

120 121
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");

122
class Tree extends WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> { }
M
Matt Bierner 已提交
123

124
export class TreeView extends Disposable implements ITreeView {
125 126 127 128

	private isVisible: boolean = false;
	private _hasIconForParentNode = false;
	private _hasIconForLeafNode = false;
129
	private _showCollapseAllAction = false;
130
	private _showRefreshAction = false;
131

S
Sandeep Somavarapu 已提交
132
	private focused: boolean = false;
133 134
	private domNode!: HTMLElement;
	private treeContainer!: HTMLElement;
135
	private _messageValue: string | undefined;
136
	private _canSelectMany: boolean = false;
137
	private messageElement!: HTMLDivElement;
138
	private tree: Tree | undefined;
139
	private treeLabels: ResourceLabels | undefined;
140

141 142 143
	private root: ITreeItem;
	private elementsToRefresh: ITreeItem[] = [];

M
Matt Bierner 已提交
144
	private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
145 146
	readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;

M
Matt Bierner 已提交
147
	private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
148 149 150 151 152
	readonly onDidCollapseItem: Event<ITreeItem> = this._onDidCollapseItem.event;

	private _onDidChangeSelection: Emitter<ITreeItem[]> = this._register(new Emitter<ITreeItem[]>());
	readonly onDidChangeSelection: Event<ITreeItem[]> = this._onDidChangeSelection.event;

M
Matt Bierner 已提交
153
	private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
154 155
	readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;

M
Matt Bierner 已提交
156
	private readonly _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
157 158
	readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;

159 160 161
	private readonly _onDidChangeWelcomeState: Emitter<void> = this._register(new Emitter<void>());
	readonly onDidChangeWelcomeState: Event<void> = this._onDidChangeWelcomeState.event;

162 163 164
	private readonly _onDidChangeTitle: Emitter<string> = this._register(new Emitter<string>());
	readonly onDidChangeTitle: Event<string> = this._onDidChangeTitle.event;

165 166
	private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>());

167
	constructor(
168
		protected readonly id: string,
169
		private _title: string,
170
		@IThemeService private readonly themeService: IThemeService,
171 172 173
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@ICommandService private readonly commandService: ICommandService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
174
		@IProgressService protected readonly progressService: IProgressService,
175
		@IContextMenuService private readonly contextMenuService: IContextMenuService,
176
		@IKeybindingService private readonly keybindingService: IKeybindingService,
177 178
		@INotificationService private readonly notificationService: INotificationService,
		@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService
179 180 181
	) {
		super();
		this.root = new Root();
182
		this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
M
Martin Aeschlimann 已提交
183
		this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
S
Sandeep Somavarapu 已提交
184 185 186 187 188
		this._register(this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('explorer.decorations')) {
				this.doRefresh([this.root]); /** soft refresh **/
			}
		}));
189 190 191
		this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
			if (views.some(v => v.id === this.id)) {
				this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } });
192 193
			}
		}));
S
SteVen Batten 已提交
194

S
Sandeep Somavarapu 已提交
195
		this.create();
196 197
	}

198 199 200 201 202 203 204 205
	get viewContainer(): ViewContainer {
		return this.viewDescriptorService.getViewContainer(this.id)!;
	}

	get viewLocation(): ViewContainerLocation {
		return this.viewDescriptorService.getViewLocation(this.id)!;
	}

206 207
	private _dataProvider: ITreeViewDataProvider | undefined;
	get dataProvider(): ITreeViewDataProvider | undefined {
208 209 210
		return this._dataProvider;
	}

211
	set dataProvider(dataProvider: ITreeViewDataProvider | undefined) {
212 213 214 215
		if (this.tree === undefined) {
			this.createTree();
		}

216 217
		if (dataProvider) {
			this._dataProvider = new class implements ITreeViewDataProvider {
218
				async getChildren(node: ITreeItem): Promise<ITreeItem[]> {
219
					if (node && node.children) {
220
						return Promise.resolve(node.children);
221
					}
222 223 224
					const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node));
					node.children = children;
					return children;
225 226
				}
			};
227
			this.updateMessage();
S
Sandeep Somavarapu 已提交
228
			this.refresh();
229
		} else {
230
			this._dataProvider = undefined;
231
			this.updateMessage();
232
		}
233 234

		this._onDidChangeWelcomeState.fire();
235 236
	}

237 238
	private _message: string | undefined;
	get message(): string | undefined {
239 240 241
		return this._message;
	}

242
	set message(message: string | undefined) {
243 244
		this._message = message;
		this.updateMessage();
245
		this._onDidChangeWelcomeState.fire();
246 247
	}

248 249 250 251 252 253 254 255 256
	get title(): string {
		return this._title;
	}

	set title(name: string) {
		this._title = name;
		this._onDidChangeTitle.fire(this._title);
	}

257 258 259 260 261 262 263 264
	get canSelectMany(): boolean {
		return this._canSelectMany;
	}

	set canSelectMany(canSelectMany: boolean) {
		this._canSelectMany = canSelectMany;
	}

265 266 267 268 269 270 271 272
	get hasIconForParentNode(): boolean {
		return this._hasIconForParentNode;
	}

	get hasIconForLeafNode(): boolean {
		return this._hasIconForLeafNode;
	}

S
Sandeep Somavarapu 已提交
273 274 275 276
	get visible(): boolean {
		return this.isVisible;
	}

277 278 279 280 281 282 283 284 285 286 287
	get showCollapseAllAction(): boolean {
		return this._showCollapseAllAction;
	}

	set showCollapseAllAction(showCollapseAllAction: boolean) {
		if (this._showCollapseAllAction !== !!showCollapseAllAction) {
			this._showCollapseAllAction = !!showCollapseAllAction;
			this._onDidChangeActions.fire();
		}
	}

288 289 290 291 292 293 294 295 296 297 298
	get showRefreshAction(): boolean {
		return this._showRefreshAction;
	}

	set showRefreshAction(showRefreshAction: boolean) {
		if (this._showRefreshAction !== !!showRefreshAction) {
			this._showRefreshAction = !!showRefreshAction;
			this._onDidChangeActions.fire();
		}
	}

299
	getPrimaryActions(): IAction[] {
300 301 302 303
		const actions: IAction[] = [];
		if (this.showRefreshAction) {
			actions.push(new Action('vs.tree.refresh', localize('refresh', "Refresh"), 'monaco-tree-action codicon-refresh', true, () => this.refresh()));
		}
304
		if (this.showCollapseAllAction) {
305
			actions.push(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(this.tree, true).run() : Promise.resolve()));
306
		}
307
		return actions;
308 309 310
	}

	getSecondaryActions(): IAction[] {
311
		return [];
312 313
	}

314
	setVisibility(isVisible: boolean): void {
S
Sandeep Somavarapu 已提交
315
		isVisible = !!isVisible;
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
		if (this.isVisible === isVisible) {
			return;
		}

		this.isVisible = isVisible;

		if (this.tree) {
			if (this.isVisible) {
				DOM.show(this.tree.getHTMLElement());
			} else {
				DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it
			}

			if (this.isVisible && this.elementsToRefresh.length) {
				this.doRefresh(this.elementsToRefresh);
				this.elementsToRefresh = [];
			}
		}
334 335

		this._onDidChangeVisibility.fire(this.isVisible);
336 337
	}

338
	focus(reveal: boolean = true): void {
S
Sandeep Somavarapu 已提交
339
		if (this.tree && this.root.children && this.root.children.length > 0) {
340 341
			// Make sure the current selected element is revealed
			const selectedElement = this.tree.getSelection()[0];
342
			if (selectedElement && reveal) {
343
				this.tree.reveal(selectedElement, 0.5);
344 345 346 347
			}

			// Pass Focus to Viewer
			this.tree.domFocus();
348 349
		} else if (this.tree) {
			this.tree.domFocus();
S
Sandeep Somavarapu 已提交
350 351
		} else {
			this.domNode.focus();
352 353 354 355
		}
	}

	show(container: HTMLElement): void {
S
Sandeep Somavarapu 已提交
356 357 358 359
		DOM.append(container, this.domNode);
	}

	private create() {
360 361
		this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
		this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
S
Sandeep Somavarapu 已提交
362
		this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
363 364
		DOM.addClass(this.treeContainer, 'file-icon-themable-tree');
		DOM.addClass(this.treeContainer, 'show-file-icons');
S
Sandeep Somavarapu 已提交
365 366 367
		const focusTracker = this._register(DOM.trackFocus(this.domNode));
		this._register(focusTracker.onDidFocus(() => this.focused = true));
		this._register(focusTracker.onDidBlur(() => this.focused = false));
368 369 370
	}

	private createTree() {
371
		const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
372
		const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
373
		this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
374
		const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task));
375 376 377
		const aligner = new Aligner(this.themeService);
		const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);

378
		this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer],
379
			dataSource, {
380
			identityProvider: new TreeViewIdentityProvider(),
M
Matt Bierner 已提交
381 382 383 384 385
			accessibilityProvider: {
				getAriaLabel(element: ITreeItem): string {
					return element.tooltip ? element.tooltip : element.label ? element.label.label : '';
				}
			},
386
			ariaLabel: this._title,
M
Matt Bierner 已提交
387 388 389 390 391 392 393 394 395 396
			keyboardNavigationLabelProvider: {
				getKeyboardNavigationLabel: (item: ITreeItem) => {
					return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined);
				}
			},
			expandOnlyOnTwistieClick: (e: ITreeItem) => !!e.command,
			collapseByDefault: (e: ITreeItem): boolean => {
				return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
			},
			multipleSelectionSupport: this.canSelectMany,
J
Joao Moreno 已提交
397
			overrideStyles: {
398
				listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
J
Joao Moreno 已提交
399
			}
M
Matt Bierner 已提交
400
		}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
401
		aligner.tree = this.tree;
402
		const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection());
403
		renderer.actionRunner = actionRunner;
404

405
		this.tree.contextKeyService.createKey<boolean>(this.id, true);
406
		this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
407 408
		this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
		this._register(this.tree.onDidChangeCollapseState(e => {
409 410 411 412
			if (!e.node.element) {
				return;
			}

413 414 415 416 417 418 419
			const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element;
			if (e.node.collapsed) {
				this._onDidCollapseItem.fire(element);
			} else {
				this._onDidExpandItem.fire(element);
			}
		}));
S
Sandeep Somavarapu 已提交
420
		this.tree.setInput(this.root).then(() => this.updateContentAreas());
421

422 423 424
		const treeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false });
		this._register(treeNavigator);
		this._register(treeNavigator.onDidOpenResource(e => {
425 426 427
			if (!e.browserEvent) {
				return;
			}
428
			const selection = this.tree!.getSelection();
429 430 431 432
			if ((selection.length === 1) && selection[0].command) {
				this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || []));
			}
		}));
433 434
	}

435
	private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
436 437 438 439 440 441 442 443 444
		const node: ITreeItem | null = treeEvent.element;
		if (node === null) {
			return;
		}
		const event: UIEvent = treeEvent.browserEvent;

		event.preventDefault();
		event.stopPropagation();

445
		this.tree!.setFocus([node]);
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
		const actions = treeMenus.getResourceContextActions(node);
		if (!actions.length) {
			return;
		}
		this.contextMenuService.showContextMenu({
			getAnchor: () => treeEvent.anchor,

			getActions: () => actions,

			getActionViewItem: (action) => {
				const keybinding = this.keybindingService.lookupKeybinding(action.id);
				if (keybinding) {
					return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() });
				}
				return undefined;
			},

			onHide: (wasCancelled?: boolean) => {
				if (wasCancelled) {
465
					this.tree!.domFocus();
466 467 468 469 470
				}
			},

			getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle }),

471
			actionRunner
472 473 474
		});
	}

475
	protected updateMessage(): void {
476 477 478 479 480 481 482
		if (this._message) {
			this.showMessage(this._message);
		} else if (!this.dataProvider) {
			this.showMessage(noDataProviderMessage);
		} else {
			this.hideMessage();
		}
S
Sandeep Somavarapu 已提交
483
		this.updateContentAreas();
484 485
	}

486
	private showMessage(message: string): void {
487
		DOM.removeClass(this.messageElement, 'hide');
488 489 490 491
		this.resetMessageElement();
		this._messageValue = message;
		if (!isFalsyOrWhitespace(this._message)) {
			this.messageElement.textContent = this._messageValue;
492
		}
493
		this.layout(this._height, this._width);
494 495
	}

496
	private hideMessage(): void {
497 498
		this.resetMessageElement();
		DOM.addClass(this.messageElement, 'hide');
499
		this.layout(this._height, this._width);
500 501
	}

502 503 504 505
	private resetMessageElement(): void {
		DOM.clearNode(this.messageElement);
	}

506 507
	private _height: number = 0;
	private _width: number = 0;
508 509 510 511 512 513
	layout(height: number, width: number) {
		if (height && width) {
			this._height = height;
			this._width = width;
			const treeHeight = height - DOM.getTotalHeight(this.messageElement);
			this.treeContainer.style.height = treeHeight + 'px';
514
			if (this.tree) {
515
				this.tree.layout(treeHeight, width);
516
			}
517 518 519 520 521 522
		}
	}

	getOptimalWidth(): number {
		if (this.tree) {
			const parentNode = this.tree.getHTMLElement();
523
			const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
524 525 526 527 528
			return DOM.getLargestChildWidth(parentNode, childNodes);
		}
		return 0;
	}

529
	async refresh(elements?: ITreeItem[]): Promise<void> {
S
Sandeep Somavarapu 已提交
530
		if (this.dataProvider && this.tree) {
531 532 533
			if (this.refreshing) {
				await Event.toPromise(this._onDidCompleteRefresh.event);
			}
S
Sandeep Somavarapu 已提交
534 535 536 537 538
			if (!elements) {
				elements = [this.root];
				// remove all waiting elements to refresh if root is asked to refresh
				this.elementsToRefresh = [];
			}
539
			for (const element of elements) {
M
Matt Bierner 已提交
540
				element.children = undefined; // reset children
541 542 543 544
			}
			if (this.isVisible) {
				return this.doRefresh(elements);
			} else {
S
Sandeep Somavarapu 已提交
545 546 547 548 549 550 551 552 553 554 555
				if (this.elementsToRefresh.length) {
					const seen: Set<string> = new Set<string>();
					this.elementsToRefresh.forEach(element => seen.add(element.handle));
					for (const element of elements) {
						if (!seen.has(element.handle)) {
							this.elementsToRefresh.push(element);
						}
					}
				} else {
					this.elementsToRefresh.push(...elements);
				}
556 557
			}
		}
558
		return undefined;
559 560
	}

561
	async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
562 563
		const tree = this.tree;
		if (tree) {
S
Sandeep Somavarapu 已提交
564
			itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
565
			await Promise.all(itemOrItems.map(element => {
566
				return tree.expand(element, false);
567
			}));
S
Sandeep Somavarapu 已提交
568
		}
R
Rob Lourens 已提交
569
		return Promise.resolve(undefined);
570 571 572
	}

	setSelection(items: ITreeItem[]): void {
S
Sandeep Somavarapu 已提交
573
		if (this.tree) {
574
			this.tree.setSelection(items);
S
Sandeep Somavarapu 已提交
575
		}
576 577 578
	}

	setFocus(item: ITreeItem): void {
S
Sandeep Somavarapu 已提交
579 580
		if (this.tree) {
			this.focus();
581
			this.tree.setFocus([item]);
S
Sandeep Somavarapu 已提交
582
		}
583 584
	}

J
Johannes Rieken 已提交
585
	reveal(item: ITreeItem): Promise<void> {
S
Sandeep Somavarapu 已提交
586
		if (this.tree) {
587
			return Promise.resolve(this.tree.reveal(item));
S
Sandeep Somavarapu 已提交
588
		}
589
		return Promise.resolve();
590 591
	}

592
	private refreshing: boolean = false;
593
	private async doRefresh(elements: ITreeItem[]): Promise<void> {
594
		const tree = this.tree;
595
		if (tree && this.visible) {
596
			this.refreshing = true;
J
Joao Moreno 已提交
597
			await Promise.all(elements.map(element => tree.updateChildren(element, true, true)));
598
			this.refreshing = false;
599
			this._onDidCompleteRefresh.fire();
600 601
			this.updateContentAreas();
			if (this.focused) {
602
				this.focus(false);
603
			}
604 605 606
		}
	}

S
Sandeep Somavarapu 已提交
607
	private updateContentAreas(): void {
S
Sandeep Somavarapu 已提交
608
		const isTreeEmpty = !this.root.children || this.root.children.length === 0;
609 610
		// Hide tree container only when there is a message and tree is empty and not refreshing
		if (this._messageValue && isTreeEmpty && !this.refreshing) {
S
Sandeep Somavarapu 已提交
611
			DOM.addClass(this.treeContainer, 'hide');
S
Sandeep Somavarapu 已提交
612
			this.domNode.setAttribute('tabindex', '0');
S
Sandeep Somavarapu 已提交
613 614 615
		} else {
			DOM.removeClass(this.treeContainer, 'hide');
			this.domNode.removeAttribute('tabindex');
S
Sandeep Somavarapu 已提交
616 617
		}
	}
618 619
}

620
class TreeViewIdentityProvider implements IIdentityProvider<ITreeItem> {
621 622 623 624 625
	getId(element: ITreeItem): { toString(): string; } {
		return element.handle;
	}
}

626
class TreeViewDelegate implements IListVirtualDelegate<ITreeItem> {
627 628 629 630 631 632 633 634 635 636 637

	getHeight(element: ITreeItem): number {
		return TreeRenderer.ITEM_HEIGHT;
	}

	getTemplateId(element: ITreeItem): string {
		return TreeRenderer.TREE_TEMPLATE_ID;
	}
}

class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
638 639

	constructor(
S
rename  
Sandeep Somavarapu 已提交
640
		private treeView: ITreeView,
641
		private withProgress: <T>(task: Promise<T>) => Promise<T>
642 643 644
	) {
	}

645 646
	hasChildren(element: ITreeItem): boolean {
		return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None);
647 648
	}

649
	getChildren(element: ITreeItem): ITreeItem[] | Promise<ITreeItem[]> {
650
		if (this.treeView.dataProvider) {
651
			return this.withProgress(this.treeView.dataProvider.getChildren(element));
652
		}
653
		return Promise.resolve([]);
654 655 656
	}
}

657 658 659
// todo@joh,sandy make this proper and contributable from extensions
registerThemingParticipant((theme, collector) => {

J
Joao Moreno 已提交
660 661 662 663
	const matchBackgroundColor = theme.getColor(listFilterMatchHighlight);
	if (matchBackgroundColor) {
		collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
		collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`);
664
	}
J
Joao Moreno 已提交
665 666 667 668
	const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder);
	if (matchBorderColor) {
		collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
		collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`);
669
	}
S
Sandeep Somavarapu 已提交
670 671 672 673
	const link = theme.getColor(textLinkForeground);
	if (link) {
		collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
	}
674 675 676
	const focusBorderColor = theme.getColor(focusBorder);
	if (focusBorderColor) {
		collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focusBorderColor}; outline-offset: -1px; }`);
S
Sandeep Somavarapu 已提交
677
	}
S
Sandeep Somavarapu 已提交
678 679 680 681
	const codeBackground = theme.getColor(textCodeBlockBackground);
	if (codeBackground) {
		collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`);
	}
682 683
});

684 685 686 687 688 689 690
interface ITreeExplorerTemplateData {
	elementDisposable: IDisposable;
	container: HTMLElement;
	resourceLabel: IResourceLabel;
	icon: HTMLElement;
	actionBar: ActionBar;
}
691

692 693 694
class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyScore, ITreeExplorerTemplateData> {
	static readonly ITEM_HEIGHT = 22;
	static readonly TREE_TEMPLATE_ID = 'treeExplorer';
695

696 697
	private _actionRunner: MultipleSelectionActionRunner | undefined;

698 699 700
	constructor(
		private treeViewId: string,
		private menus: TreeMenus,
B
Benjamin Pasero 已提交
701
		private labels: ResourceLabels,
702
		private actionViewItemProvider: IActionViewItemProvider,
703
		private aligner: Aligner,
704
		@IThemeService private readonly themeService: IThemeService,
705 706
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@ILabelService private readonly labelService: ILabelService
707
	) {
708
		super();
709 710
	}

711
	get templateId(): string {
712 713 714
		return TreeRenderer.TREE_TEMPLATE_ID;
	}

715 716 717 718
	set actionRunner(actionRunner: MultipleSelectionActionRunner) {
		this._actionRunner = actionRunner;
	}

719
	renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
720 721 722
		DOM.addClass(container, 'custom-view-tree-node-item');

		const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
723

724
		const resourceLabel = this.labels.create(container, { supportHighlights: true });
725
		const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
726
		const actionBar = new ActionBar(actionsContainer, {
727
			actionViewItemProvider: this.actionViewItemProvider
728 729
		});

730
		return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
731 732
	}

733 734 735
	renderElement(element: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
		templateData.elementDisposable.dispose();
		const node = element.element;
736
		const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
M
Matt Bierner 已提交
737
		const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
R
Rob Lourens 已提交
738 739
		const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined;
		const label = treeItemLabel ? treeItemLabel.label : undefined;
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
		const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => {
			if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) {
				return ({ start: 0, end: 0 });
			}
			if (start < 0) {
				start = label.length + start;
			}
			if (end < 0) {
				end = label.length + end;
			}
			if (start > end) {
				const swap = start;
				start = end;
				end = swap;
			}
			return ({ start, end });
		}) : undefined;
M
Martin Aeschlimann 已提交
757
		const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
758
		const iconUrl = icon ? URI.revive(icon) : null;
R
Rob Lourens 已提交
759
		const title = node.tooltip ? node.tooltip : resource ? undefined : label;
760 761 762 763

		// reset
		templateData.actionBar.clear();

J
Johannes Rieken 已提交
764
		if (resource || this.isFileKindThemeIcon(node.themeIcon)) {
S
Sandeep Somavarapu 已提交
765
			const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
A
Alex Ross 已提交
766
			templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
767
		} else {
A
Alex Ross 已提交
768
			templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
769 770
		}

771
		templateData.icon.title = title ? title : '';
772

J
Johannes Rieken 已提交
773 774 775
		if (iconUrl) {
			templateData.icon.className = 'custom-view-tree-node-item-icon';
			templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl);
776

J
Johannes Rieken 已提交
777 778 779 780
		} else {
			let iconClass: string | undefined;
			if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) {
				iconClass = ThemeIcon.asClassName(node.themeIcon);
781
			}
J
Johannes Rieken 已提交
782
			templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : '';
J
Johannes Rieken 已提交
783
			templateData.icon.style.backgroundImage = '';
784 785
		}

786
		templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
787
		templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
788 789 790
		if (this._actionRunner) {
			templateData.actionBar.actionRunner = this._actionRunner;
		}
791 792 793
		this.setAlignment(templateData.container, node);
		templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node)));
	}
794

795 796
	private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
		DOM.toggleClass(container.parentElement!, 'align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
797 798
	}

J
Johannes Rieken 已提交
799 800 801 802 803 804 805 806
	private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean {
		if (icon) {
			return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id;
		} else {
			return false;
		}
	}

807 808 809 810 811 812 813 814 815 816 817 818
	private getFileKind(node: ITreeItem): FileKind {
		if (node.themeIcon) {
			switch (node.themeIcon.id) {
				case FileThemeIcon.id:
					return FileKind.FILE;
				case FolderThemeIcon.id:
					return FileKind.FOLDER;
			}
		}
		return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
	}

819 820 821 822 823
	disposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
		templateData.elementDisposable.dispose();
	}

	disposeTemplate(templateData: ITreeExplorerTemplateData): void {
824 825
		templateData.resourceLabel.dispose();
		templateData.actionBar.dispose();
826
		templateData.elementDisposable.dispose();
827 828 829 830
	}
}

class Aligner extends Disposable {
831
	private _tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
832

833
	constructor(private themeService: IThemeService) {
834 835 836
		super();
	}

837 838
	set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
		this._tree = tree;
839 840
	}

841 842
	public alignIconWithTwisty(treeItem: ITreeItem): boolean {
		if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
843 844
			return false;
		}
845
		if (!this.hasIcon(treeItem)) {
846 847
			return false;
		}
848

849 850 851 852 853 854 855
		if (this._tree) {
			const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput();
			if (this.hasIcon(parent)) {
				return false;
			}
			return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c));
		} else {
856 857 858 859 860
			return false;
		}
	}

	private hasIcon(node: ITreeItem): boolean {
M
Martin Aeschlimann 已提交
861
		const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
		if (icon) {
			return true;
		}
		if (node.resourceUri || node.themeIcon) {
			const fileIconTheme = this.themeService.getFileIconTheme();
			const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None;
			if (isFolder) {
				return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons;
			}
			return fileIconTheme.hasFileIcons;
		}
		return false;
	}
}

class MultipleSelectionActionRunner extends ActionRunner {

879
	constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) {
880
		super();
881 882 883 884 885
		this._register(this.onDidRun(e => {
			if (e.error) {
				notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id));
			}
		}));
886 887
	}

888
	runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
889 890
		const selection = this.getSelectedResources();
		let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
891
		let actionInSelected: boolean = false;
892
		if (selection.length > 1) {
893 894 895 896 897 898 899 900 901 902
			selectionHandleArgs = selection.map(selected => {
				if (selected.handle === context.$treeItemHandle) {
					actionInSelected = true;
				}
				return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle };
			});
		}

		if (!actionInSelected) {
			selectionHandleArgs = undefined;
903 904
		}

905
		return action.run(...[context, selectionHandleArgs]);
906 907 908 909 910 911 912
	}
}

class TreeMenus extends Disposable implements IDisposable {

	constructor(
		private id: string,
913 914 915
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IMenuService private readonly menuService: IMenuService,
		@IContextMenuService private readonly contextMenuService: IContextMenuService
916 917 918 919 920 921 922 923 924 925 926 927
	) {
		super();
	}

	getResourceActions(element: ITreeItem): IAction[] {
		return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary;
	}

	getResourceContextActions(element: ITreeItem): IAction[] {
		return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
	}

M
Matt Bierner 已提交
928
	private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } {
929 930 931 932 933 934 935 936
		const contextKeyService = this.contextKeyService.createScoped();
		contextKeyService.createKey('view', this.id);
		contextKeyService.createKey(context.key, context.value);

		const menu = this.menuService.createMenu(menuId, contextKeyService);
		const primary: IAction[] = [];
		const secondary: IAction[] = [];
		const result = { primary, secondary };
937
		createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
938 939 940 941 942 943 944

		menu.dispose();
		contextKeyService.dispose();

		return result;
	}
}
945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984

export class CustomTreeView extends TreeView {

	private activated: boolean = false;

	constructor(
		id: string,
		title: string,
		@IThemeService themeService: IThemeService,
		@IInstantiationService instantiationService: IInstantiationService,
		@ICommandService commandService: ICommandService,
		@IConfigurationService configurationService: IConfigurationService,
		@IProgressService progressService: IProgressService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IKeybindingService keybindingService: IKeybindingService,
		@INotificationService notificationService: INotificationService,
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
		@IExtensionService private readonly extensionService: IExtensionService,
	) {
		super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService);
	}

	setVisibility(isVisible: boolean): void {
		super.setVisibility(isVisible);
		if (this.visible) {
			this.activate();
		}
	}

	private activate() {
		if (!this.activated) {
			this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
				.then(() => timeout(2000))
				.then(() => {
					this.updateMessage();
				});
			this.activated = true;
		}
	}
}