editorGroup.ts 21.9 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 23 24
	editor: EditorInput;
}

25
export interface EditorIdentifier extends IEditorIdentifier {
I
isidor 已提交
26
	groupId: GroupIdentifier;
27
	editor: EditorInput;
28 29
}

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

37
export interface ISerializedEditorInput {
38 39 40 41
	id: string;
	value: string;
}

42
export interface ISerializedEditorGroup {
43
	id: number;
44 45
	editors: ISerializedEditorInput[];
	mru: number[];
M
Matt Bierner 已提交
46
	preview?: number;
47
	sticky?: number;
48 49
}

B
Benjamin Pasero 已提交
50 51
export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
	const group = obj as ISerializedEditorGroup;
52 53 54 55

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

B
Benjamin Pasero 已提交
56
export class EditorGroup extends Disposable {
57 58 59

	private static IDS = 0;

60 61
	//#region events

62 63
	private readonly _onDidActivateEditor = this._register(new Emitter<EditorInput>());
	readonly onDidActivateEditor = this._onDidActivateEditor.event;
64

65 66
	private readonly _onDidOpenEditor = this._register(new Emitter<EditorInput>());
	readonly onDidOpenEditor = this._onDidOpenEditor.event;
67

68 69
	private readonly _onDidCloseEditor = this._register(new Emitter<EditorCloseEvent>());
	readonly onDidCloseEditor = this._onDidCloseEditor.event;
70

71 72
	private readonly _onDidDisposeEditor = this._register(new Emitter<EditorInput>());
	readonly onDidDisposeEditor = this._onDidDisposeEditor.event;
73

74 75
	private readonly _onDidChangeEditorDirty = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorDirty = this._onDidChangeEditorDirty.event;
76

77 78
	private readonly _onDidChangeEditorLabel = this._register(new Emitter<EditorInput>());
	readonly onDidEditorLabelChange = this._onDidChangeEditorLabel.event;
79

80 81
	private readonly _onDidMoveEditor = this._register(new Emitter<EditorInput>());
	readonly onDidMoveEditor = this._onDidMoveEditor.event;
82

83 84
	private readonly _onDidChangeEditorPinned = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorPinned = this._onDidChangeEditorPinned.event;
85

86 87 88
	private readonly _onDidChangeEditorSticky = this._register(new Emitter<EditorInput>());
	readonly onDidChangeEditorSticky = this._onDidChangeEditorSticky.event;

89
	//#endregion
B
Benjamin Pasero 已提交
90

B
Benjamin Pasero 已提交
91
	private _id: GroupIdentifier;
B
Benjamin Pasero 已提交
92
	get id(): GroupIdentifier { return this._id; }
93

94 95
	private editors: EditorInput[] = [];
	private mru: EditorInput[] = [];
96

B
Benjamin Pasero 已提交
97 98
	private preview: EditorInput | null = null; // editor in preview state
	private active: EditorInput | null = null;  // editor in active state
99
	private sticky: number = -1; // index of first editor in sticky state
100

B
Benjamin Pasero 已提交
101 102
	private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
	private focusRecentEditorAfterClose: boolean | undefined;
103

104
	constructor(
105
		labelOrSerializedGroup: ISerializedEditorGroup | undefined,
106 107
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IConfigurationService private readonly configurationService: IConfigurationService
108
	) {
109 110
		super();

111
		if (isSerializedEditorGroup(labelOrSerializedGroup)) {
B
Benjamin Pasero 已提交
112
			this._id = this.deserialize(labelOrSerializedGroup);
113
		} else {
114
			this._id = EditorGroup.IDS++;
115
		}
116

117
		this.onConfigurationUpdated();
118 119 120 121
		this.registerListeners();
	}

	private registerListeners(): void {
122
		this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
123 124
	}

125
	private onConfigurationUpdated(): void {
126
		this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
127
		this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
128 129 130 131 132 133

		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 已提交
134 135
	}

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

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	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 已提交
159 160
	}

B
Benjamin Pasero 已提交
161 162
	getEditorByIndex(index: number): EditorInput | undefined {
		return this.editors[index];
163 164
	}

M
Matt Bierner 已提交
165
	get activeEditor(): EditorInput | null {
B
Benjamin Pasero 已提交
166 167 168
		return this.active;
	}

169
	isActive(editor: EditorInput): boolean {
B
Benjamin Pasero 已提交
170
		return this.matches(this.active, editor);
B
Benjamin Pasero 已提交
171 172
	}

M
Matt Bierner 已提交
173
	get previewEditor(): EditorInput | null {
B
Benjamin Pasero 已提交
174 175 176
		return this.preview;
	}

177
	openEditor(candidate: EditorInput, options?: IEditorOpenOptions): EditorInput {
178 179
		const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
		const makePinned = options?.pinned || options?.sticky;
B
Benjamin Pasero 已提交
180
		const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
B
Benjamin Pasero 已提交
181

182
		const existingEditorAndIndex = this.findEditor(candidate);
183

B
Benjamin Pasero 已提交
184
		// New editor
185
		if (!existingEditorAndIndex) {
186
			const newEditor = candidate;
187
			const indexOfActive = this.indexOf(this.active);
B
Benjamin Pasero 已提交
188

189
			// Insert into specific position
190
			let targetIndex: number;
191 192 193
			if (options && typeof options.index === 'number') {
				targetIndex = options.index;
			}
B
Benjamin Pasero 已提交
194

195
			// Insert to the BEGINNING
196
			else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
197
				targetIndex = 0;
198 199 200 201 202 203

				// 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;
				}
204 205 206
			}

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

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
			// 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 已提交
232
				}
233
			}
B
Benjamin Pasero 已提交
234

235 236 237 238 239 240 241 242
			// 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;
				}
243 244
			}

245 246
			// Insert into our list of editors if pinned or we have no preview editor
			if (makePinned || !this.preview) {
247
				this.splice(targetIndex, false, newEditor);
B
Benjamin Pasero 已提交
248 249
			}

250 251
			// Handle preview
			if (!makePinned) {
B
Benjamin Pasero 已提交
252 253

				// Replace existing preview with this editor if we have a preview
254 255
				if (this.preview) {
					const indexOfPreview = this.indexOf(this.preview);
256 257
					if (targetIndex > indexOfPreview) {
						targetIndex--; // accomodate for the fact that the preview editor closes
258 259
					}

260
					this.replaceEditor(this.preview, newEditor, targetIndex, !makeActive);
261
				}
B
Benjamin Pasero 已提交
262

263
				this.preview = newEditor;
B
Benjamin Pasero 已提交
264 265
			}

266
			// Listeners
267
			this.registerEditorListeners(newEditor);
268

B
Benjamin Pasero 已提交
269
			// Event
270
			this._onDidOpenEditor.fire(newEditor);
B
Benjamin Pasero 已提交
271

272
			// Handle active
B
Benjamin Pasero 已提交
273
			if (makeActive) {
274
				this.doSetActive(newEditor);
B
Benjamin Pasero 已提交
275
			}
276 277

			return newEditor;
B
Benjamin Pasero 已提交
278 279 280 281
		}

		// Existing editor
		else {
282
			const [existingEditor] = existingEditorAndIndex;
B
Benjamin Pasero 已提交
283 284 285

			// Pin it
			if (makePinned) {
286
				this.doPin(existingEditor);
B
Benjamin Pasero 已提交
287 288 289 290
			}

			// Activate it
			if (makeActive) {
291
				this.doSetActive(existingEditor);
B
Benjamin Pasero 已提交
292
			}
B
Benjamin Pasero 已提交
293 294 295

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

299 300 301 302 303 304
			// 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));
			}

305
			return existingEditor;
B
Benjamin Pasero 已提交
306 307 308
		}
	}

B
Benjamin Pasero 已提交
309
	private registerEditorListeners(editor: EditorInput): void {
310
		const listeners = new DisposableStore();
311 312

		// Re-emit disposal of editor input as our own event
313
		listeners.add(Event.once(editor.onDispose)(() => {
314
			if (this.indexOf(editor) >= 0) {
315
				this._onDidDisposeEditor.fire(editor);
316
			}
317 318 319
		}));

		// Re-Emit dirty state changes
320
		listeners.add(editor.onDidChangeDirty(() => {
321
			this._onDidChangeEditorDirty.fire(editor);
322
		}));
323

B
Benjamin Pasero 已提交
324
		// Re-Emit label changes
325
		listeners.add(editor.onDidChangeLabel(() => {
326
			this._onDidChangeEditorLabel.fire(editor);
B
Benjamin Pasero 已提交
327 328
		}));

329
		// Clean up dispose listeners once the editor gets closed
330
		listeners.add(this.onDidCloseEditor(event => {
331
			if (event.editor.matches(editor)) {
332
				dispose(listeners);
333
			}
334
		}));
335 336
	}

B
Benjamin Pasero 已提交
337
	private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void {
338
		const event = this.doCloseEditor(toReplace, openNext, true); // optimization to prevent multiple setActive() in one call
339 340 341 342

		// 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 已提交
343
		this.splice(replaceIndex, false, replaceWith);
344 345

		if (event) {
346
			this._onDidCloseEditor.fire(event);
347 348 349
		}
	}

350
	closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined {
351
		const event = this.doCloseEditor(candidate, openNext, false);
352 353

		if (event) {
354
			this._onDidCloseEditor.fire(event);
355

356
			return event.editor;
357
		}
358

R
Rob Lourens 已提交
359
		return undefined;
360 361
	}

362
	private doCloseEditor(candidate: EditorInput, openNext: boolean, replaced: boolean): EditorCloseEvent | undefined {
363
		const index = this.indexOf(candidate);
B
Benjamin Pasero 已提交
364
		if (index === -1) {
365
			return undefined; // not found
B
Benjamin Pasero 已提交
366 367
		}

368
		const editor = this.editors[index];
369
		const sticky = this.isSticky(index);
370

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

			// More than one editor
B
Benjamin Pasero 已提交
375
			if (this.mru.length > 1) {
376
				let newActive: EditorInput;
377
				if (this.focusRecentEditorAfterClose) {
378
					newActive = this.mru[1]; // active editor is always first in MRU, so pick second editor after as new active
B
Benjamin Pasero 已提交
379
				} else {
380 381
					if (index === this.editors.length - 1) {
						newActive = this.editors[index - 1]; // last editor is closed, pick previous as new active
B
Benjamin Pasero 已提交
382
					} else {
383 384 385
						newActive = this.editors[index + 1]; // pick next editor as new active
					}
				}
B
Benjamin Pasero 已提交
386

387
				this.doSetActive(newActive);
B
Benjamin Pasero 已提交
388 389 390 391 392 393 394 395 396
			}

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

		// Preview Editor closed
B
Benjamin Pasero 已提交
397
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
398 399 400
			this.preview = null;
		}

B
Benjamin Pasero 已提交
401
		// Remove from arrays
B
Benjamin Pasero 已提交
402
		this.splice(index, true);
B
Benjamin Pasero 已提交
403 404

		// Event
405
		return { editor, replaced, sticky, index, groupId: this.id };
B
Benjamin Pasero 已提交
406 407
	}

408
	moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
409

410 411 412 413 414
		// 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 已提交
415
		}
416

417
		const index = this.indexOf(candidate);
B
Benjamin Pasero 已提交
418
		if (index < 0 || toIndex === index) {
B
Benjamin Pasero 已提交
419 420 421
			return;
		}

422 423
		const editor = this.editors[index];

424 425 426 427 428 429 430 431 432 433
		// 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 已提交
434 435 436 437 438
		// Move
		this.editors.splice(index, 1);
		this.editors.splice(toIndex, 0, editor);

		// Event
439
		this._onDidMoveEditor.fire(editor);
440 441

		return editor;
B
Benjamin Pasero 已提交
442 443
	}

444
	setActive(candidate: EditorInput): EditorInput | undefined {
445 446
		const res = this.findEditor(candidate);
		if (!res) {
B
Benjamin Pasero 已提交
447 448 449
			return; // not found
		}

450 451
		const [editor] = res;

452
		this.doSetActive(editor);
453 454

		return editor;
455 456 457
	}

	private doSetActive(editor: EditorInput): void {
B
Benjamin Pasero 已提交
458
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
459 460 461 462 463
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
464
		// Bring to front in MRU list
465 466 467
		const mruIndex = this.indexOf(editor, this.mru);
		this.mru.splice(mruIndex, 1);
		this.mru.unshift(editor);
B
Benjamin Pasero 已提交
468

B
Benjamin Pasero 已提交
469
		// Event
470
		this._onDidActivateEditor.fire(editor);
B
Benjamin Pasero 已提交
471 472
	}

473
	pin(candidate: EditorInput): EditorInput | undefined {
474 475
		const res = this.findEditor(candidate);
		if (!res) {
476 477 478
			return; // not found
		}

479 480
		const [editor] = res;

481
		this.doPin(editor);
482 483

		return editor;
484 485 486
	}

	private doPin(editor: EditorInput): void {
487
		if (this.isPinned(editor)) {
B
Benjamin Pasero 已提交
488
			return; // can only pin a preview editor
B
Benjamin Pasero 已提交
489 490 491 492 493 494
		}

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

		// Event
495
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
496 497
	}

498
	unpin(candidate: EditorInput): EditorInput | undefined {
499 500
		const res = this.findEditor(candidate);
		if (!res) {
501 502
			return; // not found
		}
503

504 505
		const [editor] = res;

506
		this.doUnpin(editor);
507 508

		return editor;
509 510 511
	}

	private doUnpin(editor: EditorInput): void {
B
Benjamin Pasero 已提交
512 513 514 515 516 517 518 519 520
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

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

		// Event
521
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
522 523

		// Close old preview editor if any
M
Matt Bierner 已提交
524 525 526
		if (oldPreview) {
			this.closeEditor(oldPreview);
		}
B
Benjamin Pasero 已提交
527 528
	}

529 530 531 532 533 534 535 536 537 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
	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++;
566 567 568

		// Event
		this._onDidChangeEditorSticky.fire(editor);
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
	}

	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--;
594 595 596

		// Event
		this._onDidChangeEditorSticky.fire(editor);
597 598 599 600 601
	}

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

604
		let index: number;
605 606
		if (typeof candidateOrIndex === 'number') {
			index = candidateOrIndex;
607
		} else {
608
			index = this.indexOf(candidateOrIndex);
609 610
		}

611 612
		if (index < 0) {
			return false;
B
Benjamin Pasero 已提交
613 614
		}

615
		return index <= this.sticky;
B
Benjamin Pasero 已提交
616 617
	}

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

621 622 623 624 625
		// Perform on sticky index
		if (del && this.isSticky(index)) {
			this.sticky--;
		}

626
		// Perform on editors array
627
		if (editor) {
628 629 630
			this.editors.splice(index, del ? 1 : 0, editor);
		} else {
			this.editors.splice(index, del ? 1 : 0);
631
		}
B
Benjamin Pasero 已提交
632

633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
		// 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);
				}
650
			}
B
Benjamin Pasero 已提交
651

652 653 654
			// Remove / Replace
			else {
				const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
B
Benjamin Pasero 已提交
655

656 657 658 659
				// Remove
				if (del && !editor) {
					this.mru.splice(indexInMRU, 1); // remove from MRU
				}
B
Benjamin Pasero 已提交
660

661 662 663 664
				// Replace
				else if (del && editor) {
					this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
				}
B
Benjamin Pasero 已提交
665
			}
666 667
		}
	}
B
Benjamin Pasero 已提交
668

669
	indexOf(candidate: IEditorInput | null, editors = this.editors): number {
B
Benjamin Pasero 已提交
670 671
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
672 673
		}

B
Benjamin Pasero 已提交
674
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
675
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
676 677 678 679 680 681
				return i;
			}
		}

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

683
	private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
684 685 686 687 688
		const index = this.indexOf(candidate, this.editors);
		if (index === -1) {
			return undefined;
		}

689
		return [this.editors[index], index];
690 691
	}

692
	contains(candidate: EditorInput, options?: { supportSideBySide?: boolean, strictEquals?: boolean }): boolean {
693
		for (const editor of this.editors) {
694
			if (this.matches(editor, candidate, options?.strictEquals)) {
695 696
				return true;
			}
697

698
			if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) {
B
Benjamin Pasero 已提交
699
				if (this.matches(editor.primary, candidate, options?.strictEquals) || this.matches(editor.secondary, candidate, options?.strictEquals)) {
700 701 702
					return true;
				}
			}
703 704 705 706 707
		}

		return false;
	}

708
	private matches(editor: IEditorInput | null, candidate: IEditorInput | null, strictEquals?: boolean): boolean {
709 710 711 712
		if (!editor || !candidate) {
			return false;
		}

713 714 715 716
		if (strictEquals) {
			return editor === candidate;
		}

717
		return editor.matches(candidate);
B
Benjamin Pasero 已提交
718
	}
719

720
	clone(): EditorGroup {
R
Rob Lourens 已提交
721
		const group = this.instantiationService.createInstance(EditorGroup, undefined);
722 723 724 725
		group.editors = this.editors.slice(0);
		group.mru = this.mru.slice(0);
		group.preview = this.preview;
		group.active = this.active;
726
		group.sticky = this.sticky;
727 728 729 730 731

		return group;
	}

	serialize(): ISerializedEditorGroup {
732
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
733 734 735

		// Serialize all editor inputs so that we can store them.
		// Editors that cannot be serialized need to be ignored
736
		// from mru, active, preview and sticky if any.
737 738
		let serializableEditors: EditorInput[] = [];
		let serializedEditors: ISerializedEditorInput[] = [];
M
Matt Bierner 已提交
739
		let serializablePreviewIndex: number | undefined;
740 741 742 743 744 745 746
		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());
747
			if (factory) {
748 749 750
				const value = factory.serialize(editor);

				// Editor can be serialized
751
				if (typeof value === 'string') {
752 753 754 755
					canSerializeEditor = true;

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

757
					if (this.preview === editor) {
B
Benjamin Pasero 已提交
758 759
						serializablePreviewIndex = serializableEditors.length - 1;
					}
760
				}
761 762 763 764 765

				// Editor cannot be serialized
				else {
					canSerializeEditor = false;
				}
766 767
			}

768 769 770 771 772 773 774
			// 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);
775 776

		return {
777
			id: this.id,
778 779
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
780
			preview: serializablePreviewIndex,
781
			sticky: serializableSticky >= 0 ? serializableSticky : undefined
782 783 784
		};
	}

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

788 789 790 791 792 793 794 795
		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
		}

796 797 798
		this.editors = coalesce(data.editors.map((e, index) => {
			let editor: EditorInput | undefined = undefined;

B
Benjamin Pasero 已提交
799 800
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
801
				editor = factory.deserialize(this.instantiationService, e.value);
B
Benjamin Pasero 已提交
802 803 804
				if (editor) {
					this.registerEditorListeners(editor);
				}
805
			}
806

807 808
			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 已提交
809 810
			}

811
			return editor;
M
Matt Bierner 已提交
812
		}));
B
Benjamin Pasero 已提交
813

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

816
		this.active = this.mru[0];
B
Benjamin Pasero 已提交
817

M
Matt Bierner 已提交
818 819 820
		if (typeof data.preview === 'number') {
			this.preview = this.editors[data.preview];
		}
B
Benjamin Pasero 已提交
821

822 823 824 825
		if (typeof data.sticky === 'number') {
			this.sticky = data.sticky;
		}

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