Skip to content

Add "Close All Other Windows" command to VS Code #251291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions src/vs/workbench/electron-browser/actions/test/windowActions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import * as sinon from 'sinon';
import { CloseAllOtherWindowsAction } from '../windowActions.js';
import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { INativeHostService } from '../../../../platform/native/common/native.js';
import { IOpenedMainWindow, IOpenedAuxiliaryWindow } from '../../../../platform/window/common/window.js';
import { URI } from '../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';

suite('CloseAllOtherWindowsAction', () => {

ensureNoDisposablesAreLeakedInTestSuite();

let instantiationService: TestInstantiationService;
let nativeHostService: MockNativeHostService;
let action: CloseAllOtherWindowsAction;

class MockNativeHostService implements Partial<INativeHostService> {
private windows: Array<IOpenedMainWindow | IOpenedAuxiliaryWindow> = [];
private closedWindows: number[] = [];

setWindows(windows: Array<IOpenedMainWindow | IOpenedAuxiliaryWindow>) {
this.windows = windows;
}

getClosedWindows(): number[] {
return [...this.closedWindows];
}

async getWindows(): Promise<Array<IOpenedMainWindow | IOpenedAuxiliaryWindow>> {
return this.windows;
}

async closeWindow(options?: { targetWindowId?: number }): Promise<void> {
if (options?.targetWindowId) {
this.closedWindows.push(options.targetWindowId);
}
}
}

setup(() => {
instantiationService = new TestInstantiationService();
nativeHostService = new MockNativeHostService();
instantiationService.set(INativeHostService, nativeHostService);
action = new CloseAllOtherWindowsAction();
});

test('should close all windows except current', async () => {
// Set up multiple windows
const windows: IOpenedMainWindow[] = [
{
id: 1,
workspace: { id: 'workspace1', uri: URI.file('/workspace1') },
title: 'Window 1',
filename: '',
dirty: false
},
{
id: 2,
workspace: { id: 'workspace2', uri: URI.file('/workspace2') },
title: 'Window 2',
filename: '',
dirty: false
},
{
id: 3,
workspace: { id: 'workspace3', uri: URI.file('/workspace3') },
title: 'Window 3',
filename: '',
dirty: false
}
];

nativeHostService.setWindows(windows);

// Mock getActiveWindow to return window ID 1
const domModule = await import('../../../../base/browser/dom.js');
const getActiveWindowStub = sinon.stub(domModule, 'getActiveWindow').returns({ vscodeWindowId: 1 } as any);

try {
// Run the action
await action.run(instantiationService);

// Verify that only windows 2 and 3 were closed (not window 1 which is current)
const closedWindows = nativeHostService.getClosedWindows();
assert.strictEqual(closedWindows.length, 2);
assert.ok(closedWindows.includes(2));
assert.ok(closedWindows.includes(3));
assert.ok(!closedWindows.includes(1));
} finally {
getActiveWindowStub.restore();
}
});

test('should handle no other windows to close', async () => {
// Set up only one window (current)
const windows: IOpenedMainWindow[] = [
{
id: 1,
workspace: { id: 'workspace1', uri: URI.file('/workspace1') },
title: 'Window 1',
filename: '',
dirty: false
}
];

nativeHostService.setWindows(windows);

// Mock getActiveWindow to return window ID 1
const domModule = await import('../../../../base/browser/dom.js');
const getActiveWindowStub = sinon.stub(domModule, 'getActiveWindow').returns({ vscodeWindowId: 1 } as any);

try {
// Run the action
await action.run(instantiationService);

// Verify no windows were closed
const closedWindows = nativeHostService.getClosedWindows();
assert.strictEqual(closedWindows.length, 0);
} finally {
getActiveWindowStub.restore();
}
});

test('should handle auxiliary windows', async () => {
// Set up main and auxiliary windows
const windows: Array<IOpenedMainWindow | IOpenedAuxiliaryWindow> = [
{
id: 1,
workspace: { id: 'workspace1', uri: URI.file('/workspace1') },
title: 'Main Window',
filename: '',
dirty: false
},
{
id: 2,
parentId: 1,
title: 'Auxiliary Window',
filename: ''
}
];

nativeHostService.setWindows(windows);

// Mock getActiveWindow to return window ID 1
const domModule = await import('../../../../base/browser/dom.js');
const getActiveWindowStub = sinon.stub(domModule, 'getActiveWindow').returns({ vscodeWindowId: 1 } as any);

try {
// Run the action
await action.run(instantiationService);

// Verify that auxiliary window was closed
const closedWindows = nativeHostService.getClosedWindows();
assert.strictEqual(closedWindows.length, 1);
assert.ok(closedWindows.includes(2));
assert.ok(!closedWindows.includes(1));
} finally {
getActiveWindowStub.restore();
}
});
});
39 changes: 39 additions & 0 deletions src/vs/workbench/electron-browser/actions/windowActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,45 @@ export class CloseWindowAction extends Action2 {
}
}

export class CloseAllOtherWindowsAction extends Action2 {

static readonly ID = 'workbench.action.closeAllOtherWindows';

constructor() {
super({
id: CloseAllOtherWindowsAction.ID,
title: {
...localize2('closeAllOtherWindows', "Close All Other Windows"),
mnemonicTitle: localize({ key: 'miCloseAllOtherWindows', comment: ['&& denotes a mnemonic'] }, "Close All &&Other Windows"),
},
f1: true,
menu: {
id: MenuId.MenubarFileMenu,
group: '6_close',
order: 5
}
});
}

override async run(accessor: ServicesAccessor): Promise<void> {
const nativeHostService = accessor.get(INativeHostService);

const currentWindowId = getActiveWindow().vscodeWindowId;
const windows = await nativeHostService.getWindows({ includeAuxiliaryWindows: true });

// Close all windows except the current one
const closePromises: Promise<void>[] = [];
for (const window of windows) {
if (window.id !== currentWindowId) {
closePromises.push(nativeHostService.closeWindow({ targetWindowId: window.id }));
}
}

// Wait for all windows to close
await Promise.allSettled(closePromises);
}
}

abstract class BaseZoomAction extends Action2 {

private static readonly ZOOM_LEVEL_SETTING_KEY = 'window.zoomLevel';
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/electron-browser/desktop.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
import { KeyMod, KeyCode } from '../../base/common/keyCodes.js';
import { isLinux, isMacintosh, isWindows } from '../../base/common/platform.js';
import { ConfigureRuntimeArgumentsAction, ToggleDevToolsAction, ReloadWindowWithExtensionsDisabledAction, OpenUserDataFolderAction, ShowGPUInfoAction, StopTracing } from './actions/developerActions.js';
import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ToggleWindowAlwaysOnTopAction, DisableWindowAlwaysOnTopAction, EnableWindowAlwaysOnTopAction } from './actions/windowActions.js';
import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseWindowAction, CloseAllOtherWindowsAction, SwitchWindowAction, QuickSwitchWindowAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler, ToggleWindowAlwaysOnTopAction, DisableWindowAlwaysOnTopAction, EnableWindowAlwaysOnTopAction } from './actions/windowActions.js';
import { ContextKeyExpr } from '../../platform/contextkey/common/contextkey.js';
import { KeybindingsRegistry, KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js';
import { CommandsRegistry } from '../../platform/commands/common/commands.js';
Expand Down Expand Up @@ -43,6 +43,7 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../common/contri
registerAction2(SwitchWindowAction);
registerAction2(QuickSwitchWindowAction);
registerAction2(CloseWindowAction);
registerAction2(CloseAllOtherWindowsAction);
registerAction2(ToggleWindowAlwaysOnTopAction);
registerAction2(EnableWindowAlwaysOnTopAction);
registerAction2(DisableWindowAlwaysOnTopAction);
Expand Down