From e9d9599b0f8e3e3a276b38718ce78920c8af9a78 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 26 Mar 2026 23:04:23 +0100 Subject: [PATCH] Remember panel visibility per session --- .../browser/changesView.contribution.ts | 2 - .../layout/browser/layout.contribution.ts | 9 ++ .../layout/browser/layoutController.ts | 118 ++++++++++++++++++ src/vs/sessions/sessions.common.main.ts | 1 + 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/vs/sessions/contrib/layout/browser/layout.contribution.ts create mode 100644 src/vs/sessions/contrib/layout/browser/layoutController.ts diff --git a/src/vs/sessions/contrib/changes/browser/changesView.contribution.ts b/src/vs/sessions/contrib/changes/browser/changesView.contribution.ts index 11648ded361..6d265c8dc3a 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.contribution.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.contribution.ts @@ -13,7 +13,6 @@ import { IViewContainersRegistry, ViewContainerLocation, IViewsRegistry, Extensi import { CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID, ChangesViewPane, ChangesViewPaneContainer } from './changesView.js'; import './changesViewActions.js'; import './fixCIChecksAction.js'; -import { ChangesViewController } from './changesViewController.js'; import { ChangesTitleBarContribution } from './changesTitleBarWidget.js'; const changesViewIcon = registerIcon('changes-view-icon', Codicon.gitCompare, localize2('changesViewIcon', 'View icon for the Changes view.').value); @@ -44,5 +43,4 @@ viewsRegistry.registerViews([{ windowVisibility: WindowVisibility.Sessions }], changesViewContainer); -registerWorkbenchContribution2(ChangesViewController.ID, ChangesViewController, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChangesTitleBarContribution.ID, ChangesTitleBarContribution, WorkbenchPhase.AfterRestored); diff --git a/src/vs/sessions/contrib/layout/browser/layout.contribution.ts b/src/vs/sessions/contrib/layout/browser/layout.contribution.ts new file mode 100644 index 00000000000..1ecb9700946 --- /dev/null +++ b/src/vs/sessions/contrib/layout/browser/layout.contribution.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; +import { LayoutController } from './layoutController.js'; + +registerWorkbenchContribution2(LayoutController.ID, LayoutController, WorkbenchPhase.BlockRestore); diff --git a/src/vs/sessions/contrib/layout/browser/layoutController.ts b/src/vs/sessions/contrib/layout/browser/layoutController.ts new file mode 100644 index 00000000000..64869e67124 --- /dev/null +++ b/src/vs/sessions/contrib/layout/browser/layoutController.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { autorun, derived } from '../../../../base/common/observable.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../base/common/map.js'; +import { URI } from '../../../../base/common/uri.js'; +import { IChatService } from '../../../../workbench/contrib/chat/common/chatService/chatService.js'; +import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js'; +import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; +import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; +import { CHANGES_VIEW_ID } from '../../changes/browser/changesView.js'; + +interface IPendingTurnState { + readonly hadChangesBeforeSend: boolean; + readonly submittedAt: number; +} + +export class LayoutController extends Disposable { + + static readonly ID = 'workbench.contrib.sessionsLayoutController'; + + private readonly _pendingTurnStateByResource = new ResourceMap(); + private readonly _panelVisibilityBySession = new ResourceMap(); + + constructor( + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, + @ISessionsManagementService private readonly _sessionManagementService: ISessionsManagementService, + @IChatService private readonly _chatService: IChatService, + @IViewsService private readonly _viewsService: IViewsService, + ) { + super(); + + const activeSessionHasChangesObs = derived(reader => { + const activeSession = this._sessionManagementService.activeSession.read(reader); + if (!activeSession) { + return false; + } + const changes = activeSession.changes.read(reader); + return changes.length > 0; + }); + + // Switch between sessions — sync auxiliary bar and panel visibility + this._register(autorun(reader => { + const activeSession = this._sessionManagementService.activeSession.read(reader); + const activeSessionHasChanges = activeSessionHasChangesObs.read(reader); + this._syncAuxiliaryBarVisibility(activeSessionHasChanges); + this._syncPanelVisibility(activeSession?.resource); + })); + + // When a turn is completed, check if there were changes before the turn and + // if there are changes after the turn. If there were no changes before the + // turn and there are changes after the turn, show the auxiliary bar. + this._register(autorun((reader) => { + const activeSession = this._sessionManagementService.activeSession.read(reader); + const activeSessionHasChanges = activeSessionHasChangesObs.read(reader); + if (!activeSession) { + return; + } + + const pendingTurnState = this._pendingTurnStateByResource.get(activeSession.resource); + if (!pendingTurnState) { + return; + } + + const lastTurnEnd = activeSession.lastTurnEnd.read(reader); + const turnCompleted = !!lastTurnEnd && lastTurnEnd.getTime() >= pendingTurnState.submittedAt; + if (!turnCompleted) { + return; + } + + if (!pendingTurnState.hadChangesBeforeSend && activeSessionHasChanges) { + this._layoutService.setPartHidden(false, Parts.AUXILIARYBAR_PART); + } + + this._pendingTurnStateByResource.delete(activeSession.resource); + })); + + this._register(this._chatService.onDidSubmitRequest(({ chatSessionResource }) => { + this._pendingTurnStateByResource.set(chatSessionResource, { + hadChangesBeforeSend: activeSessionHasChangesObs.get(), + submittedAt: Date.now(), + }); + })); + + // Track panel visibility changes by the user + this._register(this._layoutService.onDidChangePartVisibility(e => { + if (e.partId !== Parts.PANEL_PART) { + return; + } + const activeSession = this._sessionManagementService.activeSession.get(); + if (activeSession) { + this._panelVisibilityBySession.set(activeSession.resource, e.visible); + } + })); + } + + private _syncAuxiliaryBarVisibility(hasChanges: boolean): void { + if (hasChanges) { + this._viewsService.openView(CHANGES_VIEW_ID, false); + } else { + this._layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); + } + } + + private _syncPanelVisibility(sessionResource: URI | undefined): void { + if (!sessionResource) { + this._layoutService.setPartHidden(true, Parts.PANEL_PART); + return; + } + + const wasVisible = this._panelVisibilityBySession.get(sessionResource); + // Default to hidden if we have no record for this session + this._layoutService.setPartHidden(wasVisible !== true, Parts.PANEL_PART); + } +} diff --git a/src/vs/sessions/sessions.common.main.ts b/src/vs/sessions/sessions.common.main.ts index 046d6d2d945..8e96974a0ab 100644 --- a/src/vs/sessions/sessions.common.main.ts +++ b/src/vs/sessions/sessions.common.main.ts @@ -457,6 +457,7 @@ import './contrib/copilotChatSessions/browser/copilotChatSessions.contribution.j import './contrib/sessions/browser/sessions.contribution.js'; import './contrib/sessions/browser/customizationsToolbar.contribution.js'; import './contrib/changes/browser/changesView.contribution.js'; +import './contrib/layout/browser/layout.contribution.js'; import './contrib/codeReview/browser/codeReview.contributions.js'; import './contrib/files/browser/files.contribution.js'; import './contrib/github/browser/github.contribution.js';