editorGroup.ts 35.7 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';

M
Matt Bierner 已提交
8
import { Event, Emitter, once } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
9
import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorStacksModel, IEditorGroup, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, IStacksModelChangeEvent, SideBySideEditorInput, CloseDirection } from 'vs/workbench/common/editor';
10
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
11 12
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
13
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
J
Johannes Rieken 已提交
14
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
15
import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle';
16
import { Registry } from 'vs/platform/registry/common/platform';
B
Benjamin Pasero 已提交
17
import { Position } from 'vs/platform/editor/common/editor';
B
wip  
Benjamin Pasero 已提交
18
import { ResourceMap } from 'vs/base/common/map';
B
Benjamin Pasero 已提交
19

B
Benjamin Pasero 已提交
20 21 22 23 24 25 26 27 28
const EditorOpenPositioning = {
	LEFT: 'left',
	RIGHT: 'right',
	FIRST: 'first',
	LAST: 'last'
};

const OPEN_POSITIONING_CONFIG = 'workbench.editor.openPositioning';

B
Benjamin Pasero 已提交
29
export interface EditorCloseEvent extends IEditorCloseEvent {
30 31 32
	editor: EditorInput;
}

33
export interface EditorIdentifier extends IEditorIdentifier {
34
	group: GroupIdentifier;
35
	editor: EditorInput;
36 37
}

B
Benjamin Pasero 已提交
38 39 40
export interface IEditorOpenOptions {
	pinned?: boolean;
	active?: boolean;
B
Benjamin Pasero 已提交
41
	index?: number;
B
Benjamin Pasero 已提交
42 43
}

44
export interface ISerializedEditorInput {
45 46 47 48
	id: string;
	value: string;
}

49
export interface ISerializedEditorGroup {
50
	id: number;
51 52 53 54 55
	editors: ISerializedEditorInput[];
	mru: number[];
	preview: number;
}

56 57 58 59 60 61
export function isSerializedEditorGroup(obj?: any): obj is ISerializedEditorGroup {
	const group = obj as ISerializedEditorGroup;

	return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru);
}

62
export class EditorGroup extends Disposable implements IEditorGroup {
63 64 65

	private static IDS = 0;

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
	//#region events

	private readonly _onDidEditorActivate = this._register(new Emitter<EditorInput>());
	get onDidEditorActivate(): Event<EditorInput> { return this._onDidEditorActivate.event; }

	private readonly _onDidEditorOpen = this._register(new Emitter<EditorInput>());
	get onDidEditorOpen(): Event<EditorInput> { return this._onDidEditorOpen.event; }

	private readonly _onDidEditorClose = this._register(new Emitter<EditorCloseEvent>());
	get onDidEditorClose(): Event<EditorCloseEvent> { return this._onDidEditorClose.event; }

	private readonly _onDidEditorDispose = this._register(new Emitter<EditorInput>());
	get onDidEditorDispose(): Event<EditorInput> { return this._onDidEditorDispose.event; }

	private readonly _onDidEditorBecomeDirty = this._register(new Emitter<EditorInput>());
	get onDidEditorBecomeDirty(): Event<EditorInput> { return this._onDidEditorBecomeDirty.event; }

	private readonly _onDidEditorLabelChange = this._register(new Emitter<EditorInput>());
	get onDidEditorLabelChange(): Event<EditorInput> { return this._onDidEditorLabelChange.event; }

	private readonly _onDidEditorMove = this._register(new Emitter<EditorInput>());
	get onDidEditorMove(): Event<EditorInput> { return this._onDidEditorMove.event; }

	private readonly _onDidEditorPin = this._register(new Emitter<EditorInput>());
	get onDidEditorPin(): Event<EditorInput> { return this._onDidEditorPin.event; }

	private readonly _onDidEditorUnpin = this._register(new Emitter<EditorInput>());
	get onDidEditorUnpin(): Event<EditorInput> { return this._onDidEditorUnpin.event; }

	//#endregion
B
Benjamin Pasero 已提交
96

97 98
	private _id: GroupIdentifier;

99 100 101
	private editors: EditorInput[] = [];
	private mru: EditorInput[] = [];
	private mapResourceToEditorCount: ResourceMap<number> = new ResourceMap<number>();
102 103 104 105 106 107

	private preview: EditorInput; // editor in preview state
	private active: EditorInput;  // editor in active state

	private editorOpenPositioning: 'left' | 'right' | 'first' | 'last';

108
	constructor(
B
Benjamin Pasero 已提交
109
		labelOrSerializedGroup: ISerializedEditorGroup,
110 111
		@IInstantiationService private instantiationService: IInstantiationService,
		@IConfigurationService private configurationService: IConfigurationService
112
	) {
113 114
		super();

115 116
		if (isSerializedEditorGroup(labelOrSerializedGroup)) {
			this.deserialize(labelOrSerializedGroup);
117
		} else {
118
			this._id = EditorGroup.IDS++;
119
		}
120

121
		this.onConfigurationUpdated();
122 123 124 125
		this.registerListeners();
	}

	private registerListeners(): void {
126
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
127 128
	}

129 130
	private onConfigurationUpdated(event?: IConfigurationChangeEvent): void {
		this.editorOpenPositioning = this.configurationService.getValue(OPEN_POSITIONING_CONFIG);
B
Benjamin Pasero 已提交
131 132
	}

133
	get id(): GroupIdentifier {
134 135 136
		return this._id;
	}

137
	get count(): number {
138 139 140
		return this.editors.length;
	}

141
	getEditors(mru?: boolean): EditorInput[] {
B
Benjamin Pasero 已提交
142
		return mru ? this.mru.slice(0) : this.editors.slice(0);
B
Benjamin Pasero 已提交
143 144
	}

145 146 147
	getEditor(index: number): EditorInput;
	getEditor(resource: URI): EditorInput;
	getEditor(arg1: any): EditorInput {
B
Benjamin Pasero 已提交
148 149 150 151 152 153 154 155 156 157 158
		if (typeof arg1 === 'number') {
			return this.editors[arg1];
		}

		const resource: URI = arg1;
		if (!this.contains(resource)) {
			return null; // fast check for resource opened or not
		}

		for (let i = 0; i < this.editors.length; i++) {
			const editor = this.editors[i];
159
			const editorResource = toResource(editor, { supportSideBySide: true });
B
Benjamin Pasero 已提交
160
			if (editorResource && editorResource.toString() === resource.toString()) {
B
Benjamin Pasero 已提交
161
				return editor;
B
Benjamin Pasero 已提交
162 163 164 165
			}
		}

		return null;
166 167
	}

168
	get activeEditor(): EditorInput {
B
Benjamin Pasero 已提交
169 170 171
		return this.active;
	}

172
	isActive(editor: EditorInput): boolean {
B
Benjamin Pasero 已提交
173
		return this.matches(this.active, editor);
B
Benjamin Pasero 已提交
174 175
	}

176
	get previewEditor(): EditorInput {
B
Benjamin Pasero 已提交
177 178 179
		return this.preview;
	}

180
	isPreview(editor: EditorInput): boolean {
B
Benjamin Pasero 已提交
181
		return this.matches(this.preview, editor);
B
Benjamin Pasero 已提交
182 183
	}

184
	openEditor(editor: EditorInput, options?: IEditorOpenOptions): void {
B
Benjamin Pasero 已提交
185 186 187
		const index = this.indexOf(editor);

		const makePinned = options && options.pinned;
B
Benjamin Pasero 已提交
188
		const makeActive = (options && options.active) || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
B
Benjamin Pasero 已提交
189 190 191

		// New editor
		if (index === -1) {
192 193
			let targetIndex: number;
			const indexOfActive = this.indexOf(this.active);
B
Benjamin Pasero 已提交
194

195 196 197 198
			// Insert into specific position
			if (options && typeof options.index === 'number') {
				targetIndex = options.index;
			}
B
Benjamin Pasero 已提交
199

200
			// Insert to the BEGINNING
201
			else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
202 203 204 205
				targetIndex = 0;
			}

			// Insert to the END
206
			else if (this.editorOpenPositioning === EditorOpenPositioning.LAST) {
207
				targetIndex = this.editors.length;
208
			}
B
Benjamin Pasero 已提交
209

210
			// Insert to the LEFT of active editor
211
			else if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
212 213 214
				if (indexOfActive === 0 || !this.editors.length) {
					targetIndex = 0; // to the left becoming first editor in list
				} else {
215
					targetIndex = indexOfActive; // to the left of active editor
B
Benjamin Pasero 已提交
216
				}
217
			}
B
Benjamin Pasero 已提交
218

219 220 221 222 223
			// Insert to the RIGHT of active editor
			else {
				targetIndex = indexOfActive + 1;
			}

224 225 226
			// Insert into our list of editors if pinned or we have no preview editor
			if (makePinned || !this.preview) {
				this.splice(targetIndex, false, editor);
B
Benjamin Pasero 已提交
227 228
			}

229 230
			// Handle preview
			if (!makePinned) {
B
Benjamin Pasero 已提交
231 232

				// Replace existing preview with this editor if we have a preview
233 234
				if (this.preview) {
					const indexOfPreview = this.indexOf(this.preview);
235 236
					if (targetIndex > indexOfPreview) {
						targetIndex--; // accomodate for the fact that the preview editor closes
237 238
					}

239
					this.replaceEditor(this.preview, editor, targetIndex, !makeActive);
240
				}
B
Benjamin Pasero 已提交
241

B
Benjamin Pasero 已提交
242 243 244
				this.preview = editor;
			}

245
			// Listeners
B
Benjamin Pasero 已提交
246
			this.registerEditorListeners(editor);
247

B
Benjamin Pasero 已提交
248
			// Event
249
			this._onDidEditorOpen.fire(editor);
B
Benjamin Pasero 已提交
250

251
			// Handle active
B
Benjamin Pasero 已提交
252
			if (makeActive) {
B
Benjamin Pasero 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
				this.setActive(editor);
			}
		}

		// Existing editor
		else {

			// Pin it
			if (makePinned) {
				this.pin(editor);
			}

			// Activate it
			if (makeActive) {
				this.setActive(editor);
			}
B
Benjamin Pasero 已提交
269 270 271 272 273

			// Respect index
			if (options && typeof options.index === 'number') {
				this.moveEditor(editor, options.index);
			}
B
Benjamin Pasero 已提交
274 275 276
		}
	}

B
Benjamin Pasero 已提交
277
	private registerEditorListeners(editor: EditorInput): void {
278
		const unbind: IDisposable[] = [];
279 280

		// Re-emit disposal of editor input as our own event
281 282
		const onceDispose = once(editor.onDispose);
		unbind.push(onceDispose(() => {
283
			if (this.indexOf(editor) >= 0) {
284
				this._onDidEditorDispose.fire(editor);
285
			}
286 287 288 289
		}));

		// Re-Emit dirty state changes
		unbind.push(editor.onDidChangeDirty(() => {
290
			this._onDidEditorBecomeDirty.fire(editor);
291
		}));
292

B
Benjamin Pasero 已提交
293 294
		// Re-Emit label changes
		unbind.push(editor.onDidChangeLabel(() => {
295
			this._onDidEditorLabelChange.fire(editor);
B
Benjamin Pasero 已提交
296 297
		}));

298
		// Clean up dispose listeners once the editor gets closed
299
		unbind.push(this.onDidEditorClose(event => {
300
			if (event.editor.matches(editor)) {
301
				dispose(unbind);
302
			}
303
		}));
304 305
	}

306 307
	private replaceEditor(toReplace: EditorInput, replaceWidth: EditorInput, replaceIndex: number, openNext = true): void {
		const event = this.doCloseEditor(toReplace, openNext, true); // optimization to prevent multiple setActive() in one call
308 309 310 311 312 313 314

		// We want to first add the new editor into our model before emitting the close event because
		// firing the close event can trigger a dispose on the same editor that is now being added.
		// This can lead into opening a disposed editor which is not what we want.
		this.splice(replaceIndex, false, replaceWidth);

		if (event) {
315
			this._onDidEditorClose.fire(event);
316 317 318
		}
	}

319
	closeEditor(editor: EditorInput, openNext = true): number {
320
		const event = this.doCloseEditor(editor, openNext, false);
321 322

		if (event) {
323
			this._onDidEditorClose.fire(event);
324 325

			return event.index;
326
		}
327 328

		return void 0;
329 330
	}

331
	private doCloseEditor(editor: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent {
B
Benjamin Pasero 已提交
332 333
		const index = this.indexOf(editor);
		if (index === -1) {
334
			return null; // not found
B
Benjamin Pasero 已提交
335 336 337
		}

		// Active Editor closed
B
Benjamin Pasero 已提交
338
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
339 340

			// More than one editor
B
Benjamin Pasero 已提交
341 342
			if (this.mru.length > 1) {
				this.setActive(this.mru[1]); // active editor is always first in MRU, so pick second editor after as new active
B
Benjamin Pasero 已提交
343 344 345 346 347 348 349 350 351
			}

			// One Editor
			else {
				this.active = null;
			}
		}

		// Preview Editor closed
B
Benjamin Pasero 已提交
352
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
353 354 355
			this.preview = null;
		}

B
Benjamin Pasero 已提交
356
		// Remove from arrays
B
Benjamin Pasero 已提交
357
		this.splice(index, true);
B
Benjamin Pasero 已提交
358 359

		// Event
360
		return { editor, replaced, index, group: this.id };
B
Benjamin Pasero 已提交
361 362
	}

B
Benjamin Pasero 已提交
363
	closeEditors(except: EditorInput, direction?: CloseDirection): void {
B
Benjamin Pasero 已提交
364 365 366 367 368 369
		const index = this.indexOf(except);
		if (index === -1) {
			return; // not found
		}

		// Close to the left
B
Benjamin Pasero 已提交
370
		if (direction === CloseDirection.LEFT) {
B
Benjamin Pasero 已提交
371 372 373 374 375 376
			for (let i = index - 1; i >= 0; i--) {
				this.closeEditor(this.editors[i]);
			}
		}

		// Close to the right
B
Benjamin Pasero 已提交
377
		else if (direction === CloseDirection.RIGHT) {
B
Benjamin Pasero 已提交
378 379 380 381 382 383 384
			for (let i = this.editors.length - 1; i > index; i--) {
				this.closeEditor(this.editors[i]);
			}
		}

		// Both directions
		else {
B
Benjamin Pasero 已提交
385
			this.mru.filter(e => !this.matches(e, except)).forEach(e => this.closeEditor(e));
B
Benjamin Pasero 已提交
386 387 388
		}
	}

389
	closeAllEditors(): void {
390 391

		// Optimize: close all non active editors first to produce less upstream work
B
Benjamin Pasero 已提交
392
		this.mru.filter(e => !this.matches(e, this.active)).forEach(e => this.closeEditor(e));
393 394 395
		this.closeEditor(this.active);
	}

396
	moveEditor(editor: EditorInput, toIndex: number): void {
B
Benjamin Pasero 已提交
397 398 399 400 401 402 403 404 405 406
		const index = this.indexOf(editor);
		if (index < 0) {
			return;
		}

		// Move
		this.editors.splice(index, 1);
		this.editors.splice(toIndex, 0, editor);

		// Event
407
		this._onDidEditorMove.fire(editor);
B
Benjamin Pasero 已提交
408 409
	}

410
	setActive(editor: EditorInput): void {
B
Benjamin Pasero 已提交
411 412 413 414 415
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

B
Benjamin Pasero 已提交
416
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
417 418 419 420 421
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
422
		// Bring to front in MRU list
B
Benjamin Pasero 已提交
423 424
		this.setMostRecentlyUsed(editor);

B
Benjamin Pasero 已提交
425
		// Event
426
		this._onDidEditorActivate.fire(editor);
B
Benjamin Pasero 已提交
427 428
	}

429
	pin(editor: EditorInput): void {
430 431 432 433 434
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

B
Benjamin Pasero 已提交
435 436
		if (!this.isPreview(editor)) {
			return; // can only pin a preview editor
B
Benjamin Pasero 已提交
437 438 439 440 441 442
		}

		// Convert the preview editor to be a pinned editor
		this.preview = null;

		// Event
443
		this._onDidEditorPin.fire(editor);
B
Benjamin Pasero 已提交
444 445
	}

446
	unpin(editor: EditorInput): void {
447 448 449 450
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}
451

B
Benjamin Pasero 已提交
452 453 454 455 456 457 458 459 460
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

		// Set new
		const oldPreview = this.preview;
		this.preview = editor;

		// Event
461
		this._onDidEditorUnpin.fire(editor);
B
Benjamin Pasero 已提交
462 463 464 465 466

		// Close old preview editor if any
		this.closeEditor(oldPreview);
	}

467 468 469
	isPinned(editor: EditorInput): boolean;
	isPinned(index: number): boolean;
	isPinned(arg1: EditorInput | number): boolean {
470 471 472 473 474 475 476 477 478 479 480
		let editor: EditorInput;
		let index: number;
		if (typeof arg1 === 'number') {
			editor = this.editors[arg1];
			index = arg1;
		} else {
			editor = arg1;
			index = this.indexOf(editor);
		}

		if (index === -1 || !editor) {
B
Benjamin Pasero 已提交
481 482 483 484 485 486 487
			return false; // editor not found
		}

		if (!this.preview) {
			return true; // no preview editor
		}

B
Benjamin Pasero 已提交
488
		return !this.matches(this.preview, editor);
B
Benjamin Pasero 已提交
489 490
	}

B
Benjamin Pasero 已提交
491
	private splice(index: number, del: boolean, editor?: EditorInput): void {
492
		const editorToDeleteOrReplace = this.editors[index];
B
Benjamin Pasero 已提交
493

B
Benjamin Pasero 已提交
494
		const args: any[] = [index, del ? 1 : 0];
495 496 497
		if (editor) {
			args.push(editor);
		}
B
Benjamin Pasero 已提交
498

499
		// Perform on editors array
500
		this.editors.splice.apply(this.editors, args);
B
Benjamin Pasero 已提交
501

502
		// Add
B
Benjamin Pasero 已提交
503
		if (!del && editor) {
504 505
			this.mru.push(editor); // make it LRU editor
			this.updateResourceMap(editor, false /* add */); // add new to resource map
B
Benjamin Pasero 已提交
506 507
		}

B
Benjamin Pasero 已提交
508
		// Remove / Replace
B
Benjamin Pasero 已提交
509
		else {
B
Benjamin Pasero 已提交
510 511
			const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);

512
			// Remove
B
Benjamin Pasero 已提交
513
			if (del && !editor) {
514 515
				this.mru.splice(indexInMRU, 1); // remove from MRU
				this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove from resource map
B
Benjamin Pasero 已提交
516 517
			}

518
			// Replace
B
Benjamin Pasero 已提交
519
			else {
520 521 522
				this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
				this.updateResourceMap(editor, false /* add */); // add new to resource map
				this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove replaced from resource map
B
Benjamin Pasero 已提交
523
			}
B
Benjamin Pasero 已提交
524 525 526
		}
	}

527
	private updateResourceMap(editor: EditorInput, remove: boolean): void {
528
		const resource = toResource(editor, { supportSideBySide: true });
529
		if (resource) {
530 531 532

			// It is possible to have the same resource opened twice (once as normal input and once as diff input)
			// So we need to do ref counting on the resource to provide the correct picture
B
wip  
Benjamin Pasero 已提交
533
			let counter = this.mapResourceToEditorCount.get(resource) || 0;
B
Benjamin Pasero 已提交
534
			let newCounter: number;
535 536 537 538 539 540 541 542
			if (remove) {
				if (counter > 1) {
					newCounter = counter - 1;
				}
			} else {
				newCounter = counter + 1;
			}

B
wip  
Benjamin Pasero 已提交
543
			this.mapResourceToEditorCount.set(resource, newCounter);
544 545 546
		}
	}

547
	indexOf(candidate: EditorInput, editors = this.editors): number {
B
Benjamin Pasero 已提交
548 549
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
550 551
		}

B
Benjamin Pasero 已提交
552
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
553
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
554 555 556 557 558 559
				return i;
			}
		}

		return -1;
	}
B
Benjamin Pasero 已提交
560

561 562 563
	contains(editorOrResource: EditorInput | URI): boolean;
	contains(editor: EditorInput, supportSideBySide?: boolean): boolean;
	contains(editorOrResource: EditorInput | URI, supportSideBySide?: boolean): boolean {
564
		if (editorOrResource instanceof EditorInput) {
565 566 567 568 569 570 571 572 573 574 575 576 577
			const index = this.indexOf(editorOrResource);
			if (index >= 0) {
				return true;
			}

			if (supportSideBySide && editorOrResource instanceof SideBySideEditorInput) {
				const index = this.indexOf(editorOrResource.master);
				if (index >= 0) {
					return true;
				}
			}

			return false;
578 579
		}

580
		const counter = this.mapResourceToEditorCount.get(editorOrResource);
581 582

		return typeof counter === 'number' && counter > 0;
583 584
	}

B
Benjamin Pasero 已提交
585 586 587 588 589 590 591 592 593
	private setMostRecentlyUsed(editor: EditorInput): void {
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // editor not found
		}

		const mruIndex = this.indexOf(editor, this.mru);

		// Remove old index
B
Benjamin Pasero 已提交
594
		this.mru.splice(mruIndex, 1);
B
Benjamin Pasero 已提交
595

B
Benjamin Pasero 已提交
596
		// Set editor to front
B
Benjamin Pasero 已提交
597 598
		this.mru.unshift(editor);
	}
B
Benjamin Pasero 已提交
599 600 601 602

	private matches(editorA: EditorInput, editorB: EditorInput): boolean {
		return !!editorA && !!editorB && editorA.matches(editorB);
	}
603

604
	clone(): EditorGroup {
B
Benjamin Pasero 已提交
605
		const group = this.instantiationService.createInstance(EditorGroup, void 0);
606 607 608 609 610 611 612 613 614 615 616
		group.editors = this.editors.slice(0);
		group.mru = this.mru.slice(0);
		group.mapResourceToEditorCount = this.mapResourceToEditorCount.clone();
		group.preview = this.preview;
		group.active = this.active;
		group.editorOpenPositioning = this.editorOpenPositioning;

		return group;
	}

	serialize(): ISerializedEditorGroup {
617
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
618 619 620 621 622 623

		// Serialize all editor inputs so that we can store them.
		// Editors that cannot be serialized need to be ignored
		// from mru, active and preview if any.
		let serializableEditors: EditorInput[] = [];
		let serializedEditors: ISerializedEditorInput[] = [];
B
Benjamin Pasero 已提交
624
		let serializablePreviewIndex: number;
625
		this.editors.forEach(e => {
626
			let factory = registry.getEditorInputFactory(e.getTypeId());
627 628 629
			if (factory) {
				let value = factory.serialize(e);
				if (typeof value === 'string') {
630
					serializedEditors.push({ id: e.getTypeId(), value });
631
					serializableEditors.push(e);
B
Benjamin Pasero 已提交
632 633 634 635

					if (this.preview === e) {
						serializablePreviewIndex = serializableEditors.length - 1;
					}
636 637 638 639
				}
			}
		});

B
Benjamin Pasero 已提交
640
		const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0);
641 642

		return {
643
			id: this.id,
644 645
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
646
			preview: serializablePreviewIndex,
647 648 649 650
		};
	}

	private deserialize(data: ISerializedEditorGroup): void {
651
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
652

653 654 655 656 657 658 659 660
		if (typeof data.id === 'number') {
			this._id = data.id;

			EditorGroup.IDS = Math.max(data.id + 1, EditorGroup.IDS); // make sure our ID generator is always larger
		} else {
			this._id = EditorGroup.IDS++; // backwards compatibility
		}

661
		this.editors = data.editors.map(e => {
B
Benjamin Pasero 已提交
662 663 664
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
				const editor = factory.deserialize(this.instantiationService, e.value);
665

B
Benjamin Pasero 已提交
666
				this.registerEditorListeners(editor);
B
Benjamin Pasero 已提交
667
				this.updateResourceMap(editor, false /* add */);
668

B
Benjamin Pasero 已提交
669 670 671 672 673
				return editor;
			}

			return null;
		}).filter(e => !!e);
674 675 676 677 678 679
		this.mru = data.mru.map(i => this.editors[i]);
		this.active = this.mru[0];
		this.preview = this.editors[data.preview];
	}
}

680 681
//#region legacy stacks model

682 683 684
interface ISerializedEditorStacksModel {
	groups: ISerializedEditorGroup[];
	active: number;
B
Benjamin Pasero 已提交
685 686 687
}

export class EditorStacksModel implements IEditorStacksModel {
688

689
	private static readonly STORAGE_KEY = 'editorStacks.model';
690 691

	private toDispose: IDisposable[];
B
Benjamin Pasero 已提交
692 693
	private loaded: boolean;

B
Benjamin Pasero 已提交
694
	private _groups: EditorGroup[];
695
	private _activeGroup: EditorGroup;
696
	private groupToIdentifier: { [id: number]: EditorGroup };
B
Benjamin Pasero 已提交
697

M
Matt Bierner 已提交
698 699 700 701 702 703
	private readonly _onGroupOpened: Emitter<EditorGroup>;
	private readonly _onGroupClosed: Emitter<EditorGroup>;
	private readonly _onGroupMoved: Emitter<EditorGroup>;
	private readonly _onGroupActivated: Emitter<EditorGroup>;
	private readonly _onGroupDeactivated: Emitter<EditorGroup>;
	private readonly _onGroupRenamed: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
704

M
Matt Bierner 已提交
705 706 707 708
	private readonly _onEditorDisposed: Emitter<EditorIdentifier>;
	private readonly _onEditorDirty: Emitter<EditorIdentifier>;
	private readonly _onEditorLabelChange: Emitter<EditorIdentifier>;
	private readonly _onEditorOpened: Emitter<EditorIdentifier>;
B
Benjamin Pasero 已提交
709

M
Matt Bierner 已提交
710 711
	private readonly _onWillCloseEditor: Emitter<EditorCloseEvent>;
	private readonly _onEditorClosed: Emitter<EditorCloseEvent>;
B
Benjamin Pasero 已提交
712

M
Matt Bierner 已提交
713
	private readonly _onModelChanged: Emitter<IStacksModelChangeEvent>;
B
Benjamin Pasero 已提交
714

715
	constructor(
716
		private restoreFromStorage: boolean,
717 718
		@IStorageService private storageService: IStorageService,
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
719
		@IInstantiationService private instantiationService: IInstantiationService
720 721 722
	) {
		this.toDispose = [];

B
Benjamin Pasero 已提交
723
		this._groups = [];
724
		this.groupToIdentifier = Object.create(null);
B
Benjamin Pasero 已提交
725

B
Benjamin Pasero 已提交
726 727 728
		this._onGroupOpened = new Emitter<EditorGroup>();
		this._onGroupClosed = new Emitter<EditorGroup>();
		this._onGroupActivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
729
		this._onGroupDeactivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
730
		this._onGroupMoved = new Emitter<EditorGroup>();
731
		this._onGroupRenamed = new Emitter<EditorGroup>();
732
		this._onModelChanged = new Emitter<IStacksModelChangeEvent>();
733 734
		this._onEditorDisposed = new Emitter<EditorIdentifier>();
		this._onEditorDirty = new Emitter<EditorIdentifier>();
B
Benjamin Pasero 已提交
735
		this._onEditorLabelChange = new Emitter<EditorIdentifier>();
736
		this._onEditorOpened = new Emitter<EditorIdentifier>();
B
Benjamin Pasero 已提交
737 738
		this._onWillCloseEditor = new Emitter<EditorCloseEvent>();
		this._onEditorClosed = new Emitter<EditorCloseEvent>();
739

B
Benjamin Pasero 已提交
740
		this.toDispose.push(this._onGroupOpened, this._onGroupClosed, this._onGroupActivated, this._onGroupDeactivated, this._onGroupMoved, this._onGroupRenamed, this._onModelChanged, this._onEditorDisposed, this._onEditorDirty, this._onEditorLabelChange, this._onEditorOpened, this._onEditorClosed, this._onWillCloseEditor);
B
Benjamin Pasero 已提交
741

742 743 744 745
		this.registerListeners();
	}

	private registerListeners(): void {
746
		this.toDispose.push(this.lifecycleService.onShutdown(reason => this.onShutdown()));
B
Benjamin Pasero 已提交
747 748
	}

749
	get onGroupOpened(): Event<EditorGroup> {
B
Benjamin Pasero 已提交
750 751 752
		return this._onGroupOpened.event;
	}

753
	get onGroupClosed(): Event<EditorGroup> {
B
Benjamin Pasero 已提交
754 755 756
		return this._onGroupClosed.event;
	}

757
	get onGroupActivated(): Event<EditorGroup> {
B
Benjamin Pasero 已提交
758 759 760
		return this._onGroupActivated.event;
	}

761
	get onGroupDeactivated(): Event<EditorGroup> {
B
Benjamin Pasero 已提交
762 763 764
		return this._onGroupDeactivated.event;
	}

765
	get onGroupMoved(): Event<EditorGroup> {
B
Benjamin Pasero 已提交
766 767 768
		return this._onGroupMoved.event;
	}

769
	get onGroupRenamed(): Event<EditorGroup> {
770 771 772
		return this._onGroupRenamed.event;
	}

773
	get onModelChanged(): Event<IStacksModelChangeEvent> {
774 775 776
		return this._onModelChanged.event;
	}

777
	get onEditorDisposed(): Event<EditorIdentifier> {
778 779 780
		return this._onEditorDisposed.event;
	}

781
	get onEditorDirty(): Event<EditorIdentifier> {
782 783 784
		return this._onEditorDirty.event;
	}

785
	get onEditorLabelChange(): Event<EditorIdentifier> {
B
Benjamin Pasero 已提交
786 787 788
		return this._onEditorLabelChange.event;
	}

789
	get onEditorOpened(): Event<EditorIdentifier> {
790 791 792
		return this._onEditorOpened.event;
	}

793
	get onWillCloseEditor(): Event<EditorCloseEvent> {
794 795 796
		return this._onWillCloseEditor.event;
	}

797
	get onEditorClosed(): Event<EditorCloseEvent> {
798 799 800
		return this._onEditorClosed.event;
	}

801
	get groups(): EditorGroup[] {
B
Benjamin Pasero 已提交
802 803
		this.ensureLoaded();

B
Benjamin Pasero 已提交
804 805 806
		return this._groups.slice(0);
	}

807
	get activeGroup(): EditorGroup {
B
Benjamin Pasero 已提交
808 809
		this.ensureLoaded();

810
		return this._activeGroup;
B
Benjamin Pasero 已提交
811 812
	}

813
	isActive(group: EditorGroup): boolean {
B
Benjamin Pasero 已提交
814 815 816
		return this.activeGroup === group;
	}

817
	getGroup(id: GroupIdentifier): EditorGroup {
B
Benjamin Pasero 已提交
818 819
		this.ensureLoaded();

820 821 822
		return this.groupToIdentifier[id];
	}

823
	openGroup(label: string, activate = true, index?: number): EditorGroup {
B
Benjamin Pasero 已提交
824 825
		this.ensureLoaded();

826
		const group = this.doCreateGroup(label);
B
Benjamin Pasero 已提交
827

828 829 830 831 832
		// Direct index provided
		if (typeof index === 'number') {
			this._groups[index] = group;
		}

B
Benjamin Pasero 已提交
833
		// First group
834
		else if (!this._activeGroup) {
B
Benjamin Pasero 已提交
835 836 837
			this._groups.push(group);
		}

838
		// Subsequent group (open to the right of active one)
B
Benjamin Pasero 已提交
839
		else {
840
			this._groups.splice(this.indexOf(this._activeGroup) + 1, 0, group);
B
Benjamin Pasero 已提交
841 842 843
		}

		// Event
844
		this.fireEvent(this._onGroupOpened, group, true);
B
Benjamin Pasero 已提交
845

846
		// Activate if we are first or set to activate groups
847
		if (!this._activeGroup || activate) {
848 849
			this.setActive(group);
		}
B
Benjamin Pasero 已提交
850 851 852 853

		return group;
	}

854
	renameGroup(group: EditorGroup, label: string): void {
B
Benjamin Pasero 已提交
855 856
		this.ensureLoaded();

B
Benjamin Pasero 已提交
857
		this.fireEvent(this._onGroupRenamed, group, false);
B
Benjamin Pasero 已提交
858
	}
859

860
	closeGroup(group: EditorGroup): void {
B
Benjamin Pasero 已提交
861 862
		this.ensureLoaded();

B
Benjamin Pasero 已提交
863 864 865 866 867 868
		const index = this.indexOf(group);
		if (index < 0) {
			return; // group does not exist
		}

		// Active group closed: Find a new active one to the right
869
		if (group === this._activeGroup) {
B
Benjamin Pasero 已提交
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884

			// More than one group
			if (this._groups.length > 1) {
				let newActiveGroup: EditorGroup;
				if (this._groups.length > index + 1) {
					newActiveGroup = this._groups[index + 1]; // make next group to the right active
				} else {
					newActiveGroup = this._groups[index - 1]; // make next group to the left active
				}

				this.setActive(newActiveGroup);
			}

			// One group
			else {
885
				this._activeGroup = null;
B
Benjamin Pasero 已提交
886 887 888
			}
		}

B
Benjamin Pasero 已提交
889
		// Close Editors in Group first and dispose then
890
		group.closeAllEditors();
B
Benjamin Pasero 已提交
891
		group.dispose();
892

B
Benjamin Pasero 已提交
893 894
		// Splice from groups
		this._groups.splice(index, 1);
895
		this.groupToIdentifier[group.id] = void 0;
B
Benjamin Pasero 已提交
896

897
		// Events
898
		this.fireEvent(this._onGroupClosed, group, true);
899 900 901
		for (let i = index; i < this._groups.length; i++) {
			this.fireEvent(this._onGroupMoved, this._groups[i], true); // send move event for groups to the right that moved to the left into the closed group position
		}
B
Benjamin Pasero 已提交
902 903
	}

904
	closeGroups(except?: EditorGroup): void {
B
Benjamin Pasero 已提交
905
		this.ensureLoaded();
906 907

		// Optimize: close all non active groups first to produce less upstream work
908
		this.groups.filter(g => g !== this._activeGroup && g !== except).forEach(g => this.closeGroup(g));
B
Benjamin Pasero 已提交
909 910

		// Close active unless configured to skip
911 912
		if (this._activeGroup !== except) {
			this.closeGroup(this._activeGroup);
B
Benjamin Pasero 已提交
913
		}
914 915
	}

916
	setActive(group: EditorGroup): void {
B
Benjamin Pasero 已提交
917 918
		this.ensureLoaded();

919
		if (this._activeGroup === group) {
920 921 922
			return;
		}

B
Benjamin Pasero 已提交
923
		const oldActiveGroup = this._activeGroup;
924
		this._activeGroup = group;
B
Benjamin Pasero 已提交
925

926
		this.fireEvent(this._onGroupActivated, group, false);
B
Benjamin Pasero 已提交
927 928 929
		if (oldActiveGroup) {
			this.fireEvent(this._onGroupDeactivated, oldActiveGroup, false);
		}
B
Benjamin Pasero 已提交
930 931
	}

932
	moveGroup(group: EditorGroup, toIndex: number): void {
B
Benjamin Pasero 已提交
933 934
		this.ensureLoaded();

B
Benjamin Pasero 已提交
935 936 937 938 939 940 941 942 943 944
		const index = this.indexOf(group);
		if (index < 0) {
			return;
		}

		// Move
		this._groups.splice(index, 1);
		this._groups.splice(toIndex, 0, group);

		// Event
B
Benjamin Pasero 已提交
945 946 947
		for (let i = Math.min(index, toIndex); i <= Math.max(index, toIndex) && i < this._groups.length; i++) {
			this.fireEvent(this._onGroupMoved, this._groups[i], true); // send move event for groups to the right that moved to the left into the closed group position
		}
B
Benjamin Pasero 已提交
948 949
	}

B
Benjamin Pasero 已提交
950 951 952
	private indexOf(group: EditorGroup): number {
		return this._groups.indexOf(group);
	}
953

954
	findGroup(editor: EditorInput, activeOnly?: boolean): EditorGroup {
B
Benjamin Pasero 已提交
955 956
		const groupsToCheck = (this.activeGroup ? [this.activeGroup] : []).concat(this.groups.filter(g => g !== this.activeGroup));

957 958
		for (let i = 0; i < groupsToCheck.length; i++) {
			const group = groupsToCheck[i];
B
Benjamin Pasero 已提交
959 960
			const editorsToCheck = (group.activeEditor ? [group.activeEditor] : []).concat(group.getEditors().filter(e => e !== group.activeEditor));

961
			for (let j = 0; j < editorsToCheck.length; j++) {
B
Benjamin Pasero 已提交
962 963 964
				const editorToCheck = editorsToCheck[j];

				if ((!activeOnly || group.isActive(editorToCheck)) && editor.matches(editorToCheck)) {
965 966 967 968
					return group;
				}
			}
		}
B
Benjamin Pasero 已提交
969 970

		return void 0;
971 972
	}

973 974 975
	positionOfGroup(group: IEditorGroup): Position;
	positionOfGroup(group: EditorGroup): Position;
	positionOfGroup(group: EditorGroup): Position {
B
Benjamin Pasero 已提交
976 977
		return this.indexOf(group);
	}
B
Benjamin Pasero 已提交
978

979
	groupAt(position: Position): EditorGroup {
980 981
		this.ensureLoaded();

B
Benjamin Pasero 已提交
982 983
		return this._groups[position];
	}
B
Benjamin Pasero 已提交
984

985
	next(jumpGroups: boolean, cycleAtEnd = true): IEditorIdentifier {
986 987 988 989 990 991 992 993 994 995
		this.ensureLoaded();

		if (!this.activeGroup) {
			return null;
		}

		const index = this.activeGroup.indexOf(this.activeGroup.activeEditor);

		// Return next in group
		if (index + 1 < this.activeGroup.count) {
996
			return { group: this.activeGroup.id, editor: this.activeGroup.getEditor(index + 1) };
997 998
		}

999 1000
		// Return first if we are not jumping groups
		if (!jumpGroups) {
1001 1002 1003
			if (!cycleAtEnd) {
				return null;
			}
1004
			return { group: this.activeGroup.id, editor: this.activeGroup.getEditor(0) };
1005 1006
		}

1007 1008 1009 1010
		// Return first in next group
		const indexOfGroup = this.indexOf(this.activeGroup);
		const nextGroup = this.groups[indexOfGroup + 1];
		if (nextGroup) {
1011
			return { group: nextGroup.id, editor: nextGroup.getEditor(0) };
1012 1013
		}

1014 1015 1016 1017 1018
		// Return null if we are not cycling at the end
		if (!cycleAtEnd) {
			return null;
		}

1019 1020
		// Return first in first group
		const firstGroup = this.groups[0];
1021
		return { group: firstGroup.id, editor: firstGroup.getEditor(0) };
1022 1023
	}

1024
	previous(jumpGroups: boolean, cycleAtStart = true): IEditorIdentifier {
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
		this.ensureLoaded();

		if (!this.activeGroup) {
			return null;
		}

		const index = this.activeGroup.indexOf(this.activeGroup.activeEditor);

		// Return previous in group
		if (index > 0) {
1035
			return { group: this.activeGroup.id, editor: this.activeGroup.getEditor(index - 1) };
1036 1037
		}

1038 1039
		// Return last if we are not jumping groups
		if (!jumpGroups) {
1040 1041 1042
			if (!cycleAtStart) {
				return null;
			}
1043
			return { group: this.activeGroup.id, editor: this.activeGroup.getEditor(this.activeGroup.count - 1) };
1044 1045
		}

1046 1047 1048 1049
		// Return last in previous group
		const indexOfGroup = this.indexOf(this.activeGroup);
		const previousGroup = this.groups[indexOfGroup - 1];
		if (previousGroup) {
1050
			return { group: previousGroup.id, editor: previousGroup.getEditor(previousGroup.count - 1) };
1051 1052
		}

1053 1054 1055 1056 1057
		// Return null if we are not cycling at the start
		if (!cycleAtStart) {
			return null;
		}

1058 1059
		// Return last in last group
		const lastGroup = this.groups[this.groups.length - 1];
1060
		return { group: lastGroup.id, editor: lastGroup.getEditor(lastGroup.count - 1) };
1061 1062
	}

1063
	last(): IEditorIdentifier {
1064 1065 1066 1067 1068 1069
		this.ensureLoaded();

		if (!this.activeGroup) {
			return null;
		}

1070
		return { group: this.activeGroup.id, editor: this.activeGroup.getEditor(this.activeGroup.count - 1) };
1071 1072
	}

1073
	private save(): void {
1074 1075
		const serialized = this.serialize();

1076 1077 1078 1079 1080
		if (serialized.groups.length) {
			this.storageService.store(EditorStacksModel.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE);
		} else {
			this.storageService.remove(EditorStacksModel.STORAGE_KEY, StorageScope.WORKSPACE);
		}
1081 1082 1083
	}

	private serialize(): ISerializedEditorStacksModel {
1084

1085
		// Exclude now empty groups (can happen if an editor cannot be serialized)
1086
		let serializableGroups = this._groups.map(g => g.serialize()).filter(g => g.editors.length > 0);
1087

1088
		// Only consider active index if we do not have empty groups
B
Benjamin Pasero 已提交
1089 1090 1091 1092 1093 1094 1095
		let serializableActiveIndex: number;
		if (serializableGroups.length > 0) {
			if (serializableGroups.length === this._groups.length) {
				serializableActiveIndex = this.indexOf(this._activeGroup);
			} else {
				serializableActiveIndex = 0;
			}
1096 1097
		}

1098
		return {
1099
			groups: serializableGroups,
1100
			active: serializableActiveIndex
1101 1102 1103
		};
	}

1104
	private fireEvent(emitter: Emitter<EditorGroup>, group: EditorGroup, isStructuralChange: boolean): void {
1105
		emitter.fire(group);
1106
		this._onModelChanged.fire({ group, structural: isStructuralChange });
1107 1108
	}

B
Benjamin Pasero 已提交
1109 1110 1111 1112 1113 1114 1115
	private ensureLoaded(): void {
		if (!this.loaded) {
			this.loaded = true;
			this.load();
		}
	}

1116
	private load(): void {
1117
		if (!this.restoreFromStorage) {
B
Benjamin Pasero 已提交
1118 1119 1120
			return; // do not load from last session if the user explicitly asks to open a set of files
		}

1121 1122 1123 1124
		const modelRaw = this.storageService.get(EditorStacksModel.STORAGE_KEY, StorageScope.WORKSPACE);
		if (modelRaw) {
			const serialized: ISerializedEditorStacksModel = JSON.parse(modelRaw);

1125
			const invalidId = this.doValidate(serialized);
B
Benjamin Pasero 已提交
1126
			if (invalidId) {
B
Benjamin Pasero 已提交
1127 1128
				console.warn(`Ignoring invalid stacks model (Error code: ${invalidId}): ${JSON.stringify(serialized)}`);
				console.warn(serialized);
B
Benjamin Pasero 已提交
1129 1130 1131
				return;
			}

1132
			this._groups = serialized.groups.map(s => this.doCreateGroup(s));
1133
			this._activeGroup = this._groups[serialized.active];
1134 1135 1136
		}
	}

1137
	private doValidate(serialized: ISerializedEditorStacksModel): number {
B
Benjamin Pasero 已提交
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
		if (!serialized.groups.length && typeof serialized.active === 'number') {
			return 1; // Invalid active (we have no groups, but an active one)
		}

		if (serialized.groups.length && !serialized.groups[serialized.active]) {
			return 2; // Invalid active (we cannot find the active one in group)
		}

		if (serialized.groups.length > 3) {
			return 3; // Too many groups
		}

1150
		if (serialized.groups.some(g => !g.editors.length)) {
B
Benjamin Pasero 已提交
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
			return 4; // Some empty groups
		}

		if (serialized.groups.some(g => g.editors.length !== g.mru.length)) {
			return 5; // MRU out of sync with editors
		}

		if (serialized.groups.some(g => typeof g.preview === 'number' && !g.editors[g.preview])) {
			return 6; // Invalid preview editor
		}

		return 0;
	}

1165 1166 1167
	private doCreateGroup(arg1: string | ISerializedEditorGroup): EditorGroup {
		const group = this.instantiationService.createInstance(EditorGroup, arg1);

1168 1169
		this.groupToIdentifier[group.id] = group;

1170
		// Funnel editor changes in the group through our event aggregator
1171
		const unbind: IDisposable[] = [];
1172 1173 1174 1175 1176 1177 1178 1179
		unbind.push(group.onDidEditorClose(event => this._onModelChanged.fire({ group, editor: event.editor, structural: true })));
		unbind.push(group.onDidEditorOpen(editor => this._onModelChanged.fire({ group, editor: editor, structural: true })));
		unbind.push(group.onDidEditorMove(editor => this._onModelChanged.fire({ group, editor: editor, structural: true })));
		unbind.push(group.onDidEditorActivate(editor => this._onModelChanged.fire({ group, editor })));
		unbind.push(group.onDidEditorBecomeDirty(editor => this._onModelChanged.fire({ group, editor })));
		unbind.push(group.onDidEditorLabelChange(editor => this._onModelChanged.fire({ group, editor })));
		unbind.push(group.onDidEditorPin(editor => this._onModelChanged.fire({ group, editor })));
		unbind.push(group.onDidEditorUnpin(editor => this._onModelChanged.fire({ group, editor })));
1180
		unbind.push(group.onDidEditorOpen(editor => this._onEditorOpened.fire({ editor, group: group.id })));
1181
		unbind.push(group.onDidEditorClose(event => {
B
Benjamin Pasero 已提交
1182
			this._onWillCloseEditor.fire(event);
1183 1184 1185
			this.handleOnEditorClosed(event);
			this._onEditorClosed.fire(event);
		}));
1186 1187 1188
		unbind.push(group.onDidEditorDispose(editor => this._onEditorDisposed.fire({ editor, group: group.id })));
		unbind.push(group.onDidEditorBecomeDirty(editor => this._onEditorDirty.fire({ editor, group: group.id })));
		unbind.push(group.onDidEditorLabelChange(editor => this._onEditorLabelChange.fire({ editor, group: group.id })));
1189
		unbind.push(this.onGroupClosed(g => {
1190
			if (g === group) {
1191
				dispose(unbind);
1192
			}
1193
		}));
1194 1195 1196 1197

		return group;
	}

B
Benjamin Pasero 已提交
1198
	private handleOnEditorClosed(event: EditorCloseEvent): void {
1199
		const editor = event.editor;
1200
		const editorsToClose = [editor];
1201

1202 1203 1204 1205
		// Include both sides of side by side editors when being closed and not opened multiple times
		if (editor instanceof SideBySideEditorInput && !this.isOpen(editor)) {
			editorsToClose.push(editor.master, editor.details);
		}
1206

1207 1208
		// Close the editor when it is no longer open in any group including diff editors
		editorsToClose.forEach(editorToClose => {
B
Benjamin Pasero 已提交
1209
			const resource = editorToClose ? editorToClose.getResource() : void 0; // prefer resource to not close right-hand side editors of a diff editor
1210 1211
			if (!this.isOpen(resource || editorToClose)) {
				editorToClose.close();
1212
			}
1213
		});
1214 1215
	}

1216
	isOpen(editorOrResource: URI | EditorInput): boolean {
1217
		return this._groups.some(group => group.contains(editorOrResource));
1218
	}
1219

1220
	count(editor: EditorInput): number {
1221
		return this._groups.filter(group => group.contains(editor)).length;
1222 1223
	}

1224 1225 1226 1227 1228
	private onShutdown(): void {
		this.save();

		dispose(this.toDispose);
	}
1229

1230
	validate(): void {
1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
		const serialized = this.serialize();
		const invalidId = this.doValidate(serialized);
		if (invalidId) {
			console.warn(`Ignoring invalid stacks model (Error code: ${invalidId}): ${JSON.stringify(serialized)}`);
			console.warn(serialized);
		} else {
			console.log('Stacks Model OK!');
		}
	}

1241
	toString(): string {
B
Benjamin Pasero 已提交
1242 1243
		this.ensureLoaded();

1244 1245
		const lines: string[] = [];

B
Benjamin Pasero 已提交
1246 1247 1248 1249
		if (!this.groups.length) {
			return '<No Groups>';
		}

1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267
		this.groups.forEach(g => {
			g.getEditors().forEach(e => {
				let label = `\t${e.getName()}`;

				if (g.previewEditor === e) {
					label = `${label} [preview]`;
				}

				if (g.activeEditor === e) {
					label = `${label} [active]`;
				}

				lines.push(label);
			});
		});

		return lines.join('\n');
	}
1268 1269 1270
}

//#endregion