From 9fffd2bd580f825cbafacccb1de56d936e5d2df1 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 27 Jan 2026 00:47:01 +0100 Subject: [PATCH] add actions to switch workbench mode (#290595) * add actions to switch workbench mode * feedback --- src/vs/workbench/browser/contextkeys.ts | 9 +++- src/vs/workbench/browser/web.main.ts | 2 +- src/vs/workbench/common/contextkeys.ts | 2 + .../agentSessions/agentSessionsActions.ts | 47 ++++++++++++++++++- .../electron-browser/chat.contribution.ts | 4 +- .../electron-browser/desktop.main.ts | 2 +- .../layout/browser/workbenchModeService.ts | 46 ++++++++++++------ 7 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 366e5759558..4be91f9e5fb 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -6,7 +6,7 @@ import { Disposable } from '../../base/common/lifecycle.js'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from '../../platform/contextkey/common/contextkey.js'; import { IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from '../../platform/contextkey/common/contextkeys.js'; -import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsAgentSessionsWorkspaceContext } from '../common/contextkeys.js'; +import { SplitEditorsVertically, InEditorZenModeContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsMainWindowFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, TitleBarVisibleContext, TitleBarStyleContext, IsAuxiliaryWindowFocusedContext, ActiveEditorGroupEmptyContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorGroupLockedContext, MultipleEditorGroupsContext, EditorsVisibleContext, AuxiliaryBarMaximizedContext, InAutomationContext, IsAgentSessionsWorkspaceContext, WorkbenchModeContext } from '../common/contextkeys.js'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { IWorkbenchEnvironmentService } from '../services/environment/common/environmentService.js'; @@ -23,6 +23,7 @@ import { getTitleBarStyle } from '../../platform/window/common/window.js'; import { mainWindow } from '../../base/browser/window.js'; import { isFullscreen, onDidChangeFullscreen } from '../../base/browser/browser.js'; import { IEditorService } from '../services/editor/common/editorService.js'; +import { IWorkbenchModeService } from '../services/layout/common/workbenchModeService.js'; export class WorkbenchContextKeysHandler extends Disposable { @@ -48,6 +49,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private virtualWorkspaceContext: IContextKey; private temporaryWorkspaceContext: IContextKey; private isAgentSessionsWorkspaceContext: IContextKey; + private workbenchModeContext: IContextKey; private inAutomationContext: IContextKey; private inZenModeContext: IContextKey; @@ -77,6 +79,7 @@ export class WorkbenchContextKeysHandler extends Disposable { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, + @IWorkbenchModeService private readonly workbenchModeService: IWorkbenchModeService, ) { super(); @@ -96,6 +99,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.temporaryWorkspaceContext = TemporaryWorkspaceContext.bindTo(this.contextKeyService); this.isAgentSessionsWorkspaceContext = IsAgentSessionsWorkspaceContext.bindTo(this.contextKeyService); this.isAgentSessionsWorkspaceContext.set(!!this.contextService.getWorkspace().isAgentSessionsWorkspace); + this.workbenchModeContext = WorkbenchModeContext.bindTo(this.contextKeyService); + this.workbenchModeContext.set(this.workbenchModeService.workbenchMode ?? ''); this.updateWorkspaceContextKeys(); // Capabilities @@ -227,6 +232,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.updateWorkspaceContextKeys(); })); + this._register(this.workbenchModeService.onDidChangeWorkbenchMode(mode => this.workbenchModeContext.set(mode ?? ''))); + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { this.updateSplitEditorsVerticallyContext(); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 048fb005145..b9a2f073d8f 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -390,7 +390,7 @@ export class BrowserMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Layout Mode - const workbenchModeService: WorkbenchModeService = this._register(new WorkbenchModeService(configurationService, fileService, environmentService, uriIdentityService, logService)); + const workbenchModeService: WorkbenchModeService = this._register(new WorkbenchModeService(configurationService, fileService, environmentService, uriIdentityService, logService, storageService)); serviceCollection.set(IWorkbenchModeService, workbenchModeService); try { await workbenchModeService.initialize(); diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index b2de5c2f640..3a27ea16cac 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -35,6 +35,8 @@ export const TemporaryWorkspaceContext = new RawContextKey('temporaryWo export const IsAgentSessionsWorkspaceContext = new RawContextKey('isAgentSessionsWorkspace', false, localize('isAgentSessionsWorkspace', "Whether the current workspace is the agent sessions workspace.")); +export const WorkbenchModeContext = new RawContextKey('workbenchMode', '', localize('workbenchMode', "The current workbench mode.")); + export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSystemAccess', false, true); // Support for FileSystemAccess web APIs (https://wicg.github.io/file-system-access) export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); diff --git a/src/vs/workbench/contrib/chat/electron-browser/agentSessions/agentSessionsActions.ts b/src/vs/workbench/contrib/chat/electron-browser/agentSessions/agentSessionsActions.ts index e42ec4575cb..19cb4fbee53 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/agentSessions/agentSessionsActions.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/agentSessions/agentSessionsActions.ts @@ -7,11 +7,12 @@ import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions import { localize2 } from '../../../../../nls.js'; import { Action2 } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { ProductQualityContext } from '../../../../../platform/contextkey/common/contextkeys.js'; import { INativeEnvironmentService } from '../../../../../platform/environment/common/environment.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { INativeHostService } from '../../../../../platform/native/common/native.js'; import { ChatEntitlementContextKeys } from '../../../../services/chat/common/chatEntitlementService.js'; +import { IWorkbenchModeService } from '../../../../services/layout/common/workbenchModeService.js'; +import { IsAgentSessionsWorkspaceContext, WorkbenchModeContext } from '../../../../common/contextkeys.js'; import { CHAT_CATEGORY } from '../../browser/actions/chatActions.js'; export class OpenAgentSessionsWindowAction extends Action2 { @@ -20,7 +21,7 @@ export class OpenAgentSessionsWindowAction extends Action2 { id: 'workbench.action.openAgentSessionsWindow', title: localize2('openAgentSessionsWindow', "Open Agent Sessions Window"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(ChatEntitlementContextKeys.Setup.hidden.negate(), ProductQualityContext.notEqualsTo('stable')), + precondition: ChatEntitlementContextKeys.Setup.hidden.negate(), f1: true, }); } @@ -45,3 +46,45 @@ export class OpenAgentSessionsWindowAction extends Action2 { await nativeHostService.openWindow([{ workspaceUri }], { forceNewWindow: true }); } } + +export class SwitchToAgentSessionsModeAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.switchToAgentSessionsMode', + title: localize2('switchToAgentSessionsMode', "Switch to Agent Sessions Mode"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and( + ChatEntitlementContextKeys.Setup.hidden.negate(), + IsAgentSessionsWorkspaceContext.toNegated(), + WorkbenchModeContext.notEqualsTo('agent-sessions') + ), + f1: true, + }); + } + + async run(accessor: ServicesAccessor) { + const workbenchModeService = accessor.get(IWorkbenchModeService); + await workbenchModeService.setWorkbenchMode('agent-sessions'); + } +} + +export class SwitchToNormalModeAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.switchToNormalMode', + title: localize2('switchToNormalMode', "Switch to Default Mode"), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and( + ChatEntitlementContextKeys.Setup.hidden.negate(), + IsAgentSessionsWorkspaceContext.toNegated(), + WorkbenchModeContext.notEqualsTo('') + ), + f1: true, + }); + } + + async run(accessor: ServicesAccessor) { + const workbenchModeService = accessor.get(IWorkbenchModeService); + await workbenchModeService.setWorkbenchMode(undefined); + } +} diff --git a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts index 7deac3c64b1..c6dc91591cd 100644 --- a/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts @@ -35,7 +35,7 @@ import { registerChatDeveloperActions } from './actions/chatDeveloperActions.js' import { registerChatExportZipAction } from './actions/chatExportZip.js'; import { HoldToVoiceChatInChatViewAction, InlineVoiceChatAction, KeywordActivationContribution, QuickVoiceChatAction, ReadChatResponseAloud, StartVoiceChatAction, StopListeningAction, StopListeningAndSubmitAction, StopReadAloud, StopReadChatItemAloud, VoiceChatInChatViewAction } from './actions/voiceChatActions.js'; import { NativeBuiltinToolsContribution } from './builtInTools/tools.js'; -import { OpenAgentSessionsWindowAction } from './agentSessions/agentSessionsActions.js'; +import { OpenAgentSessionsWindowAction, SwitchToAgentSessionsModeAction, SwitchToNormalModeAction } from './agentSessions/agentSessionsActions.js'; class ChatCommandLineHandler extends Disposable { @@ -190,6 +190,8 @@ class ChatLifecycleHandler extends Disposable { } registerAction2(OpenAgentSessionsWindowAction); +registerAction2(SwitchToAgentSessionsModeAction); +registerAction2(SwitchToNormalModeAction); registerAction2(StartVoiceChatAction); registerAction2(VoiceChatInChatViewAction); diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 0efed2ad18a..b4399ffbe70 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -325,7 +325,7 @@ export class DesktopMain extends Disposable { ]); // Workbench Mode - const workbenchModeService: WorkbenchModeService = this._register(new WorkbenchModeService(configurationService, fileService, environmentService, uriIdentityService, logService)); + const workbenchModeService: WorkbenchModeService = this._register(new WorkbenchModeService(configurationService, fileService, environmentService, uriIdentityService, logService, storageService)); serviceCollection.set(IWorkbenchModeService, workbenchModeService); try { diff --git a/src/vs/workbench/services/layout/browser/workbenchModeService.ts b/src/vs/workbench/services/layout/browser/workbenchModeService.ts index cdcacee0f01..e80ab6c958f 100644 --- a/src/vs/workbench/services/layout/browser/workbenchModeService.ts +++ b/src/vs/workbench/services/layout/browser/workbenchModeService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from '../../../../base/common/event.js'; -import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { IFileService } from '../../../../platform/files/common/files.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; import { IWorkbenchModeConfiguration, IWorkbenchModeService } from '../common/workbenchModeService.js'; @@ -16,30 +16,36 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { Registry } from '../../../../platform/registry/common/platform.js'; import { Extensions, IConfigurationDefaults, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; import { IStringDictionary } from '../../../../base/common/collections.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; export class WorkbenchModeService extends Disposable implements IWorkbenchModeService { declare readonly _serviceBrand: undefined; + private static readonly WORKBENCH_MODE_STORAGE_KEY = 'workbench.mode'; + private _workbenchMode: string | undefined; get workbenchMode(): string | undefined { return this._workbenchMode; } private readonly _onDidChangeWorkbenchMode = this._register(new Emitter()); readonly onDidChangeWorkbenchMode: Event = this._onDidChangeWorkbenchMode.event; - private readonly workbenchModeFileWatcher = this._register(new MutableDisposable()); + private readonly workbenchModeFileWatcherDiposables = this._register(new DisposableStore()); private readonly configurationRegistry = Registry.as(Extensions.Configuration); private configurationDefaults: IConfigurationDefaults | undefined; constructor( - @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IStorageService private readonly storageService: IStorageService ) { super(); - this._workbenchMode = workspaceContextService.getWorkspace().isAgentSessionsWorkspace ? 'agent-sessions' : undefined; + this._workbenchMode = this.workspaceContextService.getWorkspace().isAgentSessionsWorkspace + ? 'agent-sessions' + : this.storageService.get(WorkbenchModeService.WORKBENCH_MODE_STORAGE_KEY, StorageScope.WORKSPACE); this.watchCurrentModeFile(); } @@ -48,10 +54,7 @@ export class WorkbenchModeService extends Disposable implements IWorkbenchModeSe } private async updateWorkbenchModeConfiguration(): Promise { - if (!this._workbenchMode) { - return; - } - const workbenchModeConfiguration = await this.getWorkbenchModeConfiguration(this._workbenchMode); + const workbenchModeConfiguration = this._workbenchMode ? await this.getWorkbenchModeConfiguration(this._workbenchMode) : undefined; this.updateConfigurationDefaults(workbenchModeConfiguration?.settings); } @@ -69,18 +72,18 @@ export class WorkbenchModeService extends Disposable implements IWorkbenchModeSe private watchCurrentModeFile(): void { if (!this._workbenchMode) { - this.workbenchModeFileWatcher.clear(); + this.workbenchModeFileWatcherDiposables.clear(); return; } const workbenchModeFileUri = this.getWorkbenchModeFileUri(this._workbenchMode); if (!workbenchModeFileUri) { - this.workbenchModeFileWatcher.clear(); + this.workbenchModeFileWatcherDiposables.clear(); return; } - this.workbenchModeFileWatcher.value = this.fileService.watch(workbenchModeFileUri); - this._register(this.fileService.onDidFilesChange(e => { + this.workbenchModeFileWatcherDiposables.add(this.fileService.watch(workbenchModeFileUri)); + this.workbenchModeFileWatcherDiposables.add(this.fileService.onDidFilesChange(e => { if (e.affects(workbenchModeFileUri)) { this.updateWorkbenchModeConfiguration(); this._onDidChangeWorkbenchMode.fire(this._workbenchMode); @@ -139,13 +142,26 @@ export class WorkbenchModeService extends Disposable implements IWorkbenchModeSe } async setWorkbenchMode(modeId: string | undefined): Promise { + if (this.workspaceContextService.getWorkspace().isAgentSessionsWorkspace) { + throw new Error('Cannot set workbench mode in an agent sessions workspace'); + } + if (this._workbenchMode === modeId) { return; } - this._workbenchMode = modeId; - this.updateWorkbenchModeConfiguration(); + this.updateWorkbenchMode(modeId); + await this.updateWorkbenchModeConfiguration(); this.watchCurrentModeFile(); this._onDidChangeWorkbenchMode.fire(modeId); } + + private updateWorkbenchMode(modeId: string | undefined): void { + this._workbenchMode = modeId; + if (modeId === undefined) { + this.storageService.remove(WorkbenchModeService.WORKBENCH_MODE_STORAGE_KEY, StorageScope.WORKSPACE); + } else { + this.storageService.store(WorkbenchModeService.WORKBENCH_MODE_STORAGE_KEY, modeId, StorageScope.WORKSPACE, StorageTarget.MACHINE); + } + } }