editorGroup.ts 22.1 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');
B
Benjamin Pasero 已提交
133 134
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

276 277 278 279
			return {
				editor: newEditor,
				isNew: true
			};
B
Benjamin Pasero 已提交
280 281 282 283
		}

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

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

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

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

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

307 308 309 310
			return {
				editor: existingEditor,
				isNew: false
			};
B
Benjamin Pasero 已提交
311 312 313
		}
	}

B
Benjamin Pasero 已提交
314
	private registerEditorListeners(editor: EditorInput): void {
315
		const listeners = new DisposableStore();
316 317

		// Re-emit disposal of editor input as our own event
318
		listeners.add(Event.once(editor.onDispose)(() => {
319
			if (this.indexOf(editor) >= 0) {
320
				this._onDidDisposeEditor.fire(editor);
321
			}
322 323 324
		}));

		// Re-Emit dirty state changes
325
		listeners.add(editor.onDidChangeDirty(() => {
326
			this._onDidChangeEditorDirty.fire(editor);
327
		}));
328

B
Benjamin Pasero 已提交
329
		// Re-Emit label changes
330
		listeners.add(editor.onDidChangeLabel(() => {
331
			this._onDidChangeEditorLabel.fire(editor);
B
Benjamin Pasero 已提交
332 333
		}));

334
		// Clean up dispose listeners once the editor gets closed
335
		listeners.add(this.onDidCloseEditor(event => {
336
			if (event.editor.matches(editor)) {
337
				dispose(listeners);
338
			}
339
		}));
340 341
	}

B
Benjamin Pasero 已提交
342
	private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void {
343
		const event = this.doCloseEditor(toReplace, openNext, true); // optimization to prevent multiple setActive() in one call
344 345 346 347

		// 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 已提交
348
		this.splice(replaceIndex, false, replaceWith);
349 350

		if (event) {
351
			this._onDidCloseEditor.fire(event);
352 353 354
		}
	}

355
	closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined {
356
		const event = this.doCloseEditor(candidate, openNext, false);
357 358

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

361
			return event.editor;
362
		}
363

R
Rob Lourens 已提交
364
		return undefined;
365 366
	}

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

373
		const editor = this.editors[index];
374
		const sticky = this.isSticky(index);
375

B
Benjamin Pasero 已提交
376
		// Active Editor closed
B
Benjamin Pasero 已提交
377
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
378 379

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

392
				this.doSetActive(newActive);
B
Benjamin Pasero 已提交
393 394 395 396 397 398 399 400 401
			}

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

		// Preview Editor closed
B
Benjamin Pasero 已提交
402
		if (this.matches(this.preview, editor)) {
B
Benjamin Pasero 已提交
403 404 405
			this.preview = null;
		}

B
Benjamin Pasero 已提交
406
		// Remove from arrays
B
Benjamin Pasero 已提交
407
		this.splice(index, true);
B
Benjamin Pasero 已提交
408 409

		// Event
410
		return { editor, replaced, sticky, index, groupId: this.id };
B
Benjamin Pasero 已提交
411 412
	}

413
	moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
414

415 416 417 418 419
		// 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 已提交
420
		}
421

422
		const index = this.indexOf(candidate);
B
Benjamin Pasero 已提交
423
		if (index < 0 || toIndex === index) {
B
Benjamin Pasero 已提交
424 425 426
			return;
		}

427 428
		const editor = this.editors[index];

429 430 431 432 433 434 435 436 437 438
		// 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 已提交
439 440 441 442 443
		// Move
		this.editors.splice(index, 1);
		this.editors.splice(toIndex, 0, editor);

		// Event
444
		this._onDidMoveEditor.fire(editor);
445 446

		return editor;
B
Benjamin Pasero 已提交
447 448
	}

449
	setActive(candidate: EditorInput): EditorInput | undefined {
450 451
		const res = this.findEditor(candidate);
		if (!res) {
B
Benjamin Pasero 已提交
452 453 454
			return; // not found
		}

455 456
		const [editor] = res;

457
		this.doSetActive(editor);
458 459

		return editor;
460 461 462
	}

	private doSetActive(editor: EditorInput): void {
B
Benjamin Pasero 已提交
463
		if (this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
464 465 466 467 468
			return; // already active
		}

		this.active = editor;

B
Benjamin Pasero 已提交
469
		// Bring to front in MRU list
470 471 472
		const mruIndex = this.indexOf(editor, this.mru);
		this.mru.splice(mruIndex, 1);
		this.mru.unshift(editor);
B
Benjamin Pasero 已提交
473

B
Benjamin Pasero 已提交
474
		// Event
475
		this._onDidActivateEditor.fire(editor);
B
Benjamin Pasero 已提交
476 477
	}

478
	pin(candidate: EditorInput): EditorInput | undefined {
479 480
		const res = this.findEditor(candidate);
		if (!res) {
481 482 483
			return; // not found
		}

484 485
		const [editor] = res;

486
		this.doPin(editor);
487 488

		return editor;
489 490 491
	}

	private doPin(editor: EditorInput): void {
492
		if (this.isPinned(editor)) {
B
Benjamin Pasero 已提交
493
			return; // can only pin a preview editor
B
Benjamin Pasero 已提交
494 495 496 497 498 499
		}

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

		// Event
500
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
501 502
	}

503
	unpin(candidate: EditorInput): EditorInput | undefined {
504 505
		const res = this.findEditor(candidate);
		if (!res) {
506 507
			return; // not found
		}
508

509 510
		const [editor] = res;

511
		this.doUnpin(editor);
512 513

		return editor;
514 515 516
	}

	private doUnpin(editor: EditorInput): void {
B
Benjamin Pasero 已提交
517 518 519 520 521 522 523 524 525
		if (!this.isPinned(editor)) {
			return; // can only unpin a pinned editor
		}

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

		// Event
526
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
527 528

		// Close old preview editor if any
M
Matt Bierner 已提交
529 530 531
		if (oldPreview) {
			this.closeEditor(oldPreview);
		}
B
Benjamin Pasero 已提交
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 566 567 568 569 570
	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++;
571 572 573

		// Event
		this._onDidChangeEditorSticky.fire(editor);
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
	}

	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--;
599 600 601

		// Event
		this._onDidChangeEditorSticky.fire(editor);
602 603 604 605 606
	}

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

609
		let index: number;
610 611
		if (typeof candidateOrIndex === 'number') {
			index = candidateOrIndex;
612
		} else {
613
			index = this.indexOf(candidateOrIndex);
614 615
		}

616 617
		if (index < 0) {
			return false;
B
Benjamin Pasero 已提交
618 619
		}

620
		return index <= this.sticky;
B
Benjamin Pasero 已提交
621 622
	}

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

626 627 628 629 630
		// Perform on sticky index
		if (del && this.isSticky(index)) {
			this.sticky--;
		}

631
		// Perform on editors array
632
		if (editor) {
633 634 635
			this.editors.splice(index, del ? 1 : 0, editor);
		} else {
			this.editors.splice(index, del ? 1 : 0);
636
		}
B
Benjamin Pasero 已提交
637

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
		// 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);
				}
655
			}
B
Benjamin Pasero 已提交
656

657 658 659
			// Remove / Replace
			else {
				const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
B
Benjamin Pasero 已提交
660

661 662 663 664
				// Remove
				if (del && !editor) {
					this.mru.splice(indexInMRU, 1); // remove from MRU
				}
B
Benjamin Pasero 已提交
665

666 667 668 669
				// Replace
				else if (del && editor) {
					this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
				}
B
Benjamin Pasero 已提交
670
			}
671 672
		}
	}
B
Benjamin Pasero 已提交
673

674
	indexOf(candidate: IEditorInput | null, editors = this.editors): number {
B
Benjamin Pasero 已提交
675 676
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
677 678
		}

B
Benjamin Pasero 已提交
679
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
680
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
681 682 683 684 685 686
				return i;
			}
		}

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

688
	private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
689 690 691 692 693
		const index = this.indexOf(candidate, this.editors);
		if (index === -1) {
			return undefined;
		}

694
		return [this.editors[index], index];
695 696
	}

697
	contains(candidate: EditorInput, options?: { supportSideBySide?: boolean, strictEquals?: boolean }): boolean {
698
		for (const editor of this.editors) {
699
			if (this.matches(editor, candidate, options?.strictEquals)) {
700 701
				return true;
			}
702

703
			if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) {
B
Benjamin Pasero 已提交
704
				if (this.matches(editor.primary, candidate, options?.strictEquals) || this.matches(editor.secondary, candidate, options?.strictEquals)) {
705 706 707
					return true;
				}
			}
708 709 710 711 712
		}

		return false;
	}

713
	private matches(editor: IEditorInput | null, candidate: IEditorInput | null, strictEquals?: boolean): boolean {
714 715 716 717
		if (!editor || !candidate) {
			return false;
		}

718 719 720 721
		if (strictEquals) {
			return editor === candidate;
		}

722
		return editor.matches(candidate);
B
Benjamin Pasero 已提交
723
	}
724

725
	clone(): EditorGroup {
R
Rob Lourens 已提交
726
		const group = this.instantiationService.createInstance(EditorGroup, undefined);
727 728

		// Copy over group properties
729 730 731 732
		group.editors = this.editors.slice(0);
		group.mru = this.mru.slice(0);
		group.preview = this.preview;
		group.active = this.active;
733
		group.sticky = this.sticky;
734

735 736 737 738 739
		// Ensure to register listeners for each editor
		for (const editor of group.editors) {
			group.registerEditorListeners(editor);
		}

740 741 742 743
		return group;
	}

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

		// Serialize all editor inputs so that we can store them.
		// Editors that cannot be serialized need to be ignored
748
		// from mru, active, preview and sticky if any.
749 750
		let serializableEditors: EditorInput[] = [];
		let serializedEditors: ISerializedEditorInput[] = [];
M
Matt Bierner 已提交
751
		let serializablePreviewIndex: number | undefined;
752 753 754 755 756 757 758
		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());
759
			if (factory) {
760 761 762
				const value = factory.serialize(editor);

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

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

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

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

780 781 782 783 784 785 786
			// 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);
787 788

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

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

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

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

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

819 820
			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 已提交
821 822
			}

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

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

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

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

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

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