explorerViewlet.ts 15.5 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import 'vs/css!./media/explorerviewlet';
J
Johannes Rieken 已提交
9
import { IDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
10
import { IAction, IActionRunner } from 'vs/base/common/actions';
J
Johannes Rieken 已提交
11 12 13
import { TPromise } from 'vs/base/common/winjs.base';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { Scope } from 'vs/workbench/common/memento';
14
import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration } from 'vs/workbench/parts/files/common/files';
J
Johannes Rieken 已提交
15
import { IViewletView, Viewlet } from 'vs/workbench/browser/viewlet';
S
Sandeep Somavarapu 已提交
16
import { SplitView } from 'vs/base/browser/ui/splitview/splitview';
J
Johannes Rieken 已提交
17 18 19 20 21
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ActionRunner, FileViewletState } from 'vs/workbench/parts/files/browser/views/explorerViewer';
import { ExplorerView } from 'vs/workbench/parts/files/browser/views/explorerView';
import { EmptyView } from 'vs/workbench/parts/files/browser/views/emptyView';
import { OpenEditorsView } from 'vs/workbench/parts/files/browser/views/openEditorsView';
22
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
J
Johannes Rieken 已提交
23 24 25 26 27 28 29 30 31 32
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
B
Benjamin Pasero 已提交
33
import { IThemeService } from 'vs/platform/theme/common/themeService';
34
import { attachHeaderViewStyler } from 'vs/platform/theme/common/styler';
S
Sandeep Somavarapu 已提交
35
import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/parts/views/browser/views';
E
Erich Gamma 已提交
36

37 38 39 40 41
interface IViewState {
	collapsed: boolean;
	size: number;
}

E
Erich Gamma 已提交
42
export class ExplorerViewlet extends Viewlet {
43 44 45

	private static EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state';

E
Erich Gamma 已提交
46 47 48 49 50
	private viewletContainer: Builder;
	private splitView: SplitView;
	private views: IViewletView[];

	private explorerView: ExplorerView;
I
isidor 已提交
51
	private openEditorsView: OpenEditorsView;
B
Benjamin Pasero 已提交
52 53
	private emptyView: EmptyView;

I
isidor 已提交
54
	private openEditorsVisible: boolean;
S
Sandeep Somavarapu 已提交
55
	private lastFocusedView: IViewletView;
56
	private focusListener: IDisposable;
57
	private delayEditorOpeningInOpenedEditors: boolean;
E
Erich Gamma 已提交
58 59 60

	private viewletSettings: any;
	private viewletState: FileViewletState;
61
	private dimension: Dimension;
E
Erich Gamma 已提交
62

A
Alex Dima 已提交
63
	private viewletVisibleContextKey: IContextKey<boolean>;
64

E
Erich Gamma 已提交
65 66 67
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
S
Sandeep Somavarapu 已提交
68
		@IStorageService private storageService: IStorageService,
69 70
		@IEditorGroupService private editorGroupService: IEditorGroupService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
I
isidor 已提交
71
		@IConfigurationService private configurationService: IConfigurationService,
72
		@IInstantiationService private instantiationService: IInstantiationService,
B
Benjamin Pasero 已提交
73
		@IContextKeyService contextKeyService: IContextKeyService,
S
Sandeep Somavarapu 已提交
74
		@IThemeService themeService: IThemeService,
E
Erich Gamma 已提交
75
	) {
B
Benjamin Pasero 已提交
76
		super(VIEWLET_ID, telemetryService, themeService);
E
Erich Gamma 已提交
77

78 79
		this.views = [];

E
Erich Gamma 已提交
80
		this.viewletState = new FileViewletState();
81
		this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService);
E
Erich Gamma 已提交
82 83

		this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);
84
		this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config));
S
Sandeep Somavarapu 已提交
85
		ViewsRegistry.onViewsRegistered(viewDescriptors => this.addViews(viewDescriptors.filter(viewDescriptor => ViewLocation.Explorer === viewDescriptor.location)));
E
Erich Gamma 已提交
86 87 88 89 90 91
	}

	public create(parent: Builder): TPromise<void> {
		super.create(parent);

		this.viewletContainer = parent.div().addClass('explorer-viewlet');
92

S
Sandeep Somavarapu 已提交
93
		return this.render();
94 95 96
	}

	public getActions(): IAction[] {
S
Sandeep Somavarapu 已提交
97 98
		if (this.views.length === 1) {
			return this.views[0].getActions();
99
		}
B
Benjamin Pasero 已提交
100
		return [];
101 102
	}

S
Sandeep Somavarapu 已提交
103 104
	private render(): TPromise<void> {
		const config = this.configurationService.getConfiguration<IFilesConfiguration>();
105

106
		// No need to delay if preview is disabled
107
		this.delayEditorOpeningInOpenedEditors = !!config.workbench.editor.enablePreview;
108

109
		// Open editors view should always be visible in no folder workspace.
S
Sandeep Somavarapu 已提交
110
		this.openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0;
111

S
Sandeep Somavarapu 已提交
112 113
		this.views = [];
		this.viewletContainer.clearChildren();
E
Erich Gamma 已提交
114

S
Sandeep Somavarapu 已提交
115
		this.splitView = new SplitView(this.viewletContainer.getHTMLElement());
116

S
Sandeep Somavarapu 已提交
117 118 119 120
		// Track focus
		this.focusListener = this.splitView.onFocus((view: IViewletView) => {
			this.lastFocusedView = view;
		});
E
Erich Gamma 已提交
121

S
Sandeep Somavarapu 已提交
122
		const customViews = ViewsRegistry.getViews(ViewLocation.Explorer);
E
Erich Gamma 已提交
123

S
Sandeep Somavarapu 已提交
124 125 126 127 128
		if (this.openEditorsVisible) {
			// Open editors view
			this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings);
			this.views.push(this.openEditorsView);
		}
I
isidor 已提交
129

130 131
		const viewsState = JSON.parse(this.storageService.get(ExplorerViewlet.EXPLORER_VIEWS_STATE, this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));

S
Sandeep Somavarapu 已提交
132
		// Explorer view
133
		this.views.push(this.createExplorerOrEmptyView(viewsState));
E
Erich Gamma 已提交
134

S
Sandeep Somavarapu 已提交
135 136
		// custom views
		for (const view of customViews) {
S
Sandeep Somavarapu 已提交
137 138 139
			this.views.push(this.instantiationService.createInstance(view.ctor, view.id, {
				name: view.name,
				actionRunner: this.getActionRunner(),
140
				collapsed: viewsState[view.id] ? (<IViewState>viewsState[view.id]).collapsed : true
S
Sandeep Somavarapu 已提交
141
			}));
S
Sandeep Somavarapu 已提交
142
		}
143

144 145 146
		for (let i = 0; i < this.views.length; i++) {
			const view = this.views[i];
			attachHeaderViewStyler(view, this.themeService, { noContrastBorder: i === 0 });
147
			this.splitView.addView(view, viewsState[view.id] ? (<IViewState>viewsState[view.id]).size : void 0);
I
isidor 已提交
148
		}
149

S
Sandeep Somavarapu 已提交
150
		this.lastFocusedView = this.explorerView;
E
Erich Gamma 已提交
151

S
Sandeep Somavarapu 已提交
152
		return TPromise.join(this.views.map(view => view.create())).then(() => void 0).then(() => {
S
Sandeep Somavarapu 已提交
153 154 155 156
			if (this.views.length === 1) {
				this.views[0].hideHeader();

			}
S
Sandeep Somavarapu 已提交
157 158 159
			if (this.dimension) {
				this.layout(this.dimension);
			}
160

S
Sandeep Somavarapu 已提交
161 162 163 164 165
			// Update title area since the title actions have changed.
			this.updateTitleArea();
			return this.setVisible(this.isVisible()).then(() => this.focus()); // Focus the viewlet since that triggers a rerender.
		});
	}
E
Erich Gamma 已提交
166

S
Sandeep Somavarapu 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
	private updateOpenEditorsView(): void {
		if (!this.splitView) {
			return;
		}

		if (this.openEditorsVisible) {
			this.openEditorsView = this.instantiationService.createInstance(OpenEditorsView, this.getActionRunner(), this.viewletSettings);
			this.views.unshift(this.openEditorsView);
			this.splitView.addView(this.openEditorsView, undefined, 0);
			this.openEditorsView.create().then(() => {
				if (this.views.length === 2) {
					this.views[1].showHeader();
				}
				if (this.dimension) {
					this.layout(this.dimension);
				}
				// Update title area since the title actions have changed.
				this.updateTitleArea();
			});
		} else {
			this.views.shift();
			this.splitView.removeView(this.openEditorsView);
			this.openEditorsView.dispose();
			this.openEditorsView = null;

			if (this.views.length === 1) {
				this.views[0].hideHeader();
			}
			if (this.dimension) {
				this.layout(this.dimension);
			}
			// Update title area since the title actions have changed.
			this.updateTitleArea();
		}
	}

	private addViews(viewDescriptors: IViewDescriptor[]): void {
		if (!this.splitView || !viewDescriptors.length) {
			return;
		}
		const views = [];

209
		const viewsState = JSON.parse(this.storageService.get(ExplorerViewlet.EXPLORER_VIEWS_STATE, this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));
210 211 212
		for (const viewDescriptor of viewDescriptors) {
			const view = this.instantiationService.createInstance(viewDescriptor.ctor, viewDescriptor.id, {
				name: viewDescriptor.name,
S
Sandeep Somavarapu 已提交
213
				actionRunner: this.getActionRunner(),
214
				collapsed: viewsState[viewDescriptor.id] ? (<IViewState>viewsState[viewDescriptor.id]).collapsed : true
S
Sandeep Somavarapu 已提交
215 216 217 218
			});
			views.push(view);
			this.views.push(view);
			attachHeaderViewStyler(view, this.themeService);
219
			this.splitView.addView(view, viewsState[view.id] ? (<IViewState>viewsState[view.id]).size : void 0);
S
Sandeep Somavarapu 已提交
220 221 222 223 224 225 226 227 228 229 230 231 232 233
		}

		TPromise.join(views.map(view => view.create())).then(() => void 0).then(() => {
			this.views[0].showHeader();

			if (this.dimension) {
				this.layout(this.dimension);
			}

			// Update title area since the title actions have changed.
			this.updateTitleArea();
		});
	}

S
Sandeep Somavarapu 已提交
234 235 236 237
	private onConfigurationUpdated(config: IFilesConfiguration): void {
		// Open editors view should always be visible in no folder workspace.
		const openEditorsVisible = !this.contextService.hasWorkspace() || config.explorer.openEditors.visible !== 0;
		if (this.openEditorsVisible !== openEditorsVisible) {
S
Sandeep Somavarapu 已提交
238 239
			this.openEditorsVisible = openEditorsVisible;
			this.updateOpenEditorsView();
S
Sandeep Somavarapu 已提交
240
		}
E
Erich Gamma 已提交
241 242
	}

243
	private createExplorerOrEmptyView(viewsState: any): IViewletView {
B
Benjamin Pasero 已提交
244
		let explorerOrEmptyView: ExplorerView | EmptyView;
E
Erich Gamma 已提交
245 246

		// With a Workspace
B
Benjamin Pasero 已提交
247
		if (this.contextService.hasWorkspace()) {
248 249 250 251 252 253

			// Create a delegating editor service for the explorer to be able to delay the refresh in the opened
			// editors view above. This is a workaround for being able to double click on a file to make it pinned
			// without causing the animation in the opened editors view to kick in and change scroll position.
			// We try to be smart and only use the delay if we recognize that the user action is likely to cause
			// a new entry in the opened editors view.
254 255
			const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
			delegatingEditorService.setEditorOpenHandler((input: EditorInput, options?: EditorOptions, arg3?: any) => {
256 257
				if (this.openEditorsView) {
					let delay = 0;
258
					if (this.delayEditorOpeningInOpenedEditors && (arg3 === false /* not side by side */ || typeof arg3 !== 'number' /* no explicit position */)) {
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
						const activeGroup = this.editorGroupService.getStacksModel().activeGroup;
						if (!activeGroup || !activeGroup.previewEditor) {
							delay = 250; // a new editor entry is likely because there is either no group or no preview in group
						}
					}

					this.openEditorsView.setStructuralRefreshDelay(delay);
				}

				const onSuccessOrError = (editor?: BaseEditor) => {
					if (this.openEditorsView) {
						this.openEditorsView.setStructuralRefreshDelay(0);
					}

					return editor;
				};

				return this.editorService.openEditor(input, options, arg3).then(onSuccessOrError, onSuccessOrError);
			});

			const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
280 281 282 283
			this.explorerView = explorerOrEmptyView = explorerInstantiator.createInstance(ExplorerView, this.viewletState, {
				collapsed: viewsState[ExplorerView.ID] ? (<IViewState>viewsState[ExplorerView.ID]).collapsed : false,
				actionRunner: this.getActionRunner()
			}, this.viewletSettings, void 0);
E
Erich Gamma 已提交
284 285 286 287
		}

		// No workspace
		else {
288 289 290 291
			this.emptyView = explorerOrEmptyView = this.instantiationService.createInstance(EmptyView, {
				collapsed: viewsState[EmptyView.ID] ? (<IViewState>viewsState[EmptyView.ID]).collapsed : false,
				actionRunner: this.getActionRunner()
			});
I
isidor 已提交
292
		}
S
Sandeep Somavarapu 已提交
293 294

		return explorerOrEmptyView;
E
Erich Gamma 已提交
295 296 297 298 299 300
	}

	public getExplorerView(): ExplorerView {
		return this.explorerView;
	}

I
isidor 已提交
301 302
	public getOpenEditorsView(): OpenEditorsView {
		return this.openEditorsView;
303 304
	}

E
Erich Gamma 已提交
305
	public setVisible(visible: boolean): TPromise<void> {
306 307
		this.viewletVisibleContextKey.set(visible);

E
Erich Gamma 已提交
308
		return super.setVisible(visible).then(() => {
309
			return TPromise.join(this.views.map((view) => view.setVisible(visible))).then(() => void 0);
E
Erich Gamma 已提交
310 311 312 313 314 315
		});
	}

	public focus(): void {
		super.focus();

B
Benjamin Pasero 已提交
316 317
		const hasOpenedEditors = !!this.editorGroupService.getStacksModel().activeGroup;

318
		if (this.lastFocusedView && this.lastFocusedView.isExpanded() && this.hasSelectionOrFocus(this.lastFocusedView)) {
B
Benjamin Pasero 已提交
319 320 321 322
			if (this.lastFocusedView !== this.openEditorsView || hasOpenedEditors) {
				this.lastFocusedView.focusBody();
				return;
			}
323 324
		}

B
Benjamin Pasero 已提交
325
		if (this.hasSelectionOrFocus(this.openEditorsView) && hasOpenedEditors) {
I
isidor 已提交
326
			return this.openEditorsView.focusBody();
327 328
		}

329
		if (this.hasSelectionOrFocus(this.explorerView)) {
330
			return this.explorerView.focusBody();
E
Erich Gamma 已提交
331
		}
332

B
Benjamin Pasero 已提交
333 334
		if (this.openEditorsView && this.openEditorsView.isExpanded() && hasOpenedEditors) {
			return this.openEditorsView.focusBody(); // we have entries in the opened editors view to focus on
335 336 337 338 339 340
		}

		if (this.explorerView && this.explorerView.isExpanded()) {
			return this.explorerView.focusBody();
		}

B
Benjamin Pasero 已提交
341 342 343 344
		if (this.emptyView && this.emptyView.isExpanded()) {
			return this.emptyView.focusBody();
		}

I
isidor 已提交
345
		return this.openEditorsView.focus();
346 347
	}

S
Sandeep Somavarapu 已提交
348
	private hasSelectionOrFocus(view: IViewletView): boolean {
349 350 351 352 353 354 355 356
		if (!view) {
			return false;
		}

		if (!view.isExpanded()) {
			return false;
		}

I
isidor 已提交
357
		if (view instanceof ExplorerView || view instanceof OpenEditorsView) {
358 359 360 361 362 363 364
			const viewer = view.getViewer();
			if (!viewer) {
				return false;
			}

			return !!viewer.getFocus() || (viewer.getSelection() && viewer.getSelection().length > 0);

365 366
		}

367
		return false;
E
Erich Gamma 已提交
368 369 370
	}

	public layout(dimension: Dimension): void {
371
		this.dimension = dimension;
S
Sandeep Somavarapu 已提交
372
		this.splitView.layout(dimension.height);
E
Erich Gamma 已提交
373 374 375 376 377 378 379 380 381 382
	}

	public getActionRunner(): IActionRunner {
		if (!this.actionRunner) {
			this.actionRunner = new ActionRunner(this.viewletState);
		}

		return this.actionRunner;
	}

383 384 385 386
	public getViewletState(): FileViewletState {
		return this.viewletState;
	}

M
Maxime Quandalle 已提交
387
	public getOptimalWidth(): number {
388
		const additionalMargin = 16;
S
Sandeep Somavarapu 已提交
389
		const openedEditorsViewWidth = this.openEditorsView ? this.openEditorsView.getOptimalWidth() : 0;
390 391 392 393
		const explorerView = this.getExplorerView();
		const explorerViewWidth = explorerView ? explorerView.getOptimalWidth() : 0;
		const optimalWidth = Math.max(openedEditorsViewWidth, explorerViewWidth);

M
Maxime Quandalle 已提交
394 395 396
		return optimalWidth + additionalMargin;
	}

E
Erich Gamma 已提交
397
	public shutdown(): void {
398 399 400 401 402 403
		const viewletState = this.views.reduce((result, view) => {
			result[view.id] = this.getViewState(view);
			return result;
		}, {});
		this.storageService.store(ExplorerViewlet.EXPLORER_VIEWS_STATE, JSON.stringify(viewletState), this.contextService.hasWorkspace() ? StorageScope.WORKSPACE : StorageScope.GLOBAL);

E
Erich Gamma 已提交
404 405 406 407 408
		this.views.forEach((view) => view.shutdown());

		super.shutdown();
	}

409 410 411 412 413 414 415
	private getViewState(view: IViewletView): IViewState {
		return {
			collapsed: !view.isExpanded(),
			size: view.size > 0 ? view.size : void 0
		};
	}

E
Erich Gamma 已提交
416
	public dispose(): void {
S
Sandeep Somavarapu 已提交
417 418 419 420 421

		for (const view of this.views) {
			view.dispose();
		}

E
Erich Gamma 已提交
422
		if (this.splitView) {
423 424
			this.splitView = null;
		}
B
Benjamin Pasero 已提交
425

I
isidor 已提交
426 427 428
		if (this.explorerView) {
			this.explorerView = null;
		}
B
Benjamin Pasero 已提交
429

430 431 432
		if (this.openEditorsView) {
			this.openEditorsView = null;
		}
433

B
Benjamin Pasero 已提交
434 435 436 437
		if (this.emptyView) {
			this.emptyView = null;
		}

438 439 440
		if (this.focusListener) {
			this.focusListener.dispose();
			this.focusListener = null;
E
Erich Gamma 已提交
441 442 443
		}
	}
}