From f677e140e31b69a6e0b9e0909bd29bf8395f6e55 Mon Sep 17 00:00:00 2001 From: j4rviscmd Date: Sat, 23 May 2026 03:04:24 +0900 Subject: [PATCH] fix: suppress "View not found" alert and update pane index on group removal - Guard removeGroup call with existence check to prevent double-removal race condition when TerminalKillFocusRestoreController and VSCode's built-in closeEmptyGroups both try to remove the same group - Add onDidAddGroup/onDidRemoveGroup events to IEditorGroupsView interface so MultiEditorTabsControl can update the [N] pane index badge when groups are added or removed - Add Event.None stubs to TestEditorGroupAccessor for new interface events Co-Authored-By: Claude Opus 4.7 --- .../workbench/browser/parts/editor/editor.ts | 3 +++ .../browser/parts/editor/editorGroupView.ts | 2 +- .../parts/editor/multiEditorTabsControl.ts | 4 +++ .../test/browser/workbenchTestServices.ts | 26 +++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index c6b6025d3a9..0bb44e12d9b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -223,6 +223,9 @@ export interface IEditorGroupsView { readonly onDidVisibilityChange: Event; + readonly onDidAddGroup: Event; + readonly onDidRemoveGroup: Event; + getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined; getGroups(order: GroupsOrder): IEditorGroupView[]; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index bc63d7878cb..9b517047e4b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1654,7 +1654,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidActiveEditorChange.fire({ editor: undefined }); // Remove empty group if we should - if (closeEmptyGroup) { + if (closeEmptyGroup && this.groupsView.getGroup(this.id)) { this.groupsView.removeGroup(this, preserveFocus); } } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index a2d3ca749d8..6e30fb2f492 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -188,6 +188,10 @@ export class MultiEditorTabsControl extends EditorTabsControl { this._register(this.editorService.onDidVisibleEditorsChange(() => { this.editorGroupIndexScheduler.value = scheduleAtNextAnimationFrame(getWindow(this.parent), () => this.updateEditorGroupIndex()); })); + + // React to group add/remove to update group index badge (Coderm) + this._register(this.groupsView.onDidRemoveGroup(() => this.updateEditorGroupIndex())); + this._register(this.groupsView.onDidAddGroup(() => this.updateEditorGroupIndex())); } protected override create(parent: HTMLElement): HTMLElement { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 16023edfd24..fdda4c9dbb2 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1032,6 +1032,8 @@ export class TestEditorGroupAccessor implements IEditorGroupsView { onDidChangeEditorPartOptions = Event.None; onDidVisibilityChange = Event.None; + onDidAddGroup = Event.None; + onDidRemoveGroup = Event.None; getGroup(identifier: number): IEditorGroupView | undefined { throw new Error('Method not implemented.'); } getGroups(order: GroupsOrder): IEditorGroupView[] { throw new Error('Method not implemented.'); } @@ -1797,6 +1799,9 @@ export class TestWorkspacesService implements IWorkspacesService { async getWorkspaceIdentifier(workspacePath: URI): Promise { throw new Error('Method not implemented.'); } } +/** + * No-op {@link ITerminalInstanceService} stub for workbench tests. + */ export class TestTerminalInstanceService implements ITerminalInstanceService { onDidCreateInstance = Event.None; onDidRegisterBackend = Event.None; @@ -1810,6 +1815,12 @@ export class TestTerminalInstanceService implements ITerminalInstanceService { getRegisteredBackends(): IterableIterator { throw new Error('Method not implemented.'); } } +/** + * No-op {@link ITerminalEditorService} stub for workbench tests. + * Every method throws `"Method not implemented."` — only the events + * are wired to `Event.None` so consumers that subscribe during setup + * do not explode. + */ export class TestTerminalEditorService implements ITerminalEditorService { _serviceBrand: undefined; activeInstance: ITerminalInstance | undefined; @@ -1836,6 +1847,10 @@ export class TestTerminalEditorService implements ITerminalEditorService { findPrevious(): void { throw new Error('Method not implemented.'); } } +/** + * No-op {@link ITerminalGroupService} stub for workbench tests. + * Mirrors the pattern used by other `Test*Service` classes in this file. + */ export class TestTerminalGroupService implements ITerminalGroupService { _serviceBrand: undefined; activeInstance: ITerminalInstance | undefined; @@ -1883,6 +1898,9 @@ export class TestTerminalGroupService implements ITerminalGroupService { updateVisibility(): void { throw new Error('Method not implemented.'); } } +/** + * No-op {@link ITerminalProfileService} stub for workbench tests. + */ export class TestTerminalProfileService implements ITerminalProfileService { _serviceBrand: undefined; availableProfiles: ITerminalProfile[] = []; @@ -1901,6 +1919,10 @@ export class TestTerminalProfileService implements ITerminalProfileService { overrideDefaultProfile(extensionIdentifier: string, id: string): IDisposable { return Disposable.None; } } +/** + * No-op {@link ITerminalProfileResolverService} stub for workbench tests. + * Returns minimal defaults so callers that await profile resolution succeed. + */ export class TestTerminalProfileResolverService implements ITerminalProfileResolverService { _serviceBrand: undefined; defaultProfileName = ''; @@ -1916,6 +1938,10 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise { throw new Error('Method not implemented.'); } } +/** + * Test-friendly subclass of {@link TerminalConfigurationService} that + * exposes the internal font-metrics and config setters for unit tests. + */ export class TestTerminalConfigurationService extends TerminalConfigurationService { get fontMetrics() { return this._fontMetrics; } // eslint-disable-next-line local/code-no-any-casts