treeView.ts 37.1 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 } from 'vs/base/common/actions';
11 12
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
13
import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
14
import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
15
import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } 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
	}

	getOptimalWidth(): number {
S
rename  
Sandeep Somavarapu 已提交
96
		return this.treeView.getOptimalWidth();
97 98 99
	}

	private updateTreeVisibility(): void {
100
		this.treeView.setVisibility(this.isBodyVisible());
101 102 103 104
	}
}

class Root implements ITreeItem {
S
Sandeep Somavarapu 已提交
105
	label = { label: 'root' };
106
	handle = '0';
S
Sandeep Somavarapu 已提交
107
	parentHandle: string | undefined = undefined;
108
	collapsibleState = TreeItemCollapsibleState.Expanded;
109
	children: ITreeItem[] | undefined = undefined;
110 111
}

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

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

116
export class TreeView extends Disposable implements ITreeView {
117 118 119 120

	private isVisible: boolean = false;
	private _hasIconForParentNode = false;
	private _hasIconForLeafNode = false;
121 122 123 124 125

	private readonly collapseAllContextKey: RawContextKey<boolean>;
	private readonly collapseAllContext: IContextKey<boolean>;
	private readonly refreshContextKey: RawContextKey<boolean>;
	private readonly refreshContext: IContextKey<boolean>;
126

S
Sandeep Somavarapu 已提交
127
	private focused: boolean = false;
128 129
	private domNode!: HTMLElement;
	private treeContainer!: HTMLElement;
130
	private _messageValue: string | undefined;
131
	private _canSelectMany: boolean = false;
132
	private messageElement!: HTMLDivElement;
133
	private tree: Tree | undefined;
134
	private treeLabels: ResourceLabels | undefined;
135

136 137 138
	private root: ITreeItem;
	private elementsToRefresh: ITreeItem[] = [];

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

M
Matt Bierner 已提交
142
	private readonly _onDidCollapseItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
143 144 145 146 147
	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 已提交
148
	private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
149 150
	readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;

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

154 155 156
	private readonly _onDidChangeWelcomeState: Emitter<void> = this._register(new Emitter<void>());
	readonly onDidChangeWelcomeState: Event<void> = this._onDidChangeWelcomeState.event;

157 158 159
	private readonly _onDidChangeTitle: Emitter<string> = this._register(new Emitter<string>());
	readonly onDidChangeTitle: Event<string> = this._onDidChangeTitle.event;

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

162
	constructor(
163
		protected readonly id: string,
164
		private _title: string,
165
		@IThemeService private readonly themeService: IThemeService,
166 167 168
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@ICommandService private readonly commandService: ICommandService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
169
		@IProgressService protected readonly progressService: IProgressService,
170
		@IContextMenuService private readonly contextMenuService: IContextMenuService,
171
		@IKeybindingService private readonly keybindingService: IKeybindingService,
172
		@INotificationService private readonly notificationService: INotificationService,
173 174
		@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
		@IContextKeyService contextKeyService: IContextKeyService
175 176 177
	) {
		super();
		this.root = new Root();
178 179 180 181 182
		this.collapseAllContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableCollapseAll`, false);
		this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService);
		this.refreshContextKey = new RawContextKey<boolean>(`treeView.${this.id}.enableRefresh`, false);
		this.refreshContext = this.refreshContextKey.bindTo(contextKeyService);

183
		this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
M
Martin Aeschlimann 已提交
184
		this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
S
Sandeep Somavarapu 已提交
185 186 187 188 189
		this._register(this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('explorer.decorations')) {
				this.doRefresh([this.root]); /** soft refresh **/
			}
		}));
190 191 192
		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 } });
193 194
			}
		}));
195
		this.registerActions();
S
SteVen Batten 已提交
196

S
Sandeep Somavarapu 已提交
197
		this.create();
198 199
	}

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

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

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

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

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

		this._onDidChangeWelcomeState.fire();
237 238
	}

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

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

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

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

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

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

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

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

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

279
	get showCollapseAllAction(): boolean {
280
		return !!this.collapseAllContext.get();
281 282 283
	}

	set showCollapseAllAction(showCollapseAllAction: boolean) {
284
		this.collapseAllContext.set(showCollapseAllAction);
285 286
	}

287
	get showRefreshAction(): boolean {
288
		return !!this.refreshContext.get();
289 290 291
	}

	set showRefreshAction(showRefreshAction: boolean) {
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
		this.refreshContext.set(showRefreshAction);
	}

	private registerActions() {
		const that = this;
		registerAction2(class extends Action2 {
			constructor() {
				super({
					id: `workbench.actions.treeView.${that.id}.refresh`,
					title: localize('refresh', "Refresh"),
					menu: {
						id: MenuId.ViewTitle,
						when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey),
						group: 'navigation',
						order: Number.MAX_SAFE_INTEGER - 1,
					},
					icon: { id: 'codicon/refresh' }
				});
			}
			async run(): Promise<void> {
				return that.refresh();
			}
		});
		registerAction2(class extends Action2 {
			constructor() {
				super({
					id: `workbench.actions.treeView.${that.id}.collapseAll`,
					title: localize('collapseAll', "Collapse All"),
					menu: {
						id: MenuId.ViewTitle,
						when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey),
						group: 'navigation',
						order: Number.MAX_SAFE_INTEGER,
					},
					icon: { id: 'codicon/collapse-all' }
				});
			}
			async run(): Promise<void> {
				if (that.tree) {
					return new CollapseAllAction<ITreeItem, ITreeItem, FuzzyScore>(that.tree, true).run();
				}
			}
		});
335 336
	}

337
	setVisibility(isVisible: boolean): void {
S
Sandeep Somavarapu 已提交
338
		isVisible = !!isVisible;
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
		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 = [];
			}
		}
357 358

		this._onDidChangeVisibility.fire(this.isVisible);
359 360
	}

361
	focus(reveal: boolean = true): void {
S
Sandeep Somavarapu 已提交
362
		if (this.tree && this.root.children && this.root.children.length > 0) {
363 364
			// Make sure the current selected element is revealed
			const selectedElement = this.tree.getSelection()[0];
365
			if (selectedElement && reveal) {
366
				this.tree.reveal(selectedElement, 0.5);
367 368 369 370
			}

			// Pass Focus to Viewer
			this.tree.domFocus();
371 372
		} else if (this.tree) {
			this.tree.domFocus();
S
Sandeep Somavarapu 已提交
373 374
		} else {
			this.domNode.focus();
375 376 377 378
		}
	}

	show(container: HTMLElement): void {
S
Sandeep Somavarapu 已提交
379 380 381 382
		DOM.append(container, this.domNode);
	}

	private create() {
383 384
		this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
		this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
S
Sandeep Somavarapu 已提交
385
		this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
386 387
		DOM.addClass(this.treeContainer, 'file-icon-themable-tree');
		DOM.addClass(this.treeContainer, 'show-file-icons');
S
Sandeep Somavarapu 已提交
388 389 390
		const focusTracker = this._register(DOM.trackFocus(this.domNode));
		this._register(focusTracker.onDidFocus(() => this.focused = true));
		this._register(focusTracker.onDidBlur(() => this.focused = false));
391 392 393
	}

	private createTree() {
394
		const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
395
		const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
396
		this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
397
		const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task));
398 399 400
		const aligner = new Aligner(this.themeService);
		const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);

401
		this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer],
402
			dataSource, {
403
			identityProvider: new TreeViewIdentityProvider(),
M
Matt Bierner 已提交
404 405 406 407 408
			accessibilityProvider: {
				getAriaLabel(element: ITreeItem): string {
					return element.tooltip ? element.tooltip : element.label ? element.label.label : '';
				}
			},
409
			ariaLabel: this._title,
M
Matt Bierner 已提交
410 411 412 413 414 415 416 417 418 419
			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 已提交
420
			overrideStyles: {
421
				listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
J
Joao Moreno 已提交
422
			}
M
Matt Bierner 已提交
423
		}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
424
		aligner.tree = this.tree;
425
		const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection());
426
		renderer.actionRunner = actionRunner;
427

428
		this.tree.contextKeyService.createKey<boolean>(this.id, true);
429
		this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
430 431
		this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
		this._register(this.tree.onDidChangeCollapseState(e => {
432 433 434 435
			if (!e.node.element) {
				return;
			}

436 437 438 439 440 441 442
			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 已提交
443
		this.tree.setInput(this.root).then(() => this.updateContentAreas());
444

445 446 447
		const treeNavigator = ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false });
		this._register(treeNavigator);
		this._register(treeNavigator.onDidOpenResource(e => {
448 449 450
			if (!e.browserEvent) {
				return;
			}
451
			const selection = this.tree!.getSelection();
452 453 454 455
			if ((selection.length === 1) && selection[0].command) {
				this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || []));
			}
		}));
456 457
	}

458
	private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
459 460 461 462 463 464 465 466 467
		const node: ITreeItem | null = treeEvent.element;
		if (node === null) {
			return;
		}
		const event: UIEvent = treeEvent.browserEvent;

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

468
		this.tree!.setFocus([node]);
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
		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) {
488
					this.tree!.domFocus();
489 490 491 492 493
				}
			},

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

494
			actionRunner
495 496 497
		});
	}

498
	protected updateMessage(): void {
499 500 501 502 503 504 505
		if (this._message) {
			this.showMessage(this._message);
		} else if (!this.dataProvider) {
			this.showMessage(noDataProviderMessage);
		} else {
			this.hideMessage();
		}
S
Sandeep Somavarapu 已提交
506
		this.updateContentAreas();
507 508
	}

509
	private showMessage(message: string): void {
510
		DOM.removeClass(this.messageElement, 'hide');
511 512 513 514
		this.resetMessageElement();
		this._messageValue = message;
		if (!isFalsyOrWhitespace(this._message)) {
			this.messageElement.textContent = this._messageValue;
515
		}
516
		this.layout(this._height, this._width);
517 518
	}

519
	private hideMessage(): void {
520 521
		this.resetMessageElement();
		DOM.addClass(this.messageElement, 'hide');
522
		this.layout(this._height, this._width);
523 524
	}

525 526 527 528
	private resetMessageElement(): void {
		DOM.clearNode(this.messageElement);
	}

529 530
	private _height: number = 0;
	private _width: number = 0;
531 532 533 534 535 536
	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';
537
			if (this.tree) {
538
				this.tree.layout(treeHeight, width);
539
			}
540 541 542 543 544 545
		}
	}

	getOptimalWidth(): number {
		if (this.tree) {
			const parentNode = this.tree.getHTMLElement();
546
			const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
547 548 549 550 551
			return DOM.getLargestChildWidth(parentNode, childNodes);
		}
		return 0;
	}

552
	async refresh(elements?: ITreeItem[]): Promise<void> {
S
Sandeep Somavarapu 已提交
553
		if (this.dataProvider && this.tree) {
554 555 556
			if (this.refreshing) {
				await Event.toPromise(this._onDidCompleteRefresh.event);
			}
S
Sandeep Somavarapu 已提交
557 558 559 560 561
			if (!elements) {
				elements = [this.root];
				// remove all waiting elements to refresh if root is asked to refresh
				this.elementsToRefresh = [];
			}
562
			for (const element of elements) {
M
Matt Bierner 已提交
563
				element.children = undefined; // reset children
564 565 566 567
			}
			if (this.isVisible) {
				return this.doRefresh(elements);
			} else {
S
Sandeep Somavarapu 已提交
568 569 570 571 572 573 574 575 576 577 578
				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);
				}
579 580
			}
		}
581
		return undefined;
582 583
	}

584
	async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
585 586
		const tree = this.tree;
		if (tree) {
S
Sandeep Somavarapu 已提交
587
			itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
588
			await Promise.all(itemOrItems.map(element => {
589
				return tree.expand(element, false);
590
			}));
S
Sandeep Somavarapu 已提交
591
		}
R
Rob Lourens 已提交
592
		return Promise.resolve(undefined);
593 594 595
	}

	setSelection(items: ITreeItem[]): void {
S
Sandeep Somavarapu 已提交
596
		if (this.tree) {
597
			this.tree.setSelection(items);
S
Sandeep Somavarapu 已提交
598
		}
599 600 601
	}

	setFocus(item: ITreeItem): void {
S
Sandeep Somavarapu 已提交
602 603
		if (this.tree) {
			this.focus();
604
			this.tree.setFocus([item]);
S
Sandeep Somavarapu 已提交
605
		}
606 607
	}

J
Johannes Rieken 已提交
608
	reveal(item: ITreeItem): Promise<void> {
S
Sandeep Somavarapu 已提交
609
		if (this.tree) {
610
			return Promise.resolve(this.tree.reveal(item));
S
Sandeep Somavarapu 已提交
611
		}
612
		return Promise.resolve();
613 614
	}

615
	private refreshing: boolean = false;
616
	private async doRefresh(elements: ITreeItem[]): Promise<void> {
617
		const tree = this.tree;
618
		if (tree && this.visible) {
619
			this.refreshing = true;
J
Joao Moreno 已提交
620
			await Promise.all(elements.map(element => tree.updateChildren(element, true, true)));
621
			this.refreshing = false;
622
			this._onDidCompleteRefresh.fire();
623 624
			this.updateContentAreas();
			if (this.focused) {
625
				this.focus(false);
626
			}
627 628 629
		}
	}

S
Sandeep Somavarapu 已提交
630
	private updateContentAreas(): void {
S
Sandeep Somavarapu 已提交
631
		const isTreeEmpty = !this.root.children || this.root.children.length === 0;
632 633
		// 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 已提交
634
			DOM.addClass(this.treeContainer, 'hide');
S
Sandeep Somavarapu 已提交
635
			this.domNode.setAttribute('tabindex', '0');
S
Sandeep Somavarapu 已提交
636 637 638
		} else {
			DOM.removeClass(this.treeContainer, 'hide');
			this.domNode.removeAttribute('tabindex');
S
Sandeep Somavarapu 已提交
639 640
		}
	}
641 642
}

643
class TreeViewIdentityProvider implements IIdentityProvider<ITreeItem> {
644 645 646 647 648
	getId(element: ITreeItem): { toString(): string; } {
		return element.handle;
	}
}

649
class TreeViewDelegate implements IListVirtualDelegate<ITreeItem> {
650 651 652 653 654 655 656 657 658 659 660

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

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

class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
661 662

	constructor(
S
rename  
Sandeep Somavarapu 已提交
663
		private treeView: ITreeView,
664
		private withProgress: <T>(task: Promise<T>) => Promise<T>
665 666 667
	) {
	}

668 669
	hasChildren(element: ITreeItem): boolean {
		return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None);
670 671
	}

672
	getChildren(element: ITreeItem): ITreeItem[] | Promise<ITreeItem[]> {
673
		if (this.treeView.dataProvider) {
674
			return this.withProgress(this.treeView.dataProvider.getChildren(element));
675
		}
676
		return Promise.resolve([]);
677 678 679
	}
}

680 681 682
// todo@joh,sandy make this proper and contributable from extensions
registerThemingParticipant((theme, collector) => {

J
Joao Moreno 已提交
683 684 685 686
	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}; }`);
687
	}
J
Joao Moreno 已提交
688 689 690 691
	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; }`);
692
	}
S
Sandeep Somavarapu 已提交
693 694 695 696
	const link = theme.getColor(textLinkForeground);
	if (link) {
		collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
	}
697 698 699
	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 已提交
700
	}
S
Sandeep Somavarapu 已提交
701 702 703 704
	const codeBackground = theme.getColor(textCodeBlockBackground);
	if (codeBackground) {
		collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`);
	}
705 706
});

707 708 709 710 711 712 713
interface ITreeExplorerTemplateData {
	elementDisposable: IDisposable;
	container: HTMLElement;
	resourceLabel: IResourceLabel;
	icon: HTMLElement;
	actionBar: ActionBar;
}
714

715 716 717
class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyScore, ITreeExplorerTemplateData> {
	static readonly ITEM_HEIGHT = 22;
	static readonly TREE_TEMPLATE_ID = 'treeExplorer';
718

719 720
	private _actionRunner: MultipleSelectionActionRunner | undefined;

721 722 723
	constructor(
		private treeViewId: string,
		private menus: TreeMenus,
B
Benjamin Pasero 已提交
724
		private labels: ResourceLabels,
725
		private actionViewItemProvider: IActionViewItemProvider,
726
		private aligner: Aligner,
727
		@IThemeService private readonly themeService: IThemeService,
728 729
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@ILabelService private readonly labelService: ILabelService
730
	) {
731
		super();
732 733
	}

734
	get templateId(): string {
735 736 737
		return TreeRenderer.TREE_TEMPLATE_ID;
	}

738 739 740 741
	set actionRunner(actionRunner: MultipleSelectionActionRunner) {
		this._actionRunner = actionRunner;
	}

742
	renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
743 744 745
		DOM.addClass(container, 'custom-view-tree-node-item');

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

747
		const resourceLabel = this.labels.create(container, { supportHighlights: true });
748
		const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
749
		const actionBar = new ActionBar(actionsContainer, {
750
			actionViewItemProvider: this.actionViewItemProvider
751 752
		});

753
		return { resourceLabel, icon, actionBar, container, elementDisposable: Disposable.None };
754 755
	}

756 757 758
	renderElement(element: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
		templateData.elementDisposable.dispose();
		const node = element.element;
759
		const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
M
Matt Bierner 已提交
760
		const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined;
R
Rob Lourens 已提交
761 762
		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;
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
		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 已提交
780
		const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
781
		const iconUrl = icon ? URI.revive(icon) : null;
R
Rob Lourens 已提交
782
		const title = node.tooltip ? node.tooltip : resource ? undefined : label;
783 784 785 786

		// reset
		templateData.actionBar.clear();

J
Johannes Rieken 已提交
787
		if (resource || this.isFileKindThemeIcon(node.themeIcon)) {
S
Sandeep Somavarapu 已提交
788
			const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
A
Alex Ross 已提交
789
			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) });
790
		} else {
A
Alex Ross 已提交
791
			templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) });
792 793
		}

794
		templateData.icon.title = title ? title : '';
795

J
Johannes Rieken 已提交
796 797 798
		if (iconUrl) {
			templateData.icon.className = 'custom-view-tree-node-item-icon';
			templateData.icon.style.backgroundImage = DOM.asCSSUrl(iconUrl);
799

J
Johannes Rieken 已提交
800 801 802 803
		} else {
			let iconClass: string | undefined;
			if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) {
				iconClass = ThemeIcon.asClassName(node.themeIcon);
804
			}
J
Johannes Rieken 已提交
805
			templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : '';
J
Johannes Rieken 已提交
806
			templateData.icon.style.backgroundImage = '';
807 808
		}

809
		templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
810
		templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
811 812 813
		if (this._actionRunner) {
			templateData.actionBar.actionRunner = this._actionRunner;
		}
814 815 816
		this.setAlignment(templateData.container, node);
		templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node)));
	}
817

818 819
	private setAlignment(container: HTMLElement, treeItem: ITreeItem) {
		DOM.toggleClass(container.parentElement!, 'align-icon-with-twisty', this.aligner.alignIconWithTwisty(treeItem));
820 821
	}

J
Johannes Rieken 已提交
822 823 824 825 826 827 828 829
	private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean {
		if (icon) {
			return icon.id === FileThemeIcon.id || icon.id === FolderThemeIcon.id;
		} else {
			return false;
		}
	}

830 831 832 833 834 835 836 837 838 839 840 841
	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;
	}

842 843 844 845 846
	disposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
		templateData.elementDisposable.dispose();
	}

	disposeTemplate(templateData: ITreeExplorerTemplateData): void {
847 848
		templateData.resourceLabel.dispose();
		templateData.actionBar.dispose();
849
		templateData.elementDisposable.dispose();
850 851 852 853
	}
}

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

856
	constructor(private themeService: IThemeService) {
857 858 859
		super();
	}

860 861
	set tree(tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>) {
		this._tree = tree;
862 863
	}

864 865
	public alignIconWithTwisty(treeItem: ITreeItem): boolean {
		if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) {
866 867
			return false;
		}
868
		if (!this.hasIcon(treeItem)) {
869 870
			return false;
		}
871

872 873 874 875 876 877 878
		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 {
879 880 881 882 883
			return false;
		}
	}

	private hasIcon(node: ITreeItem): boolean {
M
Martin Aeschlimann 已提交
884
		const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark;
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
		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 {

902
	constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) {
903
		super();
904 905 906 907 908
		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));
			}
		}));
909 910
	}

911
	runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> {
912 913
		const selection = this.getSelectedResources();
		let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
914
		let actionInSelected: boolean = false;
915
		if (selection.length > 1) {
916 917 918 919 920 921 922 923 924 925
			selectionHandleArgs = selection.map(selected => {
				if (selected.handle === context.$treeItemHandle) {
					actionInSelected = true;
				}
				return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle };
			});
		}

		if (!actionInSelected) {
			selectionHandleArgs = undefined;
926 927
		}

928
		return action.run(...[context, selectionHandleArgs]);
929 930 931 932 933 934 935
	}
}

class TreeMenus extends Disposable implements IDisposable {

	constructor(
		private id: string,
936 937 938
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IMenuService private readonly menuService: IMenuService,
		@IContextMenuService private readonly contextMenuService: IContextMenuService
939 940 941 942 943 944 945 946 947 948 949 950
	) {
		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 已提交
951
	private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } {
952 953 954 955 956 957 958 959
		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 };
960
		createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
961 962 963 964 965 966 967

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

		return result;
	}
}
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,
985
		@IContextKeyService contextKeyService: IContextKeyService,
986 987
		@IExtensionService private readonly extensionService: IExtensionService,
	) {
988
		super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService);
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
	}

	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;
		}
	}
}