Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/browser/workbench.ts
13389 views
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 '../../workbench/browser/style.js';
7
import './media/style.css';
8
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js';
9
import { Emitter, Event, setGlobalLeakWarningThreshold } from '../../base/common/event.js';
10
import { getActiveDocument, getActiveElement, getClientArea, getWindowId, getWindows, IDimension, isAncestorUsingFlowTo, isHTMLElement, size, Dimension, runWhenWindowIdle } from '../../base/browser/dom.js';
11
import { DeferredPromise, RunOnceScheduler } from '../../base/common/async.js';
12
import { isFullscreen, onDidChangeFullscreen, isChrome, isFirefox, isSafari } from '../../base/browser/browser.js';
13
import { mark } from '../../base/common/performance.js';
14
import { onUnexpectedError, setUnexpectedErrorHandler } from '../../base/common/errors.js';
15
import { isWindows, isLinux, isWeb, isNative, isMacintosh } from '../../base/common/platform.js';
16
import { Parts, Position, PanelAlignment, IWorkbenchLayoutService, SINGLE_WINDOW_PARTS, MULTI_WINDOW_PARTS, IPartVisibilityChangeEvent, positionToString } from '../../workbench/services/layout/browser/layoutService.js';
17
import { ILayoutOffsetInfo } from '../../platform/layout/browser/layoutService.js';
18
import { Part } from '../../workbench/browser/part.js';
19
import { Direction, ISerializableView, ISerializedGrid, ISerializedLeafNode, ISerializedNode, IViewSize, Orientation, SerializableGrid } from '../../base/browser/ui/grid/grid.js';
20
import { IEditorGroupsService } from '../../workbench/services/editor/common/editorGroupsService.js';
21
import { IEditorService } from '../../workbench/services/editor/common/editorService.js';
22
import { IPaneCompositePartService } from '../../workbench/services/panecomposite/browser/panecomposite.js';
23
import { IViewDescriptorService, ViewContainerLocation } from '../../workbench/common/views.js';
24
import { ILogService } from '../../platform/log/common/log.js';
25
import { IInstantiationService, refineServiceDecorator, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
26
import { ITitleService } from '../../workbench/services/title/browser/titleService.js';
27
import { mainWindow, CodeWindow } from '../../base/browser/window.js';
28
import { coalesce } from '../../base/common/arrays.js';
29
import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js';
30
import { InstantiationService } from '../../platform/instantiation/common/instantiationService.js';
31
import { getSingletonServiceDescriptors } from '../../platform/instantiation/common/extensions.js';
32
import { ILifecycleService, LifecyclePhase, WillShutdownEvent } from '../../workbench/services/lifecycle/common/lifecycle.js';
33
import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js';
34
import { IConfigurationChangeEvent, IConfigurationService } from '../../platform/configuration/common/configuration.js';
35
import { IHostService } from '../../workbench/services/host/browser/host.js';
36
import { IDialogService } from '../../platform/dialogs/common/dialogs.js';
37
import { INotificationService } from '../../platform/notification/common/notification.js';
38
import { NotificationService } from '../../workbench/services/notification/common/notificationService.js';
39
import { IHoverService, WorkbenchHoverDelegate } from '../../platform/hover/browser/hover.js';
40
import { setHoverDelegateFactory } from '../../base/browser/ui/hover/hoverDelegateFactory.js';
41
import { setBaseLayerHoverDelegate } from '../../base/browser/ui/hover/hoverDelegate2.js';
42
import { Registry } from '../../platform/registry/common/platform.js';
43
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../workbench/common/contributions.js';
44
import { IEditorFactoryRegistry, EditorExtensions } from '../../workbench/common/editor.js';
45
import { setARIAContainer } from '../../base/browser/ui/aria/aria.js';
46
import { FontMeasurements } from '../../editor/browser/config/fontMeasurements.js';
47
import { createBareFontInfoFromRawSettings } from '../../editor/common/config/fontInfoFromSettings.js';
48
import { toErrorMessage } from '../../base/common/errorMessage.js';
49
import { WorkbenchContextKeysHandler } from '../../workbench/browser/contextkeys.js';
50
import { PixelRatio } from '../../base/browser/pixelRatio.js';
51
import { AccessibilityProgressSignalScheduler } from '../../platform/accessibilitySignal/browser/progressAccessibilitySignalScheduler.js';
52
import { setProgressAccessibilitySignalScheduler } from '../../base/browser/ui/progressbar/progressAccessibilitySignal.js';
53
import { AccessibleViewRegistry } from '../../platform/accessibility/browser/accessibleViewRegistry.js';
54
import { NotificationAccessibleView } from '../../workbench/browser/parts/notifications/notificationAccessibleView.js';
55
import { NotificationsCenter } from '../../workbench/browser/parts/notifications/notificationsCenter.js';
56
import { NotificationsAlerts } from '../../workbench/browser/parts/notifications/notificationsAlerts.js';
57
import { NotificationsStatus } from '../../workbench/browser/parts/notifications/notificationsStatus.js';
58
import { registerNotificationCommands } from '../../workbench/browser/parts/notifications/notificationsCommands.js';
59
import { NotificationsToasts } from '../../workbench/browser/parts/notifications/notificationsToasts.js';
60
import { IMarkdownRendererService } from '../../platform/markdown/browser/markdownRenderer.js';
61
import { EditorMarkdownCodeBlockRenderer } from '../../editor/browser/widget/markdownRenderer/browser/editorMarkdownCodeBlockRenderer.js';
62
import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js';
63
import { TitleService } from './parts/titlebarPart.js';
64
import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js';
65
import { EditorMaximizedContext, IsPhoneLayoutContext, KeyboardVisibleContext } from '../common/contextkeys.js';
66
import {
67
NotificationsPosition,
68
NotificationsSettings,
69
getNotificationsPosition
70
} from '../../workbench/common/notifications.js';
71
import { SessionsLayoutPolicy } from './layoutPolicy.js';
72
import { MobileNavigationStack } from './mobileNavigationStack.js';
73
import { MobileTitlebarPart } from './parts/mobile/mobileTitlebarPart.js';
74
import { autorun } from '../../base/common/observable.js';
75
import { ISessionsManagementService } from '../services/sessions/common/sessionsManagement.js';
76
77
//#region Workbench Options
78
79
export interface IWorkbenchOptions {
80
/**
81
* Extra classes to be added to the workbench container.
82
*/
83
extraClasses?: string[];
84
}
85
86
//#endregion
87
88
//#region Layout Classes
89
90
enum LayoutClasses {
91
SIDEBAR_HIDDEN = 'nosidebar',
92
MAIN_EDITOR_AREA_HIDDEN = 'nomaineditorarea',
93
PANEL_HIDDEN = 'nopanel',
94
AUXILIARYBAR_HIDDEN = 'noauxiliarybar',
95
CHATBAR_HIDDEN = 'nochatbar',
96
STATUSBAR_HIDDEN = 'nostatusbar',
97
SHELL_GRADIENT_BACKGROUND = 'shell-gradient-background',
98
FULLSCREEN = 'fullscreen',
99
MAXIMIZED = 'maximized',
100
PHONE_LAYOUT = 'phone-layout'
101
}
102
103
//#endregion
104
105
//#region Part Visibility State
106
107
interface IPartVisibilityState {
108
sidebar: boolean;
109
auxiliaryBar: boolean;
110
editor: boolean;
111
panel: boolean;
112
chatBar: boolean;
113
}
114
115
//#endregion
116
117
export interface IAgentWorkbenchLayoutService extends IWorkbenchLayoutService {
118
isEditorMaximized(): boolean;
119
setEditorMaximized(maximized: boolean): void;
120
121
readonly onDidChangeEditorMaximized: Event<void>;
122
}
123
124
export const IAgentWorkbenchLayoutService = refineServiceDecorator<IWorkbenchLayoutService, IAgentWorkbenchLayoutService>(IWorkbenchLayoutService);
125
126
export class Workbench extends Disposable implements IAgentWorkbenchLayoutService {
127
128
declare readonly _serviceBrand: undefined;
129
130
//#region Lifecycle Events
131
132
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
133
readonly onWillShutdown = this._onWillShutdown.event;
134
135
private readonly _onDidShutdown = this._register(new Emitter<void>());
136
readonly onDidShutdown = this._onDidShutdown.event;
137
138
//#endregion
139
140
//#region Events
141
142
private readonly _onDidChangeZenMode = this._register(new Emitter<boolean>());
143
readonly onDidChangeZenMode = this._onDidChangeZenMode.event;
144
145
private readonly _onDidChangeMainEditorCenteredLayout = this._register(new Emitter<boolean>());
146
readonly onDidChangeMainEditorCenteredLayout = this._onDidChangeMainEditorCenteredLayout.event;
147
148
private readonly _onDidChangePanelAlignment = this._register(new Emitter<PanelAlignment>());
149
readonly onDidChangePanelAlignment = this._onDidChangePanelAlignment.event;
150
151
private readonly _onDidChangeWindowMaximized = this._register(new Emitter<{ windowId: number; maximized: boolean }>());
152
readonly onDidChangeWindowMaximized = this._onDidChangeWindowMaximized.event;
153
154
private readonly _onDidChangePanelPosition = this._register(new Emitter<string>());
155
readonly onDidChangePanelPosition = this._onDidChangePanelPosition.event;
156
157
private readonly _onDidChangePartVisibility = this._register(new Emitter<IPartVisibilityChangeEvent>());
158
readonly onDidChangePartVisibility = this._onDidChangePartVisibility.event;
159
160
private readonly _onDidChangeNotificationsVisibility = this._register(new Emitter<boolean>());
161
readonly onDidChangeNotificationsVisibility = this._onDidChangeNotificationsVisibility.event;
162
163
private readonly _onDidChangeAuxiliaryBarMaximized = this._register(new Emitter<void>());
164
readonly onDidChangeAuxiliaryBarMaximized = this._onDidChangeAuxiliaryBarMaximized.event;
165
166
private readonly _onDidChangeEditorMaximized = this._register(new Emitter<void>());
167
readonly onDidChangeEditorMaximized = this._onDidChangeEditorMaximized.event;
168
169
private readonly _onDidLayoutMainContainer = this._register(new Emitter<IDimension>());
170
readonly onDidLayoutMainContainer = this._onDidLayoutMainContainer.event;
171
172
private readonly _onDidLayoutActiveContainer = this._register(new Emitter<IDimension>());
173
readonly onDidLayoutActiveContainer = this._onDidLayoutActiveContainer.event;
174
175
private readonly _onDidLayoutContainer = this._register(new Emitter<{ container: HTMLElement; dimension: IDimension }>());
176
readonly onDidLayoutContainer = this._onDidLayoutContainer.event;
177
178
private readonly _onDidAddContainer = this._register(new Emitter<{ container: HTMLElement; disposables: DisposableStore }>());
179
readonly onDidAddContainer = this._onDidAddContainer.event;
180
181
private readonly _onDidChangeActiveContainer = this._register(new Emitter<void>());
182
readonly onDidChangeActiveContainer = this._onDidChangeActiveContainer.event;
183
184
//#endregion
185
186
//#region Properties
187
188
readonly mainContainer = document.createElement('div');
189
190
get activeContainer(): HTMLElement {
191
return this.getContainerFromDocument(getActiveDocument());
192
}
193
194
get containers(): Iterable<HTMLElement> {
195
const containers: HTMLElement[] = [];
196
for (const { window } of getWindows()) {
197
containers.push(this.getContainerFromDocument(window.document));
198
}
199
return containers;
200
}
201
202
private getContainerFromDocument(targetDocument: Document): HTMLElement {
203
if (targetDocument === this.mainContainer.ownerDocument) {
204
return this.mainContainer;
205
} else {
206
// eslint-disable-next-line no-restricted-syntax
207
return targetDocument.body.getElementsByClassName('monaco-workbench')[0] as HTMLElement;
208
}
209
}
210
211
private _mainContainerDimension!: IDimension;
212
get mainContainerDimension(): IDimension { return this._mainContainerDimension; }
213
214
get activeContainerDimension(): IDimension {
215
return this.getContainerDimension(this.activeContainer);
216
}
217
218
private getContainerDimension(container: HTMLElement): IDimension {
219
if (container === this.mainContainer) {
220
return this.mainContainerDimension;
221
} else {
222
return getClientArea(container);
223
}
224
}
225
226
get mainContainerOffset(): ILayoutOffsetInfo {
227
return this.computeContainerOffset();
228
}
229
230
get activeContainerOffset(): ILayoutOffsetInfo {
231
return this.computeContainerOffset();
232
}
233
234
private computeContainerOffset(): ILayoutOffsetInfo {
235
let top = 0;
236
let quickPickTop = 0;
237
238
if (this.isVisible(Parts.TITLEBAR_PART, mainWindow)) {
239
top = this.getPart(Parts.TITLEBAR_PART).maximumHeight;
240
quickPickTop = top;
241
} else if (this.mobileTopBarElement) {
242
// On phone layout the MobileTitlebarPart replaces the titlebar
243
top = this.mobileTopBarElement.offsetHeight;
244
quickPickTop = top;
245
}
246
247
return { top, quickPickTop };
248
}
249
250
//#endregion
251
252
//#region State
253
254
private readonly parts = new Map<string, Part>();
255
private workbenchGrid!: SerializableGrid<ISerializableView>;
256
257
private titleBarPartView!: ISerializableView;
258
private sideBarPartView!: ISerializableView;
259
private panelPartView!: ISerializableView;
260
private auxiliaryBarPartView!: ISerializableView;
261
private editorPartView!: ISerializableView;
262
263
private chatBarPartView!: ISerializableView;
264
265
private readonly partVisibility: IPartVisibilityState = {
266
sidebar: true,
267
auxiliaryBar: true,
268
editor: false,
269
panel: false,
270
chatBar: true
271
};
272
273
private mainWindowFullscreen = false;
274
private readonly maximized = new Set<number>();
275
private readonly layoutPolicy = this._register(new SessionsLayoutPolicy());
276
private readonly mobileNavStack = this._register(new MobileNavigationStack());
277
private mobileTopBarElement: HTMLElement | undefined;
278
private readonly mobileTopBarDisposables = this._register(new DisposableStore());
279
280
private _editorMaximized = false;
281
private _editorLastNonMaximizedVisibility: IPartVisibilityState | undefined;
282
283
private readonly restoredPromise = new DeferredPromise<void>();
284
readonly whenRestored = this.restoredPromise.p;
285
private restored = false;
286
287
readonly openedDefaultEditors = false;
288
289
//#endregion
290
291
//#region Services
292
293
private editorGroupService!: IEditorGroupsService;
294
private editorService!: IEditorService;
295
private paneCompositeService!: IPaneCompositePartService;
296
private viewDescriptorService!: IViewDescriptorService;
297
private sessionsManagementService!: ISessionsManagementService;
298
private instantiationService!: IInstantiationService;
299
300
//#endregion
301
302
constructor(
303
protected readonly parent: HTMLElement,
304
private readonly options: IWorkbenchOptions | undefined,
305
private readonly serviceCollection: ServiceCollection,
306
private readonly logService: ILogService
307
) {
308
super();
309
310
// Sessions-scoped mobile viewport tweaks. These are applied here
311
// (rather than in the shared workbench.html) so that the regular
312
// code-web workbench — which does not handle safe-area insets — is
313
// not affected on notched mobile devices.
314
// The viewport `<meta>` tag is injected by the shared workbench.html,
315
// so we cannot use dom.ts `h()` to create it. Look it up by tag name
316
// and filter by the `name` attribute to avoid a selector query.
317
// eslint-disable-next-line no-restricted-syntax
318
const metaElements = mainWindow.document.head.getElementsByTagName('meta');
319
let viewportMeta: HTMLMetaElement | undefined;
320
for (let i = 0; i < metaElements.length; i++) {
321
if (metaElements[i].name === 'viewport') {
322
viewportMeta = metaElements[i];
323
break;
324
}
325
}
326
if (viewportMeta && !viewportMeta.content.includes('viewport-fit=')) {
327
viewportMeta.content = `${viewportMeta.content}, viewport-fit=cover`;
328
}
329
330
// Perf: measure workbench startup time
331
mark('code/willStartWorkbench');
332
333
this.registerErrorHandler(logService);
334
}
335
336
//#region Error Handling
337
338
private registerErrorHandler(logService: ILogService): void {
339
// Increase stack trace limit for better errors stacks
340
if (!isFirefox) {
341
Error.stackTraceLimit = 100;
342
}
343
344
// Listen on unhandled rejection events
345
// Note: intentionally not registered as disposable to handle
346
// errors that can occur during shutdown phase.
347
mainWindow.addEventListener('unhandledrejection', (event) => {
348
// See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
349
onUnexpectedError(event.reason);
350
351
// Prevent the printing of this event to the console
352
event.preventDefault();
353
});
354
355
// Install handler for unexpected errors
356
setUnexpectedErrorHandler(error => this.handleUnexpectedError(error, logService));
357
}
358
359
private previousUnexpectedError: { message: string | undefined; time: number } = { message: undefined, time: 0 };
360
private handleUnexpectedError(error: unknown, logService: ILogService): void {
361
const message = toErrorMessage(error, true);
362
if (!message) {
363
return;
364
}
365
366
const now = Date.now();
367
if (message === this.previousUnexpectedError.message && now - this.previousUnexpectedError.time <= 1000) {
368
return; // Return if error message identical to previous and shorter than 1 second
369
}
370
371
this.previousUnexpectedError.time = now;
372
this.previousUnexpectedError.message = message;
373
374
// Log it
375
logService.error(message);
376
}
377
378
//#endregion
379
380
//#region Startup
381
382
startup(): IInstantiationService {
383
try {
384
// Configure emitter leak warning threshold
385
this._register(setGlobalLeakWarningThreshold(175));
386
387
// Services
388
const instantiationService = this.initServices(this.serviceCollection);
389
390
instantiationService.invokeFunction(accessor => {
391
const lifecycleService = accessor.get(ILifecycleService);
392
const storageService = accessor.get(IStorageService);
393
const configurationService = accessor.get(IConfigurationService);
394
const hostService = accessor.get(IHostService);
395
const hoverService = accessor.get(IHoverService);
396
const dialogService = accessor.get(IDialogService);
397
const notificationService = accessor.get(INotificationService) as NotificationService;
398
const markdownRendererService = accessor.get(IMarkdownRendererService);
399
400
// On web, the configuration service needs access to the
401
// instantiation service for dynamic configuration resolution.
402
if (isWeb && typeof (configurationService as IConfigurationService & { acquireInstantiationService?(i: IInstantiationService): void }).acquireInstantiationService === 'function') {
403
(configurationService as IConfigurationService & { acquireInstantiationService(i: IInstantiationService): void }).acquireInstantiationService(instantiationService);
404
}
405
406
// Set code block renderer for markdown rendering
407
markdownRendererService.setDefaultCodeBlockRenderer(instantiationService.createInstance(EditorMarkdownCodeBlockRenderer));
408
409
// Default Hover Delegate must be registered before creating any workbench/layout components
410
setHoverDelegateFactory((placement, enableInstantHover) => instantiationService.createInstance(WorkbenchHoverDelegate, placement, { instantHover: enableInstantHover }, {}));
411
setBaseLayerHoverDelegate(hoverService);
412
413
// Layout
414
this.initLayout(accessor);
415
416
// Registries - this creates and registers all parts
417
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).start(accessor);
418
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).start(accessor);
419
420
// Context Keys
421
this._register(instantiationService.createInstance(WorkbenchContextKeysHandler));
422
423
// Editor Maximized Context Key
424
const editorMaximizedContext = EditorMaximizedContext.bindTo(accessor.get(IContextKeyService));
425
this._register(this.onDidChangeEditorMaximized(() => {
426
editorMaximizedContext.set(this.isEditorMaximized());
427
}));
428
429
// Phone Layout Context Key
430
const contextKeyService = accessor.get(IContextKeyService);
431
const isPhoneLayoutCtx = IsPhoneLayoutContext.bindTo(contextKeyService);
432
this._register(autorun(reader => {
433
isPhoneLayoutCtx.set(this.layoutPolicy.viewportClass.read(reader) === 'phone');
434
}));
435
436
// Virtual keyboard detection via visualViewport API.
437
// Use `window.innerHeight` (layout viewport) as the baseline
438
// rather than a captured initial height. Layout viewport
439
// updates on orientation change and split-screen resizes, so
440
// comparing against it avoids stale baselines on landscape
441
// launches, Android split-screen, and iOS URL-bar collapse.
442
if (mainWindow.visualViewport) {
443
const keyboardVisibleCtx = KeyboardVisibleContext.bindTo(contextKeyService);
444
const KEYBOARD_HEIGHT_THRESHOLD_PX = 100;
445
446
const onViewportResize = () => {
447
const vp = mainWindow.visualViewport;
448
if (!vp) {
449
return;
450
}
451
const heightDiff = mainWindow.innerHeight - vp.height;
452
keyboardVisibleCtx.set(heightDiff > KEYBOARD_HEIGHT_THRESHOLD_PX);
453
};
454
455
mainWindow.visualViewport.addEventListener('resize', onViewportResize);
456
this._register({ dispose: () => mainWindow.visualViewport?.removeEventListener('resize', onViewportResize) });
457
}
458
459
// Orientation changes produce a window `resize` event which
460
// is already handled by `registerLayoutListeners()`. No
461
// separate matchMedia listener is needed — the previous
462
// implementation caused a redundant second layout.
463
464
// Register Listeners
465
this.registerListeners(lifecycleService, storageService, configurationService, hostService, dialogService);
466
467
// Render Workbench
468
this.renderWorkbench(instantiationService, notificationService, storageService, configurationService);
469
470
// Workbench Layout
471
this.createWorkbenchLayout();
472
473
// Create mobile navigation after grid exists (so DOM order is correct)
474
if (this.layoutPolicy.viewportClass.get() === 'phone') {
475
this.createMobileTitlebar();
476
}
477
478
// Workbench Management
479
this.createWorkbenchManagement(instantiationService);
480
481
// Layout
482
this.layout();
483
484
// Restore
485
this.restore(lifecycleService);
486
});
487
488
return instantiationService;
489
} catch (error) {
490
onUnexpectedError(error);
491
492
throw error; // rethrow because this is a critical issue we cannot handle properly here
493
}
494
}
495
496
private initServices(serviceCollection: ServiceCollection): IInstantiationService {
497
// Layout Service
498
serviceCollection.set(IAgentWorkbenchLayoutService, this);
499
500
// Title Service - agent sessions titlebar with dedicated part overrides
501
serviceCollection.set(ITitleService, new SyncDescriptor(TitleService, []));
502
503
// All Contributed Services
504
const contributedServices = getSingletonServiceDescriptors();
505
for (const [id, descriptor] of contributedServices) {
506
serviceCollection.set(id, descriptor);
507
}
508
509
const instantiationService = new InstantiationService(serviceCollection, true);
510
511
// Wrap up
512
instantiationService.invokeFunction(accessor => {
513
const lifecycleService = accessor.get(ILifecycleService);
514
lifecycleService.phase = LifecyclePhase.Ready;
515
});
516
517
return instantiationService;
518
}
519
520
private registerListeners(lifecycleService: ILifecycleService, storageService: IStorageService, configurationService: IConfigurationService, hostService: IHostService, dialogService: IDialogService): void {
521
// Configuration changes
522
this._register(configurationService.onDidChangeConfiguration(e => this.updateFontAliasing(e, configurationService)));
523
524
// Font Info
525
if (isNative) {
526
this._register(storageService.onWillSaveState(e => {
527
if (e.reason === WillSaveStateReason.SHUTDOWN) {
528
this.storeFontInfo(storageService);
529
}
530
}));
531
} else {
532
this._register(lifecycleService.onWillShutdown(() => this.storeFontInfo(storageService)));
533
}
534
535
// Lifecycle
536
this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event)));
537
this._register(lifecycleService.onDidShutdown(() => {
538
this._onDidShutdown.fire();
539
this.dispose();
540
}));
541
542
// Flush storage on window focus loss
543
this._register(hostService.onDidChangeFocus(focus => {
544
if (!focus) {
545
storageService.flush();
546
}
547
}));
548
549
// Dialogs showing/hiding
550
this._register(dialogService.onWillShowDialog(() => this.mainContainer.classList.add('modal-dialog-visible')));
551
this._register(dialogService.onDidShowDialog(() => this.mainContainer.classList.remove('modal-dialog-visible')));
552
}
553
554
//#region Font Aliasing and Caching
555
556
private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined;
557
private updateFontAliasing(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService) {
558
if (!isMacintosh) {
559
return; // macOS only
560
}
561
562
if (e && !e.affectsConfiguration('workbench.fontAliasing')) {
563
return;
564
}
565
566
const aliasing = configurationService.getValue<'default' | 'antialiased' | 'none' | 'auto'>('workbench.fontAliasing');
567
if (this.fontAliasing === aliasing) {
568
return;
569
}
570
571
this.fontAliasing = aliasing;
572
573
// Remove all
574
const fontAliasingValues: (typeof aliasing)[] = ['antialiased', 'none', 'auto'];
575
this.mainContainer.classList.remove(...fontAliasingValues.map(value => `monaco-font-aliasing-${value}`));
576
577
// Add specific
578
if (fontAliasingValues.some(option => option === aliasing)) {
579
this.mainContainer.classList.add(`monaco-font-aliasing-${aliasing}`);
580
}
581
}
582
583
private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void {
584
const storedFontInfoRaw = storageService.get('editorFontInfo', StorageScope.APPLICATION);
585
if (storedFontInfoRaw) {
586
try {
587
const storedFontInfo = JSON.parse(storedFontInfoRaw);
588
if (Array.isArray(storedFontInfo)) {
589
FontMeasurements.restoreFontInfo(mainWindow, storedFontInfo);
590
}
591
} catch (err) {
592
/* ignore */
593
}
594
}
595
596
FontMeasurements.readFontInfo(mainWindow, createBareFontInfoFromRawSettings(configurationService.getValue('editor'), PixelRatio.getInstance(mainWindow).value));
597
}
598
599
private storeFontInfo(storageService: IStorageService): void {
600
const serializedFontInfo = FontMeasurements.serializeFontInfo(mainWindow);
601
if (serializedFontInfo) {
602
storageService.store('editorFontInfo', JSON.stringify(serializedFontInfo), StorageScope.APPLICATION, StorageTarget.MACHINE);
603
}
604
}
605
606
//#endregion
607
608
private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void {
609
// ARIA & Signals
610
setARIAContainer(this.mainContainer);
611
setProgressAccessibilitySignalScheduler((msDelayTime: number, msLoopTime?: number) => instantiationService.createInstance(AccessibilityProgressSignalScheduler, msDelayTime, msLoopTime));
612
613
// Initialize viewport classification before building layout classes
614
const initialDimension = getClientArea(this.parent);
615
this.layoutPolicy.update(initialDimension.width, initialDimension.height);
616
617
// Apply initial part visibility from layout policy (phone hides sidebar, etc.)
618
const visibilityDefaults = this.layoutPolicy.getPartVisibilityDefaults();
619
this.partVisibility.sidebar = visibilityDefaults.sidebar;
620
this.partVisibility.auxiliaryBar = visibilityDefaults.auxiliaryBar;
621
this.partVisibility.panel = visibilityDefaults.panel;
622
this.partVisibility.chatBar = visibilityDefaults.chatBar;
623
this.partVisibility.editor = visibilityDefaults.editor;
624
625
// State specific classes
626
const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';
627
const workbenchClasses = coalesce([
628
'monaco-workbench',
629
'agent-sessions-workbench',
630
LayoutClasses.SHELL_GRADIENT_BACKGROUND,
631
platformClass,
632
isWeb ? 'web' : undefined,
633
isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined,
634
...this.getLayoutClasses(),
635
...(this.options?.extraClasses ? this.options.extraClasses : [])
636
]);
637
638
this.mainContainer.classList.add(...workbenchClasses);
639
640
// Apply font aliasing
641
this.updateFontAliasing(undefined, configurationService);
642
643
// Warm up font cache information before building up too many dom elements
644
this.restoreFontInfo(storageService, configurationService);
645
646
// Create Parts (editor starts hidden and is shown when an editor opens)
647
for (const { id, role, classes } of [
648
{ id: Parts.TITLEBAR_PART, role: 'none', classes: ['titlebar'] },
649
{ id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', 'left'] },
650
{ id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', 'right'] },
651
{ id: Parts.CHATBAR_PART, role: 'main', classes: ['chatbar', 'basepanel', 'right'] },
652
{ id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] },
653
]) {
654
const partContainer = this.createPartContainer(id, role, classes);
655
656
mark(`code/willCreatePart/${id}`);
657
this.getPart(id).create(partContainer);
658
mark(`code/didCreatePart/${id}`);
659
}
660
661
// Create Editor Part (hidden by default)
662
this.createEditorPart();
663
664
// Notification Handlers
665
this.createNotificationsHandlers(instantiationService, notificationService, configurationService);
666
667
// Add Workbench to DOM
668
this.parent.appendChild(this.mainContainer);
669
}
670
671
private createMobileTitlebar(): void {
672
this.mobileTopBarDisposables.clear();
673
const mobileTitlebar = this.mobileTopBarDisposables.add(this.instantiationService.createInstance(MobileTitlebarPart, this.mainContainer));
674
this.mobileTopBarElement = mobileTitlebar.element;
675
676
// Hamburger: toggle sidebar drawer overlay
677
this.mobileTopBarDisposables.add(mobileTitlebar.onDidClickHamburger(() => {
678
this.toggleMobileSidebarDrawer();
679
}));
680
681
// New session: open new chat view
682
this.mobileTopBarDisposables.add(mobileTitlebar.onDidClickNewSession(() => {
683
this.sessionsManagementService.openNewSessionView();
684
}));
685
}
686
687
private toggleMobileSidebarDrawer(): void {
688
const isOpen = this.partVisibility.sidebar;
689
if (isOpen) {
690
this.closeMobileSidebarDrawer();
691
} else {
692
this.openMobileSidebarDrawer();
693
}
694
}
695
696
private openMobileSidebarDrawer(): void {
697
// Push a history entry so the Android back button dismisses the drawer.
698
// Must come before setSideBarHidden(false) so layoutMobileSidebar() sees
699
// the drawer state.
700
if (!this.mobileNavStack.has('sidebar')) {
701
this.mobileNavStack.push('sidebar');
702
}
703
704
// Show sidebar in grid — the actual drawer dimensions are applied by
705
// layoutMobileSidebar() from within layout(), which uses the full
706
// viewport width below the mobile top bar on phone. The toggle button
707
// in the top bar remains visible and is used to close the drawer.
708
this.setSideBarHidden(false);
709
}
710
711
private closeMobileSidebarDrawer(): void {
712
// Hide sidebar in grid
713
this.setSideBarHidden(true);
714
715
// Sync the navigation stack with the browser history: if there is a
716
// pending 'sidebar' entry (UI-initiated close), rewind history without
717
// firing onDidPop. If we're being called from the back-button path
718
// (onDidPop already fired), this is a no-op.
719
if (this.mobileNavStack.has('sidebar')) {
720
this.mobileNavStack.popSilently('sidebar');
721
}
722
}
723
724
private createNotificationsHandlers(
725
instantiationService: IInstantiationService,
726
notificationService: NotificationService,
727
configurationService: IConfigurationService
728
): void {
729
// Instantiate Notification components
730
const notificationsCenter = this._register(instantiationService.createInstance(NotificationsCenter, this.mainContainer, notificationService.model));
731
const notificationsToasts = this._register(instantiationService.createInstance(NotificationsToasts, this.mainContainer, notificationService.model));
732
this._register(instantiationService.createInstance(NotificationsAlerts, notificationService.model));
733
const notificationsStatus = this._register(instantiationService.createInstance(NotificationsStatus, notificationService.model));
734
735
// Visibility
736
this._register(notificationsCenter.onDidChangeVisibility(() => {
737
notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible);
738
notificationsToasts.update(notificationsCenter.isVisible);
739
}));
740
741
this._register(notificationsToasts.onDidChangeVisibility(() => {
742
notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible);
743
}));
744
745
// Register Commands
746
registerNotificationCommands(notificationsCenter, notificationsToasts, notificationService.model);
747
748
// Register notification accessible view
749
AccessibleViewRegistry.register(new NotificationAccessibleView());
750
751
// The shared notification controllers apply a top-right inline offset based on the
752
// default workbench custom titlebar height. The sessions workbench has its own
753
// fixed chrome, so re-apply the sessions-specific top-right offset after they run.
754
this.registerSessionsNotificationOffsets(configurationService, notificationsCenter, notificationsToasts);
755
756
// Register with Layout
757
this.registerNotifications({
758
onDidChangeNotificationsVisibility: Event.map(
759
Event.any(notificationsToasts.onDidChangeVisibility, notificationsCenter.onDidChangeVisibility),
760
() => notificationsToasts.isVisible || notificationsCenter.isVisible
761
)
762
});
763
}
764
765
private registerSessionsNotificationOffsets(
766
configurationService: IConfigurationService,
767
notificationsCenter: NotificationsCenter,
768
notificationsToasts: NotificationsToasts
769
): void {
770
const applySessionsNotificationOffsets = () => {
771
const position = getNotificationsPosition(configurationService);
772
const notificationsCenterContainer = this.getWorkbenchChildByClassName('notifications-center');
773
const notificationsToastsContainer = this.getWorkbenchChildByClassName('notifications-toasts');
774
775
if (position === NotificationsPosition.TOP_RIGHT) {
776
notificationsCenterContainer?.style.setProperty('top', '40px');
777
notificationsToastsContainer?.style.setProperty('top', '40px');
778
}
779
};
780
781
this._register(this.onDidLayoutMainContainer(() => applySessionsNotificationOffsets()));
782
this._register(notificationsCenter.onDidChangeVisibility(() => applySessionsNotificationOffsets()));
783
this._register(notificationsToasts.onDidChangeVisibility(() => applySessionsNotificationOffsets()));
784
this._register(configurationService.onDidChangeConfiguration(e => {
785
if (e.affectsConfiguration(NotificationsSettings.NOTIFICATIONS_POSITION)) {
786
applySessionsNotificationOffsets();
787
}
788
}));
789
}
790
791
private getWorkbenchChildByClassName(className: string): HTMLElement | undefined {
792
for (const child of this.mainContainer.children) {
793
if (isHTMLElement(child) && child.classList.contains(className)) {
794
return child;
795
}
796
}
797
798
return undefined;
799
}
800
801
private createPartContainer(id: string, role: string, classes: string[]): HTMLElement {
802
const part = document.createElement('div');
803
part.classList.add('part', ...classes);
804
part.id = id;
805
part.setAttribute('role', role);
806
return part;
807
}
808
809
private createEditorPart(): void {
810
const editorPartContainer = document.createElement('div');
811
editorPartContainer.classList.add('part', 'editor');
812
editorPartContainer.id = Parts.EDITOR_PART;
813
editorPartContainer.setAttribute('role', 'main');
814
815
mark('code/willCreatePart/workbench.parts.editor');
816
this.getPart(Parts.EDITOR_PART).create(editorPartContainer, { restorePreviousState: false });
817
mark('code/didCreatePart/workbench.parts.editor');
818
819
this.mainContainer.appendChild(editorPartContainer);
820
}
821
822
private restore(lifecycleService: ILifecycleService): void {
823
// Update perf marks
824
mark('code/didStartWorkbench');
825
performance.measure('perf: workbench create & restore', 'code/didLoadWorkbenchMain', 'code/didStartWorkbench');
826
827
// Restore parts (open default view containers)
828
this.restoreParts();
829
830
// Set lifecycle phase to `Restored`
831
lifecycleService.phase = LifecyclePhase.Restored;
832
833
// Mark as restored
834
this.setRestored();
835
836
// Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec)
837
const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => {
838
this._register(runWhenWindowIdle(mainWindow, () => lifecycleService.phase = LifecyclePhase.Eventually, 2500));
839
}, 2500));
840
eventuallyPhaseScheduler.schedule();
841
}
842
843
private restoreParts(): void {
844
// Open default view containers for each visible part
845
const partsToRestore: { location: ViewContainerLocation; visible: boolean }[] = [
846
{ location: ViewContainerLocation.Sidebar, visible: this.partVisibility.sidebar },
847
{ location: ViewContainerLocation.Panel, visible: this.partVisibility.panel },
848
{ location: ViewContainerLocation.AuxiliaryBar, visible: this.partVisibility.auxiliaryBar },
849
{ location: ViewContainerLocation.ChatBar, visible: this.partVisibility.chatBar },
850
];
851
852
for (const { location, visible } of partsToRestore) {
853
if (visible) {
854
const defaultViewContainer = this.viewDescriptorService.getDefaultViewContainer(location);
855
if (defaultViewContainer) {
856
this.paneCompositeService.openPaneComposite(defaultViewContainer.id, location);
857
}
858
}
859
}
860
}
861
862
//#endregion
863
864
//#region Initialization
865
866
initLayout(accessor: ServicesAccessor): void {
867
// Services - accessing these triggers their instantiation
868
// which creates and registers the parts
869
this.editorGroupService = accessor.get(IEditorGroupsService);
870
this.editorService = accessor.get(IEditorService);
871
this.paneCompositeService = accessor.get(IPaneCompositePartService);
872
this.viewDescriptorService = accessor.get(IViewDescriptorService);
873
this.sessionsManagementService = accessor.get(ISessionsManagementService);
874
this.instantiationService = accessor.get(IInstantiationService);
875
accessor.get(ITitleService);
876
877
// Register layout listeners
878
this.registerLayoutListeners();
879
880
// Editor opens should only affect the main editor part when
881
// they actually target one of the main editor groups. Modal
882
// opens stay neutral.
883
this._register(this.editorService.onWillOpenEditor(e => {
884
const targetsMainEditorPart = this.editorGroupService.mainPart.groups.some(group => group.id === e.groupId);
885
if (!targetsMainEditorPart) {
886
return;
887
}
888
889
if (!this.partVisibility.editor) {
890
this.setEditorHidden(false);
891
}
892
}));
893
894
// Hide editor part when last editor closes
895
this._register(this.editorService.onDidCloseEditor(() => {
896
if (this.partVisibility.editor && this.areAllGroupsEmpty()) {
897
this.setEditorHidden(true);
898
}
899
}));
900
901
// Initialize layout state (must be done before createWorkbenchLayout)
902
this._mainContainerDimension = getClientArea(this.parent, new Dimension(800, 600));
903
this.layoutPolicy.update(this._mainContainerDimension.width, this._mainContainerDimension.height);
904
905
// Update part visibility based on final viewport classification
906
const visDefaults = this.layoutPolicy.getPartVisibilityDefaults();
907
this.partVisibility.sidebar = visDefaults.sidebar;
908
this.partVisibility.auxiliaryBar = visDefaults.auxiliaryBar;
909
this.partVisibility.panel = visDefaults.panel;
910
this.partVisibility.chatBar = visDefaults.chatBar;
911
this.partVisibility.editor = visDefaults.editor;
912
}
913
914
private areAllGroupsEmpty(): boolean {
915
for (const group of this.editorGroupService.groups) {
916
if (!group.isEmpty) {
917
return false;
918
}
919
}
920
return true;
921
}
922
923
private registerLayoutListeners(): void {
924
// Fullscreen changes
925
this._register(onDidChangeFullscreen(windowId => {
926
if (windowId === getWindowId(mainWindow)) {
927
this.mainWindowFullscreen = isFullscreen(mainWindow);
928
this.updateFullscreenClass();
929
this.layout();
930
}
931
}));
932
933
// Window resize — needed for device emulation and mobile viewport changes
934
const onWindowResize = () => this.layout();
935
mainWindow.addEventListener('resize', onWindowResize);
936
this._register({ dispose: () => mainWindow.removeEventListener('resize', onWindowResize) });
937
}
938
939
private updateFullscreenClass(): void {
940
if (this.mainWindowFullscreen) {
941
this.mainContainer.classList.add(LayoutClasses.FULLSCREEN);
942
} else {
943
this.mainContainer.classList.remove(LayoutClasses.FULLSCREEN);
944
}
945
}
946
947
//#endregion
948
949
//#region Workbench Layout Creation
950
951
createWorkbenchLayout(): void {
952
const titleBar = this.getPart(Parts.TITLEBAR_PART);
953
const editorPart = this.getPart(Parts.EDITOR_PART);
954
const panelPart = this.getPart(Parts.PANEL_PART);
955
const auxiliaryBarPart = this.getPart(Parts.AUXILIARYBAR_PART);
956
const sideBar = this.getPart(Parts.SIDEBAR_PART);
957
const chatBarPart = this.getPart(Parts.CHATBAR_PART);
958
959
// View references for parts in the grid
960
this.titleBarPartView = titleBar;
961
this.sideBarPartView = sideBar;
962
this.panelPartView = panelPart;
963
this.auxiliaryBarPartView = auxiliaryBarPart;
964
this.chatBarPartView = chatBarPart;
965
this.editorPartView = editorPart;
966
967
const viewMap: { [key: string]: ISerializableView } = {
968
[Parts.TITLEBAR_PART]: this.titleBarPartView,
969
[Parts.PANEL_PART]: this.panelPartView,
970
[Parts.SIDEBAR_PART]: this.sideBarPartView,
971
[Parts.AUXILIARYBAR_PART]: this.auxiliaryBarPartView,
972
[Parts.CHATBAR_PART]: this.chatBarPartView,
973
[Parts.EDITOR_PART]: this.editorPartView
974
};
975
976
const fromJSON = ({ type }: { type: string }) => viewMap[type];
977
const workbenchGrid = SerializableGrid.deserialize(
978
this.createGridDescriptor(),
979
{ fromJSON },
980
{ proportionalLayout: false }
981
);
982
983
this.mainContainer.prepend(workbenchGrid.element);
984
this.mainContainer.setAttribute('role', 'application');
985
this.workbenchGrid = workbenchGrid;
986
this.workbenchGrid.edgeSnapping = this.mainWindowFullscreen;
987
988
// Listen for part visibility changes (for parts in grid)
989
for (const part of [titleBar, panelPart, sideBar, auxiliaryBarPart, chatBarPart, editorPart]) {
990
this._register(part.onDidVisibilityChange(visible => {
991
if (part === sideBar) {
992
this.setSideBarHidden(!visible);
993
} else if (part === panelPart) {
994
this.setPanelHidden(!visible);
995
} else if (part === auxiliaryBarPart) {
996
this.setAuxiliaryBarHidden(!visible);
997
} else if (part === chatBarPart) {
998
this.setChatBarHidden(!visible);
999
} else if (part === editorPart) {
1000
this.setEditorHidden(!visible);
1001
}
1002
1003
this._onDidChangePartVisibility.fire({ partId: part.getId(), visible });
1004
this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension);
1005
}));
1006
}
1007
1008
// Wire up mobile nav stack: back-button pops close the corresponding part
1009
this._register(this.mobileNavStack.onDidPop(layer => {
1010
switch (layer) {
1011
case 'sidebar':
1012
this.closeMobileSidebarDrawer();
1013
break;
1014
case 'panel':
1015
this.setPanelHidden(true);
1016
break;
1017
case 'auxbar':
1018
this.setAuxiliaryBarHidden(true);
1019
break;
1020
case 'editor':
1021
// Editor modal close is handled by the editor service
1022
break;
1023
}
1024
}));
1025
}
1026
1027
createWorkbenchManagement(_instantiationService: IInstantiationService): void {
1028
// No floating toolbars in this layout
1029
}
1030
1031
/**
1032
* Creates the grid descriptor for the Agent Sessions layout.
1033
*
1034
* Structure (horizontal orientation):
1035
* - Sidebar (left, spans full height from top to bottom)
1036
* - Right section (vertical):
1037
* - Titlebar (top of right section)
1038
* - Top right (horizontal): Chat Bar | Editor | Auxiliary Bar
1039
* - Panel (below chat, editor, and auxiliary bar)
1040
*/
1041
private createGridDescriptor(): ISerializedGrid {
1042
const { width, height } = this._mainContainerDimension;
1043
1044
return this.createDesktopGridDescriptor(width, height);
1045
}
1046
1047
/**
1048
* Standard multi-part layout for all viewport classes.
1049
* On phone, the titlebar is hidden via CSS and a MobileTitlebarPart
1050
* is prepended before the grid. Sidebar/panel/auxbar are hidden
1051
* in the grid via partVisibility defaults.
1052
*/
1053
private createDesktopGridDescriptor(width: number, height: number): ISerializedGrid {
1054
1055
// Default sizes from layout policy
1056
const sizes = this.layoutPolicy.getPartSizes(width, height);
1057
// For hidden parts, still provide a reasonable cached size for when they're shown later
1058
const sideBarSize = this.partVisibility.sidebar ? sizes.sideBarSize : Math.max(sizes.sideBarSize, 250);
1059
const auxiliaryBarSize = this.partVisibility.auxiliaryBar ? sizes.auxiliaryBarSize : Math.max(sizes.auxiliaryBarSize, 300);
1060
const panelSize = this.partVisibility.panel ? sizes.panelSize : Math.max(sizes.panelSize, 250);
1061
const editorSize = 600;
1062
const titleBarHeight = this.titleBarPartView?.minimumHeight ?? 30;
1063
1064
// Calculate right section width — when sidebar is hidden it takes no space
1065
const effectiveSideBarWidth = this.partVisibility.sidebar ? sideBarSize : 0;
1066
const rightSectionWidth = Math.max(0, width - effectiveSideBarWidth);
1067
const effectiveAuxBarWidth = this.partVisibility.auxiliaryBar ? auxiliaryBarSize : 0;
1068
const effectiveEditorWidth = this.partVisibility.editor ? editorSize : 0;
1069
const chatBarWidth = Math.max(0, rightSectionWidth - effectiveAuxBarWidth - effectiveEditorWidth);
1070
1071
const contentHeight = Math.max(0, height - titleBarHeight);
1072
const topRightHeight = Math.max(0, contentHeight - panelSize);
1073
1074
const isPhone = this.layoutPolicy.viewportClass.get() === 'phone';
1075
1076
const titleBarNode: ISerializedLeafNode = {
1077
type: 'leaf',
1078
data: { type: Parts.TITLEBAR_PART },
1079
size: titleBarHeight,
1080
visible: !isPhone
1081
};
1082
1083
const sideBarNode: ISerializedLeafNode = {
1084
type: 'leaf',
1085
data: { type: Parts.SIDEBAR_PART },
1086
size: sideBarSize,
1087
visible: this.partVisibility.sidebar
1088
};
1089
1090
const auxiliaryBarNode: ISerializedLeafNode = {
1091
type: 'leaf',
1092
data: { type: Parts.AUXILIARYBAR_PART },
1093
size: auxiliaryBarSize,
1094
visible: this.partVisibility.auxiliaryBar
1095
};
1096
1097
const chatBarNode: ISerializedLeafNode = {
1098
type: 'leaf',
1099
data: { type: Parts.CHATBAR_PART },
1100
size: chatBarWidth,
1101
visible: this.partVisibility.chatBar
1102
};
1103
1104
const editorNode: ISerializedLeafNode = {
1105
type: 'leaf',
1106
data: { type: Parts.EDITOR_PART },
1107
size: editorSize,
1108
visible: this.partVisibility.editor
1109
};
1110
1111
const panelNode: ISerializedLeafNode = {
1112
type: 'leaf',
1113
data: { type: Parts.PANEL_PART },
1114
size: panelSize,
1115
visible: this.partVisibility.panel
1116
};
1117
1118
// Top right section: Chat Bar | Editor | Auxiliary Bar (horizontal)
1119
const topRightSection: ISerializedNode = {
1120
type: 'branch',
1121
data: [chatBarNode, editorNode, auxiliaryBarNode],
1122
size: topRightHeight
1123
};
1124
1125
// Right section: Top Right | Panel (vertical)
1126
const rightSection: ISerializedNode = {
1127
type: 'branch',
1128
data: [topRightSection, panelNode],
1129
size: rightSectionWidth
1130
};
1131
1132
// Content section: Sidebar | Right section (horizontal)
1133
const contentSection: ISerializedNode = {
1134
type: 'branch',
1135
data: [sideBarNode, rightSection],
1136
size: contentHeight
1137
};
1138
1139
const result: ISerializedGrid = {
1140
root: {
1141
type: 'branch',
1142
size: width,
1143
data: [
1144
titleBarNode,
1145
contentSection
1146
]
1147
},
1148
orientation: Orientation.VERTICAL,
1149
width,
1150
height
1151
};
1152
1153
return result;
1154
}
1155
1156
//#endregion
1157
1158
//#region Layout Methods
1159
1160
private _previousViewportClass: string | undefined;
1161
1162
layout(): void {
1163
this._mainContainerDimension = getClientArea(
1164
this.mainWindowFullscreen ? mainWindow.document.body : this.parent
1165
);
1166
1167
// Update viewport classification and toggle mobile CSS classes
1168
const previousClass = this._previousViewportClass;
1169
this.layoutPolicy.update(this._mainContainerDimension.width, this._mainContainerDimension.height);
1170
const currentClass = this.layoutPolicy.viewportClass.get();
1171
this.mainContainer.classList.toggle(LayoutClasses.PHONE_LAYOUT, currentClass === 'phone');
1172
1173
// When viewport class changes at runtime (e.g., device emulation toggle),
1174
// update part visibility and create/destroy mobile components
1175
if (previousClass !== undefined && previousClass !== currentClass) {
1176
if (currentClass === 'phone' && !this.mobileTopBarElement) {
1177
this.createMobileTitlebar();
1178
// Hide titlebar in grid on phone (replaced by MobileTitlebarPart)
1179
this.workbenchGrid.setViewVisible(this.titleBarPartView, false);
1180
// On phone, only chat is visible — hide everything else first
1181
const defaults = this.layoutPolicy.getPartVisibilityDefaults();
1182
if (this.partVisibility.sidebar !== defaults.sidebar) {
1183
this.setSideBarHidden(!defaults.sidebar);
1184
}
1185
if (this.partVisibility.auxiliaryBar !== defaults.auxiliaryBar) {
1186
this.setAuxiliaryBarHidden(!defaults.auxiliaryBar);
1187
}
1188
if (this.partVisibility.panel !== defaults.panel) {
1189
this.setPanelHidden(!defaults.panel);
1190
}
1191
} else if (currentClass !== 'phone' && this.mobileTopBarElement) {
1192
// Remove mobile components when leaving phone layout
1193
this.mobileTopBarDisposables.clear();
1194
this.mobileTopBarElement = undefined;
1195
// Restore titlebar in grid
1196
this.workbenchGrid.setViewVisible(this.titleBarPartView, true);
1197
// Restore desktop part visibility
1198
const defaults = this.layoutPolicy.getPartVisibilityDefaults();
1199
if (this.partVisibility.sidebar !== defaults.sidebar) {
1200
this.setSideBarHidden(!defaults.sidebar);
1201
}
1202
if (this.partVisibility.chatBar !== defaults.chatBar) {
1203
this.setChatBarHidden(!defaults.chatBar);
1204
}
1205
if (this.partVisibility.auxiliaryBar !== defaults.auxiliaryBar) {
1206
this.setAuxiliaryBarHidden(!defaults.auxiliaryBar);
1207
}
1208
if (this.partVisibility.panel !== defaults.panel) {
1209
this.setPanelHidden(!defaults.panel);
1210
}
1211
}
1212
1213
// Re-run updateStyles() on pane composite parts so that
1214
// mobile Part subclasses can re-apply or clear card-chrome
1215
// inline styles based on the new `.phone-layout` class.
1216
for (const partId of [Parts.CHATBAR_PART, Parts.SIDEBAR_PART, Parts.AUXILIARYBAR_PART, Parts.PANEL_PART]) {
1217
this.parts.get(partId)?.updateStyles();
1218
}
1219
}
1220
this._previousViewportClass = currentClass;
1221
1222
this.logService.trace(`Workbench#layout, height: ${this._mainContainerDimension.height}, width: ${this._mainContainerDimension.width}`);
1223
1224
size(this.mainContainer, this._mainContainerDimension.width, this._mainContainerDimension.height);
1225
1226
// On phone, subtract the mobile top bar height from the grid
1227
const mobileTopBarHeight = this.mobileTopBarElement?.offsetHeight ?? 0;
1228
const gridHeight = this._mainContainerDimension.height - mobileTopBarHeight;
1229
1230
// Layout the grid widget
1231
this.workbenchGrid.layout(this._mainContainerDimension.width, gridHeight);
1232
this.layoutMobileSidebar();
1233
1234
// Emit as event
1235
this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension);
1236
}
1237
1238
private layoutMobileSidebar(): void {
1239
const sidebarContainer = this.getContainer(mainWindow, Parts.SIDEBAR_PART);
1240
const sidebarPart = this.getPart(Parts.SIDEBAR_PART);
1241
if (!sidebarContainer) {
1242
return;
1243
}
1244
1245
// On phone the sidebar renders as a full-viewport overlay drawer.
1246
// Geometry is fully expressed in CSS — see
1247
// `mobileChatShell.css` (split-view-view fills the grid) and
1248
// `sidebarPart.css` (drawer animation, z-index). We avoid setting
1249
// inline position/size styles here because writing them after the
1250
// grid has already laid out and painted the sidebar causes a
1251
// visible one-frame snap on toggle.
1252
const isPhone = this.layoutPolicy.viewportClass.get() === 'phone';
1253
if (!isPhone || !this.partVisibility.sidebar) {
1254
sidebarContainer.classList.remove('mobile-overlay-sidebar');
1255
return;
1256
}
1257
1258
sidebarContainer.classList.add('mobile-overlay-sidebar');
1259
1260
// Re-layout the sidebar Part with the drawer's content dimensions
1261
// so its internal composite/list sizing matches the CSS-positioned
1262
// drawer (grid area minus the mobile top bar).
1263
const topBarHeight = this.mobileTopBarElement?.offsetHeight ?? 48;
1264
const drawerWidth = this._mainContainerDimension.width;
1265
const drawerHeight = Math.max(0, this._mainContainerDimension.height - topBarHeight);
1266
sidebarPart.layout(drawerWidth, drawerHeight, topBarHeight, 0);
1267
}
1268
1269
private handleContainerDidLayout(container: HTMLElement, dimension: IDimension): void {
1270
this._onDidLayoutContainer.fire({ container, dimension });
1271
if (container === this.mainContainer) {
1272
this._onDidLayoutMainContainer.fire(dimension);
1273
}
1274
if (container === this.activeContainer) {
1275
this._onDidLayoutActiveContainer.fire(dimension);
1276
}
1277
}
1278
1279
getLayoutClasses(): string[] {
1280
return coalesce([
1281
!this.partVisibility.sidebar ? LayoutClasses.SIDEBAR_HIDDEN : undefined,
1282
!this.partVisibility.editor ? LayoutClasses.MAIN_EDITOR_AREA_HIDDEN : undefined,
1283
!this.partVisibility.panel ? LayoutClasses.PANEL_HIDDEN : undefined,
1284
!this.partVisibility.auxiliaryBar ? LayoutClasses.AUXILIARYBAR_HIDDEN : undefined,
1285
!this.partVisibility.chatBar ? LayoutClasses.CHATBAR_HIDDEN : undefined,
1286
LayoutClasses.STATUSBAR_HIDDEN, // agents window never has a status bar
1287
this.mainWindowFullscreen ? LayoutClasses.FULLSCREEN : undefined,
1288
this.layoutPolicy.viewportClass.get() === 'phone' ? LayoutClasses.PHONE_LAYOUT : undefined,
1289
]);
1290
}
1291
1292
//#endregion
1293
1294
//#region Part Management
1295
1296
registerPart(part: Part): IDisposable {
1297
const id = part.getId();
1298
this.parts.set(id, part);
1299
return toDisposable(() => this.parts.delete(id));
1300
}
1301
1302
getPart(key: Parts): Part {
1303
const part = this.parts.get(key);
1304
if (!part) {
1305
throw new Error(`Unknown part ${key}`);
1306
}
1307
return part;
1308
}
1309
1310
hasFocus(part: Parts): boolean {
1311
const container = this.getContainer(mainWindow, part);
1312
if (!container) {
1313
return false;
1314
}
1315
1316
const activeElement = getActiveElement();
1317
if (!activeElement) {
1318
return false;
1319
}
1320
1321
return isAncestorUsingFlowTo(activeElement, container);
1322
}
1323
1324
focusPart(part: MULTI_WINDOW_PARTS, targetWindow: Window): void;
1325
focusPart(part: SINGLE_WINDOW_PARTS): void;
1326
focusPart(part: Parts, targetWindow: Window = mainWindow): void {
1327
switch (part) {
1328
case Parts.EDITOR_PART:
1329
this.editorGroupService.activeGroup.focus();
1330
break;
1331
case Parts.PANEL_PART:
1332
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)?.focus();
1333
break;
1334
case Parts.SIDEBAR_PART:
1335
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)?.focus();
1336
break;
1337
case Parts.AUXILIARYBAR_PART:
1338
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)?.focus();
1339
break;
1340
case Parts.CHATBAR_PART:
1341
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.ChatBar)?.focus();
1342
break;
1343
default: {
1344
const container = this.getContainer(targetWindow, part);
1345
container?.focus();
1346
}
1347
}
1348
}
1349
1350
focus(): void {
1351
this.focusPart(Parts.CHATBAR_PART);
1352
}
1353
1354
//#endregion
1355
1356
//#region Container Methods
1357
1358
getContainer(targetWindow: Window): HTMLElement;
1359
getContainer(targetWindow: Window, part: Parts): HTMLElement | undefined;
1360
getContainer(targetWindow: Window, part?: Parts): HTMLElement | undefined {
1361
if (typeof part === 'undefined') {
1362
return this.getContainerFromDocument(targetWindow.document);
1363
}
1364
1365
if (targetWindow === mainWindow) {
1366
return this.parts.get(part)?.getContainer();
1367
}
1368
1369
// For auxiliary windows, only editor part is supported
1370
if (part === Parts.EDITOR_PART) {
1371
const container = this.getContainerFromDocument(targetWindow.document);
1372
const partCandidate = this.editorGroupService.getPart(container);
1373
if (partCandidate instanceof Part) {
1374
return partCandidate.getContainer();
1375
}
1376
}
1377
1378
return undefined;
1379
}
1380
1381
whenContainerStylesLoaded(_window: CodeWindow): Promise<void> | undefined {
1382
return undefined;
1383
}
1384
1385
//#endregion
1386
1387
//#region Part Visibility
1388
1389
isActivityBarHidden(): boolean {
1390
return true; // No activity bar in this layout
1391
}
1392
1393
isVisible(part: SINGLE_WINDOW_PARTS): boolean;
1394
isVisible(part: MULTI_WINDOW_PARTS, targetWindow: Window): boolean;
1395
isVisible(part: Parts, targetWindow?: Window): boolean {
1396
switch (part) {
1397
case Parts.TITLEBAR_PART:
1398
// On phone layout the grid titlebar is hidden (replaced by MobileTitlebarPart)
1399
return this.layoutPolicy.viewportClass.get() !== 'phone';
1400
case Parts.SIDEBAR_PART:
1401
return this.partVisibility.sidebar;
1402
case Parts.AUXILIARYBAR_PART:
1403
return this.partVisibility.auxiliaryBar;
1404
case Parts.EDITOR_PART:
1405
return this.partVisibility.editor;
1406
case Parts.PANEL_PART:
1407
return this.partVisibility.panel;
1408
case Parts.CHATBAR_PART:
1409
return this.partVisibility.chatBar;
1410
case Parts.ACTIVITYBAR_PART:
1411
case Parts.STATUSBAR_PART:
1412
case Parts.BANNER_PART:
1413
default:
1414
return false;
1415
}
1416
}
1417
1418
setPartHidden(hidden: boolean, part: Parts): void {
1419
switch (part) {
1420
case Parts.SIDEBAR_PART:
1421
this.setSideBarHidden(hidden);
1422
break;
1423
case Parts.AUXILIARYBAR_PART:
1424
this.setAuxiliaryBarHidden(hidden);
1425
break;
1426
case Parts.EDITOR_PART:
1427
this.setEditorHidden(hidden);
1428
break;
1429
case Parts.PANEL_PART:
1430
this.setPanelHidden(hidden);
1431
break;
1432
case Parts.CHATBAR_PART:
1433
this.setChatBarHidden(hidden);
1434
break;
1435
}
1436
}
1437
1438
private setSideBarHidden(hidden: boolean): void {
1439
if (this.partVisibility.sidebar === !hidden) {
1440
return;
1441
}
1442
1443
this.partVisibility.sidebar = !hidden;
1444
this.mainContainer.classList.toggle(LayoutClasses.SIDEBAR_HIDDEN, hidden);
1445
1446
// Propagate to grid
1447
this.workbenchGrid.setViewVisible(
1448
this.sideBarPartView,
1449
!hidden,
1450
);
1451
1452
// If sidebar becomes hidden, also hide the current active pane composite
1453
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
1454
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
1455
}
1456
1457
// If sidebar becomes visible, show last active Viewlet or default viewlet
1458
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
1459
const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar) ??
1460
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id;
1461
if (viewletToOpen) {
1462
this.paneCompositeService.openPaneComposite(viewletToOpen, ViewContainerLocation.Sidebar);
1463
}
1464
}
1465
1466
this.layoutMobileSidebar();
1467
}
1468
1469
private setAuxiliaryBarHidden(hidden: boolean): void {
1470
if (this.partVisibility.auxiliaryBar === !hidden) {
1471
return;
1472
}
1473
1474
this.partVisibility.auxiliaryBar = !hidden;
1475
this.mainContainer.classList.toggle(LayoutClasses.AUXILIARYBAR_HIDDEN, hidden);
1476
1477
// Propagate to grid
1478
this.workbenchGrid.setViewVisible(
1479
this.auxiliaryBarPartView,
1480
!hidden,
1481
);
1482
1483
// If auxiliary bar becomes hidden, also hide the current active pane composite
1484
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
1485
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar);
1486
}
1487
1488
// If auxiliary bar becomes visible, show last active pane composite or default
1489
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
1490
const paneCompositeToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.AuxiliaryBar) ??
1491
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id;
1492
if (paneCompositeToOpen) {
1493
this.paneCompositeService.openPaneComposite(paneCompositeToOpen, ViewContainerLocation.AuxiliaryBar);
1494
}
1495
}
1496
}
1497
1498
private setEditorHidden(hidden: boolean): void {
1499
if (this.partVisibility.editor === !hidden) {
1500
return;
1501
}
1502
1503
// If hiding the editor while maximized
1504
if (hidden && this._editorMaximized) {
1505
this.setEditorMaximized(false);
1506
}
1507
1508
this.partVisibility.editor = !hidden;
1509
this.mainContainer.classList.toggle(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN, hidden);
1510
1511
if (this.editorPartView) {
1512
this.workbenchGrid.setViewVisible(this.editorPartView, !hidden);
1513
}
1514
}
1515
1516
private setPanelHidden(hidden: boolean): void {
1517
if (this.partVisibility.panel === !hidden) {
1518
return;
1519
}
1520
1521
// If hiding and the panel is maximized, exit maximized state first
1522
if (hidden && this.workbenchGrid.hasMaximizedView()) {
1523
this.workbenchGrid.exitMaximizedView();
1524
}
1525
1526
const panelHadFocus = !hidden || this.hasFocus(Parts.PANEL_PART);
1527
1528
this.partVisibility.panel = !hidden;
1529
this.mainContainer.classList.toggle(LayoutClasses.PANEL_HIDDEN, hidden);
1530
1531
// Propagate to grid
1532
this.workbenchGrid.setViewVisible(
1533
this.panelPartView,
1534
!hidden,
1535
);
1536
1537
// If panel becomes hidden, also hide the current active pane composite
1538
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
1539
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
1540
1541
// Focus the chat bar when hiding the panel if it had focus
1542
if (panelHadFocus) {
1543
this.focusPart(Parts.CHATBAR_PART);
1544
}
1545
}
1546
1547
// If panel becomes visible, show last active panel or default and focus it
1548
if (!hidden) {
1549
if (!this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
1550
const panelToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Panel) ??
1551
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id;
1552
if (panelToOpen) {
1553
this.paneCompositeService.openPaneComposite(panelToOpen, ViewContainerLocation.Panel);
1554
}
1555
}
1556
1557
this.focusPart(Parts.PANEL_PART);
1558
}
1559
}
1560
1561
private setChatBarHidden(hidden: boolean): void {
1562
if (this.partVisibility.chatBar === !hidden) {
1563
return;
1564
}
1565
1566
this.partVisibility.chatBar = !hidden;
1567
this.mainContainer.classList.toggle(LayoutClasses.CHATBAR_HIDDEN, hidden);
1568
1569
// Propagate to grid
1570
this.workbenchGrid.setViewVisible(this.chatBarPartView, !hidden);
1571
1572
// If chat bar becomes hidden, also hide the current active pane composite
1573
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.ChatBar)) {
1574
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.ChatBar);
1575
}
1576
1577
// If chat bar becomes visible, show last active pane composite or default
1578
if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.ChatBar)) {
1579
const paneCompositeToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.ChatBar) ??
1580
this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.ChatBar)?.id;
1581
if (paneCompositeToOpen) {
1582
this.paneCompositeService.openPaneComposite(paneCompositeToOpen, ViewContainerLocation.ChatBar);
1583
}
1584
}
1585
}
1586
1587
//#endregion
1588
1589
//#region Position Methods (Fixed - Not Configurable)
1590
1591
getSideBarPosition(): Position {
1592
return Position.LEFT; // Always left in this layout
1593
}
1594
1595
getPanelPosition(): Position {
1596
return Position.BOTTOM; // Always bottom in this layout
1597
}
1598
1599
setPanelPosition(_position: Position): void {
1600
// No-op: Panel position is fixed in this layout
1601
}
1602
1603
getPanelAlignment(): PanelAlignment {
1604
return 'justify'; // Full width panel
1605
}
1606
1607
setPanelAlignment(_alignment: PanelAlignment): void {
1608
// No-op: Panel alignment is fixed in this layout
1609
}
1610
1611
//#endregion
1612
1613
//#region Size Methods
1614
1615
getSize(part: Parts): IViewSize {
1616
const view = this.getPartView(part);
1617
if (!view) {
1618
return { width: 0, height: 0 };
1619
}
1620
return this.workbenchGrid.getViewSize(view);
1621
}
1622
1623
setSize(part: Parts, size: IViewSize): void {
1624
const view = this.getPartView(part);
1625
if (view) {
1626
this.workbenchGrid.resizeView(view, size);
1627
}
1628
}
1629
1630
resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void {
1631
const view = this.getPartView(part);
1632
if (!view) {
1633
return;
1634
}
1635
1636
const currentSize = this.workbenchGrid.getViewSize(view);
1637
this.workbenchGrid.resizeView(view, {
1638
width: currentSize.width + sizeChangeWidth,
1639
height: currentSize.height + sizeChangeHeight
1640
});
1641
}
1642
1643
private getPartView(part: Parts): ISerializableView | undefined {
1644
switch (part) {
1645
case Parts.TITLEBAR_PART:
1646
return this.titleBarPartView;
1647
case Parts.SIDEBAR_PART:
1648
return this.sideBarPartView;
1649
case Parts.AUXILIARYBAR_PART:
1650
return this.auxiliaryBarPartView;
1651
case Parts.EDITOR_PART:
1652
return this.editorPartView;
1653
case Parts.PANEL_PART:
1654
return this.panelPartView;
1655
case Parts.CHATBAR_PART:
1656
return this.chatBarPartView;
1657
default:
1658
return undefined;
1659
}
1660
}
1661
1662
getMaximumEditorDimensions(_container: HTMLElement): IDimension {
1663
// Return the available space for editor (excluding other parts)
1664
const sidebarWidth = this.partVisibility.sidebar ? this.workbenchGrid.getViewSize(this.sideBarPartView).width : 0;
1665
const auxiliaryBarWidth = this.partVisibility.auxiliaryBar ? this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width : 0;
1666
const panelHeight = this.partVisibility.panel ? this.workbenchGrid.getViewSize(this.panelPartView).height : 0;
1667
const titleBarHeight = this.workbenchGrid.getViewSize(this.titleBarPartView).height;
1668
1669
return new Dimension(
1670
this._mainContainerDimension.width - sidebarWidth - auxiliaryBarWidth,
1671
this._mainContainerDimension.height - titleBarHeight - panelHeight
1672
);
1673
}
1674
1675
//#endregion
1676
1677
//#region Unsupported Features (No-ops)
1678
1679
toggleMaximizedPanel(): void {
1680
if (!this.workbenchGrid) {
1681
return;
1682
}
1683
1684
if (this.isPanelMaximized()) {
1685
this.workbenchGrid.exitMaximizedView();
1686
} else {
1687
this.workbenchGrid.maximizeView(this.panelPartView, [this.titleBarPartView, this.sideBarPartView]);
1688
}
1689
}
1690
1691
isPanelMaximized(): boolean {
1692
if (!this.workbenchGrid) {
1693
return false;
1694
}
1695
1696
return this.workbenchGrid.isViewMaximized(this.panelPartView);
1697
}
1698
1699
toggleMaximizedAuxiliaryBar(): void {
1700
// No-op: Maximize not supported in this layout
1701
}
1702
1703
setAuxiliaryBarMaximized(_maximized: boolean): boolean {
1704
return false; // Maximize not supported
1705
}
1706
1707
isAuxiliaryBarMaximized(): boolean {
1708
return false; // Maximize not supported
1709
}
1710
1711
isEditorMaximized(): boolean {
1712
return this._editorMaximized;
1713
}
1714
1715
setEditorMaximized(maximized: boolean): void {
1716
if (maximized === this._editorMaximized) {
1717
return;
1718
}
1719
1720
if (maximized) {
1721
// Save current visibility state
1722
this._editorLastNonMaximizedVisibility = {
1723
sidebar: this.partVisibility.sidebar,
1724
auxiliaryBar: this.partVisibility.auxiliaryBar,
1725
editor: this.partVisibility.editor,
1726
panel: this.partVisibility.panel,
1727
chatBar: this.partVisibility.chatBar,
1728
};
1729
1730
// Ensure editor is visible
1731
if (!this.partVisibility.editor) {
1732
this.setEditorHidden(false);
1733
}
1734
1735
// Hide all other content parts
1736
if (this.partVisibility.sidebar) {
1737
this.setSideBarHidden(true);
1738
}
1739
if (this.partVisibility.chatBar) {
1740
this.setChatBarHidden(true);
1741
}
1742
1743
this._editorMaximized = true;
1744
} else {
1745
const state = this._editorLastNonMaximizedVisibility;
1746
1747
// Restore previous visibility state
1748
this.setSideBarHidden(!state?.sidebar);
1749
this.setChatBarHidden(!state?.chatBar);
1750
1751
this._editorMaximized = false;
1752
}
1753
1754
this._onDidChangeEditorMaximized.fire();
1755
}
1756
1757
toggleZenMode(): void {
1758
// No-op: Zen mode not supported in this layout
1759
}
1760
1761
toggleMenuBar(): void {
1762
// No-op: Menu bar toggle not supported in this layout
1763
}
1764
1765
isMainEditorLayoutCentered(): boolean {
1766
return false; // Centered layout not supported
1767
}
1768
1769
centerMainEditorLayout(_active: boolean): void {
1770
// No-op: Centered layout not supported in this layout
1771
}
1772
1773
hasMainWindowBorder(): boolean {
1774
return false;
1775
}
1776
1777
getMainWindowBorderRadius(): string | undefined {
1778
return undefined;
1779
}
1780
1781
//#endregion
1782
1783
//#region Window Maximized State
1784
1785
isWindowMaximized(targetWindow: Window): boolean {
1786
return this.maximized.has(getWindowId(targetWindow));
1787
}
1788
1789
updateWindowMaximizedState(targetWindow: Window, maximized: boolean): void {
1790
const windowId = getWindowId(targetWindow);
1791
if (maximized) {
1792
this.maximized.add(windowId);
1793
if (targetWindow === mainWindow) {
1794
this.mainContainer.classList.add(LayoutClasses.MAXIMIZED);
1795
}
1796
} else {
1797
this.maximized.delete(windowId);
1798
if (targetWindow === mainWindow) {
1799
this.mainContainer.classList.remove(LayoutClasses.MAXIMIZED);
1800
}
1801
}
1802
1803
this._onDidChangeWindowMaximized.fire({ windowId, maximized });
1804
}
1805
1806
//#endregion
1807
1808
//#region Neighbor Parts
1809
1810
getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined {
1811
if (!this.workbenchGrid) {
1812
return undefined;
1813
}
1814
1815
const view = this.getPartView(part);
1816
if (!view) {
1817
return undefined;
1818
}
1819
1820
const neighbor = this.workbenchGrid.getNeighborViews(view, direction, false);
1821
if (neighbor.length === 0) {
1822
return undefined;
1823
}
1824
1825
const neighborView = neighbor[0];
1826
1827
if (neighborView === this.titleBarPartView) {
1828
return Parts.TITLEBAR_PART;
1829
}
1830
if (neighborView === this.sideBarPartView) {
1831
return Parts.SIDEBAR_PART;
1832
}
1833
if (neighborView === this.auxiliaryBarPartView) {
1834
return Parts.AUXILIARYBAR_PART;
1835
}
1836
if (neighborView === this.editorPartView) {
1837
return Parts.EDITOR_PART;
1838
}
1839
if (neighborView === this.panelPartView) {
1840
return Parts.PANEL_PART;
1841
}
1842
if (neighborView === this.chatBarPartView) {
1843
return Parts.CHATBAR_PART;
1844
}
1845
1846
return undefined;
1847
}
1848
1849
//#endregion
1850
1851
//#region Restore
1852
1853
isRestored(): boolean {
1854
return this.restored;
1855
}
1856
1857
setRestored(): void {
1858
this.restored = true;
1859
this.restoredPromise.complete();
1860
}
1861
1862
//#endregion
1863
1864
//#region Notifications Registration
1865
1866
registerNotifications(delegate: { onDidChangeNotificationsVisibility: Event<boolean> }): void {
1867
this._register(delegate.onDidChangeNotificationsVisibility(visible => this._onDidChangeNotificationsVisibility.fire(visible)));
1868
}
1869
1870
//#endregion
1871
}
1872
1873