Skip to content

Commit 1a7db07

Browse files
sessions: restore terminal after editor maximize (#311961)
* sessions: restore terminal after editor maximize Restore the terminal panel when the sessions maximize editor flow hid it, so maximize and restore behave symmetrically. Add regression coverage for the maximize and restore command behavior and update the sessions layout spec. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: fix editor contribution test cleanup Dispose the per-test instantiation service in the editor contribution regression tests and assert the maximize call order described by the test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * sessions: fix editor contribution test disposal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 217fbd2 commit 1a7db07

3 files changed

Lines changed: 226 additions & 0 deletions

File tree

src/vs/sessions/LAYOUT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ setPartHidden(hidden: boolean, part: Parts): void
257257
- **Editor Part:**
258258
- The main editor part is hidden by default but can be shown for explicit editor workflows that target the main editor part
259259
- Modal editor opens do not change the current main editor visibility state
260+
- The sessions **Maximize Editor** action temporarily hides the panel when the visible panel is the terminal view, and the matching **Restore Editor** action reopens that terminal panel if maximize hid it
260261
- All editors open via `MODAL_GROUP` into the `ModalEditorPart` overlay, which manages its own lifecycle
261262

262263
### 6.2 Part Sizing
@@ -668,6 +669,7 @@ interface IPartVisibilityState {
668669
| 2026-04-22 | Added a sessions-workbench notification offset override so the shared notification controllers no longer push top-right notifications down to `42px`; sessions now reapply a fixed `40px` top offset for top-right notification center/toast placement. |
669670
| 2026-04-22 | Generalized the auxiliary bar snap-close prevention to trigger whenever the main editor part is visible (any editor type), so the behavior now applies automatically without maintaining an editor-type allowlist. |
670671
| 2026-04-22 | Updated the sessions auxiliary bar sizing rules so attached diff editors and integrated browser editors keep the normal 270px auxiliary-bar minimum width while disabling sash snap-to-close in that state, and the titlebar toggle continues to hide/show the secondary sidebar normally. |
672+
| 2026-04-22 | Updated the sessions **Maximize Editor** and **Restore Editor** actions so maximize hides the panel only when the terminal view is currently visible, and restore reopens that terminal panel when maximize hid it. |
671673
| 2026-04-21 | Renamed the command-center "Add Chat" titlebar action to "New Sub-Session" so the plus-button tooltip matches the sub-session workflow. |
672674
| 2026-04-21 | Removed the remaining left-margin spacing after the titlebar's VS Code and session-picker items, and dropped the command-center "Mark as Done" checkmark button next to the active session title. |
673675
| 2026-04-21 | Removed the titlebar's vertical separator bars in favor of spacing-only group separation, and removed the dot separator between the active session title and its folder/worktree metadata. |

src/vs/sessions/contrib/editor/browser/editor.contribution.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { ChangesViewPane } from '../../changes/browser/changesView.js';
2121
import { prepareMoveCopyEditors } from '../../../../workbench/browser/parts/editor/editor.js';
2222
import { Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
2323
import { MOVE_MODAL_EDITOR_TO_MAIN_COMMAND_ID } from '../../../../workbench/browser/parts/editor/editorCommands.js';
24+
import { TERMINAL_VIEW_ID } from '../../../../workbench/contrib/terminal/common/terminal.js';
25+
26+
const terminalPanelHiddenForMaximizedEditor = new WeakSet<IAgentWorkbenchLayoutService>();
2427

2528
class MaximizeMainEditorPartAction extends Action2 {
2629
static readonly ID = 'workbench.action.agentSessions.maximizeMainEditorPart';
@@ -44,6 +47,20 @@ class MaximizeMainEditorPartAction extends Action2 {
4447

4548
async run(accessor: ServicesAccessor): Promise<void> {
4649
const layoutService = accessor.get(IAgentWorkbenchLayoutService);
50+
const viewsService = accessor.get(IViewsService);
51+
let hidTerminalPanel = false;
52+
53+
if (layoutService.isVisible(Parts.PANEL_PART) && viewsService.isViewVisible(TERMINAL_VIEW_ID)) {
54+
layoutService.setPartHidden(true, Parts.PANEL_PART);
55+
hidTerminalPanel = true;
56+
}
57+
58+
if (hidTerminalPanel) {
59+
terminalPanelHiddenForMaximizedEditor.add(layoutService);
60+
} else {
61+
terminalPanelHiddenForMaximizedEditor.delete(layoutService);
62+
}
63+
4764
layoutService.setEditorMaximized(true);
4865
}
4966
}
@@ -72,7 +89,15 @@ class RestoreMainEditorPartAction extends Action2 {
7289

7390
async run(accessor: ServicesAccessor): Promise<void> {
7491
const layoutService = accessor.get(IAgentWorkbenchLayoutService);
92+
const shouldRestoreTerminalPanel = terminalPanelHiddenForMaximizedEditor.has(layoutService);
93+
7594
layoutService.setEditorMaximized(false);
95+
96+
if (shouldRestoreTerminalPanel && !layoutService.isVisible(Parts.PANEL_PART)) {
97+
layoutService.setPartHidden(false, Parts.PANEL_PART);
98+
}
99+
100+
terminalPanelHiddenForMaximizedEditor.delete(layoutService);
76101
}
77102
}
78103

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import assert from 'assert';
7+
import { mock } from '../../../../../base/test/common/mock.js';
8+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9+
import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';
10+
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
11+
import { Parts } from '../../../../../workbench/services/layout/browser/layoutService.js';
12+
import { IViewsService } from '../../../../../workbench/services/views/common/viewsService.js';
13+
import { TERMINAL_VIEW_ID } from '../../../../../workbench/contrib/terminal/common/terminal.js';
14+
import { IAgentWorkbenchLayoutService } from '../../../../browser/workbench.js';
15+
16+
// Import editor contribution to trigger action registration.
17+
import '../../browser/editor.contribution.js';
18+
19+
suite('Sessions - Editor Contribution', () => {
20+
const store = ensureNoDisposablesAreLeakedInTestSuite();
21+
22+
test('maximize editor hides the terminal panel before maximizing', async () => {
23+
const instantiationService = store.add(new TestInstantiationService());
24+
const layoutService = new class extends mock<IAgentWorkbenchLayoutService>() {
25+
readonly calls: string[] = [];
26+
readonly hiddenParts: Parts[] = [];
27+
editorMaximized = false;
28+
panelVisible = true;
29+
30+
override isVisible(part: Parts): boolean {
31+
return part === Parts.PANEL_PART ? this.panelVisible : false;
32+
}
33+
34+
override setPartHidden(hidden: boolean, part: Parts): void {
35+
if (part === Parts.PANEL_PART) {
36+
this.panelVisible = !hidden;
37+
}
38+
39+
if (hidden && part === Parts.PANEL_PART) {
40+
this.calls.push('hidePanel');
41+
this.hiddenParts.push(part);
42+
}
43+
}
44+
45+
override setEditorMaximized(maximized: boolean): void {
46+
this.calls.push(maximized ? 'maximizeEditor' : 'restoreEditor');
47+
this.editorMaximized = maximized;
48+
}
49+
};
50+
instantiationService.set(IAgentWorkbenchLayoutService, layoutService);
51+
instantiationService.set(IViewsService, new class extends mock<IViewsService>() {
52+
override isViewVisible(id: string): boolean {
53+
return id === TERMINAL_VIEW_ID;
54+
}
55+
});
56+
57+
const handler = CommandsRegistry.getCommand('workbench.action.agentSessions.maximizeMainEditorPart')?.handler;
58+
assert.ok(handler, 'Command handler should be registered');
59+
60+
await handler(instantiationService);
61+
62+
assert.deepStrictEqual(layoutService.calls, ['hidePanel', 'maximizeEditor']);
63+
assert.deepStrictEqual(layoutService.hiddenParts, [Parts.PANEL_PART]);
64+
assert.strictEqual(layoutService.editorMaximized, true);
65+
});
66+
67+
test('maximize editor keeps non-terminal panels visible', async () => {
68+
const instantiationService = store.add(new TestInstantiationService());
69+
const layoutService = new class extends mock<IAgentWorkbenchLayoutService>() {
70+
readonly hiddenParts: Parts[] = [];
71+
editorMaximized = false;
72+
panelVisible = true;
73+
74+
override isVisible(part: Parts): boolean {
75+
return part === Parts.PANEL_PART ? this.panelVisible : false;
76+
}
77+
78+
override setPartHidden(hidden: boolean, part: Parts): void {
79+
if (part === Parts.PANEL_PART) {
80+
this.panelVisible = !hidden;
81+
}
82+
83+
if (hidden && part === Parts.PANEL_PART) {
84+
this.hiddenParts.push(part);
85+
}
86+
}
87+
88+
override setEditorMaximized(maximized: boolean): void {
89+
this.editorMaximized = maximized;
90+
}
91+
};
92+
instantiationService.set(IAgentWorkbenchLayoutService, layoutService);
93+
instantiationService.set(IViewsService, new class extends mock<IViewsService>() {
94+
override isViewVisible(_id: string): boolean {
95+
return false;
96+
}
97+
});
98+
99+
const handler = CommandsRegistry.getCommand('workbench.action.agentSessions.maximizeMainEditorPart')?.handler;
100+
assert.ok(handler, 'Command handler should be registered');
101+
102+
await handler(instantiationService);
103+
104+
assert.deepStrictEqual(layoutService.hiddenParts, []);
105+
assert.strictEqual(layoutService.editorMaximized, true);
106+
});
107+
108+
test('restore editor reopens the terminal panel when maximize hid it', async () => {
109+
const instantiationService = store.add(new TestInstantiationService());
110+
const layoutService = new class extends mock<IAgentWorkbenchLayoutService>() {
111+
readonly hiddenParts: Parts[] = [];
112+
readonly shownParts: Parts[] = [];
113+
readonly maximizedStates: boolean[] = [];
114+
panelVisible = true;
115+
116+
override isVisible(part: Parts): boolean {
117+
return part === Parts.PANEL_PART ? this.panelVisible : false;
118+
}
119+
120+
override setPartHidden(hidden: boolean, part: Parts): void {
121+
if (part === Parts.PANEL_PART) {
122+
this.panelVisible = !hidden;
123+
if (hidden) {
124+
this.hiddenParts.push(part);
125+
} else {
126+
this.shownParts.push(part);
127+
}
128+
}
129+
}
130+
131+
override setEditorMaximized(maximized: boolean): void {
132+
this.maximizedStates.push(maximized);
133+
}
134+
};
135+
instantiationService.set(IAgentWorkbenchLayoutService, layoutService);
136+
instantiationService.set(IViewsService, new class extends mock<IViewsService>() {
137+
override isViewVisible(id: string): boolean {
138+
return id === TERMINAL_VIEW_ID;
139+
}
140+
});
141+
142+
const maximizeHandler = CommandsRegistry.getCommand('workbench.action.agentSessions.maximizeMainEditorPart')?.handler;
143+
const restoreHandler = CommandsRegistry.getCommand('workbench.action.agentSessions.restoreMainEditorPart')?.handler;
144+
assert.ok(maximizeHandler, 'Maximize command handler should be registered');
145+
assert.ok(restoreHandler, 'Restore command handler should be registered');
146+
147+
await maximizeHandler(instantiationService);
148+
await restoreHandler(instantiationService);
149+
150+
assert.deepStrictEqual(layoutService.hiddenParts, [Parts.PANEL_PART]);
151+
assert.deepStrictEqual(layoutService.shownParts, [Parts.PANEL_PART]);
152+
assert.deepStrictEqual(layoutService.maximizedStates, [true, false]);
153+
assert.strictEqual(layoutService.panelVisible, true);
154+
});
155+
156+
test('restore editor does not reopen the panel when maximize left it visible', async () => {
157+
const instantiationService = store.add(new TestInstantiationService());
158+
const layoutService = new class extends mock<IAgentWorkbenchLayoutService>() {
159+
readonly shownParts: Parts[] = [];
160+
readonly maximizedStates: boolean[] = [];
161+
panelVisible = true;
162+
163+
override isVisible(part: Parts): boolean {
164+
return part === Parts.PANEL_PART ? this.panelVisible : false;
165+
}
166+
167+
override setPartHidden(hidden: boolean, part: Parts): void {
168+
if (part === Parts.PANEL_PART) {
169+
this.panelVisible = !hidden;
170+
if (!hidden) {
171+
this.shownParts.push(part);
172+
}
173+
}
174+
}
175+
176+
override setEditorMaximized(maximized: boolean): void {
177+
this.maximizedStates.push(maximized);
178+
}
179+
};
180+
instantiationService.set(IAgentWorkbenchLayoutService, layoutService);
181+
instantiationService.set(IViewsService, new class extends mock<IViewsService>() {
182+
override isViewVisible(_id: string): boolean {
183+
return false;
184+
}
185+
});
186+
187+
const maximizeHandler = CommandsRegistry.getCommand('workbench.action.agentSessions.maximizeMainEditorPart')?.handler;
188+
const restoreHandler = CommandsRegistry.getCommand('workbench.action.agentSessions.restoreMainEditorPart')?.handler;
189+
assert.ok(maximizeHandler, 'Maximize command handler should be registered');
190+
assert.ok(restoreHandler, 'Restore command handler should be registered');
191+
192+
await maximizeHandler(instantiationService);
193+
await restoreHandler(instantiationService);
194+
195+
assert.deepStrictEqual(layoutService.shownParts, []);
196+
assert.deepStrictEqual(layoutService.maximizedStates, [true, false]);
197+
assert.strictEqual(layoutService.panelVisible, true);
198+
});
199+
});

0 commit comments

Comments
 (0)