editorGroupView.ts 43.5 KB
Newer Older
B
Benjamin Pasero 已提交
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.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import 'vs/css!./media/editorgroupview';
9
import { TPromise } from 'vs/base/common/winjs.base';
B
Benjamin Pasero 已提交
10
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
B
Benjamin Pasero 已提交
11
import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent } from 'vs/workbench/common/editor';
12
import { Event, Emitter, once } from 'vs/base/common/event';
13
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
14
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
15 16
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
17 18
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
19
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
20
import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
B
Benjamin Pasero 已提交
21
import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme';
22
import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService';
23 24
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl';
25 26
import { IProgressService } from 'vs/platform/progress/common/progress';
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
27
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
28
import { localize } from 'vs/nls';
B
Benjamin Pasero 已提交
29
import { isPromiseCanceledError, isErrorWithActions, IErrorWithActions } from 'vs/base/common/errors';
30 31 32
import { dispose } from 'vs/base/common/lifecycle';
import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
import { toErrorMessage } from 'vs/base/common/errorMessage';
33 34
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RunOnceWorker } from 'vs/base/common/async';
35
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
36
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
B
Benjamin Pasero 已提交
37
import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, EDITOR_TITLE_HEIGHT, EDITOR_MIN_DIMENSIONS, EDITOR_MAX_DIMENSIONS, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor';
38 39
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { join } from 'vs/base/common/paths';
40 41
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
B
Benjamin Pasero 已提交
42 43
import { ActionRunner, IAction, Action } from 'vs/base/common/actions';
import { CLOSE_EDITOR_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
44
import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl';
45 46
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
47
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
48
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
49

50
export class EditorGroupView extends Themable implements IEditorGroupView {
B
Benjamin Pasero 已提交
51

52 53
	//#region factory

54 55
	static createNew(accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView {
		return instantiationService.createInstance(EditorGroupView, accessor, null, label);
56 57
	}

58 59
	static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView {
		return instantiationService.createInstance(EditorGroupView, accessor, serialized, label);
60 61
	}

62 63
	static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView {
		return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, label);
64 65 66 67
	}

	//#endregion

68 69
	//#region events

70
	private _onDidFocus: Emitter<void> = this._register(new Emitter<void>());
71 72
	get onDidFocus(): Event<void> { return this._onDidFocus.event; }

73
	private _onWillDispose: Emitter<void> = this._register(new Emitter<void>());
74 75
	get onWillDispose(): Event<void> { return this._onWillDispose.event; }

76 77
	private _onDidGroupChange: Emitter<IGroupChangeEvent> = this._register(new Emitter<IGroupChangeEvent>());
	get onDidGroupChange(): Event<IGroupChangeEvent> { return this._onDidGroupChange.event; }
78

79 80 81
	private _onWillOpenEditor: Emitter<IEditorOpeningEvent> = this._register(new Emitter<IEditorOpeningEvent>());
	get onWillOpenEditor(): Event<IEditorOpeningEvent> { return this._onWillOpenEditor.event; }

82 83 84
	private _onDidOpenEditorFail: Emitter<EditorInput> = this._register(new Emitter<EditorInput>());
	get onDidOpenEditorFail(): Event<EditorInput> { return this._onDidOpenEditorFail.event; }

B
Benjamin Pasero 已提交
85 86
	private _onWillCloseEditor: Emitter<IEditorCloseEvent> = this._register(new Emitter<IEditorCloseEvent>());
	get onWillCloseEditor(): Event<IEditorCloseEvent> { return this._onWillCloseEditor.event; }
87

B
Benjamin Pasero 已提交
88 89
	private _onDidCloseEditor: Emitter<IEditorCloseEvent> = this._register(new Emitter<IEditorCloseEvent>());
	get onDidCloseEditor(): Event<IEditorCloseEvent> { return this._onDidCloseEditor.event; }
90

91 92
	//#endregion

93
	private _group: EditorGroup;
94
	private _disposed: boolean;
B
Benjamin Pasero 已提交
95

96
	private active: boolean;
97
	private dimension: Dimension;
98

99
	private _whenRestored: TPromise<void>;
100
	private isRestored: boolean;
101

102
	private scopedInstantiationService: IInstantiationService;
B
Benjamin Pasero 已提交
103

104
	private titleContainer: HTMLElement;
105
	private titleAreaControl: TitleControl;
106

107 108
	private progressBar: ProgressBar;

109
	private editorContainer: HTMLElement;
110
	private editorControl: EditorControl;
111

112
	private ignoreOpenEditorErrors: boolean;
113
	private disposedEditorsWorker: RunOnceWorker<EditorInput>;
114

B
Benjamin Pasero 已提交
115
	constructor(
116 117
		private accessor: IEditorGroupsAccessor,
		from: IEditorGroupView | ISerializedEditorGroup,
118
		private _label: string,
119
		@IInstantiationService private instantiationService: IInstantiationService,
120
		@IContextKeyService private contextKeyService: IContextKeyService,
121
		@IThemeService themeService: IThemeService,
122
		@INotificationService private notificationService: INotificationService,
123
		@ITelemetryService private telemetryService: ITelemetryService,
124
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
125 126 127
		@IKeybindingService private keybindingService: IKeybindingService,
		@IMenuService private menuService: IMenuService,
		@IContextMenuService private contextMenuService: IContextMenuService
B
Benjamin Pasero 已提交
128
	) {
129
		super(themeService);
130

131
		if (from instanceof EditorGroupView) {
132 133 134
			this._group = this._register(from.group.clone());
		} else if (isSerializedEditorGroup(from)) {
			this._group = this._register(instantiationService.createInstance(EditorGroup, from));
135
		} else {
B
Benjamin Pasero 已提交
136
			this._group = this._register(instantiationService.createInstance(EditorGroup, void 0));
137
		}
138

139 140
		this.disposedEditorsWorker = this._register(new RunOnceWorker(editors => this.handleDisposedEditors(editors), 0));

B
Benjamin Pasero 已提交
141
		this.create();
142

143
		this._whenRestored = this.restoreEditors(from);
144
		this._whenRestored.then(() => this.isRestored = true);
145

B
Benjamin Pasero 已提交
146
		this.registerListeners();
147 148
	}

B
Benjamin Pasero 已提交
149
	private create(): void {
150 151 152 153

		// Container
		addClasses(this.element, 'editor-group-container');

B
Benjamin Pasero 已提交
154 155
		// Container listeners
		this.registerContainerListeners();
156

157 158 159
		// Container toolbar
		this.createContainerToolbar();

160 161 162
		// Container context menu
		this.createContainerContextMenu();

B
Benjamin Pasero 已提交
163 164 165 166 167
		// Letterpress container
		const letterpressContainer = document.createElement('div');
		addClass(letterpressContainer, 'editor-group-letterpress');
		this.element.appendChild(letterpressContainer);

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
		// Progress bar
		this.progressBar = this._register(new ProgressBar(this.element));
		this._register(attachProgressBarStyler(this.progressBar, this.themeService));
		this.progressBar.hide();

		// Scoped instantiator
		this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
			[IContextKeyService, this._register(this.contextKeyService.createScoped(this.element))],
			[IProgressService, new ProgressService(this.progressBar)]
		));

		// Title container
		this.titleContainer = document.createElement('div');
		addClass(this.titleContainer, 'title');
		this.element.appendChild(this.titleContainer);

		// Title control
		this.createTitleAreaControl();

		// Editor container
		this.editorContainer = document.createElement('div');
		addClass(this.editorContainer, 'editor-container');
		this.element.appendChild(this.editorContainer);

		// Editor control
193
		this.editorControl = this._register(this.scopedInstantiationService.createInstance(EditorControl, this.editorContainer, this));
194

195 196
		// Track Focus
		this.doTrackFocus();
197 198 199 200 201

		// Update containers
		this.updateTitleContainer();
		this.updateContainer();

202 203
		// Update styles
		this.updateStyles();
204 205
	}

B
Benjamin Pasero 已提交
206 207
	private registerContainerListeners(): void {

B
Benjamin Pasero 已提交
208
		// Open new file via doubleclick on empty container
B
Benjamin Pasero 已提交
209
		this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => {
B
Benjamin Pasero 已提交
210
			if (this.isEmpty()) {
B
Benjamin Pasero 已提交
211 212 213 214 215
				EventHelper.stop(e);

				this.openEditor(this.untitledEditorService.createOrGet(), EditorOptions.create({ pinned: true }));
			}
		}));
216

B
Benjamin Pasero 已提交
217
		// Close empty editor group via middle mouse click
218
		this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => {
B
Benjamin Pasero 已提交
219
			if (this.isEmpty() && e.button === 1 /* Middle Button */) {
220 221 222 223 224
				EventHelper.stop(e);

				this.accessor.removeGroup(this);
			}
		}));
B
Benjamin Pasero 已提交
225 226
	}

227 228 229 230 231 232 233 234 235 236
	private createContainerToolbar(): void {

		// Toolbar Container
		const toolbarContainer = document.createElement('div');
		addClass(toolbarContainer, 'editor-group-container-toolbar');
		this.element.appendChild(toolbarContainer);

		// Toolbar
		const groupId = this._group.id;
		const containerToolbar = new ActionBar(toolbarContainer, {
237
			ariaLabel: localize('araLabelGroupActions', "Editor group actions"), actionRunner: this._register(new class extends ActionRunner {
238 239 240
				run(action: IAction) {
					return action.run(groupId);
				}
241
			})
242 243 244
		});

		// Toolbar actions
245
		const removeGroupAction = this._register(new Action(CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), 'close-editor-group', true, () => { this.accessor.removeGroup(this); return TPromise.as(true); }));
246 247 248 249
		const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id);
		containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : void 0 });
	}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
	private createContainerContextMenu(): void {
		const menu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroupContext, this.contextKeyService));

		this._register(addDisposableListener(this.element, EventType.CONTEXT_MENU, event => this.onShowContainerContextMenu(menu, event)));
		this._register(addDisposableListener(this.element, TouchEventType.Contextmenu, event => this.onShowContainerContextMenu(menu)));
	}

	private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void {
		if (!this.isEmpty()) {
			return; // only for empty editor groups
		}

		// Find target anchor
		let anchor: HTMLElement | { x: number, y: number } = this.element;
		if (e instanceof MouseEvent) {
			const event = new StandardMouseEvent(e);
			anchor = { x: event.posx, y: event.posy };
		}

		// Fill in contributed actions
		const actions: IAction[] = [];
271
		fillInContextMenuActions(menu, void 0, actions, this.contextMenuService);
272 273 274 275 276 277 278 279 280 281

		// Show it
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
			getActions: () => TPromise.as(actions),
			getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
			onHide: () => this.focus()
		});
	}

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
	private doTrackFocus(): void {

		// Container
		const containerFocusTracker = this._register(trackFocus(this.element));
		this._register(containerFocusTracker.onDidFocus(() => {
			if (this.isEmpty()) {
				this._onDidFocus.fire(); // only when empty to prevent accident focus
			}
		}));

		// Title Container
		const handleTitleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
			let target: HTMLElement;
			if (e instanceof MouseEvent) {
				if (e.button !== 0) {
					return void 0; // only for left mouse click
				}

				target = e.target as HTMLElement;
			} else {
				target = (e as GestureEvent).initialTarget as HTMLElement;
			}

			if (findParentWithClass(target, 'monaco-action-bar', this.titleContainer)) {
				return; // not when clicking on actions
			}

B
Benjamin Pasero 已提交
309 310 311 312
			// timeout to keep focus in editor after mouse up
			setTimeout(() => {
				this.focus();
			});
313 314
		};

B
Benjamin Pasero 已提交
315
		this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_DOWN, e => handleTitleClickOrTouch(e)));
316 317 318
		this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, e => handleTitleClickOrTouch(e)));

		// Editor Container
319
		this._register(this.editorControl.onDidFocus(() => {
320 321 322 323 324 325
			this._onDidFocus.fire();
		}));
	}

	private updateContainer(): void {

B
Benjamin Pasero 已提交
326
		// Empty Container: add some empty container attributes
327 328 329
		if (this.isEmpty()) {
			addClass(this.element, 'empty');
			this.element.tabIndex = 0;
330
			this.element.setAttribute('aria-label', localize('emptyEditorGroup', "{0} (empty)", this.label));
331 332 333 334 335 336 337 338
		}

		// Non-Empty Container: revert empty container attributes
		else {
			removeClass(this.element, 'empty');
			this.element.removeAttribute('tabIndex');
			this.element.removeAttribute('aria-label');
		}
339 340 341

		// Update styles
		this.updateStyles();
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
	}

	private updateTitleContainer(): void {
		toggleClass(this.titleContainer, 'tabs', this.accessor.partOptions.showTabs);
		toggleClass(this.titleContainer, 'show-file-icons', this.accessor.partOptions.showIcons);
	}

	private createTitleAreaControl(): void {

		// Clear old if existing
		if (this.titleAreaControl) {
			this.titleAreaControl.dispose();
			clearNode(this.titleContainer);
		}

		// Create new based on options
		if (this.accessor.partOptions.showTabs) {
359
			this.titleAreaControl = this.scopedInstantiationService.createInstance(TabsTitleControl, this.titleContainer, this.accessor, this);
360
		} else {
361
			this.titleAreaControl = this.scopedInstantiationService.createInstance(NoTabsTitleControl, this.titleContainer, this.accessor, this);
362
		}
363 364
	}

365
	private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise<void> {
B
Benjamin Pasero 已提交
366
		if (this._group.count === 0) {
367
			return TPromise.as(void 0); // nothing to show
B
Benjamin Pasero 已提交
368 369 370 371
		}

		// Determine editor options
		let options: EditorOptions;
372
		if (from instanceof EditorGroupView) {
B
Benjamin Pasero 已提交
373
			options = getActiveTextEditorOptions(from); // if we copy from another group, ensure to copy its active editor viewstate
B
Benjamin Pasero 已提交
374 375 376 377
		} else {
			options = new EditorOptions();
		}

B
Benjamin Pasero 已提交
378 379 380
		const activeEditor = this._group.activeEditor;
		options.pinned = this._group.isPinned(activeEditor);	// preserve pinned state
		options.preserveFocus = true;							// handle focus after editor is opened
B
Benjamin Pasero 已提交
381

382 383 384 385
		// Show active editor
		return this.doShowEditor(activeEditor, true, options).then(() => {

			// Set focused now if this is the active group
B
Benjamin Pasero 已提交
386 387 388 389 390 391
			if (this.accessor.activeGroup === this) {
				this.focus();
			}
		});
	}

392 393
	//#region event handling

394
	private registerListeners(): void {
395 396

		// Model Events
B
Benjamin Pasero 已提交
397
		this._register(this._group.onDidEditorPin(editor => this.onDidEditorPin(editor)));
398 399 400 401 402
		this._register(this._group.onDidEditorOpen(editor => this.onDidEditorOpen(editor)));
		this._register(this._group.onDidEditorClose(editor => this.onDidEditorClose(editor)));
		this._register(this._group.onDidEditorDispose(editor => this.onDidEditorDispose(editor)));
		this._register(this._group.onDidEditorBecomeDirty(editor => this.onDidEditorBecomeDirty(editor)));
		this._register(this._group.onDidEditorLabelChange(editor => this.onDidEditorLabelChange(editor)));
403

404 405
		// Option Changes
		this._register(this.accessor.onDidEditorPartOptionsChange(e => this.onDidEditorPartOptionsChange(e)));
406 407
	}

B
Benjamin Pasero 已提交
408 409 410 411 412 413
	private onDidEditorPin(editor: EditorInput): void {

		// Event
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_PIN, editor });
	}

414
	private onDidEditorOpen(editor: EditorInput): void {
415 416 417 418 419 420 421 422
		/* __GDPR__
			"editorOpened" : {
				"${include}": [
					"${EditorTelemetryDescriptor}"
				]
			}
		*/
		this.telemetryService.publicLog('editorOpened', editor.getTelemetryDescriptor());
423 424 425

		// Update container
		this.updateContainer();
426 427

		// Event
428
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_OPEN, editor });
429 430
	}

431
	private onDidEditorClose(event: EditorCloseEvent): void {
432 433

		// Before close
B
Benjamin Pasero 已提交
434
		this._onWillCloseEditor.fire(event);
435 436

		// Handle event
437 438 439 440
		const editor = event.editor;
		const editorsToClose = [editor];

		// Include both sides of side by side editors when being closed and not opened multiple times
441
		if (editor instanceof SideBySideEditorInput && !this.accessor.groups.some(groupView => groupView.group.contains(editor))) {
442 443 444 445 446 447
			editorsToClose.push(editor.master, editor.details);
		}

		// Close the editor when it is no longer open in any group including diff editors
		editorsToClose.forEach(editorToClose => {
			const resource = editorToClose ? editorToClose.getResource() : void 0; // prefer resource to not close right-hand side editors of a diff editor
448
			if (!this.accessor.groups.some(groupView => groupView.group.contains(resource || editorToClose))) {
449 450 451
				editorToClose.close();
			}
		});
452

453 454 455 456 457 458 459 460
		/* __GDPR__
			"editorClosed" : {
				"${include}": [
					"${EditorTelemetryDescriptor}"
				]
			}
		*/
		this.telemetryService.publicLog('editorClosed', event.editor.getTelemetryDescriptor());
461 462 463

		// Update container
		this.updateContainer();
464 465 466

		// Event
		this._onDidCloseEditor.fire(event);
467
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex: event.index });
468 469
	}

470
	private onDidEditorDispose(editor: EditorInput): void {
471 472 473 474 475 476 477 478 479 480 481 482 483

		// To prevent race conditions, we handle disposed editors in our worker with a timeout
		// because it can happen that an input is being disposed with the intent to replace
		// it with some other input right after.
		this.disposedEditorsWorker.work(editor);
	}

	private handleDisposedEditors(editors: EditorInput[]): void {

		// Split between visible and hidden editors
		let activeEditor: EditorInput;
		const inactiveEditors: EditorInput[] = [];
		editors.forEach(editor => {
484
			if (this._group.isActive(editor)) {
485
				activeEditor = editor;
486
			} else if (this._group.contains(editor)) {
487 488 489 490 491 492 493 494 495 496 497 498 499
				inactiveEditors.push(editor);
			}
		});

		// Close all inactive editors first to prevent UI flicker
		inactiveEditors.forEach(hidden => this.doCloseEditor(hidden, false));

		// Close active one last
		if (activeEditor) {
			this.doCloseEditor(activeEditor, false);
		}
	}

500
	private onDidEditorPartOptionsChange(event: IEditorPartOptionsChangeEvent): void {
501 502 503 504

		// Title container
		this.updateTitleContainer();

505 506 507
		// Title control Switch between showing tabs <=> not showing tabs
		if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) {
			this.createTitleAreaControl();
508

B
Benjamin Pasero 已提交
509 510
			if (this._group.activeEditor) {
				this.titleAreaControl.openEditor(this._group.activeEditor);
511
			}
512
		}
513

514 515 516
		// Just update title control
		else {
			this.titleAreaControl.updateOptions(event.oldPartOptions, event.newPartOptions);
517 518 519 520
		}

		// Styles
		this.updateStyles();
B
Benjamin Pasero 已提交
521 522 523 524 525

		// Pin preview editor once user disables preview
		if (event.oldPartOptions.enablePreview && !event.newPartOptions.enablePreview) {
			this.pinEditor(this._group.previewEditor);
		}
526 527
	}

528 529 530 531 532 533
	private onDidEditorBecomeDirty(editor: EditorInput): void {

		// Always show dirty editors pinned
		this.pinEditor(editor);

		// Forward to title control
534
		this.titleAreaControl.updateEditorDirty(editor);
535 536 537

		// Event
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_DIRTY, editor });
538 539 540 541 542
	}

	private onDidEditorLabelChange(editor: EditorInput): void {

		// Forward to title control
543
		this.titleAreaControl.updateEditorLabel(editor);
544 545 546

		// Event
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_LABEL, editor });
547 548
	}

549 550
	//#endregion

551
	//region IEditorGroupView
B
Benjamin Pasero 已提交
552

553 554 555 556
	get group(): EditorGroup {
		return this._group;
	}

557 558 559 560
	get label(): string {
		return this._label;
	}

561 562 563 564
	get disposed(): boolean {
		return this._disposed;
	}

565
	get whenRestored(): TPromise<void> {
566 567 568
		return this._whenRestored;
	}

569 570 571
	setLabel(label: string): void {
		if (this._label !== label) {
			this._label = label;
572
			this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_LABEL });
573 574 575
		}
	}

576
	setActive(isActive: boolean): void {
577
		this.active = isActive;
578 579 580 581 582 583

		// Update container
		toggleClass(this.element, 'active', isActive);
		toggleClass(this.element, 'inactive', !isActive);

		// Update title control
584
		this.titleAreaControl.setActive(isActive);
585 586 587

		// Update styles
		this.updateStyles();
588 589 590

		// Event
		this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_ACTIVE });
591 592
	}

593
	isEmpty(): boolean {
594
		return this._group.count === 0;
595 596
	}

B
Benjamin Pasero 已提交
597 598
	//#endregion

599
	//#region IEditorGroup
600

B
Benjamin Pasero 已提交
601 602
	//#region basics()

B
Benjamin Pasero 已提交
603
	get id(): GroupIdentifier {
604
		return this._group.id;
B
Benjamin Pasero 已提交
605 606
	}

607
	get editors(): EditorInput[] {
608
		return this._group.getEditors();
609 610 611
	}

	get count(): number {
612
		return this._group.count;
613 614
	}

615 616
	get activeControl(): BaseEditor {
		return this.editorControl ? this.editorControl.activeControl : void 0;
617 618
	}

619
	get activeEditor(): EditorInput {
620
		return this._group.activeEditor;
621 622
	}

623 624 625 626
	get previewEditor(): EditorInput {
		return this._group.previewEditor;
	}

627
	isPinned(editor: EditorInput): boolean {
628
		return this._group.isPinned(editor);
629 630 631
	}

	isActive(editor: EditorInput): boolean {
632
		return this._group.isActive(editor);
B
Benjamin Pasero 已提交
633 634
	}

B
Benjamin Pasero 已提交
635 636 637 638 639 640 641 642
	getEditors(order?: EditorsOrder): EditorInput[] {
		if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
			return this._group.getEditors(true);
		}

		return this.editors;
	}

643
	getEditor(index: number): EditorInput {
644
		return this._group.getEditor(index);
645 646
	}

647
	getIndexOfEditor(editor: EditorInput): number {
648
		return this._group.indexOf(editor);
649 650
	}

B
Benjamin Pasero 已提交
651 652 653 654 655
	isOpened(editor: EditorInput): boolean {
		return this._group.contains(editor);
	}

	focus(): void {
656 657

		// Pass focus to widgets
B
Benjamin Pasero 已提交
658 659 660 661 662
		if (this.activeControl) {
			this.activeControl.focus();
		} else {
			this.element.focus();
		}
663 664 665

		// Event
		this._onDidFocus.fire();
B
Benjamin Pasero 已提交
666 667 668 669 670 671 672 673 674
	}

	pinEditor(editor: EditorInput = this.activeEditor): void {
		if (editor && !this._group.isPinned(editor)) {

			// Update model
			this._group.pin(editor);

			// Forward to title control
675
			this.titleAreaControl.pinEditor(editor);
B
Benjamin Pasero 已提交
676 677 678
		}
	}

679 680 681 682
	invokeWithinContext<T>(fn: (accessor: ServicesAccessor) => T): T {
		return this.scopedInstantiationService.invokeFunction(fn);
	}

B
Benjamin Pasero 已提交
683 684
	//#endregion

685 686
	//#region openEditor()

687
	openEditor(editor: EditorInput, options?: EditorOptions): TPromise<void> {
688 689

		// Editor opening event allows for prevention
690
		const event = new EditorOpeningEvent(this._group.id, editor, options);
691
		this._onWillOpenEditor.fire(event);
692 693
		const prevented = event.isPrevented();
		if (prevented) {
B
Benjamin Pasero 已提交
694
			return prevented();
695 696 697 698 699 700
		}

		// Proceed with opening
		return this.doOpenEditor(editor, options);
	}

701
	private doOpenEditor(editor: EditorInput, options?: EditorOptions): TPromise<void> {
B
Benjamin Pasero 已提交
702

703
		// Determine options
704
		const openEditorOptions: IEditorOpenOptions = {
705
			index: options ? options.index : void 0,
706
			pinned: !this.accessor.partOptions.enablePreview || editor.isDirty() || (options && options.pinned) || (options && typeof options.index === 'number'),
707
			active: this._group.count === 0 || !options || !options.inactive
708
		};
709

710 711 712 713 714 715 716 717
		if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.isPreview(this._group.activeEditor)) {
			// Special case: we are to open an editor inactive and not pinned, but the current active
			// editor is also not pinned, which means it will get replaced with this one. As such,
			// the editor can only be active.
			openEditorOptions.active = true;
		}

		// Update model
718
		this._group.openEditor(editor, openEditorOptions);
B
Benjamin Pasero 已提交
719

B
Benjamin Pasero 已提交
720
		// Show editor
721 722 723 724 725 726 727 728
		const showEditorResult = this.doShowEditor(editor, openEditorOptions.active, options);

		// Set group active unless we open inactive or preserve focus
		if (openEditorOptions.active && (!options || !options.preserveFocus)) {
			this.accessor.activateGroup(this);
		}

		return showEditorResult;
B
Benjamin Pasero 已提交
729 730
	}

731
	private doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): TPromise<void> {
B
Benjamin Pasero 已提交
732 733

		// Show in editor control if the active editor changed
734
		let openEditorPromise: TPromise<void>;
B
Benjamin Pasero 已提交
735
		if (active) {
736
			openEditorPromise = this.editorControl.openEditor(editor, options).then(result => {
737 738 739

				// Editor change event
				if (result.editorChanged) {
740
					this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor });
741 742 743 744 745 746 747 748 749
				}
			}, error => {

				// Handle errors but do not bubble them up
				this.doHandleOpenEditorError(error, editor, options);
			});
		} else {
			openEditorPromise = TPromise.as(void 0);
		}
750

B
Benjamin Pasero 已提交
751
		// Show in title control after editor control because some actions depend on it
752
		this.titleAreaControl.openEditor(editor);
753

754
		return openEditorPromise;
755 756 757 758 759 760
	}

	private doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): void {

		// Report error only if this was not us restoring previous error state or
		// we are told to ignore errors that occur from opening an editor
761
		if (this.isRestored && !isPromiseCanceledError(error) && !this.ignoreOpenEditorErrors) {
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
			const actions: INotificationActions = { primary: [] };
			if (isErrorWithActions(error)) {
				actions.primary = (error as IErrorWithActions).actions;
			}

			const handle = this.notificationService.notify({
				severity: Severity.Error,
				message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
				actions
			});

			once(handle.onDidClose)(() => dispose(actions.primary));
		}

		// Event
		this._onDidOpenEditorFail.fire(editor);

		// Recover by closing the active editor (if the input is still the active one)
		if (this.activeEditor === editor) {
B
Benjamin Pasero 已提交
781
			const focusNext = !options || !options.preserveFocus;
B
Benjamin Pasero 已提交
782
			this.doCloseEditor(editor, focusNext, true /* from error */);
783
		}
784 785
	}

786 787
	//#endregion

788 789
	//#region openEditors()

790
	openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): TPromise<void> {
791 792 793 794
		if (!editors.length) {
			return TPromise.as(void 0);
		}

795 796 797
		// Do not modify original array
		editors = editors.slice(0);

798 799 800 801 802 803 804 805 806 807 808 809 810
		// Use the first editor as active editor
		const { editor, options } = editors.shift();
		return this.openEditor(editor, options).then(() => {
			const startingIndex = this.getIndexOfEditor(editor) + 1;

			// Open the other ones inactive
			return TPromise.join(editors.map(({ editor, options }, index) => {
				const adjustedEditorOptions = options || new EditorOptions();
				adjustedEditorOptions.inactive = true;
				adjustedEditorOptions.pinned = true;
				adjustedEditorOptions.index = startingIndex + index;

				return this.openEditor(editor, adjustedEditorOptions);
811
			})).then(() => void 0);
812 813 814 815 816
		});
	}

	//#endregion

B
Benjamin Pasero 已提交
817 818
	//#region moveEditor()

819
	moveEditor(editor: EditorInput, target: IEditorGroupView, options?: IMoveEditorOptions): void {
B
Benjamin Pasero 已提交
820 821 822 823 824 825 826 827

		// Move within same group
		if (this === target) {
			this.doMoveEditorInsideGroup(editor, options);
		}

		// Move across groups
		else {
B
Benjamin Pasero 已提交
828
			this.doMoveOrCopyEditorAcrossGroups(editor, target, options);
B
Benjamin Pasero 已提交
829 830 831 832 833 834 835 836 837
		}
	}

	private doMoveEditorInsideGroup(editor: EditorInput, moveOptions?: IMoveEditorOptions): void {
		const moveToIndex = moveOptions ? moveOptions.index : void 0;
		if (typeof moveToIndex !== 'number') {
			return; // do nothing if we move into same group without index
		}

838
		const currentIndex = this._group.indexOf(editor);
B
Benjamin Pasero 已提交
839 840 841 842 843
		if (currentIndex === moveToIndex) {
			return; // do nothing if editor is already at the given index
		}

		// Update model
844 845
		this._group.moveEditor(editor, moveToIndex);
		this._group.pin(editor);
B
Benjamin Pasero 已提交
846 847

		// Forward to title area
848 849
		this.titleAreaControl.moveEditor(editor, currentIndex, moveToIndex);
		this.titleAreaControl.pinEditor(editor);
850 851

		// Event
852
		this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_MOVE, editor });
B
Benjamin Pasero 已提交
853 854
	}

855
	private doMoveOrCopyEditorAcrossGroups(editor: EditorInput, target: IEditorGroupView, moveOptions: IMoveEditorOptions = Object.create(null), keepCopy?: boolean): void {
B
Benjamin Pasero 已提交
856 857 858

		// When moving an editor, try to preserve as much view state as possible by checking
		// for the editor to be a text editor and creating the options accordingly if so
B
Benjamin Pasero 已提交
859
		const options = getActiveTextEditorOptions(this, editor, EditorOptions.create(moveOptions));
B
Benjamin Pasero 已提交
860 861
		options.pinned = true; // always pin moved editor

B
Benjamin Pasero 已提交
862 863 864
		// A move to another group is an open first...
		target.openEditor(editor, options);

B
Benjamin Pasero 已提交
865 866 867 868 869 870 871 872 873 874
		// ...and a close afterwards (unless we copy)
		if (!keepCopy) {
			this.doCloseEditor(editor, false /* do not focus next one behind if any */);
		}
	}

	//#endregion

	//#region copyEditor()

875
	copyEditor(editor: EditorInput, target: IEditorGroupView, options?: ICopyEditorOptions): void {
B
Benjamin Pasero 已提交
876 877 878 879 880 881 882 883 884 885 886

		// Move within same group because we do not support to show the same editor
		// multiple times in the same group
		if (this === target) {
			this.doMoveEditorInsideGroup(editor, options);
		}

		// Copy across groups
		else {
			this.doMoveOrCopyEditorAcrossGroups(editor, target, options, true);
		}
B
Benjamin Pasero 已提交
887 888 889 890
	}

	//#endregion

891 892
	//#region closeEditor()

893
	closeEditor(editor: EditorInput = this.activeEditor): TPromise<void> {
894 895 896
		if (!editor) {
			return TPromise.as(void 0);
		}
897 898

		// Check for dirty and veto
899
		return this.handleDirty([editor]).then(veto => {
900 901 902 903 904 905 906 907 908
			if (veto) {
				return;
			}

			// Do close
			this.doCloseEditor(editor);
		});
	}

B
Benjamin Pasero 已提交
909
	private doCloseEditor(editor: EditorInput, focusNext = this.accessor.activeGroup === this, fromError?: boolean): void {
910 911

		// Closing the active editor of the group is a bit more work
B
Benjamin Pasero 已提交
912
		if (this._group.isActive(editor)) {
B
Benjamin Pasero 已提交
913
			this.doCloseActiveEditor(focusNext, fromError);
914 915 916 917 918
		}

		// Closing inactive editor is just a model update
		else {
			this.doCloseInactiveEditor(editor);
919
		}
920 921 922

		// Forward to title control
		this.titleAreaControl.closeEditor(editor);
923 924
	}

925
	private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void {
926 927
		const editorToClose = this.activeEditor;
		const editorHasFocus = isAncestor(document.activeElement, this.element);
928

929 930 931 932 933 934 935 936 937
		// Optimization: if we are about to close the last editor in this group and settings
		// are configured to close the group since it will be empty, we first set the last
		// active group as empty before closing the editor. This reduces the amount of editor
		// change events that this operation emits and will reduce flicker. Without this
		// optimization, this group (if active) would first trigger a active editor change
		// event because it became empty, only to then trigger another one when the next
		// group gets active.
		const closeEmptyGroup = this.accessor.partOptions.closeEmptyGroups;
		if (closeEmptyGroup && this.active && this._group.count === 1) {
938 939 940 941 942 943 944 945 946
			const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
			const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1]
			if (nextActiveGroup) {
				if (editorHasFocus) {
					nextActiveGroup.focus();
				} else {
					this.accessor.activateGroup(nextActiveGroup);
				}
			}
947 948
		}

949
		// Update model
950
		this._group.closeEditor(editorToClose);
B
Benjamin Pasero 已提交
951

952
		// Open next active if there are more to show
953
		const nextActiveEditor = this._group.activeEditor;
954 955
		if (nextActiveEditor) {

956 957 958 959 960 961
			// When closing an editor due to an error we can end up in a loop where we continue closing
			// editors that fail to open (e.g. when the file no longer exists). We do not want to show
			// repeated errors in this case to the user. As such, if we open the next editor and we are
			// in a scope of a previous editor failing, we silence the input errors until the editor is
			// opened.
			if (fromError) {
962
				this.ignoreOpenEditorErrors = true;
963 964
			}

B
Benjamin Pasero 已提交
965 966
			const options = !focusNext ? EditorOptions.create({ preserveFocus: true }) : void 0;
			this.openEditor(nextActiveEditor, options).then(() => {
967
				this.ignoreOpenEditorErrors = false;
968
			});
969 970
		}

971
		// Otherwise we are empty, so clear from editor control and send event
972 973 974
		else {

			// Forward to editor control
975
			this.editorControl.closeEditor(editorToClose);
976

977 978
			// Restore focus to group container as needed unless group gets closed
			if (editorHasFocus && !closeEmptyGroup) {
979 980
				this.focus();
			}
981

982 983 984
			// Events
			this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE });

985 986
			// Remove empty group if we should
			if (closeEmptyGroup) {
987 988
				this.accessor.removeGroup(this);
			}
989
		}
990
	}
991

992
	private doCloseInactiveEditor(editor: EditorInput) {
993

B
Benjamin Pasero 已提交
994
		// Update model
995
		this._group.closeEditor(editor);
996 997
	}

998
	private handleDirty(editors: EditorInput[]): TPromise<boolean /* veto */> {
999 1000 1001 1002
		if (!editors.length) {
			return TPromise.as(false); // no veto
		}

1003
		return this.doHandleDirty(editors.shift()).then(veto => {
1004 1005 1006 1007
			if (veto) {
				return veto;
			}

1008
			return this.handleDirty(editors);
1009 1010 1011
		});
	}

1012 1013 1014 1015 1016 1017 1018
	private doHandleDirty(editor: EditorInput): TPromise<boolean /* veto */> {
		if (
			!editor.isDirty() || // editor must be dirty
			this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) ||  // editor is opened in other group
			editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened
		) {
			return TPromise.as(false);
1019 1020
		}

1021 1022
		// Switch to editor that we want to handle and confirm to save/revert
		return this.openEditor(editor).then(() => editor.confirmSave().then(res => {
1023

1024 1025 1026 1027 1028 1029 1030
			// It could be that the editor saved meanwhile, so we check again
			// to see if anything needs to happen before closing for good.
			// This can happen for example if autoSave: onFocusChange is configured
			// so that the save happens when the dialog opens.
			if (!editor.isDirty()) {
				return res === ConfirmResult.CANCEL ? true : false;
			}
1031

1032 1033 1034 1035
			// Otherwise, handle accordingly
			switch (res) {
				case ConfirmResult.SAVE:
					return editor.save().then(ok => !ok);
1036

1037
				case ConfirmResult.DONT_SAVE:
1038

1039 1040
					// first try a normal revert where the contents of the editor are restored
					return editor.revert().then(ok => !ok, error => {
1041

1042 1043 1044 1045 1046 1047
						// if that fails, since we are about to close the editor, we accept that
						// the editor cannot be reverted and instead do a soft revert that just
						// enables us to close the editor. With this, a user can always close a
						// dirty editor even when reverting fails.
						return editor.revert({ soft: true }).then(ok => !ok);
					});
1048

1049 1050 1051 1052
				case ConfirmResult.CANCEL:
					return true; // veto
			}
		}));
1053 1054 1055 1056
	}

	//#endregion

1057 1058
	//#region closeEditors()

1059
	closeEditors(args: EditorInput[] | ICloseEditorsFilter): TPromise<void> {
1060 1061 1062 1063 1064 1065 1066
		if (this.isEmpty()) {
			return TPromise.as(void 0);
		}

		const editors = this.getEditorsToClose(args);

		// Check for dirty and veto
1067
		return this.handleDirty(editors.slice(0)).then(veto => {
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
			if (veto) {
				return;
			}

			// Do close
			this.doCloseEditors(editors);
		});
	}

	private getEditorsToClose(editors: EditorInput[] | ICloseEditorsFilter): EditorInput[] {
		if (Array.isArray(editors)) {
			return editors;
		}

		const filter = editors;
		const hasDirection = typeof filter.direction === 'number';

		let editorsToClose = this._group.getEditors(!hasDirection /* in MRU order only if direction is not specified */);

		// Filter: saved only
		if (filter.savedOnly) {
			editorsToClose = editorsToClose.filter(e => !e.isDirty());
		}

		// Filter: direction (left / right)
		else if (hasDirection) {
B
Benjamin Pasero 已提交
1094
			editorsToClose = (filter.direction === CloseDirection.LEFT) ?
1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
				editorsToClose.slice(0, this._group.indexOf(filter.except as EditorInput)) :
				editorsToClose.slice(this._group.indexOf(filter.except as EditorInput) + 1);
		}

		// Filter: except
		else if (filter.except) {
			editorsToClose = editorsToClose.filter(e => !e.matches(filter.except));
		}

		return editorsToClose;
	}

	private doCloseEditors(editors: EditorInput[]): void {
		const activeEditor = this.activeEditor;

		// Close all inactive editors first
		let closeActiveEditor = false;
		editors.forEach(editor => {
			if (editor !== activeEditor) {
				this.doCloseInactiveEditor(editor);
			} else {
				closeActiveEditor = true;
			}
		});

		// Close active editor last if contained in editors list to close
		if (closeActiveEditor) {
			this.doCloseActiveEditor();
		}

		// Forward to title control
		this.titleAreaControl.closeEditors(editors);
	}

	//#endregion

	//#region closeAllEditors()

1133
	closeAllEditors(): TPromise<void> {
1134
		if (this.isEmpty()) {
1135 1136 1137 1138 1139 1140 1141 1142

			// If the group is empty and the request is to close all editors, we still close
			// the editor group is the related setting to close empty groups is enabled for
			// a convinient way of removing empty editor groups for the user.
			if (this.accessor.partOptions.closeEmptyGroups) {
				this.accessor.removeGroup(this);
			}

1143 1144 1145 1146 1147
			return TPromise.as(void 0);
		}

		// Check for dirty and veto
		const editors = this._group.getEditors(true);
1148
		return this.handleDirty(editors.slice(0)).then(veto => {
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176
			if (veto) {
				return;
			}

			// Do close
			this.doCloseAllEditors();
		});
	}

	private doCloseAllEditors(): void {
		const activeEditor = this.activeEditor;

		// Close all inactive editors first
		this.editors.forEach(editor => {
			if (editor !== activeEditor) {
				this.doCloseInactiveEditor(editor);
			}
		});

		// Close active editor last
		this.doCloseActiveEditor();

		// Forward to title control
		this.titleAreaControl.closeAllEditors();
	}

	//#endregion

1177 1178
	//#region replaceEditors()

1179
	replaceEditors(editors: EditorReplacement[]): TPromise<void> {
1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243

		// Extract active vs. inactive replacements
		let activeReplacement: EditorReplacement;
		const inactiveReplacements: EditorReplacement[] = [];
		editors.forEach(({ editor, replacement, options }) => {
			if (editor.isDirty()) {
				return; // we do not handle dirty in this method, so ignore all dirty
			}

			const index = this.getIndexOfEditor(editor);
			if (index >= 0) {
				const isActiveEditor = this.isActive(editor);

				// make sure we respect the index of the editor to replace
				if (options) {
					options.index = index;
				} else {
					options = EditorOptions.create({ index });
				}

				options.inactive = !isActiveEditor;
				options.pinned = true;

				const editorToReplace = { editor, replacement, options };
				if (isActiveEditor) {
					activeReplacement = editorToReplace;
				} else {
					inactiveReplacements.push(editorToReplace);
				}
			}
		});

		// Handle inactive first
		inactiveReplacements.forEach(({ editor, replacement, options }) => {

			// Open inactive editor
			this.doOpenEditor(replacement, options);

			// Close replaced inactive edior
			this.doCloseInactiveEditor(editor);

			// Forward to title control
			this.titleAreaControl.closeEditor(editor);
		});

		// Handle active last
		if (activeReplacement) {

			// Open replacement as active editor
			return this.doOpenEditor(activeReplacement.replacement, activeReplacement.options).then(() => {

				// Close previous active editor
				this.doCloseInactiveEditor(activeReplacement.editor);

				// Forward to title control
				this.titleAreaControl.closeEditor(activeReplacement.editor);
			});
		}

		return TPromise.as(void 0);
	}

	//#endregion

1244 1245 1246
	//#endregion

	//#region Themable
B
Benjamin Pasero 已提交
1247

1248
	protected updateStyles(): void {
1249 1250

		// Container
1251
		if (this.isEmpty()) {
1252
			this.element.style.backgroundColor = this.getColor(EDITOR_GROUP_EMPTY_BACKGROUND);
1253 1254 1255
		} else {
			this.element.style.backgroundColor = null;
		}
1256

1257 1258
		// Title control
		const { showTabs } = this.accessor.partOptions;
1259
		const borderColor = this.getColor(EDITOR_GROUP_HEADER_TABS_BORDER) || this.getColor(contrastBorder);
1260 1261 1262 1263
		this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND);
		this.titleContainer.style.borderBottomWidth = (borderColor && showTabs) ? '1px' : null;
		this.titleContainer.style.borderBottomStyle = (borderColor && showTabs) ? 'solid' : null;
		this.titleContainer.style.borderBottomColor = showTabs ? borderColor : null;
1264

1265 1266
		// Editor container
		this.editorContainer.style.backgroundColor = this.getColor(editorBackground);
1267 1268
	}

B
Benjamin Pasero 已提交
1269 1270
	//#endregion

1271
	//#region ISerializableView
B
Benjamin Pasero 已提交
1272

1273
	readonly element: HTMLElement = document.createElement('div');
1274

1275 1276 1277 1278
	readonly minimumWidth = EDITOR_MIN_DIMENSIONS.width;
	readonly minimumHeight = EDITOR_MIN_DIMENSIONS.height;
	readonly maximumWidth = EDITOR_MAX_DIMENSIONS.width;
	readonly maximumHeight = EDITOR_MAX_DIMENSIONS.height;
B
Benjamin Pasero 已提交
1279

B
Benjamin Pasero 已提交
1280
	get onDidChange() { return Event.None; } // only needed if minimum sizes ever change
B
Benjamin Pasero 已提交
1281

J
Joao Moreno 已提交
1282
	layout(width: number, height: number): void {
1283
		this.dimension = new Dimension(width, height);
1284

1285
		// Forward to controls
1286 1287
		this.titleAreaControl.layout(new Dimension(this.dimension.width, EDITOR_TITLE_HEIGHT));
		this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - EDITOR_TITLE_HEIGHT));
1288 1289
	}

1290 1291
	toJSON(): ISerializedEditorGroup {
		return this._group.serialize();
B
Benjamin Pasero 已提交
1292
	}
B
Benjamin Pasero 已提交
1293

1294 1295
	//#endregion

1296
	shutdown(): void {
1297
		this.editorControl.shutdown();
1298 1299
	}

1300
	dispose(): void {
1301 1302
		this._disposed = true;

1303 1304
		this._onWillDispose.fire();

1305
		this.titleAreaControl.dispose();
1306

1307 1308
		super.dispose();
	}
1309 1310
}

B
Benjamin Pasero 已提交
1311
class EditorOpeningEvent implements IEditorOpeningEvent {
1312
	private override: () => TPromise<any>;
B
Benjamin Pasero 已提交
1313 1314 1315 1316 1317 1318 1319 1320

	constructor(
		private _group: GroupIdentifier,
		private _editor: EditorInput,
		private _options: EditorOptions
	) {
	}

I
isidor 已提交
1321
	get groupId(): GroupIdentifier {
B
Benjamin Pasero 已提交
1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332
		return this._group;
	}

	get editor(): EditorInput {
		return this._editor;
	}

	get options(): EditorOptions {
		return this._options;
	}

1333
	prevent(callback: () => TPromise<any>): void {
B
Benjamin Pasero 已提交
1334 1335 1336
		this.override = callback;
	}

1337
	isPrevented(): () => TPromise<any> {
B
Benjamin Pasero 已提交
1338 1339 1340 1341
		return this.override;
	}
}

1342 1343 1344 1345 1346 1347
export interface EditorReplacement {
	editor: EditorInput;
	replacement: EditorInput;
	options?: EditorOptions;
}

1348 1349 1350 1351 1352
registerThemingParticipant((theme, collector, environment) => {

	// Letterpress
	const letterpress = `resources/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;
	collector.addRule(`
B
Benjamin Pasero 已提交
1353
		.monaco-workbench > .part.editor > .content .editor-group-container.empty .editor-group-letterpress {
1354 1355 1356
			background-image: url('${join(environment.appRoot, letterpress)}')
		}
	`);
B
Benjamin Pasero 已提交
1357

B
Benjamin Pasero 已提交
1358 1359 1360
	// Focused Empty Group Border
	const focusedEmptyGroupBorder = theme.getColor(EDITOR_GROUP_FOCUSED_EMPTY_BORDER);
	if (focusedEmptyGroupBorder) {
B
Benjamin Pasero 已提交
1361
		collector.addRule(`
B
Benjamin Pasero 已提交
1362
			.monaco-workbench > .part.editor > .content:not(.empty) .editor-group-container.empty.active:focus {
B
Benjamin Pasero 已提交
1363
				outline-width: 1px;
B
Benjamin Pasero 已提交
1364
				outline-color: ${focusedEmptyGroupBorder};
B
Benjamin Pasero 已提交
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379
				outline-offset: -2px;
				outline-style: solid;
			}

			.monaco-workbench > .part.editor > .content.empty .editor-group-container.empty.active:focus {
				outline: none; /* never show outline for empty group if it is the last */
			}
		`);
	} else {
		collector.addRule(`
			.monaco-workbench > .part.editor > .content .editor-group-container.empty.active:focus {
				outline: none; /* disable focus outline unless active empty group border is defined */
			}
		`);
	}
B
Benjamin Pasero 已提交
1380
});