diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts index c412e4065added..c3f8312487f1b8 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts @@ -250,6 +250,32 @@ export class ModernitySettingsWidget extends Disposable { actionsRow.style.display = 'flex'; actionsRow.style.gap = '6px'; + // Developer mode toggle (T278837441): swaps between the simple locked + // chat panel and developer mode (code viewer, file tree, debugging, + // search, source control). The left panel and terminal stay locked. + if (this.mode === 'settings') { + const developerModeButton = this._register(new Button(actionsRow, { + ...defaultButtonStyles, + title: localize('modernity.developerMode.tooltip', "Swap between simple mode (locked chat panel) and developer mode (code viewer, file tree, debugging, search, source control). The left panel and the terminal stay locked in both modes."), + })); + const updateDeveloperModeButtonLabel = (): void => { + const developerMode = this.configurationService.getValue('modernity.developerMode') ?? false; + developerModeButton.label = developerMode + ? localize('modernity.developerMode.on', "Developer Mode: On") + : localize('modernity.developerMode.off', "Developer Mode: Off"); + }; + updateDeveloperModeButtonLabel(); + this._register(developerModeButton.onDidClick(async () => { + const developerMode = this.configurationService.getValue('modernity.developerMode') ?? false; + await this.configurationService.updateValue('modernity.developerMode', !developerMode, ConfigurationTarget.USER); + })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('modernity.developerMode')) { + updateDeveloperModeButtonLabel(); + } + })); + } + // Export / Import JSON buttons if (this.mode === 'settings') { const exportButton = this._register(new Button(actionsRow, { diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts index bb3d0e91234661..cdf57e97592c01 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts @@ -1887,6 +1887,12 @@ configurationRegistry.registerConfiguration({ default: false, }, // Modernity Dev Settings - extensible, workspace-scoped for paths + 'modernity.developerMode': { + type: 'boolean', + description: nls.localize('modernity.developerMode', "Developer mode swaps the locked chat panel (simple mode) for a code viewer, a file tree panel, and debugging, search and source control. The left panel and the terminal stay locked in both modes."), + default: false, + scope: ConfigurationScope.APPLICATION, + }, 'modernity.projects.uncompiledCodePath': { type: 'string', description: nls.localize('modernity.projects.uncompiledCodePath', "Directory where uncompiled / intermediate mod code is placed. Workspace setting."), diff --git a/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts b/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts index 5d51f3cadedd8d..ed611069cc8ad9 100644 --- a/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts +++ b/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts @@ -6,15 +6,35 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { ModernityDaemonStatusBarEntry } from './modernityDaemonStatus.js'; import { ModernityInferenceStatusBarEntry } from './modernityInferenceStatus.js'; -import { registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { Action2 } from '../../../../platform/actions/common/actions.js'; +import { MODERNITY_DEVELOPER_MODE_SETTING, ModernityDevModeContribution } from './modernityDevMode.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IFileService } from '../../../../platform/files/common/files.js'; +import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; registerWorkbenchContribution2(ModernityDaemonStatusBarEntry.ID, ModernityDaemonStatusBarEntry, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ModernityInferenceStatusBarEntry.ID, ModernityInferenceStatusBarEntry, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ModernityDevModeContribution.ID, ModernityDevModeContribution, WorkbenchPhase.AfterRestored); + +class ToggleModernityDeveloperModeAction extends Action2 { + constructor() { + super({ + id: 'modernity.toggleDeveloperMode', + title: { value: 'Modernity: Toggle Developer Mode', original: 'Modernity: Toggle Developer Mode' }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + const developerMode = configurationService.getValue(MODERNITY_DEVELOPER_MODE_SETTING) ?? false; + await configurationService.updateValue(MODERNITY_DEVELOPER_MODE_SETTING, !developerMode, ConfigurationTarget.USER); + } +} + +registerAction2(ToggleModernityDeveloperModeAction); class OpenModernityDaemonStatusAction extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts b/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts new file mode 100644 index 00000000000000..2f4949c768df56 --- /dev/null +++ b/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Modernity Contributors. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; +import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; +import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; + +export const MODERNITY_DEVELOPER_MODE_SETTING = 'modernity.developerMode'; + +export const MODERNITY_DEVELOPER_MODE_CONTEXT_KEY = new RawContextKey('modernityDeveloperMode', false); + +/** + * Built-in view containers unlocked in developer mode. They are hosted in the + * auxiliary bar next to the chat because the primary side bar is never + * restored: the left panel exposes custom extensions which can break the + * product (T278837441). + */ +const DEVELOPER_MODE_VIEW_CONTAINERS = [ + 'workbench.view.explorer', // file tree panel + 'workbench.view.search', + 'workbench.view.scm', + 'workbench.view.debug', +] as const; + +/** + * Swaps the workbench between the simple mode (locked chat panel) and the + * developer mode (code viewer, file tree, debugging, search, source control). + * + * Enforced in both modes, per T278837441: + * - the primary side bar and activity bar are never restored, so custom + * extensions cannot surface and break the product; + * - the panel part is never shown, so the terminal stays unreachable. + */ +export class ModernityDevModeContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.modernityDevMode'; + + private readonly developerModeContextKey: IContextKey; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IPaneCompositePartService private readonly paneCompositePartService: IPaneCompositePartService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + super(); + + this.developerModeContextKey = MODERNITY_DEVELOPER_MODE_CONTEXT_KEY.bindTo(contextKeyService); + + this.applyMode(); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(MODERNITY_DEVELOPER_MODE_SETTING)) { + this.applyMode(); + } + })); + + // The left panel and the terminal stay locked regardless of mode, so + // re-hide those parts whenever something tries to surface them. + this._register(this.layoutService.onDidChangePartVisibility(e => { + if (!e.visible) { + return; + } + if (e.partId === Parts.PANEL_PART || e.partId === Parts.SIDEBAR_PART || e.partId === Parts.ACTIVITYBAR_PART) { + this.layoutService.setPartHidden(true, e.partId as Parts); + } + })); + } + + private applyMode(): void { + const developerMode = this.configurationService.getValue(MODERNITY_DEVELOPER_MODE_SETTING) ?? false; + this.developerModeContextKey.set(developerMode); + if (developerMode) { + this.applyDeveloperMode(); + } else { + this.applySimpleMode(); + } + } + + /** + * Simple mode: the locked chat panel covers the entire screen. + */ + private applySimpleMode(): void { + // Park the developer view containers back at their default home. They + // stay unreachable because the side bar is never shown. + this.moveDeveloperViewContainers(ViewContainerLocation.Sidebar); + + this.layoutService.setPartHidden(true, Parts.ACTIVITYBAR_PART); + this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART); + this.layoutService.setPartHidden(true, Parts.PANEL_PART); + this.layoutService.setAuxiliaryBarMaximized(true); + } + + /** + * Developer mode: code viewer plus file tree, debugging, search and source + * control next to the chat. The left panel and the terminal stay locked. + */ + private applyDeveloperMode(): void { + this.layoutService.setPartHidden(true, Parts.ACTIVITYBAR_PART); + this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART); + this.layoutService.setPartHidden(true, Parts.PANEL_PART); + + this.layoutService.setAuxiliaryBarMaximized(false); + this.layoutService.setPartHidden(false, Parts.EDITOR_PART); + + this.moveDeveloperViewContainers(ViewContainerLocation.AuxiliaryBar); + this.layoutService.setPartHidden(false, Parts.AUXILIARYBAR_PART); + void this.paneCompositePartService.openPaneComposite(DEVELOPER_MODE_VIEW_CONTAINERS[0], ViewContainerLocation.AuxiliaryBar, false); + } + + private moveDeveloperViewContainers(location: ViewContainerLocation): void { + for (const id of DEVELOPER_MODE_VIEW_CONTAINERS) { + const container = this.viewDescriptorService.getViewContainerById(id); + if (container && this.viewDescriptorService.getViewContainerLocation(container) !== location) { + this.viewDescriptorService.moveViewContainerToLocation(container, location, undefined, MODERNITY_DEVELOPER_MODE_SETTING); + } + } + } +}