editorGroup.ts 21.5 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

	//#endregion
B
Benjamin Pasero 已提交
87

B
Benjamin Pasero 已提交
88
	private _id: GroupIdentifier;
B
Benjamin Pasero 已提交
89
	get id(): GroupIdentifier { return this._id; }
90

91 92
	private editors: EditorInput[] = [];
	private mru: EditorInput[] = [];
93

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

B
Benjamin Pasero 已提交
98 99
	private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
	private focusRecentEditorAfterClose: boolean | undefined;
100

101
	constructor(
102
		labelOrSerializedGroup: ISerializedEditorGroup | undefined,
103 104
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IConfigurationService private readonly configurationService: IConfigurationService
105
	) {
106 107
		super();

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

114
		this.onConfigurationUpdated();
115 116 117 118
		this.registerListeners();
	}

	private registerListeners(): void {
119
		this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
120 121
	}

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

		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 已提交
131 132
	}

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

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

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

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

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

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

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

179
		const existingEditorAndIndex = this.findEditor(candidate);
180

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

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

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

				// 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;
				}
201 202 203
			}

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

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

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

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

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

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

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

260
				this.preview = newEditor;
B
Benjamin Pasero 已提交
261 262
			}

263
			// Listeners
264
			this.registerEditorListeners(newEditor);
265

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

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

			return newEditor;
B
Benjamin Pasero 已提交
275 276 277 278
		}

		// Existing editor
		else {
279
			const [existingEditor] = existingEditorAndIndex;
B
Benjamin Pasero 已提交
280 281 282

			// Pin it
			if (makePinned) {
283
				this.doPin(existingEditor);
B
Benjamin Pasero 已提交
284 285 286 287
			}

			// Activate it
			if (makeActive) {
288
				this.doSetActive(existingEditor);
B
Benjamin Pasero 已提交
289
			}
B
Benjamin Pasero 已提交
290 291 292

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

296 297 298 299 300 301
			// 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));
			}

302
			return existingEditor;
B
Benjamin Pasero 已提交
303 304 305
		}
	}

B
Benjamin Pasero 已提交
306
	private registerEditorListeners(editor: EditorInput): void {
307
		const listeners = new DisposableStore();
308 309

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

		// Re-Emit dirty state changes
317
		listeners.add(editor.onDidChangeDirty(() => {
318
			this._onDidChangeEditorDirty.fire(editor);
319
		}));
320

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

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

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

		// 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 已提交
340
		this.splice(replaceIndex, false, replaceWith);
341 342

		if (event) {
343
			this._onDidCloseEditor.fire(event);
344 345 346
		}
	}

347
	closeEditor(candidate: EditorInput, openNext = true): EditorInput | undefined {
348
		const event = this.doCloseEditor(candidate, openNext, false);
349 350

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

353
			return event.editor;
354
		}
355

R
Rob Lourens 已提交
356
		return undefined;
357 358
	}

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

365
		const editor = this.editors[index];
366
		const sticky = this.isSticky(index);
367

B
Benjamin Pasero 已提交
368
		// Active Editor closed
B
Benjamin Pasero 已提交
369
		if (openNext && this.matches(this.active, editor)) {
B
Benjamin Pasero 已提交
370 371

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

384
				this.doSetActive(newActive);
B
Benjamin Pasero 已提交
385 386 387 388 389 390 391 392 393
			}

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

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

B
Benjamin Pasero 已提交
398
		// Remove from arrays
B
Benjamin Pasero 已提交
399
		this.splice(index, true);
B
Benjamin Pasero 已提交
400 401

		// Event
402
		return { editor, replaced, sticky, index, groupId: this.id };
B
Benjamin Pasero 已提交
403 404
	}

405
	moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
406

407 408 409 410 411
		// 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 已提交
412
		}
413

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

419 420
		const editor = this.editors[index];

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

		// Event
436
		this._onDidMoveEditor.fire(editor);
437 438

		return editor;
B
Benjamin Pasero 已提交
439 440
	}

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

447 448
		const [editor] = res;

449
		this.doSetActive(editor);
450 451

		return editor;
452 453 454
	}

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

		this.active = editor;

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

B
Benjamin Pasero 已提交
466
		// Event
467
		this._onDidActivateEditor.fire(editor);
B
Benjamin Pasero 已提交
468 469
	}

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

476 477
		const [editor] = res;

478
		this.doPin(editor);
479 480

		return editor;
481 482 483
	}

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

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

		// Event
492
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
493 494
	}

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

501 502
		const [editor] = res;

503
		this.doUnpin(editor);
504 505

		return editor;
506 507 508
	}

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

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

		// Event
518
		this._onDidChangeEditorPinned.fire(editor);
B
Benjamin Pasero 已提交
519 520

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

526 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 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
	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++;
	}

	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--;
	}

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

595
		let index: number;
596 597
		if (typeof candidateOrIndex === 'number') {
			index = candidateOrIndex;
598
		} else {
599
			index = this.indexOf(candidateOrIndex);
600 601
		}

602 603
		if (index < 0) {
			return false;
B
Benjamin Pasero 已提交
604 605
		}

606
		return index <= this.sticky;
B
Benjamin Pasero 已提交
607 608
	}

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

612 613 614 615 616
		// Perform on sticky index
		if (del && this.isSticky(index)) {
			this.sticky--;
		}

617
		// Perform on editors array
618
		if (editor) {
619 620 621
			this.editors.splice(index, del ? 1 : 0, editor);
		} else {
			this.editors.splice(index, del ? 1 : 0);
622
		}
B
Benjamin Pasero 已提交
623

624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
		// 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);
				}
641
			}
B
Benjamin Pasero 已提交
642

643 644 645
			// Remove / Replace
			else {
				const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
B
Benjamin Pasero 已提交
646

647 648 649 650
				// Remove
				if (del && !editor) {
					this.mru.splice(indexInMRU, 1); // remove from MRU
				}
B
Benjamin Pasero 已提交
651

652 653 654 655
				// Replace
				else if (del && editor) {
					this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
				}
B
Benjamin Pasero 已提交
656
			}
657 658
		}
	}
B
Benjamin Pasero 已提交
659

660
	indexOf(candidate: IEditorInput | null, editors = this.editors): number {
B
Benjamin Pasero 已提交
661 662
		if (!candidate) {
			return -1;
B
Benjamin Pasero 已提交
663 664
		}

B
Benjamin Pasero 已提交
665
		for (let i = 0; i < editors.length; i++) {
B
Benjamin Pasero 已提交
666
			if (this.matches(editors[i], candidate)) {
B
Benjamin Pasero 已提交
667 668 669 670 671 672
				return i;
			}
		}

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

674
	private findEditor(candidate: EditorInput | null): [EditorInput, number /* index */] | undefined {
675 676 677 678 679
		const index = this.indexOf(candidate, this.editors);
		if (index === -1) {
			return undefined;
		}

680
		return [this.editors[index], index];
681 682
	}

683
	contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean {
684 685 686 687
		for (const editor of this.editors) {
			if (this.matches(editor, candidate)) {
				return true;
			}
688

689 690 691 692 693
			if (searchInSideBySideEditors && editor instanceof SideBySideEditorInput) {
				if (this.matches(editor.master, candidate) || this.matches(editor.details, candidate)) {
					return true;
				}
			}
694 695 696 697 698
		}

		return false;
	}

699
	private matches(editor: IEditorInput | null, candidate: IEditorInput | null): boolean {
700 701 702 703
		if (!editor || !candidate) {
			return false;
		}

704
		return editor.matches(candidate);
B
Benjamin Pasero 已提交
705
	}
706

707
	clone(): EditorGroup {
R
Rob Lourens 已提交
708
		const group = this.instantiationService.createInstance(EditorGroup, undefined);
709 710 711 712
		group.editors = this.editors.slice(0);
		group.mru = this.mru.slice(0);
		group.preview = this.preview;
		group.active = this.active;
713
		group.sticky = this.sticky;
714 715 716 717 718

		return group;
	}

	serialize(): ISerializedEditorGroup {
719
		const registry = Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories);
720 721 722

		// Serialize all editor inputs so that we can store them.
		// Editors that cannot be serialized need to be ignored
723
		// from mru, active, preview and sticky if any.
724 725
		let serializableEditors: EditorInput[] = [];
		let serializedEditors: ISerializedEditorInput[] = [];
M
Matt Bierner 已提交
726
		let serializablePreviewIndex: number | undefined;
727 728 729 730 731 732 733
		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());
734
			if (factory) {
735 736 737
				const value = factory.serialize(editor);

				// Editor can be serialized
738
				if (typeof value === 'string') {
739 740 741 742
					canSerializeEditor = true;

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

744
					if (this.preview === editor) {
B
Benjamin Pasero 已提交
745 746
						serializablePreviewIndex = serializableEditors.length - 1;
					}
747
				}
748 749 750 751 752

				// Editor cannot be serialized
				else {
					canSerializeEditor = false;
				}
753 754
			}

755 756 757 758 759 760 761
			// 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);
762 763

		return {
764
			id: this.id,
765 766
			editors: serializedEditors,
			mru: serializableMru,
B
Benjamin Pasero 已提交
767
			preview: serializablePreviewIndex,
768
			sticky: serializableSticky >= 0 ? serializableSticky : undefined
769 770 771
		};
	}

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

775 776 777 778 779 780 781 782
		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
		}

783 784 785
		this.editors = coalesce(data.editors.map((e, index) => {
			let editor: EditorInput | undefined = undefined;

B
Benjamin Pasero 已提交
786 787
			const factory = registry.getEditorInputFactory(e.id);
			if (factory) {
788
				editor = factory.deserialize(this.instantiationService, e.value);
B
Benjamin Pasero 已提交
789 790 791
				if (editor) {
					this.registerEditorListeners(editor);
				}
792
			}
793

794 795
			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 已提交
796 797
			}

798
			return editor;
M
Matt Bierner 已提交
799
		}));
B
Benjamin Pasero 已提交
800

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

803
		this.active = this.mru[0];
B
Benjamin Pasero 已提交
804

M
Matt Bierner 已提交
805 806 807
		if (typeof data.preview === 'number') {
			this.preview = this.editors[data.preview];
		}
B
Benjamin Pasero 已提交
808

809 810 811 812
		if (typeof data.sticky === 'number') {
			this.sticky = data.sticky;
		}

B
Benjamin Pasero 已提交
813
		return this._id;
814 815
	}
}