editorStacksModel.ts 35.6 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';

J
Johannes Rieken 已提交
8
import Event, { Emitter, once } from 'vs/base/common/event';
9
import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEditorStacksModel, IEditorGroup, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, IStacksModelChangeEvent, EditorOpenPositioning, SideBySideEditorInput, OPEN_POSITIONING_CONFIG } 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 15
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
16
import { Registry } from 'vs/platform/registry/common/platform';
J
Johannes Rieken 已提交
17
import { Position, Direction } 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
export interface EditorCloseEvent extends IEditorCloseEvent {
21 22 23
	editor: EditorInput;
}

24 25
export interface EditorIdentifier extends IEditorIdentifier {
	group: EditorGroup;
26
	editor: EditorInput;
27 28
}

B
Benjamin Pasero 已提交
29 30 31
export interface IEditorOpenOptions {
	pinned?: boolean;
	active?: boolean;
B
Benjamin Pasero 已提交
32
	index?: number;
B
Benjamin Pasero 已提交
33 34
}

35
export interface ISerializedEditorInput {
36 37 38 39
	id: string;
	value: string;
}

40
export interface ISerializedEditorGroup {
41 42 43 44 45 46
	label: string;
	editors: ISerializedEditorInput[];
	mru: number[];
	preview: number;
}

B
Benjamin Pasero 已提交
47
export class EditorGroup implements IEditorGroup {
48 49 50 51

	private static IDS = 0;

	private _id: GroupIdentifier;
52 53
	private _label: string;

B
Benjamin Pasero 已提交
54 55
	private editors: EditorInput[];
	private mru: EditorInput[];
B
wip  
Benjamin Pasero 已提交
56
	private mapResourceToEditorCount: ResourceMap<number>;
B
Benjamin Pasero 已提交
57 58 59 60

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

B
Benjamin Pasero 已提交
61
	private toDispose: IDisposable[];
62
	private editorOpenPositioning: 'left' | 'right' | 'first' | 'last';
B
Benjamin Pasero 已提交
63

B
Benjamin Pasero 已提交
64 65
	private _onEditorActivated: Emitter<EditorInput>;
	private _onEditorOpened: Emitter<EditorInput>;
B
Benjamin Pasero 已提交
66
	private _onEditorClosed: Emitter<EditorCloseEvent>;
67
	private _onEditorDisposed: Emitter<EditorInput>;
68
	private _onEditorDirty: Emitter<EditorInput>;
B
Benjamin Pasero 已提交
69
	private _onEditorLabelChange: Emitter<EditorInput>;
B
Benjamin Pasero 已提交
70
	private _onEditorMoved: Emitter<EditorInput>;
B
Benjamin Pasero 已提交
71 72
	private _onEditorPinned: Emitter<EditorInput>;
	private _onEditorUnpinned: Emitter<EditorInput>;
73 74
	private _onEditorStateChanged: Emitter<EditorInput>;
	private _onEditorsStructureChanged: Emitter<EditorInput>;
B
Benjamin Pasero 已提交
75

76 77
	constructor(
		arg1: string | ISerializedEditorGroup,
78 79
		@IInstantiationService private instantiationService: IInstantiationService,
		@IConfigurationService private configurationService: IConfigurationService
80
	) {
81 82
		this._id = EditorGroup.IDS++;

B
Benjamin Pasero 已提交
83
		this.editors = [];
B
Benjamin Pasero 已提交
84
		this.mru = [];
B
Benjamin Pasero 已提交
85
		this.toDispose = [];
B
wip  
Benjamin Pasero 已提交
86
		this.mapResourceToEditorCount = new ResourceMap<number>();
87
		this.onConfigurationUpdated();
B
Benjamin Pasero 已提交
88 89 90

		this._onEditorActivated = new Emitter<EditorInput>();
		this._onEditorOpened = new Emitter<EditorInput>();
B
Benjamin Pasero 已提交
91
		this._onEditorClosed = new Emitter<EditorCloseEvent>();
92
		this._onEditorDisposed = new Emitter<EditorInput>();
93
		this._onEditorDirty = new Emitter<EditorInput>();
B
Benjamin Pasero 已提交
94
		this._onEditorLabelChange = new Emitter<EditorInput>();
B
Benjamin Pasero 已提交
95
		this._onEditorMoved = new Emitter<EditorInput>();
B
Benjamin Pasero 已提交
96 97
		this._onEditorPinned = new Emitter<EditorInput>();
		this._onEditorUnpinned = new Emitter<EditorInput>();
98 99
		this._onEditorStateChanged = new Emitter<EditorInput>();
		this._onEditorsStructureChanged = new Emitter<EditorInput>();
B
Benjamin Pasero 已提交
100

B
Benjamin Pasero 已提交
101
		this.toDispose.push(this._onEditorActivated, this._onEditorOpened, this._onEditorClosed, this._onEditorDisposed, this._onEditorDirty, this._onEditorLabelChange, this._onEditorMoved, this._onEditorPinned, this._onEditorUnpinned, this._onEditorStateChanged, this._onEditorsStructureChanged);
102 103 104 105 106 107

		if (typeof arg1 === 'object') {
			this.deserialize(arg1);
		} else {
			this._label = arg1;
		}
108 109 110 111 112

		this.registerListeners();
	}

	private registerListeners(): void {
113
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
114 115
	}

116 117
	private onConfigurationUpdated(event?: IConfigurationChangeEvent): void {
		this.editorOpenPositioning = this.configurationService.getValue(OPEN_POSITIONING_CONFIG);
B
Benjamin Pasero 已提交
118 119
	}

120 121 122 123
	public get id(): GroupIdentifier {
		return this._id;
	}

124 125 126 127
	public get label(): string {
		return this._label;
	}

128 129 130 131
	public set label(label: string) {
		this._label = label;
	}

132 133 134 135
	public get count(): number {
		return this.editors.length;
	}

B
Benjamin Pasero 已提交
136 137 138 139 140 141 142 143
	public get onEditorActivated(): Event<EditorInput> {
		return this._onEditorActivated.event;
	}

	public get onEditorOpened(): Event<EditorInput> {
		return this._onEditorOpened.event;
	}

B
Benjamin Pasero 已提交
144
	public get onEditorClosed(): Event<EditorCloseEvent> {
B
Benjamin Pasero 已提交
145 146 147
		return this._onEditorClosed.event;
	}

148 149 150 151
	public get onEditorDisposed(): Event<EditorInput> {
		return this._onEditorDisposed.event;
	}

152 153 154 155
	public get onEditorDirty(): Event<EditorInput> {
		return this._onEditorDirty.event;
	}

B
Benjamin Pasero 已提交
156 157 158 159
	public get onEditorLabelChange(): Event<EditorInput> {
		return this._onEditorLabelChange.event;
	}

B
Benjamin Pasero 已提交
160 161 162 163
	public get onEditorMoved(): Event<EditorInput> {
		return this._onEditorMoved.event;
	}

B
Benjamin Pasero 已提交
164 165 166 167 168 169 170 171
	public get onEditorPinned(): Event<EditorInput> {
		return this._onEditorPinned.event;
	}

	public get onEditorUnpinned(): Event<EditorInput> {
		return this._onEditorUnpinned.event;
	}

172 173 174 175 176 177
	public get onEditorStateChanged(): Event<EditorInput> {
		return this._onEditorStateChanged.event;
	}

	public get onEditorsStructureChanged(): Event<EditorInput> {
		return this._onEditorsStructureChanged.event;
178 179
	}

B
Benjamin Pasero 已提交
180 181
	public getEditors(mru?: boolean): EditorInput[] {
		return mru ? this.mru.slice(0) : this.editors.slice(0);
B
Benjamin Pasero 已提交
182 183
	}

B
Benjamin Pasero 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197
	public getEditor(index: number): EditorInput;
	public getEditor(resource: URI): EditorInput;
	public getEditor(arg1: any): EditorInput {
		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];
198
			const editorResource = toResource(editor, { supportSideBySide: true });
B
Benjamin Pasero 已提交
199
			if (editorResource && editorResource.toString() === resource.toString()) {
B
Benjamin Pasero 已提交
200
				return editor;
B
Benjamin Pasero 已提交
201 202 203 204
			}
		}

		return null;
205 206
	}

B
Benjamin Pasero 已提交
207 208 209 210 211
	public get activeEditor(): EditorInput {
		return this.active;
	}

	public isActive(editor: EditorInput): boolean {
B
Benjamin Pasero 已提交
212
		return this.matches(this.active, editor);
B
Benjamin Pasero 已提交
213 214 215 216 217 218 219
	}

	public get previewEditor(): EditorInput {
		return this.preview;
	}

	public isPreview(editor: EditorInput): boolean {
B
Benjamin Pasero 已提交
220
		return this.matches(this.preview, editor);
B
Benjamin Pasero 已提交
221 222 223 224 225 226
	}

	public openEditor(editor: EditorInput, options?: IEditorOpenOptions): void {
		const index = this.indexOf(editor);

		const makePinned = options && options.pinned;
B
Benjamin Pasero 已提交
227
		const makeActive = (options && options.active) || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
B
Benjamin Pasero 已提交
228 229 230

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

234 235 236 237
			// Insert into specific position
			if (options && typeof options.index === 'number') {
				targetIndex = options.index;
			}
B
Benjamin Pasero 已提交
238

239
			// Insert to the BEGINNING
240
			else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
241 242 243 244
				targetIndex = 0;
			}

			// Insert to the END
245
			else if (this.editorOpenPositioning === EditorOpenPositioning.LAST) {
246
				targetIndex = this.editors.length;
247
			}
B
Benjamin Pasero 已提交
248

249
			// Insert to the LEFT of active editor
250
			else if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
251 252 253
				if (indexOfActive === 0 || !this.editors.length) {
					targetIndex = 0; // to the left becoming first editor in list
				} else {
254
					targetIndex = indexOfActive; // to the left of active editor
B
Benjamin Pasero 已提交
255
				}
256
			}
B
Benjamin Pasero 已提交
257

258 259 260 261 262
			// Insert to the RIGHT of active editor
			else {
				targetIndex = indexOfActive + 1;
			}

263 264 265
			// 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 已提交
266 267
			}

268 269
			// Handle preview
			if (!makePinned) {
B
Benjamin Pasero 已提交
270 271

				// Replace existing preview with this editor if we have a preview
272 273
				if (this.preview) {
					const indexOfPreview = this.indexOf(this.preview);
274 275
					if (targetIndex > indexOfPreview) {
						targetIndex--; // accomodate for the fact that the preview editor closes
276 277
					}

278
					this.replaceEditor(this.preview, editor, targetIndex, !makeActive);
279
				}
B
Benjamin Pasero 已提交
280

B
Benjamin Pasero 已提交
281 282 283
				this.preview = editor;
			}

284 285
			// Listeners
			this.hookEditorListeners(editor);
286

B
Benjamin Pasero 已提交
287
			// Event
288
			this.fireEvent(this._onEditorOpened, editor, true);
B
Benjamin Pasero 已提交
289

290
			// Handle active
B
Benjamin Pasero 已提交
291
			if (makeActive) {
B
Benjamin Pasero 已提交
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
				this.setActive(editor);
			}
		}

		// Existing editor
		else {

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

			// Activate it
			if (makeActive) {
				this.setActive(editor);
			}
B
Benjamin Pasero 已提交
308 309 310 311 312

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

316
	private hookEditorListeners(editor: EditorInput): void {
317
		const unbind: IDisposable[] = [];
318 319

		// Re-emit disposal of editor input as our own event
320 321
		const onceDispose = once(editor.onDispose);
		unbind.push(onceDispose(() => {
322 323 324
			if (this.indexOf(editor) >= 0) {
				this._onEditorDisposed.fire(editor);
			}
325 326 327 328
		}));

		// Re-Emit dirty state changes
		unbind.push(editor.onDidChangeDirty(() => {
329
			this.fireEvent(this._onEditorDirty, editor, false);
330
		}));
331

B
Benjamin Pasero 已提交
332 333 334 335 336
		// Re-Emit label changes
		unbind.push(editor.onDidChangeLabel(() => {
			this.fireEvent(this._onEditorLabelChange, editor, false);
		}));

337
		// Clean up dispose listeners once the editor gets closed
338
		unbind.push(this.onEditorClosed(event => {
339
			if (event.editor.matches(editor)) {
340
				dispose(unbind);
341
			}
342
		}));
343 344
	}

B
hygiene  
Benjamin Pasero 已提交
345
	public replaceEditor(toReplace: EditorInput, replaceWidth: EditorInput, replaceIndex: number, openNext = true): void {
346 347 348 349 350 351 352 353 354 355 356 357
		const event = this.doCloseEditor(toReplace, openNext); // optimization to prevent multiple setActive() in one call

		// 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) {
			this.fireEvent(this._onEditorClosed, event, true);
		}
	}

B
Benjamin Pasero 已提交
358
	public closeEditor(editor: EditorInput, openNext = true): void {
359 360 361 362 363 364 365 366
		const event = this.doCloseEditor(editor, openNext);

		if (event) {
			this.fireEvent(this._onEditorClosed, event, true);
		}
	}

	private doCloseEditor(editor: EditorInput, openNext = true): EditorCloseEvent {
B
Benjamin Pasero 已提交
367 368
		const index = this.indexOf(editor);
		if (index === -1) {
369
			return null; // not found
B
Benjamin Pasero 已提交
370 371 372
		}

		// Active Editor closed
B
Benjamin Pasero 已提交
373
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
374 375

			// More than one editor
B
Benjamin Pasero 已提交
376 377
			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 已提交
378 379 380 381 382 383 384 385 386
			}

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

		// Preview Editor closed
387
		let pinned = true;
B
Benjamin Pasero 已提交
388
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
389
			this.preview = null;
390
			pinned = false;
B
Benjamin Pasero 已提交
391 392
		}

B
Benjamin Pasero 已提交
393
		// Remove from arrays
B
Benjamin Pasero 已提交
394
		this.splice(index, true);
B
Benjamin Pasero 已提交
395 396

		// Event
397
		return { editor, pinned, index, group: this };
B
Benjamin Pasero 已提交
398 399
	}

B
Benjamin Pasero 已提交
400
	public closeEditors(except: EditorInput, direction?: Direction): void {
B
Benjamin Pasero 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
		const index = this.indexOf(except);
		if (index === -1) {
			return; // not found
		}

		// Close to the left
		if (direction === Direction.LEFT) {
			for (let i = index - 1; i >= 0; i--) {
				this.closeEditor(this.editors[i]);
			}
		}

		// Close to the right
		else if (direction === Direction.RIGHT) {
			for (let i = this.editors.length - 1; i > index; i--) {
				this.closeEditor(this.editors[i]);
			}
		}

		// Both directions
		else {
B
Benjamin Pasero 已提交
422
			this.mru.filter(e => !this.matches(e, except)).forEach(e => this.closeEditor(e));
B
Benjamin Pasero 已提交
423 424 425
		}
	}

426 427 428
	public closeAllEditors(): void {

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

B
Benjamin Pasero 已提交
433 434 435 436 437 438 439 440 441 442 443
	public moveEditor(editor: EditorInput, toIndex: number): void {
		const index = this.indexOf(editor);
		if (index < 0) {
			return;
		}

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

		// Event
444
		this.fireEvent(this._onEditorMoved, editor, true);
B
Benjamin Pasero 已提交
445 446
	}

B
Benjamin Pasero 已提交
447 448 449 450 451 452
	public setActive(editor: EditorInput): void {
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

B
Benjamin Pasero 已提交
453
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
454 455 456 457 458
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
459
		// Bring to front in MRU list
B
Benjamin Pasero 已提交
460 461
		this.setMostRecentlyUsed(editor);

B
Benjamin Pasero 已提交
462
		// Event
463
		this.fireEvent(this._onEditorActivated, editor, false);
B
Benjamin Pasero 已提交
464 465 466
	}

	public pin(editor: EditorInput): void {
467 468 469 470 471
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

B
Benjamin Pasero 已提交
472 473
		if (!this.isPreview(editor)) {
			return; // can only pin a preview editor
B
Benjamin Pasero 已提交
474 475 476 477 478 479
		}

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

		// Event
480
		this.fireEvent(this._onEditorPinned, editor, false);
B
Benjamin Pasero 已提交
481 482
	}

B
Benjamin Pasero 已提交
483
	public unpin(editor: EditorInput): void {
484 485 486 487
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}
488

B
Benjamin Pasero 已提交
489 490 491 492 493 494 495 496 497
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

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

		// Event
498
		this.fireEvent(this._onEditorUnpinned, editor, false);
B
Benjamin Pasero 已提交
499 500 501 502 503

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

504 505 506 507 508 509 510 511 512 513 514 515 516 517
	public isPinned(editor: EditorInput): boolean;
	public isPinned(index: number): boolean;
	public isPinned(arg1: EditorInput | number): boolean {
		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 已提交
518 519 520 521 522 523 524
			return false; // editor not found
		}

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

B
Benjamin Pasero 已提交
525
		return !this.matches(this.preview, editor);
B
Benjamin Pasero 已提交
526 527
	}

B
Benjamin Pasero 已提交
528
	private fireEvent(emitter: Emitter<EditorInput | EditorCloseEvent>, arg2: EditorInput | EditorCloseEvent, isStructuralChange: boolean): void {
529
		emitter.fire(arg2);
530 531 532 533 534 535

		if (isStructuralChange) {
			this._onEditorsStructureChanged.fire(arg2 instanceof EditorInput ? arg2 : arg2.editor);
		} else {
			this._onEditorStateChanged.fire(arg2 instanceof EditorInput ? arg2 : arg2.editor);
		}
536 537
	}

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

B
Benjamin Pasero 已提交
541
		const args: any[] = [index, del ? 1 : 0];
542 543 544
		if (editor) {
			args.push(editor);
		}
B
Benjamin Pasero 已提交
545

546
		// Perform on editors array
547
		this.editors.splice.apply(this.editors, args);
B
Benjamin Pasero 已提交
548

549
		// Add
B
Benjamin Pasero 已提交
550
		if (!del && editor) {
551 552
			this.mru.push(editor); // make it LRU editor
			this.updateResourceMap(editor, false /* add */); // add new to resource map
B
Benjamin Pasero 已提交
553 554
		}

B
Benjamin Pasero 已提交
555
		// Remove / Replace
B
Benjamin Pasero 已提交
556
		else {
B
Benjamin Pasero 已提交
557 558
			const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);

559
			// Remove
B
Benjamin Pasero 已提交
560
			if (del && !editor) {
561 562
				this.mru.splice(indexInMRU, 1); // remove from MRU
				this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove from resource map
B
Benjamin Pasero 已提交
563 564
			}

565
			// Replace
B
Benjamin Pasero 已提交
566
			else {
567 568 569
				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 已提交
570
			}
B
Benjamin Pasero 已提交
571 572 573
		}
	}

574
	private updateResourceMap(editor: EditorInput, remove: boolean): void {
575
		const resource = toResource(editor, { supportSideBySide: true });
576
		if (resource) {
577 578 579

			// 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 已提交
580
			let counter = this.mapResourceToEditorCount.get(resource) || 0;
B
Benjamin Pasero 已提交
581
			let newCounter: number;
582 583 584 585 586 587 588 589
			if (remove) {
				if (counter > 1) {
					newCounter = counter - 1;
				}
			} else {
				newCounter = counter + 1;
			}

B
wip  
Benjamin Pasero 已提交
590
			this.mapResourceToEditorCount.set(resource, newCounter);
591 592 593
		}
	}

594
	public indexOf(candidate: EditorInput, editors = this.editors): number {
B
Benjamin Pasero 已提交
595 596
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
597 598
		}

B
Benjamin Pasero 已提交
599
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
600
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
601 602 603 604 605 606
				return i;
			}
		}

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

608 609 610
	public contains(editorOrResource: EditorInput | URI): boolean {
		if (editorOrResource instanceof EditorInput) {
			return this.indexOf(editorOrResource) >= 0;
611 612
		}

613
		const counter = this.mapResourceToEditorCount.get(editorOrResource);
614 615

		return typeof counter === 'number' && counter > 0;
616 617
	}

B
Benjamin Pasero 已提交
618 619 620 621 622 623 624 625 626
	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 已提交
627
		this.mru.splice(mruIndex, 1);
B
Benjamin Pasero 已提交
628

B
Benjamin Pasero 已提交
629
		// Set editor to front
B
Benjamin Pasero 已提交
630 631
		this.mru.unshift(editor);
	}
B
Benjamin Pasero 已提交
632 633 634 635

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

	public serialize(): ISerializedEditorGroup {
638
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
639 640 641 642 643 644

		// 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 已提交
645
		let serializablePreviewIndex: number;
646
		this.editors.forEach(e => {
647
			let factory = registry.getEditorInputFactory(e.getTypeId());
648 649 650
			if (factory) {
				let value = factory.serialize(e);
				if (typeof value === 'string') {
651
					serializedEditors.push({ id: e.getTypeId(), value });
652
					serializableEditors.push(e);
B
Benjamin Pasero 已提交
653 654 655 656

					if (this.preview === e) {
						serializablePreviewIndex = serializableEditors.length - 1;
					}
657 658 659 660
				}
			}
		});

B
Benjamin Pasero 已提交
661
		const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0);
662 663 664 665 666

		return {
			label: this.label,
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
667
			preview: serializablePreviewIndex,
668 669 670 671
		};
	}

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

		this._label = data.label;
675
		this.editors = data.editors.map(e => {
B
Benjamin Pasero 已提交
676 677 678
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
				const editor = factory.deserialize(this.instantiationService, e.value);
679

B
Benjamin Pasero 已提交
680 681
				this.hookEditorListeners(editor);
				this.updateResourceMap(editor, false /* add */);
682

B
Benjamin Pasero 已提交
683 684 685 686 687
				return editor;
			}

			return null;
		}).filter(e => !!e);
688 689 690 691
		this.mru = data.mru.map(i => this.editors[i]);
		this.active = this.mru[0];
		this.preview = this.editors[data.preview];
	}
B
Benjamin Pasero 已提交
692 693

	public dispose(): void {
B
Benjamin Pasero 已提交
694
		dispose(this.toDispose);
B
Benjamin Pasero 已提交
695
	}
696 697 698 699 700
}

interface ISerializedEditorStacksModel {
	groups: ISerializedEditorGroup[];
	active: number;
B
Benjamin Pasero 已提交
701 702 703
}

export class EditorStacksModel implements IEditorStacksModel {
704 705 706 707

	private static STORAGE_KEY = 'editorStacks.model';

	private toDispose: IDisposable[];
B
Benjamin Pasero 已提交
708 709
	private loaded: boolean;

B
Benjamin Pasero 已提交
710
	private _groups: EditorGroup[];
711
	private _activeGroup: EditorGroup;
712
	private groupToIdentifier: { [id: number]: EditorGroup };
B
Benjamin Pasero 已提交
713 714 715

	private _onGroupOpened: Emitter<EditorGroup>;
	private _onGroupClosed: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
716
	private _onGroupMoved: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
717
	private _onGroupActivated: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
718
	private _onGroupDeactivated: Emitter<EditorGroup>;
719
	private _onGroupRenamed: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
720

721 722
	private _onEditorDisposed: Emitter<EditorIdentifier>;
	private _onEditorDirty: Emitter<EditorIdentifier>;
B
Benjamin Pasero 已提交
723
	private _onEditorLabelChange: Emitter<EditorIdentifier>;
724
	private _onEditorOpened: Emitter<EditorIdentifier>;
B
Benjamin Pasero 已提交
725 726 727 728

	private _onWillCloseEditor: Emitter<EditorCloseEvent>;
	private _onEditorClosed: Emitter<EditorCloseEvent>;

729
	private _onModelChanged: Emitter<IStacksModelChangeEvent>;
B
Benjamin Pasero 已提交
730

731
	constructor(
732
		private restoreFromStorage: boolean,
733 734
		@IStorageService private storageService: IStorageService,
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
735
		@IInstantiationService private instantiationService: IInstantiationService
736 737 738
	) {
		this.toDispose = [];

B
Benjamin Pasero 已提交
739
		this._groups = [];
740
		this.groupToIdentifier = Object.create(null);
B
Benjamin Pasero 已提交
741

B
Benjamin Pasero 已提交
742 743 744
		this._onGroupOpened = new Emitter<EditorGroup>();
		this._onGroupClosed = new Emitter<EditorGroup>();
		this._onGroupActivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
745
		this._onGroupDeactivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
746
		this._onGroupMoved = new Emitter<EditorGroup>();
747
		this._onGroupRenamed = new Emitter<EditorGroup>();
748
		this._onModelChanged = new Emitter<IStacksModelChangeEvent>();
749 750
		this._onEditorDisposed = new Emitter<EditorIdentifier>();
		this._onEditorDirty = new Emitter<EditorIdentifier>();
B
Benjamin Pasero 已提交
751
		this._onEditorLabelChange = new Emitter<EditorIdentifier>();
752
		this._onEditorOpened = new Emitter<EditorIdentifier>();
B
Benjamin Pasero 已提交
753 754
		this._onWillCloseEditor = new Emitter<EditorCloseEvent>();
		this._onEditorClosed = new Emitter<EditorCloseEvent>();
755

B
Benjamin Pasero 已提交
756
		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 已提交
757

758 759 760 761
		this.registerListeners();
	}

	private registerListeners(): void {
762
		this.toDispose.push(this.lifecycleService.onShutdown(reason => this.onShutdown()));
B
Benjamin Pasero 已提交
763 764 765 766 767 768 769 770 771 772 773 774 775 776
	}

	public get onGroupOpened(): Event<EditorGroup> {
		return this._onGroupOpened.event;
	}

	public get onGroupClosed(): Event<EditorGroup> {
		return this._onGroupClosed.event;
	}

	public get onGroupActivated(): Event<EditorGroup> {
		return this._onGroupActivated.event;
	}

B
Benjamin Pasero 已提交
777 778 779 780
	public get onGroupDeactivated(): Event<EditorGroup> {
		return this._onGroupDeactivated.event;
	}

B
Benjamin Pasero 已提交
781 782 783 784
	public get onGroupMoved(): Event<EditorGroup> {
		return this._onGroupMoved.event;
	}

785 786 787 788
	public get onGroupRenamed(): Event<EditorGroup> {
		return this._onGroupRenamed.event;
	}

789
	public get onModelChanged(): Event<IStacksModelChangeEvent> {
790 791 792
		return this._onModelChanged.event;
	}

793
	public get onEditorDisposed(): Event<EditorIdentifier> {
794 795 796
		return this._onEditorDisposed.event;
	}

797
	public get onEditorDirty(): Event<EditorIdentifier> {
798 799 800
		return this._onEditorDirty.event;
	}

B
Benjamin Pasero 已提交
801 802 803 804
	public get onEditorLabelChange(): Event<EditorIdentifier> {
		return this._onEditorLabelChange.event;
	}

805 806 807 808
	public get onEditorOpened(): Event<EditorIdentifier> {
		return this._onEditorOpened.event;
	}

B
Benjamin Pasero 已提交
809
	public get onWillCloseEditor(): Event<EditorCloseEvent> {
810 811 812
		return this._onWillCloseEditor.event;
	}

B
Benjamin Pasero 已提交
813
	public get onEditorClosed(): Event<EditorCloseEvent> {
814 815 816
		return this._onEditorClosed.event;
	}

B
Benjamin Pasero 已提交
817
	public get groups(): EditorGroup[] {
B
Benjamin Pasero 已提交
818 819
		this.ensureLoaded();

B
Benjamin Pasero 已提交
820 821 822 823
		return this._groups.slice(0);
	}

	public get activeGroup(): EditorGroup {
B
Benjamin Pasero 已提交
824 825
		this.ensureLoaded();

826
		return this._activeGroup;
B
Benjamin Pasero 已提交
827 828
	}

B
Benjamin Pasero 已提交
829 830 831 832
	public isActive(group: EditorGroup): boolean {
		return this.activeGroup === group;
	}

833
	public getGroup(id: GroupIdentifier): EditorGroup {
B
Benjamin Pasero 已提交
834 835
		this.ensureLoaded();

836 837 838
		return this.groupToIdentifier[id];
	}

839
	public openGroup(label: string, activate = true, index?: number): EditorGroup {
B
Benjamin Pasero 已提交
840 841
		this.ensureLoaded();

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

844 845 846 847 848
		// Direct index provided
		if (typeof index === 'number') {
			this._groups[index] = group;
		}

B
Benjamin Pasero 已提交
849
		// First group
850
		else if (!this._activeGroup) {
B
Benjamin Pasero 已提交
851 852 853
			this._groups.push(group);
		}

854
		// Subsequent group (open to the right of active one)
B
Benjamin Pasero 已提交
855
		else {
856
			this._groups.splice(this.indexOf(this._activeGroup) + 1, 0, group);
B
Benjamin Pasero 已提交
857 858 859
		}

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

862
		// Activate if we are first or set to activate groups
863
		if (!this._activeGroup || activate) {
864 865
			this.setActive(group);
		}
B
Benjamin Pasero 已提交
866 867 868 869

		return group;
	}

870
	public renameGroup(group: EditorGroup, label: string): void {
B
Benjamin Pasero 已提交
871 872
		this.ensureLoaded();

B
Benjamin Pasero 已提交
873
		if (group.label !== label) {
B
Benjamin Pasero 已提交
874
			group.label = label;
875
			this.fireEvent(this._onGroupRenamed, group, false);
B
Benjamin Pasero 已提交
876
		}
B
Benjamin Pasero 已提交
877
	}
878

B
Benjamin Pasero 已提交
879
	public closeGroup(group: EditorGroup): void {
B
Benjamin Pasero 已提交
880 881
		this.ensureLoaded();

B
Benjamin Pasero 已提交
882 883 884 885 886 887
		const index = this.indexOf(group);
		if (index < 0) {
			return; // group does not exist
		}

		// Active group closed: Find a new active one to the right
888
		if (group === this._activeGroup) {
B
Benjamin Pasero 已提交
889 890 891 892 893 894 895 896 897 898 899 900 901 902 903

			// 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 {
904
				this._activeGroup = null;
B
Benjamin Pasero 已提交
905 906 907
			}
		}

B
Benjamin Pasero 已提交
908
		// Close Editors in Group first and dispose then
909
		group.closeAllEditors();
B
Benjamin Pasero 已提交
910
		group.dispose();
911

B
Benjamin Pasero 已提交
912 913
		// Splice from groups
		this._groups.splice(index, 1);
914
		this.groupToIdentifier[group.id] = void 0;
B
Benjamin Pasero 已提交
915

916
		// Events
917
		this.fireEvent(this._onGroupClosed, group, true);
918 919 920
		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 已提交
921 922
	}

B
Benjamin Pasero 已提交
923
	public closeGroups(except?: EditorGroup): void {
B
Benjamin Pasero 已提交
924
		this.ensureLoaded();
925 926

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

		// Close active unless configured to skip
930 931
		if (this._activeGroup !== except) {
			this.closeGroup(this._activeGroup);
B
Benjamin Pasero 已提交
932
		}
933 934
	}

B
Benjamin Pasero 已提交
935
	public setActive(group: EditorGroup): void {
B
Benjamin Pasero 已提交
936 937
		this.ensureLoaded();

938
		if (this._activeGroup === group) {
939 940 941
			return;
		}

B
Benjamin Pasero 已提交
942
		const oldActiveGroup = this._activeGroup;
943
		this._activeGroup = group;
B
Benjamin Pasero 已提交
944

945
		this.fireEvent(this._onGroupActivated, group, false);
B
Benjamin Pasero 已提交
946 947 948
		if (oldActiveGroup) {
			this.fireEvent(this._onGroupDeactivated, oldActiveGroup, false);
		}
B
Benjamin Pasero 已提交
949 950
	}

B
Benjamin Pasero 已提交
951
	public moveGroup(group: EditorGroup, toIndex: number): void {
B
Benjamin Pasero 已提交
952 953
		this.ensureLoaded();

B
Benjamin Pasero 已提交
954 955 956 957 958 959 960 961 962 963
		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 已提交
964 965 966
		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 已提交
967 968
	}

B
Benjamin Pasero 已提交
969 970 971
	private indexOf(group: EditorGroup): number {
		return this._groups.indexOf(group);
	}
972

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

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

980
			for (let j = 0; j < editorsToCheck.length; j++) {
B
Benjamin Pasero 已提交
981 982 983
				const editorToCheck = editorsToCheck[j];

				if ((!activeOnly || group.isActive(editorToCheck)) && editor.matches(editorToCheck)) {
984 985 986 987
					return group;
				}
			}
		}
B
Benjamin Pasero 已提交
988 989

		return void 0;
990 991
	}

992 993
	public positionOfGroup(group: IEditorGroup): Position;
	public positionOfGroup(group: EditorGroup): Position;
B
Benjamin Pasero 已提交
994 995 996
	public positionOfGroup(group: EditorGroup): Position {
		return this.indexOf(group);
	}
B
Benjamin Pasero 已提交
997 998

	public groupAt(position: Position): EditorGroup {
999 1000
		this.ensureLoaded();

B
Benjamin Pasero 已提交
1001 1002
		return this._groups[position];
	}
B
Benjamin Pasero 已提交
1003

1004
	public next(jumpGroups: boolean, cycleAtEnd = true): IEditorIdentifier {
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
		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) {
			return { group: this.activeGroup, editor: this.activeGroup.getEditor(index + 1) };
		}

1018 1019
		// Return first if we are not jumping groups
		if (!jumpGroups) {
1020 1021 1022
			if (!cycleAtEnd) {
				return null;
			}
1023 1024 1025
			return { group: this.activeGroup, editor: this.activeGroup.getEditor(0) };
		}

1026 1027 1028 1029 1030 1031 1032
		// Return first in next group
		const indexOfGroup = this.indexOf(this.activeGroup);
		const nextGroup = this.groups[indexOfGroup + 1];
		if (nextGroup) {
			return { group: nextGroup, editor: nextGroup.getEditor(0) };
		}

1033 1034 1035 1036 1037
		// Return null if we are not cycling at the end
		if (!cycleAtEnd) {
			return null;
		}

1038 1039 1040 1041 1042
		// Return first in first group
		const firstGroup = this.groups[0];
		return { group: firstGroup, editor: firstGroup.getEditor(0) };
	}

1043
	public previous(jumpGroups: boolean, cycleAtStart = true): IEditorIdentifier {
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
		this.ensureLoaded();

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

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

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

1057 1058
		// Return last if we are not jumping groups
		if (!jumpGroups) {
1059 1060 1061
			if (!cycleAtStart) {
				return null;
			}
1062 1063 1064
			return { group: this.activeGroup, editor: this.activeGroup.getEditor(this.activeGroup.count - 1) };
		}

1065 1066 1067 1068 1069 1070 1071
		// Return last in previous group
		const indexOfGroup = this.indexOf(this.activeGroup);
		const previousGroup = this.groups[indexOfGroup - 1];
		if (previousGroup) {
			return { group: previousGroup, editor: previousGroup.getEditor(previousGroup.count - 1) };
		}

1072 1073 1074 1075 1076
		// Return null if we are not cycling at the start
		if (!cycleAtStart) {
			return null;
		}

1077 1078 1079 1080 1081
		// Return last in last group
		const lastGroup = this.groups[this.groups.length - 1];
		return { group: lastGroup, editor: lastGroup.getEditor(lastGroup.count - 1) };
	}

1082
	private save(): void {
1083 1084
		const serialized = this.serialize();

1085 1086 1087 1088 1089
		if (serialized.groups.length) {
			this.storageService.store(EditorStacksModel.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE);
		} else {
			this.storageService.remove(EditorStacksModel.STORAGE_KEY, StorageScope.WORKSPACE);
		}
1090 1091 1092
	}

	private serialize(): ISerializedEditorStacksModel {
1093

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

1097
		// Only consider active index if we do not have empty groups
B
Benjamin Pasero 已提交
1098 1099 1100 1101 1102 1103 1104
		let serializableActiveIndex: number;
		if (serializableGroups.length > 0) {
			if (serializableGroups.length === this._groups.length) {
				serializableActiveIndex = this.indexOf(this._activeGroup);
			} else {
				serializableActiveIndex = 0;
			}
1105 1106
		}

1107
		return {
1108
			groups: serializableGroups,
1109
			active: serializableActiveIndex
1110 1111 1112
		};
	}

1113
	private fireEvent(emitter: Emitter<EditorGroup>, group: EditorGroup, isStructuralChange: boolean): void {
1114
		emitter.fire(group);
1115
		this._onModelChanged.fire({ group, structural: isStructuralChange });
1116 1117
	}

B
Benjamin Pasero 已提交
1118 1119 1120 1121 1122 1123 1124
	private ensureLoaded(): void {
		if (!this.loaded) {
			this.loaded = true;
			this.load();
		}
	}

1125
	private load(): void {
1126
		if (!this.restoreFromStorage) {
B
Benjamin Pasero 已提交
1127 1128 1129
			return; // do not load from last session if the user explicitly asks to open a set of files
		}

1130 1131 1132 1133
		const modelRaw = this.storageService.get(EditorStacksModel.STORAGE_KEY, StorageScope.WORKSPACE);
		if (modelRaw) {
			const serialized: ISerializedEditorStacksModel = JSON.parse(modelRaw);

1134
			const invalidId = this.doValidate(serialized);
B
Benjamin Pasero 已提交
1135
			if (invalidId) {
B
Benjamin Pasero 已提交
1136 1137
				console.warn(`Ignoring invalid stacks model (Error code: ${invalidId}): ${JSON.stringify(serialized)}`);
				console.warn(serialized);
B
Benjamin Pasero 已提交
1138 1139 1140
				return;
			}

1141
			this._groups = serialized.groups.map(s => this.doCreateGroup(s));
1142
			this._activeGroup = this._groups[serialized.active];
1143 1144 1145
		}
	}

1146
	private doValidate(serialized: ISerializedEditorStacksModel): number {
B
Benjamin Pasero 已提交
1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
		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
		}

1159
		if (serialized.groups.some(g => !g.editors.length)) {
B
Benjamin Pasero 已提交
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
			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
		}

		if (serialized.groups.some(g => !g.label)) {
			return 7; // Group without label
		}

		return 0;
	}

1178 1179 1180
	private doCreateGroup(arg1: string | ISerializedEditorGroup): EditorGroup {
		const group = this.instantiationService.createInstance(EditorGroup, arg1);

1181 1182
		this.groupToIdentifier[group.id] = group;

1183
		// Funnel editor changes in the group through our event aggregator
1184
		const unbind: IDisposable[] = [];
1185 1186
		unbind.push(group.onEditorsStructureChanged(editor => this._onModelChanged.fire({ group, editor, structural: true })));
		unbind.push(group.onEditorStateChanged(editor => this._onModelChanged.fire({ group, editor })));
C
Christof Marti 已提交
1187
		unbind.push(group.onEditorOpened(editor => this._onEditorOpened.fire({ editor, group })));
1188
		unbind.push(group.onEditorClosed(event => {
B
Benjamin Pasero 已提交
1189
			this._onWillCloseEditor.fire(event);
1190 1191 1192
			this.handleOnEditorClosed(event);
			this._onEditorClosed.fire(event);
		}));
1193 1194
		unbind.push(group.onEditorDisposed(editor => this._onEditorDisposed.fire({ editor, group })));
		unbind.push(group.onEditorDirty(editor => this._onEditorDirty.fire({ editor, group })));
B
Benjamin Pasero 已提交
1195
		unbind.push(group.onEditorLabelChange(editor => this._onEditorLabelChange.fire({ editor, group })));
1196
		unbind.push(this.onGroupClosed(g => {
1197
			if (g === group) {
1198
				dispose(unbind);
1199
			}
1200
		}));
1201 1202 1203 1204

		return group;
	}

B
Benjamin Pasero 已提交
1205
	private handleOnEditorClosed(event: EditorCloseEvent): void {
1206
		const editor = event.editor;
1207
		const editorsToClose = [editor];
1208

1209 1210 1211 1212
		// 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);
		}
1213

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

1223 1224
	public isOpen(editorOrResource: URI | EditorInput): boolean {
		return this._groups.some(group => group.contains(editorOrResource));
1225
	}
1226

1227 1228
	public count(editor: EditorInput): number {
		return this._groups.filter(group => group.contains(editor)).length;
1229 1230
	}

1231 1232 1233 1234 1235
	private onShutdown(): void {
		this.save();

		dispose(this.toDispose);
	}
1236

1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247
	public validate(): void {
		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!');
		}
	}

1248
	public toString(): string {
B
Benjamin Pasero 已提交
1249 1250
		this.ensureLoaded();

1251 1252
		const lines: string[] = [];

B
Benjamin Pasero 已提交
1253 1254 1255 1256
		if (!this.groups.length) {
			return '<No Groups>';
		}

1257
		this.groups.forEach(g => {
B
Benjamin Pasero 已提交
1258 1259
			let label = `Group: ${g.label}`;

1260
			if (this._activeGroup === g) {
B
Benjamin Pasero 已提交
1261
				label = `${label} [active]`;
1262 1263
			}

B
Benjamin Pasero 已提交
1264 1265
			lines.push(label);

1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
			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');
	}
B
Benjamin Pasero 已提交
1283
}