editorGroup.ts 22.2 KB
Newer Older
B
Benjamin Pasero 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import { Event, Emitter } from 'vs/base/common/event';
7
import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor';
J
Johannes Rieken 已提交
8
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
9
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
10
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
11
import { Registry } from 'vs/platform/registry/common/platform';
M
Matt Bierner 已提交
12
import { coalesce } from 'vs/base/common/arrays';
B
Benjamin Pasero 已提交
13

B
Benjamin Pasero 已提交
14 15 16 17 18 19 20
const EditorOpenPositioning = {
	LEFT: 'left',
	RIGHT: 'right',
	FIRST: 'first',
	LAST: 'last'
};

B
Benjamin Pasero 已提交
21
export interface EditorCloseEvent extends IEditorCloseEvent {
22
	readonly editor: EditorInput;
23 24
}

25
export interface EditorIdentifier extends IEditorIdentifier {
26 27
	readonly groupId: GroupIdentifier;
	readonly editor: EditorInput;
28 29
}

B
Benjamin Pasero 已提交
30
export interface IEditorOpenOptions {
31
	readonly pinned?: boolean;
32
	sticky?: boolean;
B
Benjamin Pasero 已提交
33
	active?: boolean;
34 35 36 37 38 39
	readonly index?: number;
}

export interface IEditorOpenResult {
	readonly editor: EditorInput;
	readonly isNew: boolean;
B
Benjamin Pasero 已提交
40 41
}

42
export interface ISerializedEditorInput {
43 44
	readonly id: string;
	readonly value: string;
45 46
}

47
export interface ISerializedEditorGroup {
48 49 50 51
	readonly id: number;
	readonly editors: ISerializedEditorInput[];
	readonly mru: number[];
	readonly preview?: number;
52
	sticky?: number;
53 54
}

B
Benjamin Pasero 已提交
55 56
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
	const group = obj as ISerializedEditorGroup;
57

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

B
Benjamin Pasero 已提交
61
export class EditorGroup extends Disposable {
62 63 64

	private static IDS = 0;

65 66
	//#region events

67 68
	private readonly _onDidActivateEditor = this._register(new Emitter<EditorInput>());
	readonly onDidActivateEditor = this._onDidActivateEditor.event;
69

70 71
	private readonly _onDidOpenEditor = this._register(new Emitter<EditorInput>());
	readonly onDidOpenEditor = this._onDidOpenEditor.event;
72

73 74
	private readonly _onDidCloseEditor = this._register(new Emitter<EditorCloseEvent>());
	readonly onDidCloseEditor = this._onDidCloseEditor.event;
75

76 77
	private readonly _onDidDisposeEditor = this._register(new Emitter<EditorInput>());
	readonly onDidDisposeEditor = this._onDidDisposeEditor.event;
78

79 80
	private readonly _onDidChangeEditorDirty = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event;
81

82 83
	private readonly _onDidChangeEditorLabel = this._register(new Emitter<EditorInput>());
	readonly onDidEditorLabelChange = this._onDidChangeEditorLabel.event;
84

85 86
	private readonly _onDidMoveEditor = this._register(new Emitter<EditorInput>());
	readonly onDidMoveEditor = this._onDidMoveEditor.event;
87

88 89
	private readonly _onDidChangeEditorPinned = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event;
90

91 92 93
	private readonly _onDidChangeEditorSticky = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorSticky = this._onDidChangeEditorSticky.event;

94
	//#endregion
B
Benjamin Pasero 已提交
95

B
Benjamin Pasero 已提交
96
	private _id: GroupIdentifier;
B
Benjamin Pasero 已提交
97
	get id(): GroupIdentifier { return this._id; }
98

99 100
	private editors: EditorInput[] = [];
	private mru: EditorInput[] = [];
101

B
Benjamin Pasero 已提交
102 103
	private preview: EditorInput | null = null; // editor in preview state
	private active: EditorInput | null = null;  // editor in active state
104
	private sticky: number = -1; // index of first editor in sticky state
105

B
Benjamin Pasero 已提交
106 107
	private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
	private focusRecentEditorAfterClose: boolean | undefined;
108

109
	constructor(
110
		labelOrSerializedGroup: ISerializedEditorGroup | undefined,
111 112
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IConfigurationService private readonly configurationService: IConfigurationService
113
	) {
114 115
		super();

116
		if (isSerializedEditorGroup(labelOrSerializedGroup)) {
B
Benjamin Pasero 已提交
117
			this._id = this.deserialize(labelOrSerializedGroup);
118
		} else {
119
			this._id = EditorGroup.IDS++;
120
		}
121

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

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

130
	private onConfigurationUpdated(): void {
131
		this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
132
		this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
133 134 135 136 137 138

		if (this.configurationService.getValue('workbench.editor.showTabs') === false) {
			// Disabling tabs disables sticky editors until we support
			// an indication of sticky editors when tabs are disabled
			this.sticky = -1;
		}
B
Benjamin Pasero 已提交
139 140
	}

141
	get count(): number {
142 143 144
		return this.editors.length;
	}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	get stickyCount(): number {
		return this.sticky + 1;
	}

	getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): EditorInput[] {
		const editors = order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0);

		if (options?.excludeSticky) {

			// MRU: need to check for index on each
			if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) {
				return editors.filter(editor => !this.isSticky(editor));
			}

			// Sequential: simply start after sticky index
			return editors.slice(this.sticky + 1);
		}

		return editors;
B
Benjamin Pasero 已提交
164 165
	}

B
Benjamin Pasero 已提交
166 167
	getEditorByIndex(index: number): EditorInput | undefined {
		return this.editors[index];
168 169
	}

M
Matt Bierner 已提交
170
	get activeEditor(): EditorInput | null {
B
Benjamin Pasero 已提交
171 172 173
		return this.active;
	}

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

M
Matt Bierner 已提交
178
	get previewEditor(): EditorInput | null {
B
Benjamin Pasero 已提交
179 180 181
		return this.preview;
	}

182
	openEditor(candidate: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult {
183 184
		const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
		const makePinned = options?.pinned || options?.sticky;
B
Benjamin Pasero 已提交
185
		const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
B
Benjamin Pasero 已提交
186

187
		const existingEditorAndIndex = this.findEditor(candidate);
188

B
Benjamin Pasero 已提交
189
		// New editor
190
		if (!existingEditorAndIndex) {
191
			const newEditor = candidate;
192
			const indexOfActive = this.indexOf(this.active);
B
Benjamin Pasero 已提交
193

194
			// Insert into specific position
195
			let targetIndex: number;
196 197 198
			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
				targetIndex = 0;
203 204 205 206 207 208

				// Always make sure targetIndex is after sticky editors
				// unless we are explicitly told to make the editor sticky
				if (!makeSticky && this.isSticky(targetIndex)) {
					targetIndex = this.sticky + 1;
				}
209 210 211
			}

			// Insert to the END
212
			else if (this.editorOpenPositioning === EditorOpenPositioning.LAST) {
213
				targetIndex = this.editors.length;
214
			}
B
Benjamin Pasero 已提交
215

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
			// Insert to LEFT or RIGHT of active editor
			else {

				// Insert to the LEFT of active editor
				if (this.editorOpenPositioning === EditorOpenPositioning.LEFT) {
					if (indexOfActive === 0 || !this.editors.length) {
						targetIndex = 0; // to the left becoming first editor in list
					} else {
						targetIndex = indexOfActive; // to the left of active editor
					}
				}

				// Insert to the RIGHT of active editor
				else {
					targetIndex = indexOfActive + 1;
				}

				// Always make sure targetIndex is after sticky editors
				// unless we are explicitly told to make the editor sticky
				if (!makeSticky && this.isSticky(targetIndex)) {
					targetIndex = this.sticky + 1;
B
Benjamin Pasero 已提交
237
				}
238
			}
B
Benjamin Pasero 已提交
239

240 241 242 243 244 245 246 247
			// If the editor becomes sticky, increment the sticky index and adjust
			// the targetIndex to be at the end of sticky editors unless already.
			if (makeSticky) {
				this.sticky++;

				if (!this.isSticky(targetIndex)) {
					targetIndex = this.sticky;
				}
248 249
			}

250 251
			// Insert into our list of editors if pinned or we have no preview editor
			if (makePinned || !this.preview) {
252
				this.splice(targetIndex, false, newEditor);
B
Benjamin Pasero 已提交
253 254
			}

255 256
			// Handle preview
			if (!makePinned) {
B
Benjamin Pasero 已提交
257 258

				// Replace existing preview with this editor if we have a preview
259 260
				if (this.preview) {
					const indexOfPreview = this.indexOf(this.preview);
261 262
					if (targetIndex > indexOfPreview) {
						targetIndex--; // accomodate for the fact that the preview editor closes
263 264
					}

265
					this.replaceEditor(this.preview, newEditor, targetIndex, !makeActive);
266
				}
B
Benjamin Pasero 已提交
267

268
				this.preview = newEditor;
B
Benjamin Pasero 已提交
269 270
			}

271
			// Listeners
272
			this.registerEditorListeners(newEditor);
273

B
Benjamin Pasero 已提交
274
			// Event
275
			this._onDidOpenEditor.fire(newEditor);
B
Benjamin Pasero 已提交
276

277
			// Handle active
B
Benjamin Pasero 已提交
278
			if (makeActive) {
279
				this.doSetActive(newEditor);
B
Benjamin Pasero 已提交
280
			}
281

282 283 284 285
			return {
				editor: newEditor,
				isNew: true
			};
B
Benjamin Pasero 已提交
286 287 288 289
		}

		// Existing editor
		else {
290
			const [existingEditor] = existingEditorAndIndex;
B
Benjamin Pasero 已提交
291 292 293

			// Pin it
			if (makePinned) {
294
				this.doPin(existingEditor);
B
Benjamin Pasero 已提交
295 296 297 298
			}

			// Activate it
			if (makeActive) {
299
				this.doSetActive(existingEditor);
B
Benjamin Pasero 已提交
300
			}
B
Benjamin Pasero 已提交
301 302 303

			// Respect index
			if (options && typeof options.index === 'number') {
304
				this.moveEditor(existingEditor, options.index);
B
Benjamin Pasero 已提交
305
			}
306

307 308 309 310 311 312
			// Stick it (intentionally after the moveEditor call in case
			// the editor was already moved into the sticky range)
			if (makeSticky) {
				this.doStick(existingEditor, this.indexOf(existingEditor));
			}

313 314 315 316
			return {
				editor: existingEditor,
				isNew: false
			};
B
Benjamin Pasero 已提交
317 318 319
		}
	}

B
Benjamin Pasero 已提交
320
	private registerEditorListeners(editor: EditorInput): void {
321
		const listeners = new DisposableStore();
322 323

		// Re-emit disposal of editor input as our own event
324
		listeners.add(Event.once(editor.onDispose)(() => {
325
			if (this.indexOf(editor) >= 0) {
326
				this._onDidDisposeEditor.fire(editor);
327
			}
328 329 330
		}));

		// Re-Emit dirty state changes
331
		listeners.add(editor.onDidChangeDirty(() => {
332
			this._onDidChangeEditorDirty.fire(editor);
333
		}));
334

B
Benjamin Pasero 已提交
335
		// Re-Emit label changes
336
		listeners.add(editor.onDidChangeLabel(() => {
337
			this._onDidChangeEditorLabel.fire(editor);
B
Benjamin Pasero 已提交
338 339
		}));

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

B
Benjamin Pasero 已提交
348
	private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void {
349
		const event = this.doCloseEditor(toReplace, openNext, true); // optimization to prevent multiple setActive() in one call
350 351 352 353

		// 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.
B
Benjamin Pasero 已提交
354
		this.splice(replaceIndex, false, replaceWith);
355 356

		if (event) {
357
			this._onDidCloseEditor.fire(event);
358 359 360
		}
	}

361
	closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined {
362
		const event = this.doCloseEditor(candidate, openNext, false);
363 364

		if (event) {
365
			this._onDidCloseEditor.fire(event);
366

367
			return event.editor;
368
		}
369

R
Rob Lourens 已提交
370
		return undefined;
371 372
	}

373
	private doCloseEditor(candidate: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent | undefined {
374
		const index = this.indexOf(candidate);
B
Benjamin Pasero 已提交
375
		if (index === -1) {
376
			return undefined; // not found
B
Benjamin Pasero 已提交
377 378
		}

379
		const editor = this.editors[index];
380
		const sticky = this.isSticky(index);
381

B
Benjamin Pasero 已提交
382
		// Active Editor closed
B
Benjamin Pasero 已提交
383
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
384 385

			// More than one editor
B
Benjamin Pasero 已提交
386
			if (this.mru.length > 1) {
387
				let newActive: EditorInput;
388
				if (this.focusRecentEditorAfterClose) {
389
					newActive = this.mru[1]; // active editor is always first in MRU, so pick second editor after as new active
B
Benjamin Pasero 已提交
390
				} else {
391 392
					if (index === this.editors.length - 1) {
						newActive = this.editors[index - 1]; // last editor is closed, pick previous as new active
B
Benjamin Pasero 已提交
393
					} else {
394 395 396
						newActive = this.editors[index + 1]; // pick next editor as new active
					}
				}
B
Benjamin Pasero 已提交
397

398
				this.doSetActive(newActive);
B
Benjamin Pasero 已提交
399 400 401 402 403 404 405 406 407
			}

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

		// Preview Editor closed
B
Benjamin Pasero 已提交
408
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
409 410 411
			this.preview = null;
		}

B
Benjamin Pasero 已提交
412
		// Remove from arrays
B
Benjamin Pasero 已提交
413
		this.splice(index, true);
B
Benjamin Pasero 已提交
414 415

		// Event
416
		return { editor, replaced, sticky, index, groupId: this.id };
B
Benjamin Pasero 已提交
417 418
	}

419
	moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
420

421 422 423 424 425
		// Ensure toIndex is in bounds of our model
		if (toIndex >= this.editors.length) {
			toIndex = this.editors.length - 1;
		} else if (toIndex < 0) {
			toIndex = 0;
M
Matt Bierner 已提交
426
		}
427

428
		const index = this.indexOf(candidate);
B
Benjamin Pasero 已提交
429
		if (index < 0 || toIndex === index) {
B
Benjamin Pasero 已提交
430 431 432
			return;
		}

433 434
		const editor = this.editors[index];

435 436 437 438 439 440 441 442 443 444
		// Adjust sticky index: editor moved out of sticky state into unsticky state
		if (this.isSticky(index) && toIndex > this.sticky) {
			this.sticky--;
		}

		// ...or editor moved into sticky state from unsticky state
		else if (!this.isSticky(index) && toIndex <= this.sticky) {
			this.sticky++;
		}

B
Benjamin Pasero 已提交
445 446 447 448 449
		// Move
		this.editors.splice(index, 1);
		this.editors.splice(toIndex, 0, editor);

		// Event
450
		this._onDidMoveEditor.fire(editor);
451 452

		return editor;
B
Benjamin Pasero 已提交
453 454
	}

455
	setActive(candidate: EditorInput): EditorInput | undefined {
456 457
		const res = this.findEditor(candidate);
		if (!res) {
B
Benjamin Pasero 已提交
458 459 460
			return; // not found
		}

461 462
		const [editor] = res;

463
		this.doSetActive(editor);
464 465

		return editor;
466 467 468
	}

	private doSetActive(editor: EditorInput): void {
B
Benjamin Pasero 已提交
469
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
470 471 472 473 474
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
475
		// Bring to front in MRU list
476 477 478
		const mruIndex = this.indexOf(editor, this.mru);
		this.mru.splice(mruIndex, 1);
		this.mru.unshift(editor);
B
Benjamin Pasero 已提交
479

B
Benjamin Pasero 已提交
480
		// Event
481
		this._onDidActivateEditor.fire(editor);
B
Benjamin Pasero 已提交
482 483
	}

484
	pin(candidate: EditorInput): EditorInput | undefined {
485 486
		const res = this.findEditor(candidate);
		if (!res) {
487 488 489
			return; // not found
		}

490 491
		const [editor] = res;

492
		this.doPin(editor);
493 494

		return editor;
495 496 497
	}

	private doPin(editor: EditorInput): void {
498
		if (this.isPinned(editor)) {
B
Benjamin Pasero 已提交
499
			return; // can only pin a preview editor
B
Benjamin Pasero 已提交
500 501 502 503 504 505
		}

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

		// Event
506
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
507 508
	}

509
	unpin(candidate: EditorInput): EditorInput | undefined {
510 511
		const res = this.findEditor(candidate);
		if (!res) {
512 513
			return; // not found
		}
514

515 516
		const [editor] = res;

517
		this.doUnpin(editor);
518 519

		return editor;
520 521 522
	}

	private doUnpin(editor: EditorInput): void {
B
Benjamin Pasero 已提交
523 524 525 526 527 528 529 530 531
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

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

		// Event
532
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
533 534

		// Close old preview editor if any
M
Matt Bierner 已提交
535 536 537
		if (oldPreview) {
			this.closeEditor(oldPreview);
		}
B
Benjamin Pasero 已提交
538 539
	}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
	isPinned(editorOrIndex: EditorInput | number): boolean {
		let editor: EditorInput;
		if (typeof editorOrIndex === 'number') {
			editor = this.editors[editorOrIndex];
		} else {
			editor = editorOrIndex;
		}

		return !this.matches(this.preview, editor);
	}

	stick(candidate: EditorInput): EditorInput | undefined {
		const res = this.findEditor(candidate);
		if (!res) {
			return; // not found
		}

		const [editor, index] = res;

		this.doStick(editor, index);

		return editor;
	}

	private doStick(editor: EditorInput, index: number): void {
		if (this.isSticky(index)) {
			return; // can only stick a non-sticky editor
		}

		// Pin editor
		this.pin(editor);

		// Move editor to be the last sticky editor
		this.moveEditor(editor, this.sticky + 1);

		// Adjust sticky index
		this.sticky++;
577 578 579

		// Event
		this._onDidChangeEditorSticky.fire(editor);
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
	}

	unstick(candidate: EditorInput): EditorInput | undefined {
		const res = this.findEditor(candidate);
		if (!res) {
			return; // not found
		}

		const [editor, index] = res;

		this.doUnstick(editor, index);

		return editor;
	}

	private doUnstick(editor: EditorInput, index: number): void {
		if (!this.isSticky(index)) {
			return; // can only unstick a sticky editor
		}

		// Move editor to be the first non-sticky editor
		this.moveEditor(editor, this.sticky);

		// Adjust sticky index
		this.sticky--;
605 606 607

		// Event
		this._onDidChangeEditorSticky.fire(editor);
608 609 610 611 612
	}

	isSticky(candidateOrIndex: EditorInput | number): boolean {
		if (this.sticky < 0) {
			return false; // no sticky editor
B
Benjamin Pasero 已提交
613 614
		}

615
		let index: number;
616 617
		if (typeof candidateOrIndex === 'number') {
			index = candidateOrIndex;
618
		} else {
619
			index = this.indexOf(candidateOrIndex);
620 621
		}

622 623
		if (index < 0) {
			return false;
B
Benjamin Pasero 已提交
624 625
		}

626
		return index <= this.sticky;
B
Benjamin Pasero 已提交
627 628
	}

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

632 633 634 635 636
		// Perform on sticky index
		if (del && this.isSticky(index)) {
			this.sticky--;
		}

637
		// Perform on editors array
638
		if (editor) {
639 640 641
			this.editors.splice(index, del ? 1 : 0, editor);
		} else {
			this.editors.splice(index, del ? 1 : 0);
642
		}
B
Benjamin Pasero 已提交
643

644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
		// Perform on MRU
		{
			// Add
			if (!del && editor) {
				if (this.mru.length === 0) {
					// the list of most recent editors is empty
					// so this editor can only be the most recent
					this.mru.push(editor);
				} else {
					// we have most recent editors. as such we
					// put this newly opened editor right after
					// the current most recent one because it cannot
					// be the most recently active one unless
					// it becomes active. but it is still more
					// active then any other editor in the list.
					this.mru.splice(1, 0, editor);
				}
661
			}
B
Benjamin Pasero 已提交
662

663 664 665
			// Remove / Replace
			else {
				const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
B
Benjamin Pasero 已提交
666

667 668 669 670
				// Remove
				if (del && !editor) {
					this.mru.splice(indexInMRU, 1); // remove from MRU
				}
B
Benjamin Pasero 已提交
671

672 673 674 675
				// Replace
				else if (del && editor) {
					this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
				}
B
Benjamin Pasero 已提交
676
			}
677 678
		}
	}
B
Benjamin Pasero 已提交
679

680
	indexOf(candidate: IEditorInput | null, editors = this.editors): number {
B
Benjamin Pasero 已提交
681 682
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
683 684
		}

B
Benjamin Pasero 已提交
685
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
686
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
687 688 689 690 691 692
				return i;
			}
		}

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

694
	private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
695 696 697 698 699
		const index = this.indexOf(candidate, this.editors);
		if (index === -1) {
			return undefined;
		}

700
		return [this.editors[index], index];
701 702
	}

703
	contains(candidate: EditorInput, options?: { supportSideBySide?: boolean, strictEquals?: boolean }): boolean {
704
		for (const editor of this.editors) {
705
			if (this.matches(editor, candidate, options?.strictEquals)) {
706 707
				return true;
			}
708

709
			if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) {
B
Benjamin Pasero 已提交
710
				if (this.matches(editor.primary, candidate, options?.strictEquals) || this.matches(editor.secondary, candidate, options?.strictEquals)) {
711 712 713
					return true;
				}
			}
714 715 716 717 718
		}

		return false;
	}

719
	private matches(editor: IEditorInput | null, candidate: IEditorInput | null, strictEquals?: boolean): boolean {
720 721 722 723
		if (!editor || !candidate) {
			return false;
		}

724 725 726 727
		if (strictEquals) {
			return editor === candidate;
		}

728
		return editor.matches(candidate);
B
Benjamin Pasero 已提交
729
	}
730

731
	clone(): EditorGroup {
R
Rob Lourens 已提交
732
		const group = this.instantiationService.createInstance(EditorGroup, undefined);
733 734 735 736
		group.editors = this.editors.slice(0);
		group.mru = this.mru.slice(0);
		group.preview = this.preview;
		group.active = this.active;
737
		group.sticky = this.sticky;
738 739 740 741 742

		return group;
	}

	serialize(): ISerializedEditorGroup {
743
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
744 745 746

		// Serialize all editor inputs so that we can store them.
		// Editors that cannot be serialized need to be ignored
747
		// from mru, active, preview and sticky if any.
748 749
		let serializableEditors: EditorInput[] = [];
		let serializedEditors: ISerializedEditorInput[] = [];
M
Matt Bierner 已提交
750
		let serializablePreviewIndex: number | undefined;
751 752 753 754 755 756 757
		let serializableSticky = this.sticky;

		for (let i = 0; i < this.editors.length; i++) {
			const editor = this.editors[i];
			let canSerializeEditor = false;

			const factory = registry.getEditorInputFactory(editor.getTypeId());
758
			if (factory) {
759 760 761
				const value = factory.serialize(editor);

				// Editor can be serialized
762
				if (typeof value === 'string') {
763 764 765 766
					canSerializeEditor = true;

					serializedEditors.push({ id: editor.getTypeId(), value });
					serializableEditors.push(editor);
B
Benjamin Pasero 已提交
767

768
					if (this.preview === editor) {
B
Benjamin Pasero 已提交
769 770
						serializablePreviewIndex = serializableEditors.length - 1;
					}
771
				}
772 773 774 775 776

				// Editor cannot be serialized
				else {
					canSerializeEditor = false;
				}
777 778
			}

779 780 781 782 783 784 785
			// Adjust index of sticky editors if the editor cannot be serialized and is pinned
			if (!canSerializeEditor && this.isSticky(i)) {
				serializableSticky--;
			}
		}

		const serializableMru = this.mru.map(editor => this.indexOf(editor, serializableEditors)).filter(i => i >= 0);
786 787

		return {
788
			id: this.id,
789 790
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
791
			preview: serializablePreviewIndex,
792
			sticky: serializableSticky >= 0 ? serializableSticky : undefined
793 794 795
		};
	}

B
Benjamin Pasero 已提交
796
	private deserialize(data: ISerializedEditorGroup): number {
797
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
798

799 800 801 802 803 804 805 806
		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
		}

807 808 809
		this.editors = coalesce(data.editors.map((e, index) => {
			let editor: EditorInput | undefined = undefined;

B
Benjamin Pasero 已提交
810 811
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
812
				editor = factory.deserialize(this.instantiationService, e.value);
B
Benjamin Pasero 已提交
813 814 815
				if (editor) {
					this.registerEditorListeners(editor);
				}
816
			}
817

818 819
			if (!editor && typeof data.sticky === 'number' && index <= data.sticky) {
				data.sticky--; // if editor cannot be deserialized but was sticky, we need to decrease sticky index
B
Benjamin Pasero 已提交
820 821
			}

822
			return editor;
M
Matt Bierner 已提交
823
		}));
B
Benjamin Pasero 已提交
824

B
Benjamin Pasero 已提交
825
		this.mru = coalesce(data.mru.map(i => this.editors[i]));
B
Benjamin Pasero 已提交
826

827
		this.active = this.mru[0];
B
Benjamin Pasero 已提交
828

M
Matt Bierner 已提交
829 830 831
		if (typeof data.preview === 'number') {
			this.preview = this.editors[data.preview];
		}
B
Benjamin Pasero 已提交
832

833 834 835 836
		if (typeof data.sticky === 'number') {
			this.sticky = data.sticky;
		}

B
Benjamin Pasero 已提交
837
		return this._id;
838 839
	}
}