editorStacksModel.ts 33.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 { IEditorRegistry, Extensions, EditorInput, toResource, IEditorStacksModel, IEditorGroup, IEditorIdentifier, IGroupEvent, GroupIdentifier, IStacksModelChangeEvent, IWorkbenchEditorConfiguration, EditorOpenPositioning, SideBySideEditorInput } from 'vs/workbench/common/editor';
10
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
11 12 13 14 15 16 17
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/platform';
import { Position, Direction } from 'vs/platform/editor/common/editor';
B
Benjamin Pasero 已提交
18

19
export interface GroupEvent extends IGroupEvent {
20 21 22
	editor: EditorInput;
}

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

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

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

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

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

	private static IDS = 0;

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

B
Benjamin Pasero 已提交
53 54
	private editors: EditorInput[];
	private mru: EditorInput[];
55
	private mapResourceToEditorCount: { [resource: string]: number };
B
Benjamin Pasero 已提交
56 57 58 59

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

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

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

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

B
Benjamin Pasero 已提交
82
		this.editors = [];
B
Benjamin Pasero 已提交
83
		this.mru = [];
B
Benjamin Pasero 已提交
84
		this.toDispose = [];
85
		this.mapResourceToEditorCount = Object.create(null);
B
Benjamin Pasero 已提交
86
		this.onConfigurationUpdated(configurationService.getConfiguration<IWorkbenchEditorConfiguration>());
B
Benjamin Pasero 已提交
87 88 89

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

B
Benjamin Pasero 已提交
100
		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);
101 102 103 104 105 106

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

		this.registerListeners();
	}

	private registerListeners(): void {
		this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)));
	}

	private onConfigurationUpdated(config: IWorkbenchEditorConfiguration): void {
116 117 118
		if (config && config.workbench && config.workbench.editor) {
			this.editorOpenPositioning = config.workbench.editor.openPositioning;
		}
B
Benjamin Pasero 已提交
119 120
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return null;
206 207
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
279
					this.closeEditor(this.preview, !makeActive); // optimization to prevent multiple setActive() in one call
280
					this.splice(targetIndex, false, editor);
281
				}
B
Benjamin Pasero 已提交
282

B
Benjamin Pasero 已提交
283 284 285
				this.preview = editor;
			}

286 287
			// Listeners
			this.hookEditorListeners(editor);
288

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

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

		// Existing editor
		else {

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
347
	public closeEditor(editor: EditorInput, openNext = true): void {
B
Benjamin Pasero 已提交
348 349 350 351 352 353
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

		// Active Editor closed
B
Benjamin Pasero 已提交
354
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
355 356

			// More than one editor
B
Benjamin Pasero 已提交
357 358
			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 已提交
359 360 361 362 363 364 365 366 367
			}

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

		// Preview Editor closed
368
		let pinned = true;
B
Benjamin Pasero 已提交
369
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
370
			this.preview = null;
371
			pinned = false;
B
Benjamin Pasero 已提交
372 373
		}

B
Benjamin Pasero 已提交
374
		// Remove from arrays
B
Benjamin Pasero 已提交
375
		this.splice(index, true);
B
Benjamin Pasero 已提交
376 377

		// Event
378
		this.fireEvent(this._onEditorClosed, { editor, pinned, index }, true);
B
Benjamin Pasero 已提交
379 380
	}

B
Benjamin Pasero 已提交
381
	public closeEditors(except: EditorInput, direction?: Direction): void {
B
Benjamin Pasero 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
		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 已提交
403
			this.mru.filter(e => !this.matches(e, except)).forEach(e => this.closeEditor(e));
B
Benjamin Pasero 已提交
404 405 406
		}
	}

407 408 409
	public closeAllEditors(): void {

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

B
Benjamin Pasero 已提交
414 415 416 417 418 419 420 421 422 423 424
	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
425
		this.fireEvent(this._onEditorMoved, editor, true);
B
Benjamin Pasero 已提交
426 427
	}

B
Benjamin Pasero 已提交
428 429 430 431 432 433
	public setActive(editor: EditorInput): void {
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}

B
Benjamin Pasero 已提交
434
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
435 436 437 438 439
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
440
		// Bring to front in MRU list
B
Benjamin Pasero 已提交
441 442
		this.setMostRecentlyUsed(editor);

B
Benjamin Pasero 已提交
443
		// Event
444
		this.fireEvent(this._onEditorActivated, editor, false);
B
Benjamin Pasero 已提交
445 446 447
	}

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

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

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

		// Event
461
		this.fireEvent(this._onEditorPinned, editor, false);
B
Benjamin Pasero 已提交
462 463
	}

B
Benjamin Pasero 已提交
464
	public unpin(editor: EditorInput): void {
465 466 467 468
		const index = this.indexOf(editor);
		if (index === -1) {
			return; // not found
		}
469

B
Benjamin Pasero 已提交
470 471 472 473 474 475 476 477 478
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

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

		// Event
479
		this.fireEvent(this._onEditorUnpinned, editor, false);
B
Benjamin Pasero 已提交
480 481 482 483 484

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

485 486 487 488 489 490 491 492 493 494 495 496 497 498
	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 已提交
499 500 501 502 503 504 505
			return false; // editor not found
		}

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

B
Benjamin Pasero 已提交
506
		return !this.matches(this.preview, editor);
B
Benjamin Pasero 已提交
507 508
	}

509
	private fireEvent(emitter: Emitter<EditorInput | GroupEvent>, arg2: EditorInput | GroupEvent, isStructuralChange: boolean): void {
510
		emitter.fire(arg2);
511 512 513 514 515 516

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

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

B
Benjamin Pasero 已提交
522
		const args: any[] = [index, del ? 1 : 0];
523 524 525
		if (editor) {
			args.push(editor);
		}
B
Benjamin Pasero 已提交
526

527
		// Perform on editors array
528
		this.editors.splice.apply(this.editors, args);
B
Benjamin Pasero 已提交
529

530
		// Add
B
Benjamin Pasero 已提交
531
		if (!del && editor) {
532 533
			this.mru.push(editor); // make it LRU editor
			this.updateResourceMap(editor, false /* add */); // add new to resource map
B
Benjamin Pasero 已提交
534 535
		}

B
Benjamin Pasero 已提交
536
		// Remove / Replace
B
Benjamin Pasero 已提交
537
		else {
B
Benjamin Pasero 已提交
538 539
			const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);

540
			// Remove
B
Benjamin Pasero 已提交
541
			if (del && !editor) {
542 543
				this.mru.splice(indexInMRU, 1); // remove from MRU
				this.updateResourceMap(editorToDeleteOrReplace, true /* delete */); // remove from resource map
B
Benjamin Pasero 已提交
544 545
			}

546
			// Replace
B
Benjamin Pasero 已提交
547
			else {
548 549 550
				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 已提交
551
			}
B
Benjamin Pasero 已提交
552 553 554
		}
	}

555
	private updateResourceMap(editor: EditorInput, remove: boolean): void {
556
		const resource = toResource(editor, { supportSideBySide: true });
557
		if (resource) {
558 559 560 561

			// 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
			let counter = this.mapResourceToEditorCount[resource.toString()] || 0;
B
Benjamin Pasero 已提交
562
			let newCounter: number;
563 564 565 566 567 568 569 570 571
			if (remove) {
				if (counter > 1) {
					newCounter = counter - 1;
				}
			} else {
				newCounter = counter + 1;
			}

			this.mapResourceToEditorCount[resource.toString()] = newCounter;
572 573 574
		}
	}

575
	public indexOf(candidate: EditorInput, editors = this.editors): number {
B
Benjamin Pasero 已提交
576 577
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
578 579
		}

B
Benjamin Pasero 已提交
580
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
581
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
582 583 584 585 586 587
				return i;
			}
		}

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

589 590 591 592 593 594 595
	public contains(candidate: EditorInput): boolean;
	public contains(resource: URI): boolean;
	public contains(arg1: any): boolean {
		if (arg1 instanceof EditorInput) {
			return this.indexOf(arg1) >= 0;
		}

596 597 598
		const counter = this.mapResourceToEditorCount[(<URI>arg1).toString()];

		return typeof counter === 'number' && counter > 0;
599 600
	}

B
Benjamin Pasero 已提交
601 602 603 604 605 606 607 608 609
	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 已提交
610
		this.mru.splice(mruIndex, 1);
B
Benjamin Pasero 已提交
611

B
Benjamin Pasero 已提交
612
		// Set editor to front
B
Benjamin Pasero 已提交
613 614
		this.mru.unshift(editor);
	}
B
Benjamin Pasero 已提交
615 616 617 618

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

	public serialize(): ISerializedEditorGroup {
621
		const registry = Registry.as<IEditorRegistry>(Extensions.Editors);
622 623 624 625 626 627

		// 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 已提交
628
		let serializablePreviewIndex: number;
629
		this.editors.forEach(e => {
630
			let factory = registry.getEditorInputFactory(e.getTypeId());
631 632 633
			if (factory) {
				let value = factory.serialize(e);
				if (typeof value === 'string') {
634
					serializedEditors.push({ id: e.getTypeId(), value });
635
					serializableEditors.push(e);
B
Benjamin Pasero 已提交
636 637 638 639

					if (this.preview === e) {
						serializablePreviewIndex = serializableEditors.length - 1;
					}
640 641 642 643
				}
			}
		});

B
Benjamin Pasero 已提交
644
		const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0);
645 646 647 648 649

		return {
			label: this.label,
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
650
			preview: serializablePreviewIndex,
651 652 653 654
		};
	}

	private deserialize(data: ISerializedEditorGroup): void {
655
		const registry = Registry.as<IEditorRegistry>(Extensions.Editors);
656 657

		this._label = data.label;
658
		this.editors = data.editors.map(e => {
B
Benjamin Pasero 已提交
659 660 661
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
				const editor = factory.deserialize(this.instantiationService, e.value);
662

B
Benjamin Pasero 已提交
663 664
				this.hookEditorListeners(editor);
				this.updateResourceMap(editor, false /* add */);
665

B
Benjamin Pasero 已提交
666 667 668 669 670
				return editor;
			}

			return null;
		}).filter(e => !!e);
671 672 673 674
		this.mru = data.mru.map(i => this.editors[i]);
		this.active = this.mru[0];
		this.preview = this.editors[data.preview];
	}
B
Benjamin Pasero 已提交
675 676

	public dispose(): void {
B
Benjamin Pasero 已提交
677
		dispose(this.toDispose);
B
Benjamin Pasero 已提交
678
	}
679 680 681 682 683
}

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

export class EditorStacksModel implements IEditorStacksModel {
687 688 689 690

	private static STORAGE_KEY = 'editorStacks.model';

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

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

	private _onGroupOpened: Emitter<EditorGroup>;
	private _onGroupClosed: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
699
	private _onGroupMoved: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
700
	private _onGroupActivated: Emitter<EditorGroup>;
B
Benjamin Pasero 已提交
701
	private _onGroupDeactivated: Emitter<EditorGroup>;
702
	private _onGroupRenamed: Emitter<EditorGroup>;
703 704
	private _onEditorDisposed: Emitter<EditorIdentifier>;
	private _onEditorDirty: Emitter<EditorIdentifier>;
B
Benjamin Pasero 已提交
705
	private _onEditorLabelChange: Emitter<EditorIdentifier>;
706
	private _onEditorOpened: Emitter<EditorIdentifier>;
707
	private _onEditorClosed: Emitter<GroupEvent>;
708
	private _onModelChanged: Emitter<IStacksModelChangeEvent>;
B
Benjamin Pasero 已提交
709

710
	constructor(
711
		private restoreFromStorage: boolean,
712 713
		@IStorageService private storageService: IStorageService,
		@ILifecycleService private lifecycleService: ILifecycleService,
B
Benjamin Pasero 已提交
714
		@IInstantiationService private instantiationService: IInstantiationService
715 716 717
	) {
		this.toDispose = [];

B
Benjamin Pasero 已提交
718
		this._groups = [];
719
		this.groupToIdentifier = Object.create(null);
B
Benjamin Pasero 已提交
720

B
Benjamin Pasero 已提交
721 722 723
		this._onGroupOpened = new Emitter<EditorGroup>();
		this._onGroupClosed = new Emitter<EditorGroup>();
		this._onGroupActivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
724
		this._onGroupDeactivated = new Emitter<EditorGroup>();
B
Benjamin Pasero 已提交
725
		this._onGroupMoved = new Emitter<EditorGroup>();
726
		this._onGroupRenamed = new Emitter<EditorGroup>();
727
		this._onModelChanged = new Emitter<IStacksModelChangeEvent>();
728 729
		this._onEditorDisposed = new Emitter<EditorIdentifier>();
		this._onEditorDirty = new Emitter<EditorIdentifier>();
B
Benjamin Pasero 已提交
730
		this._onEditorLabelChange = new Emitter<EditorIdentifier>();
731
		this._onEditorOpened = new Emitter<EditorIdentifier>();
732
		this._onEditorClosed = new Emitter<GroupEvent>();
733

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

736 737 738 739
		this.registerListeners();
	}

	private registerListeners(): void {
740
		this.toDispose.push(this.lifecycleService.onShutdown(reason => this.onShutdown()));
B
Benjamin Pasero 已提交
741 742 743 744 745 746 747 748 749 750 751 752 753 754
	}

	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 已提交
755 756 757 758
	public get onGroupDeactivated(): Event<EditorGroup> {
		return this._onGroupDeactivated.event;
	}

B
Benjamin Pasero 已提交
759 760 761 762
	public get onGroupMoved(): Event<EditorGroup> {
		return this._onGroupMoved.event;
	}

763 764 765 766
	public get onGroupRenamed(): Event<EditorGroup> {
		return this._onGroupRenamed.event;
	}

767
	public get onModelChanged(): Event<IStacksModelChangeEvent> {
768 769 770
		return this._onModelChanged.event;
	}

771
	public get onEditorDisposed(): Event<EditorIdentifier> {
772 773 774
		return this._onEditorDisposed.event;
	}

775
	public get onEditorDirty(): Event<EditorIdentifier> {
776 777 778
		return this._onEditorDirty.event;
	}

B
Benjamin Pasero 已提交
779 780 781 782
	public get onEditorLabelChange(): Event<EditorIdentifier> {
		return this._onEditorLabelChange.event;
	}

783 784 785 786
	public get onEditorOpened(): Event<EditorIdentifier> {
		return this._onEditorOpened.event;
	}

787 788 789 790
	public get onEditorClosed(): Event<GroupEvent> {
		return this._onEditorClosed.event;
	}

B
Benjamin Pasero 已提交
791
	public get groups(): EditorGroup[] {
B
Benjamin Pasero 已提交
792 793
		this.ensureLoaded();

B
Benjamin Pasero 已提交
794 795 796 797
		return this._groups.slice(0);
	}

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

800
		return this._activeGroup;
B
Benjamin Pasero 已提交
801 802
	}

B
Benjamin Pasero 已提交
803 804 805 806
	public isActive(group: EditorGroup): boolean {
		return this.activeGroup === group;
	}

807
	public getGroup(id: GroupIdentifier): EditorGroup {
B
Benjamin Pasero 已提交
808 809
		this.ensureLoaded();

810 811 812
		return this.groupToIdentifier[id];
	}

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

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

818 819 820 821 822
		// Direct index provided
		if (typeof index === 'number') {
			this._groups[index] = group;
		}

B
Benjamin Pasero 已提交
823
		// First group
824
		else if (!this._activeGroup) {
B
Benjamin Pasero 已提交
825 826 827
			this._groups.push(group);
		}

828
		// Subsequent group (open to the right of active one)
B
Benjamin Pasero 已提交
829
		else {
830
			this._groups.splice(this.indexOf(this._activeGroup) + 1, 0, group);
B
Benjamin Pasero 已提交
831 832 833
		}

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

836
		// Activate if we are first or set to activate groups
837
		if (!this._activeGroup || activate) {
838 839
			this.setActive(group);
		}
B
Benjamin Pasero 已提交
840 841 842 843

		return group;
	}

844
	public renameGroup(group: EditorGroup, label: string): void {
B
Benjamin Pasero 已提交
845 846
		this.ensureLoaded();

B
Benjamin Pasero 已提交
847
		if (group.label !== label) {
B
Benjamin Pasero 已提交
848
			group.label = label;
849
			this.fireEvent(this._onGroupRenamed, group, false);
B
Benjamin Pasero 已提交
850
		}
B
Benjamin Pasero 已提交
851
	}
852

B
Benjamin Pasero 已提交
853
	public closeGroup(group: EditorGroup): void {
B
Benjamin Pasero 已提交
854 855
		this.ensureLoaded();

B
Benjamin Pasero 已提交
856 857 858 859 860 861
		const index = this.indexOf(group);
		if (index < 0) {
			return; // group does not exist
		}

		// Active group closed: Find a new active one to the right
862
		if (group === this._activeGroup) {
B
Benjamin Pasero 已提交
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877

			// 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 {
878
				this._activeGroup = null;
B
Benjamin Pasero 已提交
879 880 881
			}
		}

B
Benjamin Pasero 已提交
882
		// Close Editors in Group first and dispose then
883
		group.closeAllEditors();
B
Benjamin Pasero 已提交
884
		group.dispose();
885

B
Benjamin Pasero 已提交
886 887
		// Splice from groups
		this._groups.splice(index, 1);
888
		this.groupToIdentifier[group.id] = void 0;
B
Benjamin Pasero 已提交
889

890
		// Events
891
		this.fireEvent(this._onGroupClosed, group, true);
892 893 894
		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 已提交
895 896
	}

B
Benjamin Pasero 已提交
897
	public closeGroups(except?: EditorGroup): void {
B
Benjamin Pasero 已提交
898
		this.ensureLoaded();
899 900

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

		// Close active unless configured to skip
904 905
		if (this._activeGroup !== except) {
			this.closeGroup(this._activeGroup);
B
Benjamin Pasero 已提交
906
		}
907 908
	}

B
Benjamin Pasero 已提交
909
	public setActive(group: EditorGroup): void {
B
Benjamin Pasero 已提交
910 911
		this.ensureLoaded();

912
		if (this._activeGroup === group) {
913 914 915
			return;
		}

B
Benjamin Pasero 已提交
916
		const oldActiveGroup = this._activeGroup;
917
		this._activeGroup = group;
B
Benjamin Pasero 已提交
918

919
		this.fireEvent(this._onGroupActivated, group, false);
B
Benjamin Pasero 已提交
920 921 922
		if (oldActiveGroup) {
			this.fireEvent(this._onGroupDeactivated, oldActiveGroup, false);
		}
B
Benjamin Pasero 已提交
923 924
	}

B
Benjamin Pasero 已提交
925
	public moveGroup(group: EditorGroup, toIndex: number): void {
B
Benjamin Pasero 已提交
926 927
		this.ensureLoaded();

B
Benjamin Pasero 已提交
928 929 930 931 932 933 934 935 936 937
		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 已提交
938 939 940
		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 已提交
941 942
	}

B
Benjamin Pasero 已提交
943 944 945
	private indexOf(group: EditorGroup): number {
		return this._groups.indexOf(group);
	}
946

947 948
	public positionOfGroup(group: IEditorGroup): Position;
	public positionOfGroup(group: EditorGroup): Position;
B
Benjamin Pasero 已提交
949 950 951
	public positionOfGroup(group: EditorGroup): Position {
		return this.indexOf(group);
	}
B
Benjamin Pasero 已提交
952 953

	public groupAt(position: Position): EditorGroup {
954 955
		this.ensureLoaded();

B
Benjamin Pasero 已提交
956 957
		return this._groups[position];
	}
B
Benjamin Pasero 已提交
958

959
	public next(jumpGroups: boolean, cycleAtEnd = true): IEditorIdentifier {
960 961 962 963 964 965 966 967 968 969 970 971 972
		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) };
		}

973 974
		// Return first if we are not jumping groups
		if (!jumpGroups) {
975 976 977
			if (!cycleAtEnd) {
				return null;
			}
978 979 980
			return { group: this.activeGroup, editor: this.activeGroup.getEditor(0) };
		}

981 982 983 984 985 986 987
		// 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) };
		}

988 989 990 991 992
		// Return null if we are not cycling at the end
		if (!cycleAtEnd) {
			return null;
		}

993 994 995 996 997
		// Return first in first group
		const firstGroup = this.groups[0];
		return { group: firstGroup, editor: firstGroup.getEditor(0) };
	}

998
	public previous(jumpGroups: boolean, cycleAtStart = true): IEditorIdentifier {
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
		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) };
		}

1012 1013
		// Return last if we are not jumping groups
		if (!jumpGroups) {
1014 1015 1016
			if (!cycleAtStart) {
				return null;
			}
1017 1018 1019
			return { group: this.activeGroup, editor: this.activeGroup.getEditor(this.activeGroup.count - 1) };
		}

1020 1021 1022 1023 1024 1025 1026
		// 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) };
		}

1027 1028 1029 1030 1031
		// Return null if we are not cycling at the start
		if (!cycleAtStart) {
			return null;
		}

1032 1033 1034 1035 1036
		// Return last in last group
		const lastGroup = this.groups[this.groups.length - 1];
		return { group: lastGroup, editor: lastGroup.getEditor(lastGroup.count - 1) };
	}

1037
	private save(): void {
1038 1039 1040 1041 1042 1043
		const serialized = this.serialize();

		this.storageService.store(EditorStacksModel.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE);
	}

	private serialize(): ISerializedEditorStacksModel {
1044

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

1048
		// Only consider active index if we do not have empty groups
B
Benjamin Pasero 已提交
1049 1050 1051 1052 1053 1054 1055
		let serializableActiveIndex: number;
		if (serializableGroups.length > 0) {
			if (serializableGroups.length === this._groups.length) {
				serializableActiveIndex = this.indexOf(this._activeGroup);
			} else {
				serializableActiveIndex = 0;
			}
1056 1057
		}

1058
		return {
1059
			groups: serializableGroups,
1060
			active: serializableActiveIndex
1061 1062 1063
		};
	}

1064
	private fireEvent(emitter: Emitter<EditorGroup>, group: EditorGroup, isStructuralChange: boolean): void {
1065
		emitter.fire(group);
1066
		this._onModelChanged.fire({ group, structural: isStructuralChange });
1067 1068
	}

B
Benjamin Pasero 已提交
1069 1070 1071 1072 1073 1074 1075
	private ensureLoaded(): void {
		if (!this.loaded) {
			this.loaded = true;
			this.load();
		}
	}

1076
	private load(): void {
1077
		if (!this.restoreFromStorage) {
B
Benjamin Pasero 已提交
1078 1079 1080
			return; // do not load from last session if the user explicitly asks to open a set of files
		}

1081 1082 1083 1084
		const modelRaw = this.storageService.get(EditorStacksModel.STORAGE_KEY, StorageScope.WORKSPACE);
		if (modelRaw) {
			const serialized: ISerializedEditorStacksModel = JSON.parse(modelRaw);

1085
			const invalidId = this.doValidate(serialized);
B
Benjamin Pasero 已提交
1086
			if (invalidId) {
B
Benjamin Pasero 已提交
1087 1088
				console.warn(`Ignoring invalid stacks model (Error code: ${invalidId}): ${JSON.stringify(serialized)}`);
				console.warn(serialized);
B
Benjamin Pasero 已提交
1089 1090 1091
				return;
			}

1092
			this._groups = serialized.groups.map(s => this.doCreateGroup(s));
1093
			this._activeGroup = this._groups[serialized.active];
1094 1095 1096
		}
	}

1097
	private doValidate(serialized: ISerializedEditorStacksModel): number {
B
Benjamin Pasero 已提交
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
		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
		}

1110
		if (serialized.groups.some(g => !g.editors.length)) {
B
Benjamin Pasero 已提交
1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128
			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;
	}

1129 1130 1131
	private doCreateGroup(arg1: string | ISerializedEditorGroup): EditorGroup {
		const group = this.instantiationService.createInstance(EditorGroup, arg1);

1132 1133
		this.groupToIdentifier[group.id] = group;

1134
		// Funnel editor changes in the group through our event aggregator
1135
		const unbind: IDisposable[] = [];
1136 1137
		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 已提交
1138
		unbind.push(group.onEditorOpened(editor => this._onEditorOpened.fire({ editor, group })));
1139 1140 1141 1142
		unbind.push(group.onEditorClosed(event => {
			this.handleOnEditorClosed(event);
			this._onEditorClosed.fire(event);
		}));
1143 1144
		unbind.push(group.onEditorDisposed(editor => this._onEditorDisposed.fire({ editor, group })));
		unbind.push(group.onEditorDirty(editor => this._onEditorDirty.fire({ editor, group })));
B
Benjamin Pasero 已提交
1145
		unbind.push(group.onEditorLabelChange(editor => this._onEditorLabelChange.fire({ editor, group })));
1146
		unbind.push(this.onGroupClosed(g => {
1147
			if (g === group) {
1148
				dispose(unbind);
1149
			}
1150
		}));
1151 1152 1153 1154

		return group;
	}

1155
	private handleOnEditorClosed(event: GroupEvent): void {
1156 1157 1158 1159 1160 1161
		const editor = event.editor;

		// Close the editor when it is no longer open in any group
		if (!this.isOpen(editor)) {
			editor.close();

S
Sandeep Somavarapu 已提交
1162 1163 1164
			// Also take care of side by side editor inputs that wrap around 2 editors
			if (editor instanceof SideBySideEditorInput) {
				[editor.master, editor.details].forEach(editor => {
1165 1166 1167 1168 1169
					if (!this.isOpen(editor)) {
						editor.close();
					}
				});
			}
1170 1171 1172
		}
	}

1173 1174 1175
	public isOpen(resource: URI): boolean;
	public isOpen(editor: EditorInput): boolean;
	public isOpen(arg1: any): boolean {
1176 1177
		return this._groups.some(group => group.contains(arg1));
	}
1178

1179 1180 1181 1182
	public count(resource: URI): number;
	public count(editor: EditorInput): number;
	public count(arg1: any): number {
		return this._groups.filter(group => group.contains(arg1)).length;
1183 1184
	}

1185 1186 1187 1188 1189
	private onShutdown(): void {
		this.save();

		dispose(this.toDispose);
	}
1190

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
	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!');
		}
	}

1202
	public toString(): string {
B
Benjamin Pasero 已提交
1203 1204
		this.ensureLoaded();

1205 1206
		const lines: string[] = [];

B
Benjamin Pasero 已提交
1207 1208 1209 1210
		if (!this.groups.length) {
			return '<No Groups>';
		}

1211
		this.groups.forEach(g => {
B
Benjamin Pasero 已提交
1212 1213
			let label = `Group: ${g.label}`;

1214
			if (this._activeGroup === g) {
B
Benjamin Pasero 已提交
1215
				label = `${label} [active]`;
1216 1217
			}

B
Benjamin Pasero 已提交
1218 1219
			lines.push(label);

1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236
			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 已提交
1237
}