From 7d5c54e0c915f1eed58fb04af27ef328e103f4b4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 17:04:23 +0200 Subject: [PATCH 01/17] :lipstick: --- .../parts/editor/multiEditorTabsControl.ts | 49 +++++++++---------- .../parts/editor/multiRowEditorTabsControl.ts | 2 +- .../common/editor/editorGroupModel.ts | 2 +- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index a54b4a431d351..c255f44bebe08 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1643,7 +1643,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private doRedrawTabActive(isGroupActive: boolean, allowBorderTop: boolean, editor: EditorInput, tabContainer: HTMLElement, tabActionBar: ActionBar): void { - const isActive = this.tabsModel.isActive(editor); const isSelected = this.tabsModel.isSelected(editor); @@ -1657,7 +1656,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (isActive) { const activeTabBorderColorBottom = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER); tabContainer.classList.toggle('tab-border-bottom', !!activeTabBorderColorBottom); - tabContainer.style.setProperty('--tab-border-bottom-color', activeTabBorderColorBottom?.toString() ?? ''); + tabContainer.style.setProperty('--tab-border-bottom-color', activeTabBorderColorBottom ?? ''); } // Set border TOP if theme defined color @@ -2219,30 +2218,30 @@ export class MultiEditorTabsControl extends EditorTabsControl { else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { const data = this.editorTransfer.getData(DraggedEditorIdentifier.prototype); if (Array.isArray(data) && data.length > 0) { - const sourceGroup = data.length ? this.editorPartsView.getGroup(data[0].identifier.groupId) : undefined; - const isLocalMove = sourceGroup === this.groupView; - - // Keep the same order when moving / copying editors within the same group - for (const de of data) { - const editor = de.identifier.editor; - - // Only allow moving/copying from a single group source - if (!sourceGroup || sourceGroup.id !== de.identifier.groupId) { - continue; - } - - const sourceEditorIndex = sourceGroup.getIndexOfEditor(editor); - if (isLocalMove && sourceEditorIndex < targetEditorIndex) { - targetEditorIndex--; - } - - if (this.isMoveOperation(e, de.identifier.groupId, editor)) { - sourceGroup.moveEditor(editor, this.groupView, { ...options, index: targetEditorIndex }); - } else { - sourceGroup.copyEditor(editor, this.groupView, { ...options, index: targetEditorIndex }); + const sourceGroup = this.editorPartsView.getGroup(data[0].identifier.groupId); + if (sourceGroup) { + for (const de of data) { + const editor = de.identifier.editor; + + // Only allow moving/copying from a single group source + if (sourceGroup.id !== de.identifier.groupId) { + continue; + } + + // Keep the same order when moving / copying editors within the same group + const sourceEditorIndex = sourceGroup.getIndexOfEditor(editor); + if (sourceGroup === this.groupView && sourceEditorIndex < targetEditorIndex) { + targetEditorIndex--; + } + + if (this.isMoveOperation(e, de.identifier.groupId, editor)) { + sourceGroup.moveEditor(editor, this.groupView, { ...options, index: targetEditorIndex }); + } else { + sourceGroup.copyEditor(editor, this.groupView, { ...options, index: targetEditorIndex }); + } + + targetEditorIndex++; } - - targetEditorIndex++; } } diff --git a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts index 908b7d85cfb80..83823d3ec8fa8 100644 --- a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts @@ -199,7 +199,7 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont return this.stickyEditorTabsControl.getHeight() + this.unstickyEditorTabsControl.getHeight(); } - public override dispose(): void { + override dispose(): void { this.parent.classList.toggle('two-tab-bars', false); super.dispose(); diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index d8b7d956adc2d..c2971b13b0a74 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -712,7 +712,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this._onDidModelChange.fire(event); } - public get selectedEditors(): EditorInput[] { + get selectedEditors(): EditorInput[] { // Return selected editors in sequential order return this.editors.filter(editor => this.isSelected(editor)); } From 460a6d2b58c4f0758d91cfe9fff22da16fe44ca9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 17:31:07 +0200 Subject: [PATCH 02/17] add todo --- .../browser/parts/editor/multiEditorTabsControl.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index c255f44bebe08..f88f5941e0919 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -889,14 +889,15 @@ export class MultiEditorTabsControl extends EditorTabsControl { const editor = this.tabsModel.getEditorByIndex(tabIndex); if (editor) { if (e.shiftKey) { - let anchor; + let anchor: EditorInput; if (this.lastSingleSelectSelectedEditor && this.tabsModel.isSelected(this.lastSingleSelectSelectedEditor)) { // The last selected editor is the anchor anchor = this.lastSingleSelectSelectedEditor; } else { // The active editor is the anchor - this.lastSingleSelectSelectedEditor = this.groupView.activeEditor!; - anchor = this.groupView.activeEditor!; + const activeEditor = assertIsDefined(this.groupView.activeEditor); + this.lastSingleSelectSelectedEditor = activeEditor; + anchor = activeEditor; } await this.selectEditorsBetween(editor, anchor); } else if ((e.ctrlKey && !isMacintosh) || (e.metaKey && isMacintosh)) { @@ -1308,7 +1309,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { break; } - selection.filter(editor => !editor.matches(currentEditor)); + selection.filter(editor => !editor.matches(currentEditor)); // TODO this is a no-op } // Select editors between anchor and target @@ -1334,7 +1335,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { return; } - let newActiveEditor = this.groupView.activeEditor!; + let newActiveEditor = assertIsDefined(this.groupView.activeEditor); // If active editor is bing unselected then find the most recently opened selected editor // that is not the editor being unselected @@ -1355,7 +1356,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { private async unselectAllEditors(): Promise { if (this.groupView.selectedEditors.length > 1) { - await this.groupView.setSelection(this.groupView.activeEditor!, []); + const activeEditor = assertIsDefined(this.groupView.activeEditor); + await this.groupView.setSelection(activeEditor, []); } } From 64496258b1a20279d2a15f047015b9943d240b01 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 17:55:39 +0200 Subject: [PATCH 03/17] todos --- .../workbench/common/editor/editorGroupModel.ts | 15 +++++++++------ .../common/editor/filteredEditorGroupModel.ts | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index c2971b13b0a74..d362f8d381cf6 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -220,9 +220,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private locked = false; - private selection: EditorInput[] = []; // editors in selected state, first one is active + private selection: EditorInput[] = []; // editors in selected state, first one is active TODO align this with transient editors and use a Set instead - private set active(editor: EditorInput | null) { + private set active(editor: EditorInput | null) { // TODO: this misses an event when selection changes and should be a method? this.selection = editor ? [editor] : []; } private get active(): EditorInput | null { @@ -714,12 +714,15 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { get selectedEditors(): EditorInput[] { // Return selected editors in sequential order - return this.editors.filter(editor => this.isSelected(editor)); + return this.editors.filter(editor => this.isSelected(editor)); // TODO I would have assumed `this.selection` to be in sequential order } - isSelected(editor: EditorInput | number): boolean { - if (typeof editor === 'number') { - editor = this.editors[editor]; + isSelected(editorOrIndex: EditorInput | number): boolean { + let editor: EditorInput; + if (typeof editorOrIndex === 'number') { + editor = this.editors[editorOrIndex]; + } else { + editor = editorOrIndex; } return !!this.selection.find(selectedEditor => this.matches(selectedEditor, editor)); diff --git a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts index fcda0bbed00a1..61d4f6a7c8009 100644 --- a/src/vs/workbench/common/editor/filteredEditorGroupModel.ts +++ b/src/vs/workbench/common/editor/filteredEditorGroupModel.ts @@ -42,7 +42,7 @@ abstract class FilteredEditorGroupModel extends Disposable implements IReadonlyE isTransient(editorOrIndex: EditorInput | number): boolean { return this.model.isTransient(editorOrIndex); } isSticky(editorOrIndex: EditorInput | number): boolean { return this.model.isSticky(editorOrIndex); } isActive(editor: EditorInput | IUntypedEditorInput): boolean { return this.model.isActive(editor); } - isSelected(editor: EditorInput | number): boolean { return this.model.isSelected(editor); } + isSelected(editorOrIndex: EditorInput | number): boolean { return this.model.isSelected(editorOrIndex); } isFirst(editor: EditorInput): boolean { return this.model.isFirst(editor, this.getEditors(EditorsOrder.SEQUENTIAL)); From 41a957e0f66fe6d10cfd19dd6407422e6035e575 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 17:57:17 +0200 Subject: [PATCH 04/17] todos --- src/vs/workbench/common/editor/editorGroupModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index d362f8d381cf6..775049352aa2b 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -717,7 +717,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return this.editors.filter(editor => this.isSelected(editor)); // TODO I would have assumed `this.selection` to be in sequential order } - isSelected(editorOrIndex: EditorInput | number): boolean { + isSelected(editorOrIndex: EditorInput | number): boolean { // TODO align with how isTransient() works let editor: EditorInput; if (typeof editorOrIndex === 'number') { editor = this.editors[editorOrIndex]; From cef965a1f541c9bd32ce7636a935ddaf17f37120 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 18:05:15 +0200 Subject: [PATCH 05/17] todos --- src/vs/workbench/common/editor/editorGroupModel.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 775049352aa2b..cbb58e0d8ce9b 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -576,7 +576,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { // One Editor else { - this.active = null; + this.active = null; // TODO this potentially changes selection without a related event. and is it expected to change the selection? } } @@ -692,11 +692,11 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private doSetActive(editor: EditorInput, editorIndex: number): void { if (this.matches(this.active, editor)) { - this.selection = [editor]; + this.selection = [editor]; // TODO this potentially changes selection without a related event. and is it expected to change the selection? return; // already active } - this.active = editor; + this.active = editor; // TODO this potentially changes selection without a related event. and is it expected to change the selection? // Bring to front in MRU list const mruIndex = this.indexOf(editor, this.mru); @@ -731,7 +731,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { const res = this.findEditor(activeSelectedEditor); if (!res) { - return; + return; // not found } const [newActiveEditor, newActiveEditorIndex] = res; @@ -742,14 +742,14 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { this.doSetActive(newActiveEditor, activeEditorIndex); - for (const candidate of inactiveSelectedEditors) { + for (const candidate of inactiveSelectedEditors) { // TODO should this move up to the public API? the idea is that we do not trust editor inputs from public API but internal is fine const editor = this.findEditor(candidate)?.[0]; if (editor && !this.isSelected(editor)) { this.selection.push(editor); } } - // Event + // Event TODO what if selection did not change? const event: IGroupModelChangeEvent = { kind: GroupModelChangeKind.EDITORS_SELECTION, }; From 995b40aa4e638de9a73c81d3c33383b761802237 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 18:08:57 +0200 Subject: [PATCH 06/17] todos --- src/vs/workbench/common/editor/editorGroupModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index cbb58e0d8ce9b..299cd62638915 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -413,7 +413,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { }; this._onDidModelChange.fire(event); - // Handle active & selection + // Handle active & selection TODO why do we call this when `makeActive` is false? this.setSelection(makeActive ? newEditor : this.activeEditor, options?.inactiveSelection ?? []); return { @@ -434,7 +434,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doPin(existingEditor, existingEditorIndex); } - // Activate / select it + // Activate / select it TODO why do we call this when `makeActive` is false? this.setSelection(makeActive ? existingEditor : this.activeEditor, options?.inactiveSelection ?? []); // Respect index From 9f7eae5bcfb7d6e9378ef34566d60ae23ebe8c86 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 21 May 2024 18:11:53 +0200 Subject: [PATCH 07/17] . --- src/vs/workbench/common/editor/editorGroupModel.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 299cd62638915..255e6f0fb5f1b 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -570,8 +570,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - const newSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor)); - this.doSetSelection(newActive, this.editors.indexOf(newActive), newSelection); + const newInactiveSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor)); + this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelection); } // One Editor @@ -584,8 +584,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { else if (!isActiveEditor) { const wasSelected = !!this.selection.find(selected => this.matches(selected, editor)); if (wasSelected) { - const newSelection = this.selection.filter(selected => !selected.matches(editor)); - this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newSelection); + const newInactiveSelection = this.selection.filter(selected => !selected.matches(editor)); + this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newInactiveSelection); } } From 0771b5f41a09194fc1937b9123f68ed899582257 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 21 May 2024 18:46:35 +0200 Subject: [PATCH 08/17] :lipstick: --- .../workbench/browser/parts/editor/multiEditorTabsControl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index f88f5941e0919..395c83aa7a172 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -1293,7 +1293,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { throw new BugIndicatingError(); } - const selection = this.groupView.selectedEditors; + let selection = this.groupView.selectedEditors; // Unselect editors on other side of anchor in relation to the target let currentIndex = anchorIndex; @@ -1309,7 +1309,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { break; } - selection.filter(editor => !editor.matches(currentEditor)); // TODO this is a no-op + selection = selection.filter(editor => !editor.matches(currentEditor)); } // Select editors between anchor and target From ca52eca2abc51cac29e8b63281827f560bd45348 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 21 May 2024 22:29:34 +0200 Subject: [PATCH 09/17] remove doSetActive --- .../common/editor/editorGroupModel.ts | 118 ++++++++++-------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 255e6f0fb5f1b..d61167af9cf3c 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -222,9 +222,6 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private selection: EditorInput[] = []; // editors in selected state, first one is active TODO align this with transient editors and use a Set instead - private set active(editor: EditorInput | null) { // TODO: this misses an event when selection changes and should be a method? - this.selection = editor ? [editor] : []; - } private get active(): EditorInput | null { return this.selection[0] ?? null; } @@ -413,7 +410,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { }; this._onDidModelChange.fire(event); - // Handle active & selection TODO why do we call this when `makeActive` is false? + // Handle active editor / selected editors this.setSelection(makeActive ? newEditor : this.activeEditor, options?.inactiveSelection ?? []); return { @@ -434,7 +431,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doPin(existingEditor, existingEditorIndex); } - // Activate / select it TODO why do we call this when `makeActive` is false? + // Handle active editor / selected editors this.setSelection(makeActive ? existingEditor : this.activeEditor, options?.inactiveSelection ?? []); // Respect index @@ -570,22 +567,22 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - const newInactiveSelection = this.selection.filter(selected => !selected.matches(newActive) && !selected.matches(editor)); + const newInactiveSelection = this.selection.filter(selected => selected !== newActive && selected !== editor); this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelection); } // One Editor else { - this.active = null; // TODO this potentially changes selection without a related event. and is it expected to change the selection? + this.selection = []; } } - // Remove from selection + // Inactive Editor closed else if (!isActiveEditor) { - const wasSelected = !!this.selection.find(selected => this.matches(selected, editor)); - if (wasSelected) { - const newInactiveSelection = this.selection.filter(selected => !selected.matches(editor)); - this.doSetSelection(this.activeEditor!, this.indexOf(this.activeEditor), newInactiveSelection); + if (this.isSelected(editor)) { + const activeEditor = this.selection[0]; + const newInactiveSelection = this.selection.filter(selected => selected !== editor && selected !== activeEditor); + this.doSetSelection(activeEditor, this.indexOf(activeEditor), newInactiveSelection); } } @@ -685,47 +682,25 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const [editor, editorIndex] = res; - this.doSetActive(editor, editorIndex); + this.doSetSelection(editor, editorIndex, []); return editor; } - private doSetActive(editor: EditorInput, editorIndex: number): void { - if (this.matches(this.active, editor)) { - this.selection = [editor]; // TODO this potentially changes selection without a related event. and is it expected to change the selection? - return; // already active - } - - this.active = editor; // TODO this potentially changes selection without a related event. and is it expected to change the selection? - - // Bring to front in MRU list - const mruIndex = this.indexOf(editor, this.mru); - this.mru.splice(mruIndex, 1); - this.mru.unshift(editor); - - // Event - const event: IGroupEditorChangeEvent = { - kind: GroupModelChangeKind.EDITOR_ACTIVE, - editor, - editorIndex - }; - this._onDidModelChange.fire(event); - } - get selectedEditors(): EditorInput[] { // Return selected editors in sequential order - return this.editors.filter(editor => this.isSelected(editor)); // TODO I would have assumed `this.selection` to be in sequential order + return this.editors.filter(editor => this.isSelected(editor)); } - isSelected(editorOrIndex: EditorInput | number): boolean { // TODO align with how isTransient() works - let editor: EditorInput; + isSelected(editorOrIndex: EditorInput | number): boolean { + let editor: EditorInput | undefined; if (typeof editorOrIndex === 'number') { editor = this.editors[editorOrIndex]; } else { - editor = editorOrIndex; + editor = this.findEditor(editorOrIndex)?.[0]; } - return !!this.selection.find(selectedEditor => this.matches(selectedEditor, editor)); + return !!editor && !!this.selection.includes(editor); } setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { @@ -736,24 +711,59 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const [newActiveEditor, newActiveEditorIndex] = res; - this.doSetSelection(newActiveEditor, newActiveEditorIndex, inactiveSelectedEditors); + // Validate inactive selection and ensure they are not duplicates + const newInactiveSelection: EditorInput[] = []; + for (const candiate of inactiveSelectedEditors) { + const res = this.findEditor(candiate); + if (!res) { + return; // not found + } + + const [inactiveEditor] = res; + + if (inactiveEditor === newActiveEditor || newInactiveSelection.includes(inactiveEditor)) { + continue; // dedupe + } + + newInactiveSelection.push(inactiveEditor); + } + + this.doSetSelection(newActiveEditor, newActiveEditorIndex, newInactiveSelection); } private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { - this.doSetActive(newActiveEditor, activeEditorIndex); + const previousActiveEditor = this.selection[0]; + const previousSelection = this.selection; + const newSelection = [newActiveEditor, ...inactiveSelectedEditors]; - for (const candidate of inactiveSelectedEditors) { // TODO should this move up to the public API? the idea is that we do not trust editor inputs from public API but internal is fine - const editor = this.findEditor(candidate)?.[0]; - if (editor && !this.isSelected(editor)) { - this.selection.push(editor); - } + // Update selection + this.selection = newSelection; + + // Update MRU if active editor has changed and fire event + const activeEditorChanged = !this.matches(previousActiveEditor, newActiveEditor); + if (activeEditorChanged) { + // Bring to front in MRU list + const mruIndex = this.indexOf(newActiveEditor, this.mru); + this.mru.splice(mruIndex, 1); + this.mru.unshift(newActiveEditor); + + // Event + const event: IGroupEditorChangeEvent = { + kind: GroupModelChangeKind.EDITOR_ACTIVE, + editor: newActiveEditor, + editorIndex: activeEditorIndex + }; + this._onDidModelChange.fire(event); } - // Event TODO what if selection did not change? - const event: IGroupModelChangeEvent = { - kind: GroupModelChangeKind.EDITORS_SELECTION, - }; - this._onDidModelChange.fire(event); + // Fire event if the selection has changed + const selectionChanged = activeEditorChanged || previousSelection.length !== newSelection.length || previousSelection.some(editor => !newSelection.includes(editor)); + if (selectionChanged) { + const event: IGroupModelChangeEvent = { + kind: GroupModelChangeKind.EDITORS_SELECTION, + }; + this._onDidModelChange.fire(event); + } } setIndex(index: number) { @@ -1143,7 +1153,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { clone.editors = this.editors.slice(0); clone.mru = this.mru.slice(0); clone.preview = this.preview; - clone.active = this.active; + clone.selection = this.selection; clone.sticky = this.sticky; // Ensure to register listeners for each editor @@ -1245,7 +1255,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.mru = coalesce(data.mru.map(i => this.editors[i])); - this.active = this.mru[0]; + this.selection = [this.mru[0]]; if (typeof data.preview === 'number') { this.preview = this.editors[data.preview]; From 98845675b45ce05f0c3dcb7511dda63b27cb175b Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Tue, 21 May 2024 23:18:29 +0200 Subject: [PATCH 10/17] :lipstick: --- src/vs/workbench/common/editor/editorGroupModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index d61167af9cf3c..e55514fb01664 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -220,7 +220,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { private locked = false; - private selection: EditorInput[] = []; // editors in selected state, first one is active TODO align this with transient editors and use a Set instead + private selection: EditorInput[] = []; // editors in selected state, first one is active private get active(): EditorInput | null { return this.selection[0] ?? null; @@ -700,7 +700,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { editor = this.findEditor(editorOrIndex)?.[0]; } - return !!editor && !!this.selection.includes(editor); + return !!editor && this.selection.includes(editor); } setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { From 1e3ff5d1c585cd725fd1788196d0f0e06e68211f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:04:46 +0200 Subject: [PATCH 11/17] review 1 --- .../common/editor/editorGroupModel.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index e55514fb01664..32503cde7ae7c 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -183,7 +183,7 @@ export interface IReadonlyEditorGroupModel { isActive(editor: EditorInput | IUntypedEditorInput): boolean; isPinned(editorOrIndex: EditorInput | number): boolean; isSticky(editorOrIndex: EditorInput | number): boolean; - isSelected(editor: EditorInput | number): boolean; + isSelected(editorOrIndex: EditorInput | number): boolean; isTransient(editorOrIndex: EditorInput | number): boolean; isFirst(editor: EditorInput, editors?: EditorInput[]): boolean; isLast(editor: EditorInput, editors?: EditorInput[]): boolean; @@ -579,7 +579,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { // Inactive Editor closed else if (!isActiveEditor) { - if (this.isSelected(editor)) { + if (this.doIsSelected(editor)) { const activeEditor = this.selection[0]; const newInactiveSelection = this.selection.filter(selected => selected !== editor && selected !== activeEditor); this.doSetSelection(activeEditor, this.indexOf(activeEditor), newInactiveSelection); @@ -688,8 +688,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } get selectedEditors(): EditorInput[] { - // Return selected editors in sequential order - return this.editors.filter(editor => this.isSelected(editor)); + return this.editors.filter(editor => this.doIsSelected(editor)); // return in sequential order } isSelected(editorOrIndex: EditorInput | number): boolean { @@ -700,7 +699,11 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { editor = this.findEditor(editorOrIndex)?.[0]; } - return !!editor && this.selection.includes(editor); + return !!editor && this.doIsSelected(editor); + } + + private doIsSelected(editor: EditorInput): boolean { + return this.selection.includes(editor); } setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { @@ -731,27 +734,27 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doSetSelection(newActiveEditor, newActiveEditorIndex, newInactiveSelection); } - private doSetSelection(newActiveEditor: EditorInput, activeEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { + private doSetSelection(activeSelectedEditor: EditorInput, activeSelectedEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { const previousActiveEditor = this.selection[0]; const previousSelection = this.selection; - const newSelection = [newActiveEditor, ...inactiveSelectedEditors]; + const newSelection = [activeSelectedEditor, ...inactiveSelectedEditors]; // Update selection this.selection = newSelection; // Update MRU if active editor has changed and fire event - const activeEditorChanged = !this.matches(previousActiveEditor, newActiveEditor); + const activeEditorChanged = !this.matches(previousActiveEditor, activeSelectedEditor); if (activeEditorChanged) { // Bring to front in MRU list - const mruIndex = this.indexOf(newActiveEditor, this.mru); + const mruIndex = this.indexOf(activeSelectedEditor, this.mru); this.mru.splice(mruIndex, 1); - this.mru.unshift(newActiveEditor); + this.mru.unshift(activeSelectedEditor); // Event const event: IGroupEditorChangeEvent = { kind: GroupModelChangeKind.EDITOR_ACTIVE, - editor: newActiveEditor, - editorIndex: activeEditorIndex + editor: activeSelectedEditor, + editorIndex: activeSelectedEditorIndex }; this._onDidModelChange.fire(event); } @@ -1153,7 +1156,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { clone.editors = this.editors.slice(0); clone.mru = this.mru.slice(0); clone.preview = this.preview; - clone.selection = this.selection; + clone.selection = this.selection.slice(0); clone.sticky = this.sticky; // Ensure to register listeners for each editor @@ -1255,7 +1258,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.mru = coalesce(data.mru.map(i => this.editors[i])); - this.selection = [this.mru[0]]; + this.selection = this.mru.length > 0 ? [this.mru[0]] : []; if (typeof data.preview === 'number') { this.preview = this.editors[data.preview]; From cf18e85d7fa9c15c7d91e163eccf61ba79d77384 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:16:54 +0200 Subject: [PATCH 12/17] review 2 --- .../common/editor/editorGroupModel.ts | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 32503cde7ae7c..c952fc074171e 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -580,9 +580,9 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { // Inactive Editor closed else if (!isActiveEditor) { if (this.doIsSelected(editor)) { - const activeEditor = this.selection[0]; + const activeEditor = this.activeEditor; const newInactiveSelection = this.selection.filter(selected => selected !== editor && selected !== activeEditor); - this.doSetSelection(activeEditor, this.indexOf(activeEditor), newInactiveSelection); + this.doSetSelection(activeEditor!, this.indexOf(activeEditor), newInactiveSelection); } } @@ -706,45 +706,44 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return this.selection.includes(editor); } - setSelection(activeSelectedEditor: EditorInput, inactiveSelectedEditors: EditorInput[]): void { - const res = this.findEditor(activeSelectedEditor); + setSelection(activeSelectedEditorCandidate: EditorInput, inactiveSelectedEditorCandidates: EditorInput[]): void { + const res = this.findEditor(activeSelectedEditorCandidate); if (!res) { return; // not found } - const [newActiveEditor, newActiveEditorIndex] = res; + const [activeSelectedEditor, activeSelectedEditorIndex] = res; - // Validate inactive selection and ensure they are not duplicates - const newInactiveSelection: EditorInput[] = []; - for (const candiate of inactiveSelectedEditors) { - const res = this.findEditor(candiate); + const inactiveSelectedEditors = new Set(); + for (const inactiveSelectedEditorCandidate of inactiveSelectedEditorCandidates) { + const res = this.findEditor(inactiveSelectedEditorCandidate); if (!res) { - return; // not found + continue; // not found } - const [inactiveEditor] = res; - - if (inactiveEditor === newActiveEditor || newInactiveSelection.includes(inactiveEditor)) { - continue; // dedupe + const [inactiveSelectedEditor] = res; + if (inactiveSelectedEditor === activeSelectedEditor) { + continue; // already selected } - newInactiveSelection.push(inactiveEditor); + inactiveSelectedEditors.add(inactiveSelectedEditor); } - this.doSetSelection(newActiveEditor, newActiveEditorIndex, newInactiveSelection); + this.doSetSelection(activeSelectedEditor, activeSelectedEditorIndex, Array.from(inactiveSelectedEditors)); } private doSetSelection(activeSelectedEditor: EditorInput, activeSelectedEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { - const previousActiveEditor = this.selection[0]; + const previousActiveEditor = this.activeEditor; const previousSelection = this.selection; const newSelection = [activeSelectedEditor, ...inactiveSelectedEditors]; // Update selection this.selection = newSelection; - // Update MRU if active editor has changed and fire event + // Update active editor if it has changed const activeEditorChanged = !this.matches(previousActiveEditor, activeSelectedEditor); if (activeEditorChanged) { + // Bring to front in MRU list const mruIndex = this.indexOf(activeSelectedEditor, this.mru); this.mru.splice(mruIndex, 1); @@ -760,10 +759,13 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } // Fire event if the selection has changed - const selectionChanged = activeEditorChanged || previousSelection.length !== newSelection.length || previousSelection.some(editor => !newSelection.includes(editor)); - if (selectionChanged) { + if ( + activeEditorChanged || + previousSelection.length !== newSelection.length || + previousSelection.some(editor => !newSelection.includes(editor)) + ) { const event: IGroupModelChangeEvent = { - kind: GroupModelChangeKind.EDITORS_SELECTION, + kind: GroupModelChangeKind.EDITORS_SELECTION }; this._onDidModelChange.fire(event); } From 207477745f95550c4506da71f733a0bc680c9a42 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:22:47 +0200 Subject: [PATCH 13/17] unrelated --- .../common/editor/editorGroupModel.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index c952fc074171e..390cb62f3982f 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -691,12 +691,12 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return this.editors.filter(editor => this.doIsSelected(editor)); // return in sequential order } - isSelected(editorOrIndex: EditorInput | number): boolean { + isSelected(editorCandidateOrIndex: EditorInput | number): boolean { let editor: EditorInput | undefined; - if (typeof editorOrIndex === 'number') { - editor = this.editors[editorOrIndex]; + if (typeof editorCandidateOrIndex === 'number') { + editor = this.editors[editorCandidateOrIndex]; } else { - editor = this.findEditor(editorOrIndex)?.[0]; + editor = this.findEditor(editorCandidateOrIndex)?.[0]; } return !!editor && this.doIsSelected(editor); @@ -856,12 +856,12 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - isPinned(editorOrIndex: EditorInput | number): boolean { + isPinned(editorCandidateOrIndex: EditorInput | number): boolean { let editor: EditorInput; - if (typeof editorOrIndex === 'number') { - editor = this.editors[editorOrIndex]; + if (typeof editorCandidateOrIndex === 'number') { + editor = this.editors[editorCandidateOrIndex]; } else { - editor = editorOrIndex; + editor = editorCandidateOrIndex; } return !this.matches(this.preview, editor); @@ -998,16 +998,16 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this._onDidModelChange.fire(event); } - isTransient(editorOrIndex: EditorInput | number): boolean { + isTransient(editorCandidateOrIndex: EditorInput | number): boolean { if (this.transient.size === 0) { return false; // no transient editor } let editor: EditorInput | undefined; - if (typeof editorOrIndex === 'number') { - editor = this.editors[editorOrIndex]; + if (typeof editorCandidateOrIndex === 'number') { + editor = this.editors[editorCandidateOrIndex]; } else { - editor = this.findEditor(editorOrIndex)?.[0]; + editor = this.findEditor(editorCandidateOrIndex)?.[0]; } return !!editor && this.transient.has(editor); From fbe127de839a4ca91e904099a3ab16938c7e8245 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:33:44 +0200 Subject: [PATCH 14/17] review 3 --- .../workbench/common/editor/editorGroupModel.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 390cb62f3982f..2d87ba894cc8e 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -12,6 +12,7 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { coalesce } from 'vs/base/common/arrays'; +import { assertIsDefined } from 'vs/base/common/types'; const EditorOpenPositioning = { LEFT: 'left', @@ -567,22 +568,26 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - const newInactiveSelection = this.selection.filter(selected => selected !== newActive && selected !== editor); - this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelection); + // Set editor active + const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== newActive); + this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelectedEditors); } // One Editor else { - this.selection = []; + this.selection = []; // TODO this misses an event? } } // Inactive Editor closed else if (!isActiveEditor) { + + // Remove Editor from selection if (this.doIsSelected(editor)) { - const activeEditor = this.activeEditor; - const newInactiveSelection = this.selection.filter(selected => selected !== editor && selected !== activeEditor); - this.doSetSelection(activeEditor!, this.indexOf(activeEditor), newInactiveSelection); + const activeEditor = assertIsDefined(this.activeEditor); + const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== activeEditor); + + this.doSetSelection(activeEditor, this.indexOf(activeEditor), newInactiveSelectedEditors); } } From 1c22ee543da904baedc643f314f4e4e00214ec47 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:41:12 +0200 Subject: [PATCH 15/17] return if invalid inactive selection --- src/vs/workbench/common/editor/editorGroupModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 2d87ba894cc8e..f2e37c8a280af 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -723,7 +723,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { for (const inactiveSelectedEditorCandidate of inactiveSelectedEditorCandidates) { const res = this.findEditor(inactiveSelectedEditorCandidate); if (!res) { - continue; // not found + return; // not found } const [inactiveSelectedEditor] = res; From a0ce904534930b6604f2c13746d58c3894d41d0f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 12:48:51 +0200 Subject: [PATCH 16/17] reduce use of matches --- src/vs/workbench/common/editor/editorGroupModel.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index f2e37c8a280af..303e13bc441da 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -297,8 +297,8 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { return this.active; } - isActive(editor: EditorInput | IUntypedEditorInput): boolean { - return this.matches(this.active, editor); + isActive(candidate: EditorInput | IUntypedEditorInput): boolean { + return this.matches(this.active, candidate); } get previewEditor(): EditorInput | null { @@ -309,7 +309,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const makeSticky = options?.sticky || (typeof options?.index === 'number' && this.isSticky(options.index)); const makePinned = options?.pinned || options?.sticky; const makeTransient = !!options?.transient; - const makeActive = options?.active || !this.activeEditor || (!makePinned && this.matches(this.preview, this.activeEditor)); + const makeActive = options?.active || !this.activeEditor || (!makePinned && this.preview === this.activeEditor); const existingEditorAndIndex = this.findEditor(candidate, options); @@ -552,7 +552,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const sticky = this.isSticky(index); // Active Editor closed - const isActiveEditor = this.matches(this.active, editor); + const isActiveEditor = this.active === editor; if (openNext && isActiveEditor) { // More than one editor @@ -592,7 +592,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } // Preview Editor closed - if (this.matches(this.preview, editor)) { + if (this.preview === editor) { this.preview = null; } @@ -746,7 +746,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.selection = newSelection; // Update active editor if it has changed - const activeEditorChanged = !this.matches(previousActiveEditor, activeSelectedEditor); + const activeEditorChanged = previousActiveEditor !== activeSelectedEditor; if (activeEditorChanged) { // Bring to front in MRU list From 9e70bb8120f16dd47116570f948c3bd49fba6b22 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 22 May 2024 13:06:24 +0200 Subject: [PATCH 17/17] send event when selection clears --- .../common/editor/editorGroupModel.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts index 303e13bc441da..6f04a87f062fc 100644 --- a/src/vs/workbench/common/editor/editorGroupModel.ts +++ b/src/vs/workbench/common/editor/editorGroupModel.ts @@ -12,7 +12,6 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { coalesce } from 'vs/base/common/arrays'; -import { assertIsDefined } from 'vs/base/common/types'; const EditorOpenPositioning = { LEFT: 'left', @@ -551,7 +550,7 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { const editor = this.editors[index]; const sticky = this.isSticky(index); - // Active Editor closed + // Active editor closed const isActiveEditor = this.active === editor; if (openNext && isActiveEditor) { @@ -568,26 +567,24 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { } } - // Set editor active + // Select editor as active const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== newActive); this.doSetSelection(newActive, this.editors.indexOf(newActive), newInactiveSelectedEditors); } - // One Editor + // Last editor closed: clear selection else { - this.selection = []; // TODO this misses an event? + this.doSetSelection(null, undefined, []); } } - // Inactive Editor closed + // Inactive editor closed else if (!isActiveEditor) { - // Remove Editor from selection + // Remove editor from inactive selection if (this.doIsSelected(editor)) { - const activeEditor = assertIsDefined(this.activeEditor); - const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== activeEditor); - - this.doSetSelection(activeEditor, this.indexOf(activeEditor), newInactiveSelectedEditors); + const newInactiveSelectedEditors = this.selection.filter(selected => selected !== editor && selected !== this.activeEditor); + this.doSetSelection(this.activeEditor, this.indexOf(this.activeEditor), newInactiveSelectedEditors); } } @@ -737,16 +734,22 @@ export class EditorGroupModel extends Disposable implements IEditorGroupModel { this.doSetSelection(activeSelectedEditor, activeSelectedEditorIndex, Array.from(inactiveSelectedEditors)); } - private doSetSelection(activeSelectedEditor: EditorInput, activeSelectedEditorIndex: number, inactiveSelectedEditors: EditorInput[]): void { + private doSetSelection(activeSelectedEditor: EditorInput | null, activeSelectedEditorIndex: number | undefined, inactiveSelectedEditors: EditorInput[]): void { const previousActiveEditor = this.activeEditor; const previousSelection = this.selection; - const newSelection = [activeSelectedEditor, ...inactiveSelectedEditors]; + + let newSelection: EditorInput[]; + if (activeSelectedEditor) { + newSelection = [activeSelectedEditor, ...inactiveSelectedEditors]; + } else { + newSelection = []; + } // Update selection this.selection = newSelection; // Update active editor if it has changed - const activeEditorChanged = previousActiveEditor !== activeSelectedEditor; + const activeEditorChanged = activeSelectedEditor && typeof activeSelectedEditorIndex === 'number' && previousActiveEditor !== activeSelectedEditor; if (activeEditorChanged) { // Bring to front in MRU list