vscode/src/vs/workbench/common/editor/editorGroupModel.ts
2021-11-24 13:58:32 +01:00

1013 lines
26 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { IEditorFactoryRegistry, GroupIdentifier, EditorsOrder, EditorExtensions, IUntypedEditorInput, SideBySideEditor, EditorCloseContext, GroupChangeKind } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { coalesce } from 'vs/base/common/arrays';
const EditorOpenPositioning = {
LEFT: 'left',
RIGHT: 'right',
FIRST: 'first',
LAST: 'last'
};
export interface IEditorOpenOptions {
readonly pinned?: boolean;
sticky?: boolean;
active?: boolean;
readonly index?: number;
readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH;
}
export interface IEditorOpenResult {
readonly editor: EditorInput;
readonly isNew: boolean;
}
export interface ISerializedEditorInput {
readonly id: string;
readonly value: string;
}
export interface ISerializedEditorGroupModel {
readonly id: number;
readonly locked?: boolean;
readonly editors: ISerializedEditorInput[];
readonly mru: number[];
readonly preview?: number;
sticky?: number;
}
export function isSerializedEditorGroupModel(group?: unknown): group is ISerializedEditorGroupModel {
const candidate = group as ISerializedEditorGroupModel | undefined;
return !!(candidate && typeof candidate === 'object' && Array.isArray(candidate.editors) && Array.isArray(candidate.mru));
}
export interface IMatchOptions {
/**
* Whether to consider a side by side editor as matching.
* By default, side by side editors will not be considered
* as matching, even if the editor is opened in one of the sides.
*/
readonly supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH;
/**
* Only consider an editor to match when the
* `candidate === editor` but not when
* `candidate.matches(editor)`.
*/
readonly strictEquals?: boolean;
}
export interface IGroupChangeEvent {
/**
* The kind of change that occured in the group.
*/
readonly kind: GroupChangeKind;
/**
* Only applies when editors change providing
* access to the editor the event is about.
*/
readonly editor?: EditorInput;
}
export interface IGroupEditorChangeEvent extends IGroupChangeEvent {
readonly editor: EditorInput;
}
export interface IGroupEditorOpenEvent extends IGroupEditorChangeEvent {
readonly kind: GroupChangeKind.EDITOR_OPEN;
/**
* Identifies the index of the editor in the group.
*/
readonly editorIndex: number;
}
export function isGroupEditorOpenEvent(e: IGroupChangeEvent): e is IGroupEditorOpenEvent {
const candidate = e as IGroupEditorOpenEvent;
return candidate.kind === GroupChangeKind.EDITOR_OPEN && candidate.editorIndex !== undefined;
}
export interface IGroupEditorMoveEvent extends IGroupEditorChangeEvent {
readonly kind: GroupChangeKind.EDITOR_MOVE;
/**
* Identifies the index of the editor in the group.
*/
readonly editorIndex: number;
/**
* Signifies the index the editor is moving from.
* `editorIndex` will contain the index the editor
* is moving to.
*/
readonly oldEditorIndex: number;
}
export function isGroupEditorMoveEvent(e: IGroupChangeEvent): e is IGroupEditorMoveEvent {
const candidate = e as IGroupEditorMoveEvent;
return candidate.kind === GroupChangeKind.EDITOR_MOVE && candidate.editorIndex !== undefined && candidate.oldEditorIndex !== undefined;
}
export interface IGroupEditorCloseEvent extends IGroupEditorChangeEvent {
readonly kind: GroupChangeKind.EDITOR_CLOSE;
/**
* Identifies the index of the editor in the group.
*/
readonly editorIndex: number;
/**
* Signifies the context in which the editor
* is being closed. This allows for understanding
* if a replace or reopen is occuring
*/
readonly context: EditorCloseContext;
/**
* Signifies whether or not the closed editor was
* sticky. This is necessary becasue state is lost
* after closing.
*/
readonly sticky: boolean;
}
export function isGroupEditorCloseEvent(e: IGroupChangeEvent): e is IGroupEditorCloseEvent {
const candidate = e as IGroupEditorCloseEvent;
return candidate.kind === GroupChangeKind.EDITOR_CLOSE && candidate.editorIndex !== undefined && candidate.context !== undefined && candidate.sticky !== undefined;
}
interface IEditorCloseResult {
readonly editor: EditorInput;
readonly context: EditorCloseContext;
readonly editorIndex: number;
readonly sticky: boolean;
}
export class EditorGroupModel extends Disposable {
private static IDS = 0;
//#region events
private readonly _onDidModelChange = this._register(new Emitter<IGroupChangeEvent>());
readonly onDidModelChange = this._onDidModelChange.event;
//#endregion
private _id: GroupIdentifier;
get id(): GroupIdentifier { return this._id; }
private editors: EditorInput[] = [];
private mru: EditorInput[] = [];
private locked = false;
private preview: EditorInput | null = null; // editor in preview state
private active: EditorInput | null = null; // editor in active state
private sticky = -1; // index of first editor in sticky state
private editorOpenPositioning: ('left' | 'right' | 'first' | 'last') | undefined;
private focusRecentEditorAfterClose: boolean | undefined;
constructor(
labelOrSerializedGroup: ISerializedEditorGroupModel | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
if (isSerializedEditorGroupModel(labelOrSerializedGroup)) {
this._id = this.deserialize(labelOrSerializedGroup);
} else {
this._id = EditorGroupModel.IDS++;
}
this.onConfigurationUpdated();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
}
private onConfigurationUpdated(): void {
this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning');
this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose');
}
get count(): number {
return this.editors.length;
}
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;
}
getEditorByIndex(index: number): EditorInput | undefined {
return this.editors[index];
}
get activeEditor(): EditorInput | null {
return this.active;
}
isActive(editor: EditorInput | IUntypedEditorInput): boolean {
return this.matches(this.active, editor);
}
get previewEditor(): EditorInput | null {
return this.preview;
}
openEditor(candidate: EditorInput, options?: IEditorOpenOptions): IEditorOpenResult {
const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index));
const makePinned = options?.pinned || options?.sticky;
const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor));
const existingEditorAndIndex = this.findEditor(candidate, options);
// New editor
if (!existingEditorAndIndex) {
const newEditor = candidate;
const indexOfActive = this.indexOf(this.active);
// Insert into specific position
let targetIndex: number;
if (options && typeof options.index === 'number') {
targetIndex = options.index;
}
// Insert to the BEGINNING
else if (this.editorOpenPositioning === EditorOpenPositioning.FIRST) {
targetIndex = 0;
// 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;
}
}
// Insert to the END
else if (this.editorOpenPositioning === EditorOpenPositioning.LAST) {
targetIndex = this.editors.length;
}
// 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;
}
}
// 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;
}
}
// Insert into our list of editors if pinned or we have no preview editor
if (makePinned || !this.preview) {
this.splice(targetIndex, false, newEditor);
}
// Handle preview
if (!makePinned) {
// Replace existing preview with this editor if we have a preview
if (this.preview) {
const indexOfPreview = this.indexOf(this.preview);
if (targetIndex > indexOfPreview) {
targetIndex--; // accomodate for the fact that the preview editor closes
}
this.replaceEditor(this.preview, newEditor, targetIndex, !makeActive);
}
this.preview = newEditor;
}
// Listeners
this.registerEditorListeners(newEditor);
// Event
const event: IGroupEditorOpenEvent = {
kind: GroupChangeKind.EDITOR_OPEN,
editor: newEditor,
editorIndex: targetIndex
};
this._onDidModelChange.fire(event);
// Handle active
if (makeActive) {
this.doSetActive(newEditor);
}
return {
editor: newEditor,
isNew: true
};
}
// Existing editor
else {
const [existingEditor] = existingEditorAndIndex;
// Pin it
if (makePinned) {
this.doPin(existingEditor);
}
// Activate it
if (makeActive) {
this.doSetActive(existingEditor);
}
// Respect index
if (options && typeof options.index === 'number') {
this.moveEditor(existingEditor, options.index);
}
// 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));
}
return {
editor: existingEditor,
isNew: false
};
}
}
private registerEditorListeners(editor: EditorInput): void {
const listeners = new DisposableStore();
// Re-emit disposal of editor input as our own event
listeners.add(Event.once(editor.onWillDispose)(() => {
if (this.indexOf(editor) >= 0) {
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_WILL_DISPOSE,
editor
});
}
}));
// Re-Emit dirty state changes
listeners.add(editor.onDidChangeDirty(() => {
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_DIRTY,
editor
});
}));
// Re-Emit label changes
listeners.add(editor.onDidChangeLabel(() => {
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_LABEL,
editor
});
}));
// Re-Emit capability changes
listeners.add(editor.onDidChangeCapabilities(() => {
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_CAPABILITIES,
editor
});
}));
// Clean up dispose listeners once the editor gets closed
listeners.add(this.onDidModelChange(event => {
if (event.kind === GroupChangeKind.EDITOR_CLOSE && event.editor?.matches(editor)) {
dispose(listeners);
}
}));
}
private replaceEditor(toReplace: EditorInput, replaceWith: EditorInput, replaceIndex: number, openNext = true): void {
const closeResult = this.doCloseEditor(toReplace, EditorCloseContext.REPLACE, openNext); // optimization to prevent multiple setActive() in one call
// 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.
this.splice(replaceIndex, false, replaceWith);
if (closeResult) {
const event: IGroupEditorCloseEvent = {
kind: GroupChangeKind.EDITOR_CLOSE,
...closeResult
};
this._onDidModelChange.fire(event);
}
}
closeEditor(candidate: EditorInput, context = EditorCloseContext.UNKNOWN, openNext = true): IEditorCloseResult | undefined {
const closeResult = this.doCloseEditor(candidate, context, openNext);
if (closeResult) {
const event: IGroupEditorCloseEvent = {
kind: GroupChangeKind.EDITOR_CLOSE,
...closeResult
};
this._onDidModelChange.fire(event);
return closeResult;
}
return undefined;
}
private doCloseEditor(candidate: EditorInput, context: EditorCloseContext, openNext: boolean): IEditorCloseResult | undefined {
const index = this.indexOf(candidate);
if (index === -1) {
return undefined; // not found
}
const editor = this.editors[index];
const sticky = this.isSticky(index);
// Active Editor closed
if (openNext && this.matches(this.active, editor)) {
// More than one editor
if (this.mru.length > 1) {
let newActive: EditorInput;
if (this.focusRecentEditorAfterClose) {
newActive = this.mru[1]; // active editor is always first in MRU, so pick second editor after as new active
} else {
if (index === this.editors.length - 1) {
newActive = this.editors[index - 1]; // last editor is closed, pick previous as new active
} else {
newActive = this.editors[index + 1]; // pick next editor as new active
}
}
this.doSetActive(newActive);
}
// One Editor
else {
this.active = null;
}
}
// Preview Editor closed
if (this.matches(this.preview, editor)) {
this.preview = null;
}
// Remove from arrays
this.splice(index, true);
// Event
return { editor, sticky, editorIndex: index, context };
}
moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined {
// Ensure toIndex is in bounds of our model
if (toIndex >= this.editors.length) {
toIndex = this.editors.length - 1;
} else if (toIndex < 0) {
toIndex = 0;
}
const index = this.indexOf(candidate);
if (index < 0 || toIndex === index) {
return;
}
const editor = this.editors[index];
// 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++;
}
// Move
this.editors.splice(index, 1);
this.editors.splice(toIndex, 0, editor);
// Event
const event: IGroupEditorMoveEvent = {
kind: GroupChangeKind.EDITOR_MOVE,
editor,
oldEditorIndex: index,
editorIndex: toIndex,
};
this._onDidModelChange.fire(event);
return editor;
}
setActive(candidate: EditorInput): EditorInput | undefined {
const res = this.findEditor(candidate);
if (!res) {
return; // not found
}
const [editor] = res;
this.doSetActive(editor);
return editor;
}
private doSetActive(editor: EditorInput): void {
if (this.matches(this.active, editor)) {
return; // already active
}
this.active = editor;
// Bring to front in MRU list
const mruIndex = this.indexOf(editor, this.mru);
this.mru.splice(mruIndex, 1);
this.mru.unshift(editor);
// Event
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_ACTIVE,
editor
});
}
pin(candidate: EditorInput): EditorInput | undefined {
const res = this.findEditor(candidate);
if (!res) {
return; // not found
}
const [editor] = res;
this.doPin(editor);
return editor;
}
private doPin(editor: EditorInput): void {
if (this.isPinned(editor)) {
return; // can only pin a preview editor
}
// Convert the preview editor to be a pinned editor
this.preview = null;
// Event
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_PIN,
editor
});
}
unpin(candidate: EditorInput): EditorInput | undefined {
const res = this.findEditor(candidate);
if (!res) {
return; // not found
}
const [editor] = res;
this.doUnpin(editor);
return editor;
}
private doUnpin(editor: EditorInput): void {
if (!this.isPinned(editor)) {
return; // can only unpin a pinned editor
}
// Set new
const oldPreview = this.preview;
this.preview = editor;
// Event
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_PIN,
editor
});
// Close old preview editor if any
if (oldPreview) {
this.closeEditor(oldPreview, EditorCloseContext.UNPIN);
}
}
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++;
// Event
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_STICKY,
editor
});
}
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--;
// Event
this._onDidModelChange.fire({
kind: GroupChangeKind.EDITOR_STICKY,
editor
});
}
isSticky(candidateOrIndex: EditorInput | number): boolean {
if (this.sticky < 0) {
return false; // no sticky editor
}
let index: number;
if (typeof candidateOrIndex === 'number') {
index = candidateOrIndex;
} else {
index = this.indexOf(candidateOrIndex);
}
if (index < 0) {
return false;
}
return index <= this.sticky;
}
private splice(index: number, del: boolean, editor?: EditorInput): void {
const editorToDeleteOrReplace = this.editors[index];
// Perform on sticky index
if (del && this.isSticky(index)) {
this.sticky--;
}
// Perform on editors array
if (editor) {
this.editors.splice(index, del ? 1 : 0, editor);
} else {
this.editors.splice(index, del ? 1 : 0);
}
// 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);
}
}
// Remove / Replace
else {
const indexInMRU = this.indexOf(editorToDeleteOrReplace, this.mru);
// Remove
if (del && !editor) {
this.mru.splice(indexInMRU, 1); // remove from MRU
}
// Replace
else if (del && editor) {
this.mru.splice(indexInMRU, 1, editor); // replace MRU at location
}
}
}
}
indexOf(candidate: EditorInput | null, editors = this.editors, options?: IMatchOptions): number {
let index = -1;
if (candidate) {
for (let i = 0; i < editors.length; i++) {
const editor = editors[i];
if (this.matches(editor, candidate, options)) {
// If we are to support side by side matching, it is possible that
// a better direct match is found later. As such, we continue finding
// a matching editor and prefer that match over the side by side one.
if (options?.supportSideBySide && editor instanceof SideBySideEditorInput && !(candidate instanceof SideBySideEditorInput)) {
index = i;
} else {
index = i;
break;
}
}
}
}
return index;
}
private findEditor(candidate: EditorInput | null, options?: IMatchOptions): [EditorInput, number /* index */] | undefined {
const index = this.indexOf(candidate, this.editors, options);
if (index === -1) {
return undefined;
}
return [this.editors[index], index];
}
contains(candidate: EditorInput | IUntypedEditorInput, options?: IMatchOptions): boolean {
for (const editor of this.editors) {
if (this.matches(editor, candidate, options)) {
return true;
}
}
return false;
}
private matches(editor: EditorInput | null, candidate: EditorInput | IUntypedEditorInput | null, options?: IMatchOptions): boolean {
if (!editor || !candidate) {
return false;
}
if (options?.supportSideBySide && editor instanceof SideBySideEditorInput && !(candidate instanceof SideBySideEditorInput)) {
switch (options.supportSideBySide) {
case SideBySideEditor.ANY:
if (this.matches(editor.primary, candidate, options) || this.matches(editor.secondary, candidate, options)) {
return true;
}
break;
case SideBySideEditor.BOTH:
if (this.matches(editor.primary, candidate, options) && this.matches(editor.secondary, candidate, options)) {
return true;
}
break;
}
}
if (options?.strictEquals) {
return editor === candidate;
}
return editor.matches(candidate);
}
get isLocked(): boolean {
return this.locked;
}
lock(locked: boolean): void {
if (this.isLocked !== locked) {
this.locked = locked;
this._onDidModelChange.fire({ kind: GroupChangeKind.GROUP_LOCKED });
}
}
clone(): EditorGroupModel {
const clone = this.instantiationService.createInstance(EditorGroupModel, undefined);
// Copy over group properties
clone.editors = this.editors.slice(0);
clone.mru = this.mru.slice(0);
clone.preview = this.preview;
clone.active = this.active;
clone.sticky = this.sticky;
// Ensure to register listeners for each editor
for (const editor of clone.editors) {
clone.registerEditorListeners(editor);
}
return clone;
}
serialize(): ISerializedEditorGroupModel {
const registry = Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory);
// Serialize all editor inputs so that we can store them.
// Editors that cannot be serialized need to be ignored
// from mru, active, preview and sticky if any.
let serializableEditors: EditorInput[] = [];
let serializedEditors: ISerializedEditorInput[] = [];
let serializablePreviewIndex: number | undefined;
let serializableSticky = this.sticky;
for (let i = 0; i < this.editors.length; i++) {
const editor = this.editors[i];
let canSerializeEditor = false;
const editorSerializer = registry.getEditorSerializer(editor);
if (editorSerializer) {
const value = editorSerializer.serialize(editor);
// Editor can be serialized
if (typeof value === 'string') {
canSerializeEditor = true;
serializedEditors.push({ id: editor.typeId, value });
serializableEditors.push(editor);
if (this.preview === editor) {
serializablePreviewIndex = serializableEditors.length - 1;
}
}
// Editor cannot be serialized
else {
canSerializeEditor = false;
}
}
// 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);
return {
id: this.id,
locked: this.locked ? true : undefined,
editors: serializedEditors,
mru: serializableMru,
preview: serializablePreviewIndex,
sticky: serializableSticky >= 0 ? serializableSticky : undefined
};
}
private deserialize(data: ISerializedEditorGroupModel): number {
const registry = Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory);
if (typeof data.id === 'number') {
this._id = data.id;
EditorGroupModel.IDS = Math.max(data.id + 1, EditorGroupModel.IDS); // make sure our ID generator is always larger
} else {
this._id = EditorGroupModel.IDS++; // backwards compatibility
}
if (data.locked) {
this.locked = true;
}
this.editors = coalesce(data.editors.map((e, index) => {
let editor: EditorInput | undefined = undefined;
const editorSerializer = registry.getEditorSerializer(e.id);
if (editorSerializer) {
const deserializedEditor = editorSerializer.deserialize(this.instantiationService, e.value);
if (deserializedEditor instanceof EditorInput) {
editor = deserializedEditor;
this.registerEditorListeners(editor);
}
}
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
}
return editor;
}));
this.mru = coalesce(data.mru.map(i => this.editors[i]));
this.active = this.mru[0];
if (typeof data.preview === 'number') {
this.preview = this.editors[data.preview];
}
if (typeof data.sticky === 'number') {
this.sticky = data.sticky;
}
return this._id;
}
}