Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/layout.ts
5221 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 { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js';
7
import { Event, Emitter } from '../../base/common/event.js';
8
import { EventType, addDisposableListener, getClientArea, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow, isActiveDocument, getWindow, getWindowId, getActiveElement, Dimension } from '../../base/browser/dom.js';
9
import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from '../../base/browser/browser.js';
10
import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from '../../base/common/platform.js';
11
import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from '../common/editor.js';
12
import { SidebarPart } from './parts/sidebar/sidebarPart.js';
13
import { PanelPart } from './parts/panel/panelPart.js';
14
import { Position, Parts, PartOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, partOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar, isHorizontal, isMultiWindowPart, IPartVisibilityChangeEvent } from '../services/layout/browser/layoutService.js';
15
import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from '../../platform/workspace/common/workspace.js';
16
import { IStorageService, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js';
17
import { IConfigurationChangeEvent, IConfigurationService, isConfigured } from '../../platform/configuration/common/configuration.js';
18
import { ITitleService } from '../services/title/browser/titleService.js';
19
import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
20
import { StartupKind, ILifecycleService } from '../services/lifecycle/common/lifecycle.js';
21
import { getMenuBarVisibility, IPath, hasNativeTitlebar, hasCustomTitlebar, TitleBarSetting, CustomTitleBarVisibility, useWindowControlsOverlay, DEFAULT_EMPTY_WINDOW_SIZE, DEFAULT_WORKSPACE_WINDOW_SIZE, hasNativeMenu, MenuSettings } from '../../platform/window/common/window.js';
22
import { IHostService } from '../services/host/browser/host.js';
23
import { IBrowserWorkbenchEnvironmentService } from '../services/environment/browser/environmentService.js';
24
import { IEditorService } from '../services/editor/common/editorService.js';
25
import { EditorGroupLayout, GroupOrientation, GroupsOrder, IEditorGroupsService } from '../services/editor/common/editorGroupsService.js';
26
import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize, Sizing } from '../../base/browser/ui/grid/grid.js';
27
import { Part } from './part.js';
28
import { IStatusbarService } from '../services/statusbar/browser/statusbar.js';
29
import { IFileService } from '../../platform/files/common/files.js';
30
import { isCodeEditor } from '../../editor/browser/editorBrowser.js';
31
import { coalesce } from '../../base/common/arrays.js';
32
import { assertReturnsDefined } from '../../base/common/types.js';
33
import { INotificationService, NotificationsFilter } from '../../platform/notification/common/notification.js';
34
import { IThemeService } from '../../platform/theme/common/themeService.js';
35
import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from '../common/theme.js';
36
import { LineNumbersType } from '../../editor/common/config/editorOptions.js';
37
import { URI } from '../../base/common/uri.js';
38
import { IViewDescriptorService, ViewContainerLocation } from '../common/views.js';
39
import { DiffEditorInput } from '../common/editor/diffEditorInput.js';
40
import { mark } from '../../base/common/performance.js';
41
import { IExtensionService } from '../services/extensions/common/extensions.js';
42
import { ILogService } from '../../platform/log/common/log.js';
43
import { DeferredPromise, Promises } from '../../base/common/async.js';
44
import { IBannerService } from '../services/banner/browser/bannerService.js';
45
import { IPaneCompositePartService } from '../services/panecomposite/browser/panecomposite.js';
46
import { AuxiliaryBarPart } from './parts/auxiliarybar/auxiliaryBarPart.js';
47
import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
48
import { IAuxiliaryWindowService } from '../services/auxiliaryWindow/browser/auxiliaryWindowService.js';
49
import { CodeWindow, mainWindow } from '../../base/browser/window.js';
50
51
//#region Layout Implementation
52
53
interface ILayoutRuntimeState {
54
activeContainerId: number;
55
mainWindowFullscreen: boolean;
56
readonly maximized: Set<number>;
57
hasFocus: boolean;
58
mainWindowBorder: boolean;
59
readonly menuBar: {
60
toggled: boolean;
61
};
62
readonly zenMode: {
63
readonly transitionDisposables: DisposableMap<string, IDisposable>;
64
};
65
}
66
67
interface IEditorToOpen {
68
readonly editor: IUntypedEditorInput;
69
readonly viewColumn?: number;
70
}
71
72
interface ILayoutInitializationState {
73
readonly views: {
74
readonly defaults: string[] | undefined;
75
readonly containerToRestore: {
76
sideBar?: string;
77
panel?: string;
78
auxiliaryBar?: string;
79
};
80
};
81
readonly editor: {
82
readonly restoreEditors: boolean;
83
readonly editorsToOpen: Promise<IEditorToOpen[]>;
84
};
85
readonly layout?: {
86
readonly editors?: EditorGroupLayout;
87
};
88
}
89
90
interface ILayoutState {
91
readonly runtime: ILayoutRuntimeState;
92
readonly initialization: ILayoutInitializationState;
93
}
94
95
enum LayoutClasses {
96
SIDEBAR_HIDDEN = 'nosidebar',
97
MAIN_EDITOR_AREA_HIDDEN = 'nomaineditorarea',
98
PANEL_HIDDEN = 'nopanel',
99
AUXILIARYBAR_HIDDEN = 'noauxiliarybar',
100
STATUSBAR_HIDDEN = 'nostatusbar',
101
FULLSCREEN = 'fullscreen',
102
MAXIMIZED = 'maximized',
103
WINDOW_BORDER = 'border'
104
}
105
106
interface IPathToOpen extends IPath {
107
readonly viewColumn?: number;
108
}
109
110
interface IInitialEditorsState {
111
readonly filesToOpenOrCreate?: IPathToOpen[];
112
readonly filesToDiff?: IPathToOpen[];
113
readonly filesToMerge?: IPathToOpen[];
114
115
readonly layout?: EditorGroupLayout;
116
}
117
118
const COMMAND_CENTER_SETTINGS = [
119
'chat.agentsControl.enabled',
120
'chat.unifiedAgentsBar.enabled',
121
'workbench.navigationControl.enabled',
122
'workbench.experimental.share.enabled',
123
];
124
125
export const TITLE_BAR_SETTINGS = [
126
LayoutSettings.ACTIVITY_BAR_LOCATION,
127
LayoutSettings.COMMAND_CENTER,
128
...COMMAND_CENTER_SETTINGS,
129
LayoutSettings.EDITOR_ACTIONS_LOCATION,
130
LayoutSettings.LAYOUT_ACTIONS,
131
MenuSettings.MenuBarVisibility,
132
TitleBarSetting.TITLE_BAR_STYLE,
133
TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY,
134
];
135
136
const DEFAULT_EMPTY_WINDOW_DIMENSIONS = new Dimension(DEFAULT_EMPTY_WINDOW_SIZE.width, DEFAULT_EMPTY_WINDOW_SIZE.height);
137
const DEFAULT_WORKSPACE_WINDOW_DIMENSIONS = new Dimension(DEFAULT_WORKSPACE_WINDOW_SIZE.width, DEFAULT_WORKSPACE_WINDOW_SIZE.height);
138
139
export abstract class Layout extends Disposable implements IWorkbenchLayoutService {
140
141
declare readonly _serviceBrand: undefined;
142
143
//#region Events
144
145
private readonly _onDidChangeZenMode = this._register(new Emitter<boolean>());
146
readonly onDidChangeZenMode = this._onDidChangeZenMode.event;
147
148
private readonly _onDidChangeMainEditorCenteredLayout = this._register(new Emitter<boolean>());
149
readonly onDidChangeMainEditorCenteredLayout = this._onDidChangeMainEditorCenteredLayout.event;
150
151
private readonly _onDidChangePanelAlignment = this._register(new Emitter<PanelAlignment>());
152
readonly onDidChangePanelAlignment = this._onDidChangePanelAlignment.event;
153
154
private readonly _onDidChangeWindowMaximized = this._register(new Emitter<{ windowId: number; maximized: boolean }>());
155
readonly onDidChangeWindowMaximized = this._onDidChangeWindowMaximized.event;
156
157
private readonly _onDidChangePanelPosition = this._register(new Emitter<string>());
158
readonly onDidChangePanelPosition = this._onDidChangePanelPosition.event;
159
160
private readonly _onDidChangePartVisibility = this._register(new Emitter<IPartVisibilityChangeEvent>());
161
readonly onDidChangePartVisibility = this._onDidChangePartVisibility.event;
162
163
private readonly _onDidChangeNotificationsVisibility = this._register(new Emitter<boolean>());
164
readonly onDidChangeNotificationsVisibility = this._onDidChangeNotificationsVisibility.event;
165
166
private readonly _onDidChangeAuxiliaryBarMaximized = this._register(new Emitter<void>());
167
readonly onDidChangeAuxiliaryBarMaximized = this._onDidChangeAuxiliaryBarMaximized.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
get activeContainer() { return this.getContainerFromDocument(getActiveDocument()); }
190
get containers(): Iterable<HTMLElement> {
191
const containers: HTMLElement[] = [];
192
for (const { window } of getWindows()) {
193
containers.push(this.getContainerFromDocument(window.document));
194
}
195
196
return containers;
197
}
198
199
private getContainerFromDocument(targetDocument: Document): HTMLElement {
200
if (targetDocument === this.mainContainer.ownerDocument) {
201
return this.mainContainer; // main window
202
} else {
203
// eslint-disable-next-line no-restricted-syntax
204
return targetDocument.body.getElementsByClassName('monaco-workbench')[0] as HTMLElement; // auxiliary window
205
}
206
}
207
208
private readonly containerStylesLoaded = new Map<number /* window ID */, Promise<void>>();
209
whenContainerStylesLoaded(window: CodeWindow): Promise<void> | undefined {
210
return this.containerStylesLoaded.get(window.vscodeWindowId);
211
}
212
213
private _mainContainerDimension!: IDimension;
214
get mainContainerDimension(): IDimension { return this._mainContainerDimension; }
215
216
get activeContainerDimension(): IDimension {
217
return this.getContainerDimension(this.activeContainer);
218
}
219
220
private getContainerDimension(container: HTMLElement): IDimension {
221
if (container === this.mainContainer) {
222
return this.mainContainerDimension; // main window
223
} else {
224
return getClientArea(container); // auxiliary window
225
}
226
}
227
228
get mainContainerOffset() {
229
return this.computeContainerOffset(mainWindow);
230
}
231
232
get activeContainerOffset() {
233
return this.computeContainerOffset(getWindow(this.activeContainer));
234
}
235
236
private computeContainerOffset(targetWindow: Window) {
237
let top = 0;
238
let quickPickTop = 0;
239
240
if (this.isVisible(Parts.BANNER_PART)) {
241
top = this.getPart(Parts.BANNER_PART).maximumHeight;
242
quickPickTop = top;
243
}
244
245
const titlebarVisible = this.isVisible(Parts.TITLEBAR_PART, targetWindow);
246
if (titlebarVisible) {
247
top += this.getPart(Parts.TITLEBAR_PART).maximumHeight;
248
quickPickTop = top;
249
}
250
251
const isCommandCenterVisible = titlebarVisible && this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) !== false;
252
if (isCommandCenterVisible) {
253
// If the command center is visible then the quickinput
254
// should go over the title bar and the banner
255
quickPickTop = 6;
256
}
257
258
return { top, quickPickTop };
259
}
260
261
//#endregion
262
263
private readonly parts = new Map<string, Part>();
264
265
private initialized = false;
266
private workbenchGrid!: SerializableGrid<ISerializableView>;
267
268
private titleBarPartView!: ISerializableView;
269
private bannerPartView!: ISerializableView;
270
private activityBarPartView!: ISerializableView;
271
private sideBarPartView!: ISerializableView;
272
private panelPartView!: ISerializableView;
273
private auxiliaryBarPartView!: ISerializableView;
274
private editorPartView!: ISerializableView;
275
private statusBarPartView!: ISerializableView;
276
277
private environmentService!: IBrowserWorkbenchEnvironmentService;
278
private extensionService!: IExtensionService;
279
private configurationService!: IConfigurationService;
280
private storageService!: IStorageService;
281
private hostService!: IHostService;
282
private editorService!: IEditorService;
283
private mainPartEditorService!: IEditorService;
284
private editorGroupService!: IEditorGroupsService;
285
private paneCompositeService!: IPaneCompositePartService;
286
private titleService!: ITitleService;
287
private viewDescriptorService!: IViewDescriptorService;
288
private contextService!: IWorkspaceContextService;
289
private notificationService!: INotificationService;
290
private themeService!: IThemeService;
291
private statusBarService!: IStatusbarService;
292
private logService!: ILogService;
293
private telemetryService!: ITelemetryService;
294
private auxiliaryWindowService!: IAuxiliaryWindowService;
295
296
private state!: ILayoutState;
297
private stateModel!: LayoutStateModel;
298
299
private disposed = false;
300
301
constructor(
302
protected readonly parent: HTMLElement,
303
private readonly layoutOptions?: { resetLayout: boolean }
304
) {
305
super();
306
}
307
308
protected initLayout(accessor: ServicesAccessor): void {
309
310
// Services
311
this.environmentService = accessor.get(IBrowserWorkbenchEnvironmentService);
312
this.configurationService = accessor.get(IConfigurationService);
313
this.hostService = accessor.get(IHostService);
314
this.contextService = accessor.get(IWorkspaceContextService);
315
this.storageService = accessor.get(IStorageService);
316
this.themeService = accessor.get(IThemeService);
317
this.extensionService = accessor.get(IExtensionService);
318
this.logService = accessor.get(ILogService);
319
this.telemetryService = accessor.get(ITelemetryService);
320
this.auxiliaryWindowService = accessor.get(IAuxiliaryWindowService);
321
322
// Parts
323
this.editorService = accessor.get(IEditorService);
324
this.editorGroupService = accessor.get(IEditorGroupsService);
325
this.mainPartEditorService = this.editorService.createScoped(this.editorGroupService.mainPart, this._store);
326
this.paneCompositeService = accessor.get(IPaneCompositePartService);
327
this.viewDescriptorService = accessor.get(IViewDescriptorService);
328
this.titleService = accessor.get(ITitleService);
329
this.notificationService = accessor.get(INotificationService);
330
this.statusBarService = accessor.get(IStatusbarService);
331
accessor.get(IBannerService);
332
333
// Listeners
334
this.registerLayoutListeners();
335
336
// State
337
this.initLayoutState(accessor.get(ILifecycleService), accessor.get(IFileService));
338
}
339
340
private registerLayoutListeners(): void {
341
342
// Restore editor if hidden and an editor is to show
343
const showEditorIfHidden = () => {
344
if (
345
this.isVisible(Parts.EDITOR_PART, mainWindow) || // already visible
346
this.mainPartEditorService.visibleEditors.length === 0 // no editor to show
347
) {
348
return;
349
}
350
351
if (this.isAuxiliaryBarMaximized()) {
352
this.toggleMaximizedAuxiliaryBar();
353
} else {
354
this.toggleMaximizedPanel();
355
}
356
};
357
358
// Maybe maximize auxiliary bar when no editors are visible
359
const maybeMaximizeAuxiliaryBar = () => {
360
if (
361
this.mainPartEditorService.visibleEditors.length === 0 &&
362
this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED) === true
363
) {
364
this.setAuxiliaryBarMaximized(true);
365
366
return true;
367
}
368
369
return false;
370
};
371
372
// Wait to register these listeners after the editor group service
373
// is ready to avoid conflicts on startup
374
this.editorGroupService.whenRestored.then(() => {
375
376
// Handle visible editors changing for parts visibility
377
this._register(this.mainPartEditorService.onDidVisibleEditorsChange(() => {
378
const handled = maybeMaximizeAuxiliaryBar();
379
if (!handled) {
380
showEditorIfHidden();
381
}
382
}));
383
this._register(this.editorGroupService.mainPart.onDidActivateGroup(showEditorIfHidden));
384
385
// Revalidate center layout when active editor changes: diff editor quits centered mode
386
this._register(this.mainPartEditorService.onDidActiveEditorChange(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED))));
387
});
388
389
// Configuration changes
390
this._register(this.configurationService.onDidChangeConfiguration(e => {
391
392
// Layout related
393
if ([
394
...TITLE_BAR_SETTINGS,
395
LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION,
396
LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE,
397
].some(setting => e.affectsConfiguration(setting))) {
398
399
// Show Command Center if command center actions enabled
400
const enabledCommandCenterAction = COMMAND_CENTER_SETTINGS.some(setting => e.affectsConfiguration(setting) && this.configurationService.getValue<boolean>(setting) === true);
401
402
if (enabledCommandCenterAction) {
403
if (this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) === false) {
404
this.configurationService.updateValue(LayoutSettings.COMMAND_CENTER, true);
405
return; // onDidChangeConfiguration will be triggered again
406
}
407
}
408
409
// Show Custom TitleBar if actions enabled in (or moved to) the titlebar
410
const editorActionsMovedToTitlebar = e.affectsConfiguration(LayoutSettings.EDITOR_ACTIONS_LOCATION) && this.configurationService.getValue<EditorActionsLocation>(LayoutSettings.EDITOR_ACTIONS_LOCATION) === EditorActionsLocation.TITLEBAR;
411
const commandCenterEnabled = e.affectsConfiguration(LayoutSettings.COMMAND_CENTER) && this.configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER);
412
const layoutControlsEnabled = e.affectsConfiguration(LayoutSettings.LAYOUT_ACTIONS) && this.configurationService.getValue<boolean>(LayoutSettings.LAYOUT_ACTIONS);
413
const activityBarMovedToTopOrBottom = e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && [ActivityBarPosition.TOP, ActivityBarPosition.BOTTOM].includes(this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION));
414
415
if (activityBarMovedToTopOrBottom || editorActionsMovedToTitlebar || commandCenterEnabled || layoutControlsEnabled) {
416
if (this.configurationService.getValue<CustomTitleBarVisibility>(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY) === CustomTitleBarVisibility.NEVER) {
417
this.configurationService.updateValue(TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, CustomTitleBarVisibility.AUTO);
418
return; // onDidChangeConfiguration will be triggered again
419
}
420
}
421
422
this.doUpdateLayoutConfiguration();
423
}
424
425
// Auxiliary Sidebar
426
if (e.affectsConfiguration(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED)) {
427
const forceMaximized = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED);
428
if (forceMaximized === true && this.mainPartEditorService.visibleEditors.length === 0) {
429
this.setAuxiliaryBarMaximized(true);
430
} else if (forceMaximized === false && this.isAuxiliaryBarMaximized()) {
431
this.setAuxiliaryBarMaximized(false);
432
}
433
}
434
}));
435
436
// Fullscreen changes
437
this._register(onDidChangeFullscreen(windowId => this.onFullscreenChanged(windowId)));
438
439
// Group changes
440
this._register(this.editorGroupService.mainPart.onDidAddGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED))));
441
this._register(this.editorGroupService.mainPart.onDidRemoveGroup(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED))));
442
this._register(this.editorGroupService.mainPart.onDidChangeGroupMaximized(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED))));
443
444
// Prevent workbench from scrolling #55456
445
this._register(addDisposableListener(this.mainContainer, EventType.SCROLL, () => this.mainContainer.scrollTop = 0));
446
447
// Menubar visibility changes
448
const showingCustomMenu = (isWindows || isLinux || isWeb) && !hasNativeTitlebar(this.configurationService);
449
if (showingCustomMenu) {
450
this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible)));
451
}
452
453
// Theme changes
454
this._register(this.themeService.onDidColorThemeChange(() => this.updateWindowBorder()));
455
456
// Window active / focus changes
457
this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChanged(focused)));
458
this._register(this.hostService.onDidChangeActiveWindow(() => this.onActiveWindowChanged()));
459
460
// WCO changes
461
if (isWeb && typeof (navigator as { windowControlsOverlay?: EventTarget }).windowControlsOverlay === 'object') {
462
this._register(addDisposableListener((navigator as unknown as { windowControlsOverlay: EventTarget }).windowControlsOverlay, 'geometrychange', () => this.onDidChangeWCO()));
463
}
464
465
// Auxiliary windows
466
this._register(this.auxiliaryWindowService.onDidOpenAuxiliaryWindow(({ window, disposables }) => {
467
const windowId = window.window.vscodeWindowId;
468
this.containerStylesLoaded.set(windowId, window.whenStylesHaveLoaded);
469
window.whenStylesHaveLoaded.then(() => this.containerStylesLoaded.delete(windowId));
470
disposables.add(toDisposable(() => this.containerStylesLoaded.delete(windowId)));
471
472
const eventDisposables = disposables.add(new DisposableStore());
473
this._onDidAddContainer.fire({ container: window.container, disposables: eventDisposables });
474
475
disposables.add(window.onDidLayout(dimension => this.handleContainerDidLayout(window.container, dimension)));
476
}));
477
}
478
479
private onMenubarToggled(visible: boolean): void {
480
if (visible !== this.state.runtime.menuBar.toggled) {
481
this.state.runtime.menuBar.toggled = visible;
482
483
const menuBarVisibility = getMenuBarVisibility(this.configurationService);
484
485
// The menu bar toggles the title bar in web because it does not need to be shown for window controls only
486
if (isWeb && menuBarVisibility === 'toggle') {
487
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled));
488
}
489
490
// The menu bar toggles the title bar in full screen for toggle and classic settings
491
else if (this.state.runtime.mainWindowFullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) {
492
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled));
493
}
494
495
// Move layout call to any time the menubar
496
// is toggled to update consumers of offset
497
// see issue #115267
498
this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension);
499
}
500
}
501
502
private handleContainerDidLayout(container: HTMLElement, dimension: IDimension): void {
503
if (container === this.mainContainer) {
504
this._onDidLayoutMainContainer.fire(dimension);
505
}
506
507
if (isActiveDocument(container)) {
508
this._onDidLayoutActiveContainer.fire(dimension);
509
}
510
511
this._onDidLayoutContainer.fire({ container, dimension });
512
}
513
514
private onFullscreenChanged(windowId: number): void {
515
if (windowId !== mainWindow.vscodeWindowId) {
516
return; // ignore all but main window
517
}
518
519
this.state.runtime.mainWindowFullscreen = isFullscreen(mainWindow);
520
521
// Apply as CSS class
522
if (this.state.runtime.mainWindowFullscreen) {
523
this.mainContainer.classList.add(LayoutClasses.FULLSCREEN);
524
} else {
525
this.mainContainer.classList.remove(LayoutClasses.FULLSCREEN);
526
527
const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO);
528
if (zenModeExitInfo.transitionedToFullScreen && this.isZenModeActive()) {
529
this.toggleZenMode();
530
}
531
}
532
533
// Change edge snapping accordingly
534
this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen;
535
536
// Changing fullscreen state of the main window has an impact
537
// on custom title bar visibility, so we need to update
538
if (hasCustomTitlebar(this.configurationService)) {
539
540
// Propagate to grid
541
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled));
542
543
// Indicate active window border
544
this.updateWindowBorder(true);
545
}
546
}
547
548
private onActiveWindowChanged(): void {
549
const activeContainerId = this.getActiveContainerId();
550
if (this.state.runtime.activeContainerId !== activeContainerId) {
551
this.state.runtime.activeContainerId = activeContainerId;
552
553
// Indicate active window border
554
this.updateWindowBorder();
555
556
this._onDidChangeActiveContainer.fire();
557
}
558
}
559
560
private onWindowFocusChanged(hasFocus: boolean): void {
561
if (this.state.runtime.hasFocus !== hasFocus) {
562
this.state.runtime.hasFocus = hasFocus;
563
564
// Indicate active window border
565
this.updateWindowBorder();
566
}
567
}
568
569
private getActiveContainerId(): number {
570
const activeContainer = this.activeContainer;
571
572
return getWindow(activeContainer).vscodeWindowId;
573
}
574
575
private doUpdateLayoutConfiguration(skipLayout?: boolean): void {
576
577
// Custom Titlebar visibility with native titlebar
578
this.updateCustomTitleBarVisibility();
579
580
// Menubar visibility
581
this.updateMenubarVisibility(!!skipLayout);
582
583
// Centered Layout
584
this.editorGroupService.whenRestored.then(() => this.centerMainEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED), skipLayout));
585
}
586
587
private setSideBarPosition(position: Position): void {
588
const activityBar = this.getPart(Parts.ACTIVITYBAR_PART);
589
const sideBar = this.getPart(Parts.SIDEBAR_PART);
590
const auxiliaryBar = this.getPart(Parts.AUXILIARYBAR_PART);
591
const newPositionValue = (position === Position.LEFT) ? 'left' : 'right';
592
const oldPositionValue = (position === Position.RIGHT) ? 'left' : 'right';
593
const panelAlignment = this.getPanelAlignment();
594
const panelPosition = this.getPanelPosition();
595
596
this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON, position);
597
598
// Adjust CSS
599
const activityBarContainer = assertReturnsDefined(activityBar.getContainer());
600
const sideBarContainer = assertReturnsDefined(sideBar.getContainer());
601
const auxiliaryBarContainer = assertReturnsDefined(auxiliaryBar.getContainer());
602
activityBarContainer.classList.remove(oldPositionValue);
603
sideBarContainer.classList.remove(oldPositionValue);
604
activityBarContainer.classList.add(newPositionValue);
605
sideBarContainer.classList.add(newPositionValue);
606
607
// Auxiliary Bar has opposite values
608
auxiliaryBarContainer.classList.remove(newPositionValue);
609
auxiliaryBarContainer.classList.add(oldPositionValue);
610
611
// Update Styles
612
activityBar.updateStyles();
613
sideBar.updateStyles();
614
auxiliaryBar.updateStyles();
615
616
// Move activity bar and side bars
617
this.adjustPartPositions(position, panelAlignment, panelPosition);
618
}
619
620
private updateWindowBorder(skipLayout = false) {
621
if (
622
isWeb ||
623
isWindows || // not working well with zooming (border often not visible)
624
(
625
(isWindows || isLinux) &&
626
useWindowControlsOverlay(this.configurationService) // Windows/Linux: not working with WCO (border cannot draw over the overlay)
627
) ||
628
hasNativeTitlebar(this.configurationService)
629
) {
630
return;
631
}
632
633
const theme = this.themeService.getColorTheme();
634
635
const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER);
636
const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER);
637
638
const didHaveMainWindowBorder = this.hasMainWindowBorder();
639
640
for (const container of this.containers) {
641
const isMainContainer = container === this.mainContainer;
642
const isActiveContainer = this.activeContainer === container;
643
644
let windowBorder = false;
645
if (!this.state.runtime.mainWindowFullscreen && (activeBorder || inactiveBorder)) {
646
windowBorder = true;
647
648
// If the inactive color is missing, fallback to the active one
649
const borderColor = isActiveContainer && this.state.runtime.hasFocus ? activeBorder : inactiveBorder ?? activeBorder;
650
container.style.setProperty('--window-border-color', borderColor?.toString() ?? 'transparent');
651
}
652
653
if (isMainContainer) {
654
this.state.runtime.mainWindowBorder = windowBorder;
655
}
656
657
container.classList.toggle(LayoutClasses.WINDOW_BORDER, windowBorder);
658
}
659
660
if (!skipLayout && didHaveMainWindowBorder !== this.hasMainWindowBorder()) {
661
this.layout();
662
}
663
}
664
665
private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void {
666
this._mainContainerDimension = getClientArea(this.parent, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? DEFAULT_EMPTY_WINDOW_DIMENSIONS : DEFAULT_WORKSPACE_WINDOW_DIMENSIONS); // running with fallback to ensure no error is thrown (https://github.com/microsoft/vscode/issues/240242)
667
668
this.stateModel = new LayoutStateModel(this.storageService, this.configurationService, this.contextService, this.environmentService);
669
this.stateModel.load({
670
mainContainerDimension: this._mainContainerDimension,
671
resetLayout: Boolean(this.layoutOptions?.resetLayout)
672
});
673
674
this._register(this.stateModel.onDidChangeState(change => {
675
if (change.key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) {
676
this.setActivityBarHidden(change.value as boolean);
677
}
678
679
if (change.key === LayoutStateKeys.STATUSBAR_HIDDEN) {
680
this.setStatusBarHidden(change.value as boolean);
681
}
682
683
if (change.key === LayoutStateKeys.SIDEBAR_POSITON) {
684
this.setSideBarPosition(change.value as Position);
685
}
686
687
if (change.key === LayoutStateKeys.PANEL_POSITION) {
688
this.setPanelPosition(change.value as Position);
689
}
690
691
if (change.key === LayoutStateKeys.PANEL_ALIGNMENT) {
692
this.setPanelAlignment(change.value as PanelAlignment);
693
}
694
695
this.doUpdateLayoutConfiguration();
696
}));
697
698
// Layout Initialization State
699
const initialEditorsState = this.getInitialEditorsState();
700
if (initialEditorsState) {
701
this.logService.trace('Initial editor state', initialEditorsState);
702
}
703
const initialLayoutState: ILayoutInitializationState = {
704
layout: {
705
editors: initialEditorsState?.layout
706
},
707
editor: {
708
restoreEditors: this.shouldRestoreEditors(this.contextService, initialEditorsState),
709
editorsToOpen: this.resolveEditorsToOpen(fileService, initialEditorsState),
710
},
711
views: {
712
defaults: this.getDefaultLayoutViews(this.environmentService, this.storageService),
713
containerToRestore: {}
714
}
715
};
716
717
// Layout Runtime State
718
const layoutRuntimeState: ILayoutRuntimeState = {
719
activeContainerId: this.getActiveContainerId(),
720
mainWindowFullscreen: isFullscreen(mainWindow),
721
hasFocus: this.hostService.hasFocus,
722
maximized: new Set<number>(),
723
mainWindowBorder: false,
724
menuBar: {
725
toggled: false,
726
},
727
zenMode: {
728
transitionDisposables: new DisposableMap(),
729
}
730
};
731
732
this.state = {
733
initialization: initialLayoutState,
734
runtime: layoutRuntimeState,
735
};
736
737
// Sidebar View Container To Restore
738
if (this.isVisible(Parts.SIDEBAR_PART)) {
739
let viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id);
740
if (
741
!this.environmentService.isBuilt ||
742
lifecycleService.startupKind === StartupKind.ReloadedWindow ||
743
this.environmentService.isExtensionDevelopment && !this.environmentService.extensionTestsLocationURI
744
) {
745
// allow to restore a non-default viewlet in development mode or when window reloads
746
} else if (
747
viewContainerToRestore !== this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id &&
748
viewContainerToRestore !== this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id
749
) {
750
// fallback to default viewlet otherwise if the viewlet is not a default viewlet
751
viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id;
752
}
753
754
if (viewContainerToRestore) {
755
this.state.initialization.views.containerToRestore.sideBar = viewContainerToRestore;
756
} else {
757
this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, true);
758
}
759
}
760
761
// Panel View Container To Restore
762
if (this.isVisible(Parts.PANEL_PART)) {
763
const viewContainerToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id);
764
765
if (viewContainerToRestore) {
766
this.state.initialization.views.containerToRestore.panel = viewContainerToRestore;
767
} else {
768
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_HIDDEN, true);
769
}
770
}
771
772
// Auxiliary View to restore
773
if (this.isVisible(Parts.AUXILIARYBAR_PART)) {
774
const viewContainerToRestore = this.storageService.get(AuxiliaryBarPart.activeViewSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id);
775
if (viewContainerToRestore) {
776
this.state.initialization.views.containerToRestore.auxiliaryBar = viewContainerToRestore;
777
} else {
778
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, true);
779
}
780
}
781
782
// Window border
783
this.updateWindowBorder(true);
784
}
785
786
private getDefaultLayoutViews(environmentService: IBrowserWorkbenchEnvironmentService, storageService: IStorageService): string[] | undefined {
787
const defaultLayout = environmentService.options?.defaultLayout;
788
if (!defaultLayout) {
789
return undefined;
790
}
791
792
if (!defaultLayout.force && !storageService.isNew(StorageScope.WORKSPACE)) {
793
return undefined;
794
}
795
796
const { views } = defaultLayout;
797
if (views?.length) {
798
return views.map(view => view.id);
799
}
800
801
return undefined;
802
}
803
804
private shouldRestoreEditors(contextService: IWorkspaceContextService, initialEditorsState: IInitialEditorsState | undefined): boolean {
805
806
// Restore editors based on a set of rules:
807
// - never when running on temporary workspace
808
// - never when `workbench.editor.restoreEditors` is disabled
809
// - not when we have files to open, unless:
810
// - always when `window.restoreWindows: preserve`
811
812
if (isTemporaryWorkspace(contextService.getWorkspace())) {
813
return false;
814
}
815
816
if (this.configurationService.getValue<boolean>(WorkbenchLayoutSettings.EDITOR_RESTORE_EDITORS) === false) {
817
return false;
818
}
819
820
const forceRestoreEditors = this.configurationService.getValue<string>('window.restoreWindows') === 'preserve';
821
return !!forceRestoreEditors || initialEditorsState === undefined;
822
}
823
824
protected willRestoreEditors(): boolean {
825
return this.state.initialization.editor.restoreEditors;
826
}
827
828
private async resolveEditorsToOpen(fileService: IFileService, initialEditorsState: IInitialEditorsState | undefined): Promise<IEditorToOpen[]> {
829
if (initialEditorsState) {
830
831
// Merge editor (single)
832
const filesToMerge = coalesce(await pathsToEditors(initialEditorsState.filesToMerge, fileService, this.logService));
833
if (filesToMerge.length === 4 && isResourceEditorInput(filesToMerge[0]) && isResourceEditorInput(filesToMerge[1]) && isResourceEditorInput(filesToMerge[2]) && isResourceEditorInput(filesToMerge[3])) {
834
return [{
835
editor: {
836
input1: { resource: filesToMerge[0].resource },
837
input2: { resource: filesToMerge[1].resource },
838
base: { resource: filesToMerge[2].resource },
839
result: { resource: filesToMerge[3].resource },
840
options: { pinned: true }
841
}
842
}];
843
}
844
845
// Diff editor (single)
846
const filesToDiff = coalesce(await pathsToEditors(initialEditorsState.filesToDiff, fileService, this.logService));
847
if (filesToDiff.length === 2) {
848
return [{
849
editor: {
850
original: { resource: filesToDiff[0].resource },
851
modified: { resource: filesToDiff[1].resource },
852
options: { pinned: true }
853
}
854
}];
855
}
856
857
// Normal editor (multiple)
858
const filesToOpenOrCreate: IEditorToOpen[] = [];
859
const resolvedFilesToOpenOrCreate = await pathsToEditors(initialEditorsState.filesToOpenOrCreate, fileService, this.logService);
860
for (let i = 0; i < resolvedFilesToOpenOrCreate.length; i++) {
861
const resolvedFileToOpenOrCreate = resolvedFilesToOpenOrCreate[i];
862
if (resolvedFileToOpenOrCreate) {
863
filesToOpenOrCreate.push({
864
editor: resolvedFileToOpenOrCreate,
865
viewColumn: initialEditorsState.filesToOpenOrCreate?.[i].viewColumn // take over `viewColumn` from initial state
866
});
867
}
868
}
869
870
return filesToOpenOrCreate;
871
}
872
873
// Empty workbench configured to open untitled file if empty
874
else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') {
875
if (this.editorGroupService.hasRestorableState) {
876
return []; // do not open any empty untitled file if we restored groups/editors from previous session
877
}
878
879
return [{
880
editor: { resource: undefined } // open empty untitled file
881
}];
882
}
883
884
return [];
885
}
886
887
private _openedDefaultEditors: boolean = false;
888
get openedDefaultEditors() { return this._openedDefaultEditors; }
889
890
private getInitialEditorsState(): IInitialEditorsState | undefined {
891
892
// Check for editors / editor layout from `defaultLayout` options first
893
const defaultLayout = this.environmentService.options?.defaultLayout;
894
if ((defaultLayout?.editors?.length || defaultLayout?.layout?.editors) && (defaultLayout.force || this.storageService.isNew(StorageScope.WORKSPACE))) {
895
this._openedDefaultEditors = true;
896
897
return {
898
layout: defaultLayout.layout?.editors,
899
filesToOpenOrCreate: defaultLayout?.editors?.map(editor => {
900
return {
901
viewColumn: editor.viewColumn,
902
fileUri: URI.revive(editor.uri),
903
openOnlyIfExists: editor.openOnlyIfExists,
904
options: editor.options
905
};
906
})
907
};
908
}
909
910
// Then check for files to open, create or diff/merge from main side
911
const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService;
912
if (filesToOpenOrCreate || filesToDiff || filesToMerge) {
913
return { filesToOpenOrCreate, filesToDiff, filesToMerge };
914
}
915
916
return undefined;
917
}
918
919
private readonly whenReadyPromise = new DeferredPromise<void>();
920
protected readonly whenReady = this.whenReadyPromise.p;
921
922
private readonly whenRestoredPromise = new DeferredPromise<void>();
923
readonly whenRestored = this.whenRestoredPromise.p;
924
private restored = false;
925
926
isRestored(): boolean {
927
return this.restored;
928
}
929
930
protected restoreParts(): void {
931
932
// distinguish long running restore operations that
933
// are required for the layout to be ready from those
934
// that are needed to signal restoring is done
935
const layoutReadyPromises: Promise<unknown>[] = [];
936
const layoutRestoredPromises: Promise<unknown>[] = [];
937
938
// Restore editors
939
layoutReadyPromises.push((async () => {
940
mark('code/willRestoreEditors');
941
942
// first ensure the editor part is ready
943
await this.editorGroupService.whenReady;
944
mark('code/restoreEditors/editorGroupsReady');
945
946
// apply editor layout if any
947
if (this.state.initialization.layout?.editors) {
948
this.editorGroupService.mainPart.applyLayout(this.state.initialization.layout.editors);
949
}
950
951
// then see for editors to open as instructed
952
// it is important that we trigger this from
953
// the overall restore flow to reduce possible
954
// flicker on startup: we want any editor to
955
// open to get a chance to open first before
956
// signaling that layout is restored, but we do
957
// not need to await the editors from having
958
// fully loaded.
959
960
const editors = await this.state.initialization.editor.editorsToOpen;
961
mark('code/restoreEditors/editorsToOpenResolved');
962
963
let openEditorsPromise: Promise<unknown> | undefined = undefined;
964
if (editors.length) {
965
966
// we have to map editors to their groups as instructed
967
// by the input. this is important to ensure that we open
968
// the editors in the groups they belong to.
969
970
const editorGroupsInVisualOrder = this.editorGroupService.mainPart.getGroups(GroupsOrder.GRID_APPEARANCE);
971
const mapEditorsToGroup = new Map<GroupIdentifier, Set<IUntypedEditorInput>>();
972
973
for (const editor of editors) {
974
const group = editorGroupsInVisualOrder[(editor.viewColumn ?? 1) - 1]; // viewColumn is index+1 based
975
976
let editorsByGroup = mapEditorsToGroup.get(group.id);
977
if (!editorsByGroup) {
978
editorsByGroup = new Set<IUntypedEditorInput>();
979
mapEditorsToGroup.set(group.id, editorsByGroup);
980
}
981
982
editorsByGroup.add(editor.editor);
983
}
984
985
openEditorsPromise = Promise.all(Array.from(mapEditorsToGroup).map(async ([groupId, editors]) => {
986
try {
987
await this.editorService.openEditors(Array.from(editors), groupId, { validateTrust: true });
988
} catch (error) {
989
this.logService.error(error);
990
}
991
}));
992
}
993
994
// do not block the overall layout ready flow from potentially
995
// slow editors to resolve on startup
996
layoutRestoredPromises.push(
997
Promise.all([
998
openEditorsPromise?.finally(() => mark('code/restoreEditors/editorsOpened')),
999
this.editorGroupService.whenRestored.finally(() => mark('code/restoreEditors/editorGroupsRestored'))
1000
]).finally(() => {
1001
// the `code/didRestoreEditors` perf mark is specifically
1002
// for when visible editors have resolved, so we only mark
1003
// if when editor group service has restored.
1004
mark('code/didRestoreEditors');
1005
})
1006
);
1007
})());
1008
1009
// Restore default views (only when `IDefaultLayout` is provided)
1010
const restoreDefaultViewsPromise = (async () => {
1011
if (this.state.initialization.views.defaults?.length) {
1012
mark('code/willOpenDefaultViews');
1013
1014
const locationsRestored: { id: string; order: number }[] = [];
1015
1016
const tryOpenView = (view: { id: string; order: number }): boolean => {
1017
const location = this.viewDescriptorService.getViewLocationById(view.id);
1018
if (location !== null) {
1019
const container = this.viewDescriptorService.getViewContainerByViewId(view.id);
1020
if (container) {
1021
if (view.order >= (locationsRestored?.[location]?.order ?? 0)) {
1022
locationsRestored[location] = { id: container.id, order: view.order };
1023
}
1024
1025
const containerModel = this.viewDescriptorService.getViewContainerModel(container);
1026
containerModel.setCollapsed(view.id, false);
1027
containerModel.setVisible(view.id, true);
1028
1029
return true;
1030
}
1031
}
1032
1033
return false;
1034
};
1035
1036
const defaultViews = [...this.state.initialization.views.defaults].reverse().map((v, index) => ({ id: v, order: index }));
1037
1038
let i = defaultViews.length;
1039
while (i) {
1040
i--;
1041
if (tryOpenView(defaultViews[i])) {
1042
defaultViews.splice(i, 1);
1043
}
1044
}
1045
1046
// If we still have views left over, wait until all extensions have been registered and try again
1047
if (defaultViews.length) {
1048
await this.extensionService.whenInstalledExtensionsRegistered();
1049
1050
let i = defaultViews.length;
1051
while (i) {
1052
i--;
1053
if (tryOpenView(defaultViews[i])) {
1054
defaultViews.splice(i, 1);
1055
}
1056
}
1057
}
1058
1059
// If we opened a view in the sidebar, stop any restore there
1060
if (locationsRestored[ViewContainerLocation.Sidebar]) {
1061
this.state.initialization.views.containerToRestore.sideBar = locationsRestored[ViewContainerLocation.Sidebar].id;
1062
}
1063
1064
// If we opened a view in the panel, stop any restore there
1065
if (locationsRestored[ViewContainerLocation.Panel]) {
1066
this.state.initialization.views.containerToRestore.panel = locationsRestored[ViewContainerLocation.Panel].id;
1067
}
1068
1069
// If we opened a view in the auxiliary bar, stop any restore there
1070
if (locationsRestored[ViewContainerLocation.AuxiliaryBar]) {
1071
this.state.initialization.views.containerToRestore.auxiliaryBar = locationsRestored[ViewContainerLocation.AuxiliaryBar].id;
1072
}
1073
1074
mark('code/didOpenDefaultViews');
1075
}
1076
})();
1077
layoutReadyPromises.push(restoreDefaultViewsPromise);
1078
1079
// Restore Sidebar
1080
layoutReadyPromises.push((async () => {
1081
1082
// Restoring views could mean that sidebar already
1083
// restored, as such we need to test again
1084
await restoreDefaultViewsPromise;
1085
if (!this.state.initialization.views.containerToRestore.sideBar) {
1086
return;
1087
}
1088
1089
mark('code/willRestoreViewlet');
1090
1091
await this.openViewContainer(ViewContainerLocation.Sidebar, this.state.initialization.views.containerToRestore.sideBar);
1092
1093
mark('code/didRestoreViewlet');
1094
})());
1095
1096
// Restore Panel
1097
layoutReadyPromises.push((async () => {
1098
1099
// Restoring views could mean that panel already
1100
// restored, as such we need to test again
1101
await restoreDefaultViewsPromise;
1102
if (!this.state.initialization.views.containerToRestore.panel) {
1103
return;
1104
}
1105
1106
mark('code/willRestorePanel');
1107
1108
await this.openViewContainer(ViewContainerLocation.Panel, this.state.initialization.views.containerToRestore.panel);
1109
1110
mark('code/didRestorePanel');
1111
})());
1112
1113
// Restore Auxiliary Bar
1114
layoutReadyPromises.push((async () => {
1115
1116
// Restoring views could mean that auxbar already
1117
// restored, as such we need to test again
1118
await restoreDefaultViewsPromise;
1119
if (!this.state.initialization.views.containerToRestore.auxiliaryBar) {
1120
return;
1121
}
1122
1123
mark('code/willRestoreAuxiliaryBar');
1124
1125
await this.openViewContainer(ViewContainerLocation.AuxiliaryBar, this.state.initialization.views.containerToRestore.auxiliaryBar);
1126
1127
mark('code/didRestoreAuxiliaryBar');
1128
})());
1129
1130
// Restore Zen Mode
1131
const zenModeWasActive = this.isZenModeActive();
1132
const restoreZenMode = getZenModeConfiguration(this.configurationService).restore;
1133
1134
if (zenModeWasActive) {
1135
this.setZenModeActive(!restoreZenMode);
1136
this.toggleZenMode(false, true);
1137
}
1138
1139
// Restore Main Editor Center Mode
1140
if (this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED)) {
1141
this.centerMainEditorLayout(true, true);
1142
}
1143
1144
// Await for promises that we recorded to update
1145
// our ready and restored states properly.
1146
Promises.settled(layoutReadyPromises).finally(() => {
1147
1148
// Focus the active maximized part in case we have
1149
// not yet focused a specific element and panel
1150
// or auxiliary bar are maximized.
1151
if (getActiveElement() === mainWindow.document.body && (this.isPanelMaximized() || this.isAuxiliaryBarMaximized())) {
1152
this.focus();
1153
}
1154
1155
this.whenReadyPromise.complete();
1156
1157
Promises.settled(layoutRestoredPromises).finally(() => {
1158
if (
1159
this.editorService.editors.length === 0 && // no editors opened or restored
1160
this.isVisible(Parts.AUXILIARYBAR_PART) && // auxiliary bar is visible
1161
!this.hasFocus(Parts.AUXILIARYBAR_PART) && // auxiliary bar does not have focus yet
1162
!this.environmentService.enableSmokeTestDriver // not in smoke test mode (where focus is sensitive)
1163
) {
1164
this.focusPart(Parts.AUXILIARYBAR_PART);
1165
}
1166
1167
this.restored = true;
1168
this.whenRestoredPromise.complete();
1169
});
1170
});
1171
}
1172
1173
private async openViewContainer(location: ViewContainerLocation, id: string, focus?: boolean): Promise<void> {
1174
let viewContainer = await this.paneCompositeService.openPaneComposite(id, location, focus);
1175
if (viewContainer) {
1176
return;
1177
}
1178
1179
// fallback to default view container
1180
viewContainer = await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(location)?.id, location, focus);
1181
if (viewContainer) {
1182
return;
1183
}
1184
1185
// finally try to just open the first visible view container
1186
await this.paneCompositeService.openPaneComposite(this.paneCompositeService.getVisiblePaneCompositeIds(location).at(0), location, focus);
1187
}
1188
1189
registerPart(part: Part): IDisposable {
1190
const id = part.getId();
1191
this.parts.set(id, part);
1192
1193
return toDisposable(() => this.parts.delete(id));
1194
}
1195
1196
protected getPart(key: Parts): Part {
1197
const part = this.parts.get(key);
1198
if (!part) {
1199
throw new Error(`Unknown part ${key}`);
1200
}
1201
1202
return part;
1203
}
1204
1205
registerNotifications(delegate: { onDidChangeNotificationsVisibility: Event<boolean> }): void {
1206
this._register(delegate.onDidChangeNotificationsVisibility(visible => this._onDidChangeNotificationsVisibility.fire(visible)));
1207
}
1208
1209
hasFocus(part: Parts): boolean {
1210
const container = this.getContainer(getActiveWindow(), part);
1211
if (!container) {
1212
return false;
1213
}
1214
1215
const activeElement = getActiveElement();
1216
if (!activeElement) {
1217
return false;
1218
}
1219
1220
return isAncestorUsingFlowTo(activeElement, container);
1221
}
1222
1223
private _getFocusedPart(): Parts | undefined {
1224
for (const part of this.parts.keys()) {
1225
if (this.hasFocus(part as Parts)) {
1226
return part as Parts;
1227
}
1228
}
1229
1230
return undefined;
1231
}
1232
1233
focusPart(part: MULTI_WINDOW_PARTS, targetWindow: Window): void;
1234
focusPart(part: SINGLE_WINDOW_PARTS): void;
1235
focusPart(part: Parts, targetWindow: Window = mainWindow): void {
1236
const container = this.getContainer(targetWindow, part) ?? this.mainContainer;
1237
1238
switch (part) {
1239
case Parts.EDITOR_PART:
1240
this.editorGroupService.getPart(container).activeGroup.focus();
1241
break;
1242
case Parts.PANEL_PART: {
1243
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)?.focus();
1244
break;
1245
}
1246
case Parts.SIDEBAR_PART: {
1247
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)?.focus();
1248
break;
1249
}
1250
case Parts.AUXILIARYBAR_PART: {
1251
this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)?.focus();
1252
break;
1253
}
1254
case Parts.ACTIVITYBAR_PART:
1255
(this.getPart(Parts.SIDEBAR_PART) as SidebarPart).focusActivityBar();
1256
break;
1257
case Parts.STATUSBAR_PART:
1258
this.statusBarService.getPart(container).focus();
1259
break;
1260
default: {
1261
container?.focus();
1262
}
1263
}
1264
}
1265
1266
getContainer(targetWindow: Window): HTMLElement;
1267
getContainer(targetWindow: Window, part: Parts): HTMLElement | undefined;
1268
getContainer(targetWindow: Window, part?: Parts): HTMLElement | undefined {
1269
if (typeof part === 'undefined') {
1270
return this.getContainerFromDocument(targetWindow.document);
1271
}
1272
1273
if (targetWindow === mainWindow) {
1274
return this.getPart(part).getContainer();
1275
}
1276
1277
// Only some parts are supported for auxiliary windows
1278
let partCandidate: unknown;
1279
if (part === Parts.EDITOR_PART) {
1280
partCandidate = this.editorGroupService.getPart(this.getContainerFromDocument(targetWindow.document));
1281
} else if (part === Parts.STATUSBAR_PART) {
1282
partCandidate = this.statusBarService.getPart(this.getContainerFromDocument(targetWindow.document));
1283
} else if (part === Parts.TITLEBAR_PART) {
1284
partCandidate = this.titleService.getPart(this.getContainerFromDocument(targetWindow.document));
1285
}
1286
1287
if (partCandidate instanceof Part) {
1288
return partCandidate.getContainer();
1289
}
1290
1291
return undefined;
1292
}
1293
1294
isVisible(part: MULTI_WINDOW_PARTS, targetWindow: Window): boolean;
1295
isVisible(part: SINGLE_WINDOW_PARTS): boolean;
1296
isVisible(part: Parts, targetWindow?: Window): boolean;
1297
isVisible(part: Parts, targetWindow: Window = mainWindow): boolean {
1298
if (targetWindow !== mainWindow && part === Parts.EDITOR_PART) {
1299
return true; // cannot hide editor part in auxiliary windows
1300
}
1301
1302
switch (part) {
1303
case Parts.TITLEBAR_PART:
1304
return this.initialized ?
1305
this.workbenchGrid.isViewVisible(this.titleBarPartView) :
1306
shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled);
1307
case Parts.SIDEBAR_PART:
1308
return !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN);
1309
case Parts.PANEL_PART:
1310
return !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN);
1311
case Parts.AUXILIARYBAR_PART:
1312
return !this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN);
1313
case Parts.STATUSBAR_PART:
1314
return !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN);
1315
case Parts.ACTIVITYBAR_PART:
1316
return !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN);
1317
case Parts.EDITOR_PART:
1318
return !this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN);
1319
case Parts.BANNER_PART:
1320
return this.initialized ? this.workbenchGrid.isViewVisible(this.bannerPartView) : false;
1321
default:
1322
return false; // any other part cannot be hidden
1323
}
1324
}
1325
1326
private shouldShowBannerFirst(): boolean {
1327
return isWeb && !isWCOEnabled();
1328
}
1329
1330
focus(): void {
1331
if (this.isPanelMaximized() && this.mainContainer === this.activeContainer) {
1332
this.focusPart(Parts.PANEL_PART);
1333
} else if (this.isAuxiliaryBarMaximized() && this.mainContainer === this.activeContainer) {
1334
this.focusPart(Parts.AUXILIARYBAR_PART);
1335
} else {
1336
this.focusPart(Parts.EDITOR_PART, getWindow(this.activeContainer));
1337
}
1338
}
1339
1340
private focusPanelOrEditor(): void {
1341
const activePanel = this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel);
1342
if ((this.hasFocus(Parts.PANEL_PART) || !this.isVisible(Parts.EDITOR_PART)) && activePanel) {
1343
activePanel.focus(); // prefer panel if it has focus or editor is hidden
1344
} else {
1345
this.focus(); // otherwise focus editor
1346
}
1347
}
1348
1349
getMaximumEditorDimensions(container: HTMLElement): IDimension {
1350
const targetWindow = getWindow(container);
1351
const containerDimension = this.getContainerDimension(container);
1352
1353
if (container === this.mainContainer) {
1354
const isPanelHorizontal = isHorizontal(this.getPanelPosition());
1355
const takenWidth =
1356
(this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) +
1357
(this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) +
1358
(this.isVisible(Parts.PANEL_PART) && !isPanelHorizontal ? this.panelPartView.minimumWidth : 0) +
1359
(this.isVisible(Parts.AUXILIARYBAR_PART) ? this.auxiliaryBarPartView.minimumWidth : 0);
1360
1361
const takenHeight =
1362
(this.isVisible(Parts.TITLEBAR_PART, targetWindow) ? this.titleBarPartView.minimumHeight : 0) +
1363
(this.isVisible(Parts.STATUSBAR_PART, targetWindow) ? this.statusBarPartView.minimumHeight : 0) +
1364
(this.isVisible(Parts.PANEL_PART) && isPanelHorizontal ? this.panelPartView.minimumHeight : 0);
1365
1366
const availableWidth = containerDimension.width - takenWidth;
1367
const availableHeight = containerDimension.height - takenHeight;
1368
1369
return { width: availableWidth, height: availableHeight };
1370
} else {
1371
const takenHeight =
1372
(this.isVisible(Parts.TITLEBAR_PART, targetWindow) ? this.titleBarPartView.minimumHeight : 0) +
1373
(this.isVisible(Parts.STATUSBAR_PART, targetWindow) ? this.statusBarPartView.minimumHeight : 0);
1374
1375
return { width: containerDimension.width, height: containerDimension.height - takenHeight };
1376
}
1377
}
1378
1379
private isZenModeActive(): boolean {
1380
return this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE);
1381
}
1382
1383
private setZenModeActive(active: boolean) {
1384
this.stateModel.setRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE, active);
1385
}
1386
1387
toggleZenMode(skipLayout?: boolean, restoring = false): void {
1388
const focusedPartPreTransition = this._getFocusedPart();
1389
1390
this.setZenModeActive(!this.isZenModeActive());
1391
this.state.runtime.zenMode.transitionDisposables.clearAndDisposeAll();
1392
1393
const setLineNumbers = (lineNumbers?: LineNumbersType) => {
1394
for (const editor of this.mainPartEditorService.visibleTextEditorControls) {
1395
1396
// To properly reset line numbers we need to read the configuration for each editor respecting it's uri.
1397
if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) {
1398
const model = editor.getModel();
1399
lineNumbers = this.configurationService.getValue('editor.lineNumbers', { resource: model.uri, overrideIdentifier: model.getLanguageId() });
1400
}
1401
if (!lineNumbers) {
1402
lineNumbers = this.configurationService.getValue('editor.lineNumbers');
1403
}
1404
1405
editor.updateOptions({ lineNumbers });
1406
}
1407
};
1408
1409
// Check if zen mode transitioned to full screen and if now we are out of zen mode
1410
// -> we need to go out of full screen (same goes for the centered editor layout)
1411
let toggleMainWindowFullScreen = false;
1412
const config = getZenModeConfiguration(this.configurationService);
1413
const zenModeExitInfo = this.stateModel.getRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO);
1414
1415
// Zen Mode Active
1416
if (this.isZenModeActive()) {
1417
1418
toggleMainWindowFullScreen = !this.state.runtime.mainWindowFullscreen && config.fullScreen && !isIOS;
1419
1420
if (!restoring) {
1421
zenModeExitInfo.transitionedToFullScreen = toggleMainWindowFullScreen;
1422
zenModeExitInfo.transitionedToCenteredEditorLayout = !this.isMainEditorLayoutCentered() && config.centerLayout;
1423
zenModeExitInfo.handleNotificationsDoNotDisturbMode = this.notificationService.getFilter() === NotificationsFilter.OFF;
1424
zenModeExitInfo.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART);
1425
zenModeExitInfo.wasVisible.panel = this.isVisible(Parts.PANEL_PART);
1426
zenModeExitInfo.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART);
1427
this.stateModel.setRuntimeValue(LayoutStateKeys.ZEN_MODE_EXIT_INFO, zenModeExitInfo);
1428
}
1429
1430
this.setPanelHidden(true, true);
1431
this.setAuxiliaryBarHidden(true, true);
1432
this.setSideBarHidden(true);
1433
1434
if (config.hideActivityBar) {
1435
this.setActivityBarHidden(true);
1436
}
1437
1438
if (config.hideStatusBar) {
1439
this.setStatusBarHidden(true);
1440
}
1441
1442
if (config.hideLineNumbers) {
1443
setLineNumbers('off');
1444
this.state.runtime.zenMode.transitionDisposables.set(ZenModeSettings.HIDE_LINENUMBERS, this.mainPartEditorService.onDidVisibleEditorsChange(() => setLineNumbers('off')));
1445
}
1446
1447
if (config.showTabs !== this.editorGroupService.partOptions.showTabs) {
1448
this.state.runtime.zenMode.transitionDisposables.set(ZenModeSettings.SHOW_TABS, this.editorGroupService.mainPart.enforcePartOptions({ showTabs: config.showTabs }));
1449
}
1450
1451
if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) {
1452
this.notificationService.setFilter(NotificationsFilter.ERROR);
1453
}
1454
1455
if (config.centerLayout) {
1456
this.centerMainEditorLayout(true, true);
1457
}
1458
1459
// Zen Mode Configuration Changes
1460
this.state.runtime.zenMode.transitionDisposables.set('configurationChange', this.configurationService.onDidChangeConfiguration(e => {
1461
1462
// Activity Bar
1463
if (e.affectsConfiguration(ZenModeSettings.HIDE_ACTIVITYBAR) || e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {
1464
const zenModeHideActivityBar = this.configurationService.getValue<boolean>(ZenModeSettings.HIDE_ACTIVITYBAR);
1465
const activityBarLocation = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);
1466
this.setActivityBarHidden(zenModeHideActivityBar ? true : (activityBarLocation === ActivityBarPosition.TOP || activityBarLocation === ActivityBarPosition.BOTTOM));
1467
}
1468
1469
// Status Bar
1470
if (e.affectsConfiguration(ZenModeSettings.HIDE_STATUSBAR)) {
1471
const zenModeHideStatusBar = this.configurationService.getValue<boolean>(ZenModeSettings.HIDE_STATUSBAR);
1472
this.setStatusBarHidden(zenModeHideStatusBar);
1473
}
1474
1475
// Center Layout
1476
if (e.affectsConfiguration(ZenModeSettings.CENTER_LAYOUT)) {
1477
const zenModeCenterLayout = this.configurationService.getValue<boolean>(ZenModeSettings.CENTER_LAYOUT);
1478
this.centerMainEditorLayout(zenModeCenterLayout, true);
1479
}
1480
1481
// Show Tabs
1482
if (e.affectsConfiguration(ZenModeSettings.SHOW_TABS)) {
1483
const zenModeShowTabs = this.configurationService.getValue<EditorTabsMode | undefined>(ZenModeSettings.SHOW_TABS) ?? 'multiple';
1484
this.state.runtime.zenMode.transitionDisposables.set(ZenModeSettings.SHOW_TABS, this.editorGroupService.mainPart.enforcePartOptions({ showTabs: zenModeShowTabs }));
1485
}
1486
1487
// Notifications
1488
if (e.affectsConfiguration(ZenModeSettings.SILENT_NOTIFICATIONS)) {
1489
const zenModeSilentNotifications = !!this.configurationService.getValue(ZenModeSettings.SILENT_NOTIFICATIONS);
1490
if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) {
1491
this.notificationService.setFilter(zenModeSilentNotifications ? NotificationsFilter.ERROR : NotificationsFilter.OFF);
1492
}
1493
}
1494
1495
// Center Layout
1496
if (e.affectsConfiguration(ZenModeSettings.HIDE_LINENUMBERS)) {
1497
const lineNumbersType = this.configurationService.getValue<boolean>(ZenModeSettings.HIDE_LINENUMBERS) ? 'off' : undefined;
1498
setLineNumbers(lineNumbersType);
1499
this.state.runtime.zenMode.transitionDisposables.set(ZenModeSettings.HIDE_LINENUMBERS, this.mainPartEditorService.onDidVisibleEditorsChange(() => setLineNumbers(lineNumbersType)));
1500
}
1501
}));
1502
}
1503
1504
// Zen Mode Inactive
1505
else {
1506
if (zenModeExitInfo.wasVisible.panel) {
1507
this.setPanelHidden(false, true);
1508
}
1509
1510
if (zenModeExitInfo.wasVisible.auxiliaryBar) {
1511
this.setAuxiliaryBarHidden(false, true);
1512
}
1513
1514
if (zenModeExitInfo.wasVisible.sideBar) {
1515
this.setSideBarHidden(false);
1516
}
1517
1518
if (!this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, true)) {
1519
this.setActivityBarHidden(false);
1520
}
1521
1522
if (!this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN, true)) {
1523
this.setStatusBarHidden(false);
1524
}
1525
1526
if (zenModeExitInfo.transitionedToCenteredEditorLayout) {
1527
this.centerMainEditorLayout(false, true);
1528
}
1529
1530
if (zenModeExitInfo.handleNotificationsDoNotDisturbMode) {
1531
this.notificationService.setFilter(NotificationsFilter.OFF);
1532
}
1533
1534
setLineNumbers();
1535
1536
toggleMainWindowFullScreen = zenModeExitInfo.transitionedToFullScreen && this.state.runtime.mainWindowFullscreen;
1537
}
1538
1539
if (!skipLayout) {
1540
this.layout();
1541
}
1542
1543
if (toggleMainWindowFullScreen) {
1544
this.hostService.toggleFullScreen(mainWindow);
1545
}
1546
1547
// restore focus if part is still visible, otherwise fallback to editor
1548
if (focusedPartPreTransition && this.isVisible(focusedPartPreTransition, getWindow(this.activeContainer))) {
1549
if (isMultiWindowPart(focusedPartPreTransition)) {
1550
this.focusPart(focusedPartPreTransition, getWindow(this.activeContainer));
1551
} else {
1552
this.focusPart(focusedPartPreTransition);
1553
}
1554
} else {
1555
this.focus();
1556
}
1557
1558
// Event
1559
this._onDidChangeZenMode.fire(this.isZenModeActive());
1560
}
1561
1562
private setStatusBarHidden(hidden: boolean): void {
1563
this.stateModel.setRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN, hidden);
1564
1565
// Adjust CSS
1566
if (hidden) {
1567
this.mainContainer.classList.add(LayoutClasses.STATUSBAR_HIDDEN);
1568
} else {
1569
this.mainContainer.classList.remove(LayoutClasses.STATUSBAR_HIDDEN);
1570
}
1571
1572
// Propagate to grid
1573
this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden);
1574
}
1575
1576
protected createWorkbenchLayout(): void {
1577
const titleBar = this.getPart(Parts.TITLEBAR_PART);
1578
const bannerPart = this.getPart(Parts.BANNER_PART);
1579
const editorPart = this.getPart(Parts.EDITOR_PART);
1580
const activityBar = this.getPart(Parts.ACTIVITYBAR_PART);
1581
const panelPart = this.getPart(Parts.PANEL_PART);
1582
const auxiliaryBarPart = this.getPart(Parts.AUXILIARYBAR_PART);
1583
const sideBar = this.getPart(Parts.SIDEBAR_PART);
1584
const statusBar = this.getPart(Parts.STATUSBAR_PART);
1585
1586
// View references for all parts
1587
this.titleBarPartView = titleBar;
1588
this.bannerPartView = bannerPart;
1589
this.sideBarPartView = sideBar;
1590
this.activityBarPartView = activityBar;
1591
this.editorPartView = editorPart;
1592
this.panelPartView = panelPart;
1593
this.auxiliaryBarPartView = auxiliaryBarPart;
1594
this.statusBarPartView = statusBar;
1595
1596
const viewMap = {
1597
[Parts.ACTIVITYBAR_PART]: this.activityBarPartView,
1598
[Parts.BANNER_PART]: this.bannerPartView,
1599
[Parts.TITLEBAR_PART]: this.titleBarPartView,
1600
[Parts.EDITOR_PART]: this.editorPartView,
1601
[Parts.PANEL_PART]: this.panelPartView,
1602
[Parts.SIDEBAR_PART]: this.sideBarPartView,
1603
[Parts.STATUSBAR_PART]: this.statusBarPartView,
1604
[Parts.AUXILIARYBAR_PART]: this.auxiliaryBarPartView
1605
};
1606
1607
const fromJSON = ({ type }: { type: Parts }) => viewMap[type];
1608
const workbenchGrid = SerializableGrid.deserialize(
1609
this.createGridDescriptor(),
1610
{ fromJSON },
1611
{ proportionalLayout: false }
1612
);
1613
1614
this.mainContainer.prepend(workbenchGrid.element);
1615
this.mainContainer.setAttribute('role', 'application');
1616
this.workbenchGrid = workbenchGrid;
1617
this.workbenchGrid.edgeSnapping = this.state.runtime.mainWindowFullscreen;
1618
1619
for (const part of [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar, auxiliaryBarPart, bannerPart]) {
1620
this._register(part.onDidVisibilityChange(visible => {
1621
if (!this.inMaximizedAuxiliaryBarTransition) {
1622
1623
// skip reacting when we are transitioning
1624
// in or out of maximised auxiliary bar to prevent
1625
// stepping on each other toes because this
1626
// transition is already dealing with all parts
1627
// visibility efficiently.
1628
1629
if (part === sideBar) {
1630
this.setSideBarHidden(!visible);
1631
} else if (part === panelPart && this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) === visible) {
1632
this.setPanelHidden(!visible, true);
1633
} else if (part === auxiliaryBarPart) {
1634
this.setAuxiliaryBarHidden(!visible, true);
1635
} else if (part === editorPart) {
1636
this.setEditorHidden(!visible);
1637
}
1638
}
1639
1640
this._onDidChangePartVisibility.fire({ partId: part.getId(), visible });
1641
this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension);
1642
}));
1643
}
1644
1645
this._register(this.storageService.onWillSaveState(() => {
1646
1647
// Side Bar Size
1648
const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN)
1649
? this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView)
1650
: this.workbenchGrid.getViewSize(this.sideBarPartView).width;
1651
this.stateModel.setInitializationValue(LayoutStateKeys.SIDEBAR_SIZE, sideBarSize as number);
1652
1653
// Panel Size
1654
const panelSize = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN)
1655
? this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView)
1656
: isHorizontal(this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION))
1657
? this.workbenchGrid.getViewSize(this.panelPartView).height
1658
: this.workbenchGrid.getViewSize(this.panelPartView).width;
1659
this.stateModel.setInitializationValue(LayoutStateKeys.PANEL_SIZE, panelSize as number);
1660
1661
// Auxiliary Bar Size
1662
const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN)
1663
? this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView)
1664
: this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width;
1665
this.stateModel.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE, auxiliaryBarSize as number);
1666
1667
this.stateModel.save(true, true);
1668
}));
1669
1670
this._register(Event.any(this.paneCompositeService.onDidPaneCompositeOpen, this.paneCompositeService.onDidPaneCompositeClose)(() => {
1671
1672
// Auxiliary Bar State
1673
this.stateModel.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_EMPTY, this.paneCompositeService.getPaneCompositeIds(ViewContainerLocation.AuxiliaryBar).length === 0);
1674
}));
1675
}
1676
1677
layout(): void {
1678
if (!this.disposed) {
1679
this._mainContainerDimension = getClientArea(this.state.runtime.mainWindowFullscreen ?
1680
mainWindow.document.body : // in fullscreen mode, make sure to use <body> element because
1681
this.parent, // in that case the workbench will span the entire site
1682
this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? DEFAULT_EMPTY_WINDOW_DIMENSIONS : DEFAULT_WORKSPACE_WINDOW_DIMENSIONS // running with fallback to ensure no error is thrown (https://github.com/microsoft/vscode/issues/240242)
1683
);
1684
this.logService.trace(`Layout#layout, height: ${this._mainContainerDimension.height}, width: ${this._mainContainerDimension.width}`);
1685
1686
size(this.mainContainer, this._mainContainerDimension.width, this._mainContainerDimension.height);
1687
1688
// Layout the grid widget
1689
this.workbenchGrid.layout(this._mainContainerDimension.width, this._mainContainerDimension.height);
1690
this.initialized = true;
1691
1692
// Emit as event
1693
this.handleContainerDidLayout(this.mainContainer, this._mainContainerDimension);
1694
}
1695
}
1696
1697
isMainEditorLayoutCentered(): boolean {
1698
return this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED);
1699
}
1700
1701
centerMainEditorLayout(active: boolean, skipLayout?: boolean): void {
1702
this.stateModel.setRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED, active);
1703
1704
const mainVisibleEditors = coalesce(this.editorGroupService.mainPart.groups.map(group => group.activeEditor));
1705
const isEditorComplex = mainVisibleEditors.some(editor => {
1706
if (editor instanceof DiffEditorInput) {
1707
return this.configurationService.getValue('diffEditor.renderSideBySide');
1708
}
1709
1710
if (editor?.hasCapability(EditorInputCapabilities.MultipleEditors)) {
1711
return true;
1712
}
1713
1714
return false;
1715
});
1716
1717
const layout = this.editorGroupService.getLayout();
1718
let hasMoreThanOneColumn = false;
1719
if (layout.orientation === GroupOrientation.HORIZONTAL) {
1720
hasMoreThanOneColumn = layout.groups.length > 1;
1721
} else {
1722
hasMoreThanOneColumn = layout.groups.some(group => group.groups && group.groups.length > 1);
1723
}
1724
1725
const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize');
1726
if (
1727
isCenteredLayoutAutoResizing &&
1728
((hasMoreThanOneColumn && !this.editorGroupService.mainPart.hasMaximizedGroup()) || isEditorComplex)
1729
) {
1730
active = false; // disable centered layout for complex editors or when there is more than one group
1731
}
1732
1733
if (this.editorGroupService.mainPart.isLayoutCentered() !== active) {
1734
this.editorGroupService.mainPart.centerLayout(active);
1735
1736
if (!skipLayout) {
1737
this.layout();
1738
}
1739
}
1740
1741
this._onDidChangeMainEditorCenteredLayout.fire(this.stateModel.getRuntimeValue(LayoutStateKeys.MAIN_EDITOR_CENTERED));
1742
}
1743
1744
getSize(part: Parts): IViewSize {
1745
return this.workbenchGrid.getViewSize(this.getPart(part));
1746
}
1747
1748
setSize(part: Parts, size: IViewSize): void {
1749
this.workbenchGrid.resizeView(this.getPart(part), size);
1750
}
1751
1752
resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void {
1753
const sizeChangePxWidth = Math.sign(sizeChangeWidth) * computeScreenAwareSize(getActiveWindow(), Math.abs(sizeChangeWidth));
1754
const sizeChangePxHeight = Math.sign(sizeChangeHeight) * computeScreenAwareSize(getActiveWindow(), Math.abs(sizeChangeHeight));
1755
1756
let viewSize: IViewSize;
1757
1758
switch (part) {
1759
case Parts.SIDEBAR_PART:
1760
viewSize = this.workbenchGrid.getViewSize(this.sideBarPartView);
1761
this.workbenchGrid.resizeView(this.sideBarPartView, {
1762
width: viewSize.width + sizeChangePxWidth,
1763
height: viewSize.height
1764
});
1765
1766
break;
1767
case Parts.PANEL_PART:
1768
viewSize = this.workbenchGrid.getViewSize(this.panelPartView);
1769
1770
this.workbenchGrid.resizeView(this.panelPartView, {
1771
width: viewSize.width + (isHorizontal(this.getPanelPosition()) ? 0 : sizeChangePxWidth),
1772
height: viewSize.height + (isHorizontal(this.getPanelPosition()) ? sizeChangePxHeight : 0)
1773
});
1774
1775
break;
1776
case Parts.AUXILIARYBAR_PART:
1777
viewSize = this.workbenchGrid.getViewSize(this.auxiliaryBarPartView);
1778
this.workbenchGrid.resizeView(this.auxiliaryBarPartView, {
1779
width: viewSize.width + sizeChangePxWidth,
1780
height: viewSize.height
1781
});
1782
break;
1783
case Parts.EDITOR_PART:
1784
viewSize = this.workbenchGrid.getViewSize(this.editorPartView);
1785
1786
// Single Editor Group
1787
if (this.editorGroupService.mainPart.count === 1) {
1788
this.workbenchGrid.resizeView(this.editorPartView, {
1789
width: viewSize.width + sizeChangePxWidth,
1790
height: viewSize.height + sizeChangePxHeight
1791
});
1792
} else {
1793
const activeGroup = this.editorGroupService.mainPart.activeGroup;
1794
1795
const { width, height } = this.editorGroupService.mainPart.getSize(activeGroup);
1796
this.editorGroupService.mainPart.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight });
1797
1798
// After resizing the editor group
1799
// if it does not change in either direction
1800
// try resizing the full editor part
1801
const { width: newWidth, height: newHeight } = this.editorGroupService.mainPart.getSize(activeGroup);
1802
if ((sizeChangePxHeight && height === newHeight) || (sizeChangePxWidth && width === newWidth)) {
1803
this.workbenchGrid.resizeView(this.editorPartView, {
1804
width: viewSize.width + (sizeChangePxWidth && width === newWidth ? sizeChangePxWidth : 0),
1805
height: viewSize.height + (sizeChangePxHeight && height === newHeight ? sizeChangePxHeight : 0)
1806
});
1807
}
1808
}
1809
1810
break;
1811
default:
1812
return; // Cannot resize other parts
1813
}
1814
}
1815
1816
private setActivityBarHidden(hidden: boolean): void {
1817
this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, hidden);
1818
this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden);
1819
}
1820
1821
private setBannerHidden(hidden: boolean): void {
1822
this.workbenchGrid.setViewVisible(this.bannerPartView, !hidden);
1823
}
1824
1825
private setEditorHidden(hidden: boolean): void {
1826
if (!hidden && this.setAuxiliaryBarMaximized(false) && this.isVisible(Parts.EDITOR_PART)) {
1827
return; // return: leaving maximised auxiliary bar made this part visible
1828
}
1829
1830
this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, hidden);
1831
1832
// Adjust CSS
1833
if (hidden) {
1834
this.mainContainer.classList.add(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN);
1835
} else {
1836
this.mainContainer.classList.remove(LayoutClasses.MAIN_EDITOR_AREA_HIDDEN);
1837
}
1838
1839
// Propagate to grid
1840
this.workbenchGrid.setViewVisible(this.editorPartView, !hidden);
1841
1842
// The editor and panel cannot be hidden at the same time
1843
// unless we have a maximized auxiliary bar
1844
if (hidden && !this.isVisible(Parts.PANEL_PART) && !this.isAuxiliaryBarMaximized()) {
1845
this.setPanelHidden(false, true);
1846
}
1847
}
1848
1849
getLayoutClasses(): string[] {
1850
return coalesce([
1851
!this.isVisible(Parts.SIDEBAR_PART) ? LayoutClasses.SIDEBAR_HIDDEN : undefined,
1852
!this.isVisible(Parts.EDITOR_PART, mainWindow) ? LayoutClasses.MAIN_EDITOR_AREA_HIDDEN : undefined,
1853
!this.isVisible(Parts.PANEL_PART) ? LayoutClasses.PANEL_HIDDEN : undefined,
1854
!this.isVisible(Parts.AUXILIARYBAR_PART) ? LayoutClasses.AUXILIARYBAR_HIDDEN : undefined,
1855
!this.isVisible(Parts.STATUSBAR_PART) ? LayoutClasses.STATUSBAR_HIDDEN : undefined,
1856
this.state.runtime.mainWindowFullscreen ? LayoutClasses.FULLSCREEN : undefined
1857
]);
1858
}
1859
1860
private setSideBarHidden(hidden: boolean): void {
1861
if (!hidden && this.setAuxiliaryBarMaximized(false) && this.isVisible(Parts.SIDEBAR_PART)) {
1862
return; // return: leaving maximised auxiliary bar made this part visible
1863
}
1864
1865
this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, hidden);
1866
1867
// Adjust CSS
1868
if (hidden) {
1869
this.mainContainer.classList.add(LayoutClasses.SIDEBAR_HIDDEN);
1870
} else {
1871
this.mainContainer.classList.remove(LayoutClasses.SIDEBAR_HIDDEN);
1872
}
1873
1874
// Propagate to grid
1875
this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden);
1876
1877
// If sidebar becomes hidden, also hide the current active Viewlet if any
1878
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
1879
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
1880
1881
if (!this.isAuxiliaryBarMaximized()) {
1882
this.focusPanelOrEditor(); // do not auto focus when auxiliary bar is maximized
1883
}
1884
}
1885
1886
// If sidebar becomes visible, show last active Viewlet or default viewlet
1887
else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
1888
const viewletToOpen = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Sidebar);
1889
if (viewletToOpen) {
1890
this.openViewContainer(ViewContainerLocation.Sidebar, viewletToOpen);
1891
}
1892
}
1893
}
1894
1895
private hasViews(id: string): boolean {
1896
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
1897
if (!viewContainer) {
1898
return false;
1899
}
1900
1901
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
1902
if (!viewContainerModel) {
1903
return false;
1904
}
1905
1906
return viewContainerModel.activeViewDescriptors.length >= 1;
1907
}
1908
1909
private adjustPartPositions(sideBarPosition: Position, panelAlignment: PanelAlignment, panelPosition: Position): void {
1910
1911
// Move activity bar and side bars
1912
const isPanelVertical = !isHorizontal(panelPosition);
1913
const sideBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left'));
1914
const auxiliaryBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left'));
1915
const preMovePanelWidth = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.panelPartView).width;
1916
const preMovePanelHeight = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumHeight) : this.workbenchGrid.getViewSize(this.panelPartView).height;
1917
const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width;
1918
const preMoveAuxiliaryBarSize = !this.isVisible(Parts.AUXILIARYBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width;
1919
1920
const focusedPart = [Parts.PANEL_PART, Parts.SIDEBAR_PART, Parts.AUXILIARYBAR_PART].find(part => this.hasFocus(part)) as SINGLE_WINDOW_PARTS | undefined;
1921
1922
if (sideBarPosition === Position.LEFT) {
1923
this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]);
1924
this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Left : Direction.Right);
1925
if (auxiliaryBarSiblingToEditor) {
1926
this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Right);
1927
} else {
1928
this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, -1]);
1929
}
1930
} else {
1931
this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, -1]);
1932
this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Right : Direction.Left);
1933
if (auxiliaryBarSiblingToEditor) {
1934
this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Left);
1935
} else {
1936
this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 0]);
1937
}
1938
}
1939
1940
// Maintain focus after moving parts
1941
if (focusedPart) {
1942
this.focusPart(focusedPart);
1943
}
1944
1945
// We moved all the side parts based on the editor and ignored the panel
1946
// Now, we need to put the panel back in the right position when it is next to the editor
1947
if (isPanelVertical) {
1948
this.workbenchGrid.moveView(this.panelPartView, preMovePanelWidth, this.editorPartView, panelPosition === Position.LEFT ? Direction.Left : Direction.Right);
1949
this.workbenchGrid.resizeView(this.panelPartView, {
1950
height: preMovePanelHeight as number,
1951
width: preMovePanelWidth as number
1952
});
1953
}
1954
1955
// Moving views in the grid can cause them to re-distribute sizing unnecessarily
1956
// Resize visible parts to the width they were before the operation
1957
if (this.isVisible(Parts.SIDEBAR_PART)) {
1958
this.workbenchGrid.resizeView(this.sideBarPartView, {
1959
height: this.workbenchGrid.getViewSize(this.sideBarPartView).height,
1960
width: preMoveSideBarSize as number
1961
});
1962
}
1963
1964
if (this.isVisible(Parts.AUXILIARYBAR_PART)) {
1965
this.workbenchGrid.resizeView(this.auxiliaryBarPartView, {
1966
height: this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).height,
1967
width: preMoveAuxiliaryBarSize as number
1968
});
1969
}
1970
}
1971
1972
setPanelAlignment(alignment: PanelAlignment): void {
1973
1974
// Panel alignment only applies to a panel in the top/bottom position
1975
if (!isHorizontal(this.getPanelPosition())) {
1976
this.setPanelPosition(Position.BOTTOM);
1977
}
1978
1979
// the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment
1980
if (alignment !== 'center' && this.isPanelMaximized()) {
1981
this.toggleMaximizedPanel();
1982
}
1983
1984
// Leave auxiliary bar maximized state because changing
1985
// panel alignment requires the editor part to be visible
1986
this.setAuxiliaryBarMaximized(false);
1987
1988
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT, alignment);
1989
1990
this.adjustPartPositions(this.getSideBarPosition(), alignment, this.getPanelPosition());
1991
1992
this._onDidChangePanelAlignment.fire(alignment);
1993
}
1994
1995
private setPanelHidden(hidden: boolean, skipLayout?: boolean): void {
1996
if (!this.workbenchGrid) {
1997
return; // Return if not initialized fully (https://github.com/microsoft/vscode/issues/105480)
1998
}
1999
2000
if (!hidden && this.setAuxiliaryBarMaximized(false) && this.isVisible(Parts.PANEL_PART)) {
2001
return; // return: leaving maximised auxiliary bar made this part visible
2002
}
2003
2004
const wasHidden = !this.isVisible(Parts.PANEL_PART);
2005
const isPanelMaximized = this.isPanelMaximized();
2006
2007
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_HIDDEN, hidden);
2008
2009
const panelOpensMaximized = this.panelOpensMaximized();
2010
2011
// Adjust CSS
2012
if (hidden) {
2013
this.mainContainer.classList.add(LayoutClasses.PANEL_HIDDEN);
2014
} else {
2015
this.mainContainer.classList.remove(LayoutClasses.PANEL_HIDDEN);
2016
}
2017
2018
// If maximized and in process of hiding, unmaximize FIRST before
2019
// changing visibility to prevent conflict with setEditorHidden
2020
// which would force panel visible again (fixes #281772)
2021
if (hidden && isPanelMaximized) {
2022
this.toggleMaximizedPanel();
2023
}
2024
2025
// Propagate layout changes to grid
2026
this.workbenchGrid.setViewVisible(this.panelPartView, !hidden);
2027
2028
// If panel part becomes hidden, also hide the current active panel if any
2029
let focusEditor = false;
2030
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
2031
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);
2032
if (
2033
!isIOS && // do not auto focus on iOS (https://github.com/microsoft/vscode/issues/127832)
2034
!this.isAuxiliaryBarMaximized() // do not auto focus when auxiliary bar is maximized
2035
) {
2036
focusEditor = true;
2037
}
2038
}
2039
2040
// If panel part becomes visible, show last active panel or default panel
2041
else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)) {
2042
let panelToOpen: string | undefined = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.Panel);
2043
2044
// verify that the panel we try to open has views before we default to it
2045
// otherwise fall back to any view that has views still refs #111463
2046
if (!panelToOpen || !this.hasViews(panelToOpen)) {
2047
panelToOpen = this.viewDescriptorService
2048
.getViewContainersByLocation(ViewContainerLocation.Panel)
2049
.find(viewContainer => this.hasViews(viewContainer.id))?.id;
2050
}
2051
2052
if (panelToOpen) {
2053
this.openViewContainer(ViewContainerLocation.Panel, panelToOpen, !skipLayout);
2054
}
2055
}
2056
2057
// Don't proceed if we have already done this before
2058
if (wasHidden === hidden) {
2059
return;
2060
}
2061
2062
// If in process of showing, toggle whether or not panel is maximized
2063
if (!hidden) {
2064
if (!skipLayout && isPanelMaximized !== panelOpensMaximized) {
2065
this.toggleMaximizedPanel();
2066
}
2067
} else {
2068
// If in process of hiding, remember whether the panel is maximized or not
2069
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, isPanelMaximized);
2070
}
2071
2072
if (focusEditor) {
2073
this.editorGroupService.mainPart.activeGroup.focus(); // Pass focus to editor group if panel part is now hidden
2074
}
2075
}
2076
2077
private inMaximizedAuxiliaryBarTransition = false;
2078
2079
isAuxiliaryBarMaximized(): boolean {
2080
return this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_WAS_LAST_MAXIMIZED);
2081
}
2082
2083
toggleMaximizedAuxiliaryBar(): void {
2084
this.setAuxiliaryBarMaximized(!this.isAuxiliaryBarMaximized());
2085
}
2086
2087
setAuxiliaryBarMaximized(maximized: boolean): boolean {
2088
if (
2089
this.inMaximizedAuxiliaryBarTransition || // prevent re-entrance
2090
(maximized === this.isAuxiliaryBarMaximized()) // return early if state is already present
2091
) {
2092
return false;
2093
}
2094
2095
if (maximized) {
2096
const state = {
2097
sideBarVisible: this.isVisible(Parts.SIDEBAR_PART),
2098
editorVisible: this.isVisible(Parts.EDITOR_PART),
2099
panelVisible: this.isVisible(Parts.PANEL_PART),
2100
auxiliaryBarVisible: this.isVisible(Parts.AUXILIARYBAR_PART)
2101
};
2102
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_WAS_LAST_MAXIMIZED, true);
2103
2104
this.inMaximizedAuxiliaryBarTransition = true;
2105
try {
2106
if (!state.auxiliaryBarVisible) {
2107
this.setAuxiliaryBarHidden(false);
2108
}
2109
2110
const size = this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width;
2111
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_SIZE, size);
2112
2113
if (state.sideBarVisible) {
2114
this.setSideBarHidden(true);
2115
}
2116
if (state.panelVisible) {
2117
this.setPanelHidden(true);
2118
}
2119
if (state.editorVisible) {
2120
this.setEditorHidden(true);
2121
}
2122
2123
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_VISIBILITY, state);
2124
} finally {
2125
this.inMaximizedAuxiliaryBarTransition = false;
2126
}
2127
} else {
2128
const state = assertReturnsDefined(this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_VISIBILITY));
2129
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_WAS_LAST_MAXIMIZED, false);
2130
2131
this.inMaximizedAuxiliaryBarTransition = true;
2132
try {
2133
this.setEditorHidden(!state?.editorVisible); // this order of updating view visibility
2134
this.setPanelHidden(!state?.panelVisible); // helps in restoring the previous view
2135
this.setSideBarHidden(!state?.sideBarVisible); // sizes we had
2136
2137
const size = this.workbenchGrid.getViewSize(this.auxiliaryBarPartView);
2138
this.workbenchGrid.resizeView(this.auxiliaryBarPartView, {
2139
width: this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_SIZE),
2140
height: size.height
2141
});
2142
} finally {
2143
this.inMaximizedAuxiliaryBarTransition = false;
2144
}
2145
}
2146
2147
this.focusPart(Parts.AUXILIARYBAR_PART);
2148
2149
this._onDidChangeAuxiliaryBarMaximized.fire();
2150
2151
return true;
2152
}
2153
2154
isPanelMaximized(): boolean {
2155
return (
2156
this.getPanelAlignment() === 'center' || // the workbench grid currently prevents us from supporting panel
2157
!isHorizontal(this.getPanelPosition()) // maximization with non-center panel alignment
2158
) && !this.isVisible(Parts.EDITOR_PART, mainWindow) && !this.isAuxiliaryBarMaximized();
2159
}
2160
2161
toggleMaximizedPanel(): void {
2162
const size = this.workbenchGrid.getViewSize(this.panelPartView);
2163
const panelPosition = this.getPanelPosition();
2164
const maximize = !this.isPanelMaximized();
2165
if (maximize) {
2166
if (this.isVisible(Parts.PANEL_PART)) {
2167
if (isHorizontal(panelPosition)) {
2168
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height);
2169
} else {
2170
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width);
2171
}
2172
}
2173
2174
this.setEditorHidden(true);
2175
} else {
2176
this.setEditorHidden(false);
2177
2178
this.workbenchGrid.resizeView(this.panelPartView, {
2179
width: isHorizontal(panelPosition) ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH),
2180
height: isHorizontal(panelPosition) ? this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) : size.height
2181
});
2182
}
2183
2184
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, maximize);
2185
}
2186
2187
private panelOpensMaximized(): boolean {
2188
if (this.getPanelAlignment() !== 'center' && isHorizontal(this.getPanelPosition())) {
2189
return false; // The workbench grid currently prevents us from supporting panel maximization with non-center panel alignment
2190
}
2191
2192
const panelOpensMaximized = partOpensMaximizedFromString(this.configurationService.getValue<string>(WorkbenchLayoutSettings.PANEL_OPENS_MAXIMIZED));
2193
const panelLastIsMaximized = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED);
2194
2195
return panelOpensMaximized === PartOpensMaximizedOptions.ALWAYS || (panelOpensMaximized === PartOpensMaximizedOptions.REMEMBER_LAST && panelLastIsMaximized);
2196
}
2197
2198
private setAuxiliaryBarHidden(hidden: boolean, skipLayout?: boolean): void {
2199
if (hidden && this.setAuxiliaryBarMaximized(false) && !this.isVisible(Parts.AUXILIARYBAR_PART)) {
2200
return; // return: leaving maximised auxiliary bar made this part hidden
2201
}
2202
2203
this.stateModel.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, hidden);
2204
2205
// Adjust CSS
2206
if (hidden) {
2207
this.mainContainer.classList.add(LayoutClasses.AUXILIARYBAR_HIDDEN);
2208
} else {
2209
this.mainContainer.classList.remove(LayoutClasses.AUXILIARYBAR_HIDDEN);
2210
}
2211
2212
// Propagate to grid
2213
this.workbenchGrid.setViewVisible(this.auxiliaryBarPartView, !hidden);
2214
2215
// If auxiliary bar becomes hidden, also hide the current active pane composite if any
2216
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
2217
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.AuxiliaryBar);
2218
this.focusPanelOrEditor();
2219
}
2220
2221
// If auxiliary bar becomes visible, show last active pane composite or default pane composite
2222
else if (!hidden && !this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar)) {
2223
let viewletToOpen: string | undefined = this.paneCompositeService.getLastActivePaneCompositeId(ViewContainerLocation.AuxiliaryBar);
2224
2225
// verify that the viewlet we try to open has views before we default to it
2226
// otherwise fall back to any view that has views still refs #111463
2227
if (!viewletToOpen || !this.hasViews(viewletToOpen)) {
2228
viewletToOpen = this.viewDescriptorService
2229
.getViewContainersByLocation(ViewContainerLocation.AuxiliaryBar)
2230
.find(viewContainer => this.hasViews(viewContainer.id))?.id;
2231
}
2232
2233
if (viewletToOpen) {
2234
this.openViewContainer(ViewContainerLocation.AuxiliaryBar, viewletToOpen, !skipLayout);
2235
}
2236
}
2237
}
2238
2239
setPartHidden(hidden: boolean, part: Parts): void {
2240
switch (part) {
2241
case Parts.ACTIVITYBAR_PART:
2242
return this.setActivityBarHidden(hidden);
2243
case Parts.SIDEBAR_PART:
2244
return this.setSideBarHidden(hidden);
2245
case Parts.EDITOR_PART:
2246
return this.setEditorHidden(hidden);
2247
case Parts.BANNER_PART:
2248
return this.setBannerHidden(hidden);
2249
case Parts.AUXILIARYBAR_PART:
2250
return this.setAuxiliaryBarHidden(hidden);
2251
case Parts.PANEL_PART:
2252
return this.setPanelHidden(hidden);
2253
}
2254
}
2255
2256
hasMainWindowBorder(): boolean {
2257
return this.state.runtime.mainWindowBorder;
2258
}
2259
2260
getMainWindowBorderRadius(): string | undefined {
2261
return this.state.runtime.mainWindowBorder && isMacintosh ? '10px' : undefined;
2262
}
2263
2264
getSideBarPosition(): Position {
2265
return this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON);
2266
}
2267
2268
getPanelAlignment(): PanelAlignment {
2269
return this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT);
2270
}
2271
2272
updateMenubarVisibility(skipLayout: boolean): void {
2273
const shouldShowTitleBar = shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled);
2274
if (!skipLayout && this.workbenchGrid && shouldShowTitleBar !== this.isVisible(Parts.TITLEBAR_PART, mainWindow)) {
2275
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar);
2276
}
2277
}
2278
2279
updateCustomTitleBarVisibility(): void {
2280
const shouldShowTitleBar = shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled);
2281
const titlebarVisible = this.isVisible(Parts.TITLEBAR_PART);
2282
if (shouldShowTitleBar !== titlebarVisible) {
2283
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar);
2284
}
2285
}
2286
2287
toggleMenuBar(): void {
2288
let currentVisibilityValue = getMenuBarVisibility(this.configurationService);
2289
if (typeof currentVisibilityValue !== 'string') {
2290
currentVisibilityValue = 'classic';
2291
}
2292
2293
let newVisibilityValue: string;
2294
if (currentVisibilityValue === 'visible' || currentVisibilityValue === 'classic') {
2295
newVisibilityValue = hasNativeMenu(this.configurationService) ? 'toggle' : 'compact';
2296
} else {
2297
newVisibilityValue = 'classic';
2298
}
2299
2300
this.configurationService.updateValue(MenuSettings.MenuBarVisibility, newVisibilityValue);
2301
}
2302
2303
getPanelPosition(): Position {
2304
return this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION);
2305
}
2306
2307
setPanelPosition(position: Position): void {
2308
if (!this.isVisible(Parts.PANEL_PART)) {
2309
this.setPanelHidden(false);
2310
}
2311
2312
const panelPart = this.getPart(Parts.PANEL_PART);
2313
const oldPositionValue = positionToString(this.getPanelPosition());
2314
const newPositionValue = positionToString(position);
2315
2316
// Adjust CSS
2317
const panelContainer = assertReturnsDefined(panelPart.getContainer());
2318
panelContainer.classList.remove(oldPositionValue);
2319
panelContainer.classList.add(newPositionValue);
2320
2321
// Update Styles
2322
panelPart.updateStyles();
2323
2324
// Layout
2325
const size = this.workbenchGrid.getViewSize(this.panelPartView);
2326
const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView);
2327
const auxiliaryBarSize = this.workbenchGrid.getViewSize(this.auxiliaryBarPartView);
2328
2329
let editorHidden = !this.isVisible(Parts.EDITOR_PART, mainWindow);
2330
2331
// Save last non-maximized size for panel before move
2332
if (newPositionValue !== oldPositionValue && !editorHidden) {
2333
2334
// Save the current size of the panel for the new orthogonal direction
2335
// If moving down, save the width of the panel
2336
// Otherwise, save the height of the panel
2337
if (isHorizontal(position)) {
2338
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width);
2339
} else if (isHorizontal(positionFromString(oldPositionValue))) {
2340
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height);
2341
}
2342
}
2343
2344
if (isHorizontal(position) && this.getPanelAlignment() !== 'center' && editorHidden) {
2345
this.toggleMaximizedPanel();
2346
editorHidden = false;
2347
}
2348
2349
this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_POSITION, position);
2350
2351
const sideBarVisible = this.isVisible(Parts.SIDEBAR_PART);
2352
const auxiliaryBarVisible = this.isVisible(Parts.AUXILIARYBAR_PART);
2353
2354
const hadFocus = this.hasFocus(Parts.PANEL_PART);
2355
2356
if (position === Position.BOTTOM) {
2357
this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Down);
2358
} else if (position === Position.TOP) {
2359
this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Up);
2360
} else if (position === Position.RIGHT) {
2361
this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Right);
2362
} else {
2363
this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Left);
2364
}
2365
2366
if (hadFocus) {
2367
this.focusPart(Parts.PANEL_PART);
2368
}
2369
2370
// Reset sidebar to original size before shifting the panel
2371
this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize);
2372
if (!sideBarVisible) {
2373
this.setSideBarHidden(true);
2374
}
2375
2376
this.workbenchGrid.resizeView(this.auxiliaryBarPartView, auxiliaryBarSize);
2377
if (!auxiliaryBarVisible) {
2378
this.setAuxiliaryBarHidden(true);
2379
}
2380
2381
if (isHorizontal(position)) {
2382
this.adjustPartPositions(this.getSideBarPosition(), this.getPanelAlignment(), position);
2383
}
2384
2385
this._onDidChangePanelPosition.fire(newPositionValue);
2386
}
2387
2388
isWindowMaximized(targetWindow: Window): boolean {
2389
return this.state.runtime.maximized.has(getWindowId(targetWindow));
2390
}
2391
2392
updateWindowMaximizedState(targetWindow: Window, maximized: boolean) {
2393
this.mainContainer.classList.toggle(LayoutClasses.MAXIMIZED, maximized);
2394
2395
const targetWindowId = getWindowId(targetWindow);
2396
if (maximized === this.state.runtime.maximized.has(targetWindowId)) {
2397
return;
2398
}
2399
2400
if (maximized) {
2401
this.state.runtime.maximized.add(targetWindowId);
2402
} else {
2403
this.state.runtime.maximized.delete(targetWindowId);
2404
}
2405
2406
this.updateWindowBorder();
2407
this._onDidChangeWindowMaximized.fire({ windowId: targetWindowId, maximized });
2408
}
2409
2410
getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined {
2411
if (!this.workbenchGrid) {
2412
return undefined;
2413
}
2414
2415
if (!this.isVisible(part, mainWindow)) {
2416
return undefined;
2417
}
2418
2419
const neighborViews = this.workbenchGrid.getNeighborViews(this.getPart(part), direction, false);
2420
2421
if (!neighborViews) {
2422
return undefined;
2423
}
2424
2425
for (const neighborView of neighborViews) {
2426
const neighborPart =
2427
[Parts.ACTIVITYBAR_PART, Parts.EDITOR_PART, Parts.PANEL_PART, Parts.AUXILIARYBAR_PART, Parts.SIDEBAR_PART, Parts.STATUSBAR_PART, Parts.TITLEBAR_PART]
2428
.find(partId => this.getPart(partId) === neighborView && this.isVisible(partId, mainWindow));
2429
2430
if (neighborPart !== undefined) {
2431
return neighborPart;
2432
}
2433
}
2434
2435
return undefined;
2436
}
2437
2438
private onDidChangeWCO(): void {
2439
const bannerFirst = this.workbenchGrid.getNeighborViews(this.titleBarPartView, Direction.Up, false).length > 0;
2440
const shouldBannerBeFirst = this.shouldShowBannerFirst();
2441
2442
if (bannerFirst !== shouldBannerBeFirst) {
2443
this.workbenchGrid.moveView(this.bannerPartView, Sizing.Distribute, this.titleBarPartView, shouldBannerBeFirst ? Direction.Up : Direction.Down);
2444
}
2445
2446
this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowCustomTitleBar(this.configurationService, mainWindow, this.state.runtime.menuBar.toggled));
2447
}
2448
2449
private arrangeEditorNodes(nodes: { editor: ISerializedNode; sideBar?: ISerializedNode; auxiliaryBar?: ISerializedNode }, availableHeight: number, availableWidth: number): ISerializedNode {
2450
if (!nodes.sideBar && !nodes.auxiliaryBar) {
2451
nodes.editor.size = availableHeight;
2452
return nodes.editor;
2453
}
2454
2455
const result = [nodes.editor];
2456
nodes.editor.size = availableWidth;
2457
if (nodes.sideBar) {
2458
if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) {
2459
result.splice(0, 0, nodes.sideBar);
2460
} else {
2461
result.push(nodes.sideBar);
2462
}
2463
2464
nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) ? 0 : nodes.sideBar.size;
2465
}
2466
2467
if (nodes.auxiliaryBar) {
2468
if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.RIGHT) {
2469
result.splice(0, 0, nodes.auxiliaryBar);
2470
} else {
2471
result.push(nodes.auxiliaryBar);
2472
}
2473
2474
nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size;
2475
}
2476
2477
return {
2478
type: 'branch',
2479
data: result,
2480
size: availableHeight,
2481
visible: result.some(node => node.visible)
2482
};
2483
}
2484
2485
private arrangeMiddleSectionNodes(nodes: { editor: ISerializedNode; panel: ISerializedNode; activityBar: ISerializedNode; sideBar: ISerializedNode; auxiliaryBar: ISerializedNode }, availableWidth: number, availableHeight: number): ISerializedNode[] {
2486
const activityBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN) ? 0 : nodes.activityBar.size;
2487
const sideBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) ? 0 : nodes.sideBar.size;
2488
const auxiliaryBarSize = this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size;
2489
const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE) ? 0 : nodes.panel.size;
2490
2491
const panelPostion = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION);
2492
const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON);
2493
2494
const result = [] as ISerializedNode[];
2495
if (!isHorizontal(panelPostion)) {
2496
result.push(nodes.editor);
2497
nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize;
2498
if (panelPostion === Position.RIGHT) {
2499
result.push(nodes.panel);
2500
} else {
2501
result.splice(0, 0, nodes.panel);
2502
}
2503
2504
if (sideBarPosition === Position.LEFT) {
2505
result.push(nodes.auxiliaryBar);
2506
result.splice(0, 0, nodes.sideBar);
2507
result.splice(0, 0, nodes.activityBar);
2508
} else {
2509
result.splice(0, 0, nodes.auxiliaryBar);
2510
result.push(nodes.sideBar);
2511
result.push(nodes.activityBar);
2512
}
2513
} else {
2514
const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT);
2515
const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left'));
2516
const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left'));
2517
2518
const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize);
2519
2520
const editorNodes = this.arrangeEditorNodes({
2521
editor: nodes.editor,
2522
sideBar: sideBarNextToEditor ? nodes.sideBar : undefined,
2523
auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined
2524
}, availableHeight - panelSize, editorSectionWidth);
2525
2526
const data = panelPostion === Position.BOTTOM ? [editorNodes, nodes.panel] : [nodes.panel, editorNodes];
2527
result.push({
2528
type: 'branch',
2529
data,
2530
size: editorSectionWidth,
2531
visible: data.some(node => node.visible)
2532
});
2533
2534
if (!sideBarNextToEditor) {
2535
if (sideBarPosition === Position.LEFT) {
2536
result.splice(0, 0, nodes.sideBar);
2537
} else {
2538
result.push(nodes.sideBar);
2539
}
2540
}
2541
2542
if (!auxiliaryBarNextToEditor) {
2543
if (sideBarPosition === Position.RIGHT) {
2544
result.splice(0, 0, nodes.auxiliaryBar);
2545
} else {
2546
result.push(nodes.auxiliaryBar);
2547
}
2548
}
2549
2550
if (sideBarPosition === Position.LEFT) {
2551
result.splice(0, 0, nodes.activityBar);
2552
} else {
2553
result.push(nodes.activityBar);
2554
}
2555
}
2556
2557
return result;
2558
}
2559
2560
private createGridDescriptor(): ISerializedGrid {
2561
const { width, height } = this._mainContainerDimension;
2562
const sideBarSize = this.stateModel.getInitializationValue(LayoutStateKeys.SIDEBAR_SIZE);
2563
const auxiliaryBarSize = this.stateModel.getInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE);
2564
const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE);
2565
2566
const titleBarHeight = this.titleBarPartView.minimumHeight;
2567
const bannerHeight = this.bannerPartView.minimumHeight;
2568
const statusBarHeight = this.statusBarPartView.minimumHeight;
2569
const activityBarWidth = this.activityBarPartView.minimumWidth;
2570
const middleSectionHeight = height - titleBarHeight - statusBarHeight;
2571
2572
const titleAndBanner: ISerializedNode[] = [
2573
{
2574
type: 'leaf',
2575
data: { type: Parts.TITLEBAR_PART },
2576
size: titleBarHeight,
2577
visible: this.isVisible(Parts.TITLEBAR_PART, mainWindow)
2578
},
2579
{
2580
type: 'leaf',
2581
data: { type: Parts.BANNER_PART },
2582
size: bannerHeight,
2583
visible: false
2584
}
2585
];
2586
2587
const activityBarNode: ISerializedLeafNode = {
2588
type: 'leaf',
2589
data: { type: Parts.ACTIVITYBAR_PART },
2590
size: activityBarWidth,
2591
visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN)
2592
};
2593
2594
const sideBarNode: ISerializedLeafNode = {
2595
type: 'leaf',
2596
data: { type: Parts.SIDEBAR_PART },
2597
size: sideBarSize,
2598
visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN)
2599
};
2600
2601
const auxiliaryBarNode: ISerializedLeafNode = {
2602
type: 'leaf',
2603
data: { type: Parts.AUXILIARYBAR_PART },
2604
size: auxiliaryBarSize,
2605
visible: this.isVisible(Parts.AUXILIARYBAR_PART)
2606
};
2607
2608
const editorNode: ISerializedLeafNode = {
2609
type: 'leaf',
2610
data: { type: Parts.EDITOR_PART },
2611
size: 0, // Update based on sibling sizes
2612
visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN)
2613
};
2614
2615
const panelNode: ISerializedLeafNode = {
2616
type: 'leaf',
2617
data: { type: Parts.PANEL_PART },
2618
size: panelSize,
2619
visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN)
2620
};
2621
2622
const middleSection: ISerializedNode[] = this.arrangeMiddleSectionNodes({
2623
activityBar: activityBarNode,
2624
auxiliaryBar: auxiliaryBarNode,
2625
editor: editorNode,
2626
panel: panelNode,
2627
sideBar: sideBarNode
2628
}, width, middleSectionHeight);
2629
2630
const result: ISerializedGrid = {
2631
root: {
2632
type: 'branch',
2633
size: width,
2634
data: [
2635
...(this.shouldShowBannerFirst() ? titleAndBanner.reverse() : titleAndBanner),
2636
{
2637
type: 'branch',
2638
data: middleSection,
2639
size: middleSectionHeight
2640
},
2641
{
2642
type: 'leaf',
2643
data: { type: Parts.STATUSBAR_PART },
2644
size: statusBarHeight,
2645
visible: !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN)
2646
}
2647
]
2648
},
2649
orientation: Orientation.VERTICAL,
2650
width,
2651
height
2652
};
2653
2654
type StartupLayoutEvent = {
2655
activityBarVisible: boolean;
2656
sideBarVisible: boolean;
2657
auxiliaryBarVisible: boolean;
2658
panelVisible: boolean;
2659
statusbarVisible: boolean;
2660
sideBarPosition: string;
2661
panelPosition: string;
2662
};
2663
2664
type StartupLayoutEventClassification = {
2665
owner: 'benibenj';
2666
comment: 'Information about the layout of the workbench during statup';
2667
activityBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the activity bar is visible' };
2668
sideBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the primary side bar is visible' };
2669
auxiliaryBarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the secondary side bar is visible' };
2670
panelVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the panel is visible' };
2671
statusbarVisible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether or the not the status bar is visible' };
2672
sideBarPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the primary side bar is on the left or right' };
2673
panelPosition: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the panel is on the top, bottom, left, or right' };
2674
};
2675
2676
const layoutDescriptor: StartupLayoutEvent = {
2677
activityBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN),
2678
sideBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN),
2679
auxiliaryBarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN),
2680
panelVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN),
2681
statusbarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN),
2682
sideBarPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON)),
2683
panelPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION)),
2684
};
2685
2686
// WARNING: Do not remove this event, it's used to track build rollout progress
2687
// Talk to @joaomoreno, @lszomoru or @jruales before doing so
2688
this.telemetryService.publicLog2<StartupLayoutEvent, StartupLayoutEventClassification>('startupLayout', layoutDescriptor);
2689
2690
return result;
2691
}
2692
2693
override dispose(): void {
2694
super.dispose();
2695
2696
this.disposed = true;
2697
}
2698
}
2699
2700
type ZenModeConfiguration = {
2701
centerLayout: boolean;
2702
fullScreen: boolean;
2703
hideActivityBar: boolean;
2704
hideLineNumbers: boolean;
2705
hideStatusBar: boolean;
2706
showTabs: 'multiple' | 'single' | 'none';
2707
restore: boolean;
2708
silentNotifications: boolean;
2709
};
2710
2711
function getZenModeConfiguration(configurationService: IConfigurationService): ZenModeConfiguration {
2712
return configurationService.getValue<ZenModeConfiguration>(WorkbenchLayoutSettings.ZEN_MODE_CONFIG);
2713
}
2714
2715
//#endregion
2716
2717
//#region Layout State Model
2718
2719
interface IWorkbenchLayoutStateKey {
2720
readonly name: string;
2721
readonly runtime: boolean;
2722
readonly defaultValue: unknown;
2723
readonly scope: StorageScope;
2724
readonly target: StorageTarget;
2725
readonly zenModeIgnore?: boolean;
2726
}
2727
2728
type StorageKeyType = string | boolean | number | object;
2729
2730
abstract class WorkbenchLayoutStateKey<T extends StorageKeyType> implements IWorkbenchLayoutStateKey {
2731
2732
abstract readonly runtime: boolean;
2733
2734
constructor(readonly name: string, readonly scope: StorageScope, readonly target: StorageTarget, public defaultValue: T) { }
2735
}
2736
2737
class RuntimeStateKey<T extends StorageKeyType> extends WorkbenchLayoutStateKey<T> {
2738
2739
readonly runtime = true;
2740
2741
constructor(name: string, scope: StorageScope, target: StorageTarget, defaultValue: T, readonly zenModeIgnore?: boolean) {
2742
super(name, scope, target, defaultValue);
2743
}
2744
}
2745
2746
class InitializationStateKey<T extends StorageKeyType> extends WorkbenchLayoutStateKey<T> {
2747
readonly runtime = false;
2748
}
2749
2750
const LayoutStateKeys = {
2751
2752
// Editor
2753
MAIN_EDITOR_CENTERED: new RuntimeStateKey<boolean>('editor.centered', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2754
2755
// Zen Mode
2756
ZEN_MODE_ACTIVE: new RuntimeStateKey<boolean>('zenMode.active', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2757
ZEN_MODE_EXIT_INFO: new RuntimeStateKey('zenMode.exitInfo', StorageScope.WORKSPACE, StorageTarget.MACHINE, {
2758
transitionedToCenteredEditorLayout: false,
2759
transitionedToFullScreen: false,
2760
handleNotificationsDoNotDisturbMode: false,
2761
wasVisible: {
2762
auxiliaryBar: false,
2763
panel: false,
2764
sideBar: false,
2765
},
2766
}),
2767
2768
// Part Sizing
2769
SIDEBAR_SIZE: new InitializationStateKey<number>('sideBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2770
AUXILIARYBAR_SIZE: new InitializationStateKey<number>('auxiliaryBar.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2771
PANEL_SIZE: new InitializationStateKey<number>('panel.size', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2772
2773
// Part State
2774
PANEL_LAST_NON_MAXIMIZED_HEIGHT: new RuntimeStateKey<number>('panel.lastNonMaximizedHeight', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2775
PANEL_LAST_NON_MAXIMIZED_WIDTH: new RuntimeStateKey<number>('panel.lastNonMaximizedWidth', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2776
PANEL_WAS_LAST_MAXIMIZED: new RuntimeStateKey<boolean>('panel.wasLastMaximized', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2777
2778
AUXILIARYBAR_WAS_LAST_MAXIMIZED: new RuntimeStateKey<boolean>('auxiliaryBar.wasLastMaximized', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2779
AUXILIARYBAR_LAST_NON_MAXIMIZED_SIZE: new RuntimeStateKey<number>('auxiliaryBar.lastNonMaximizedSize', StorageScope.PROFILE, StorageTarget.MACHINE, 300),
2780
AUXILIARYBAR_LAST_NON_MAXIMIZED_VISIBILITY: new RuntimeStateKey('auxiliaryBar.lastNonMaximizedVisibility', StorageScope.WORKSPACE, StorageTarget.MACHINE, {
2781
sideBarVisible: false,
2782
editorVisible: false,
2783
panelVisible: false,
2784
auxiliaryBarVisible: false
2785
}),
2786
AUXILIARYBAR_EMPTY: new InitializationStateKey<boolean>('auxiliaryBar.empty', StorageScope.PROFILE, StorageTarget.MACHINE, false),
2787
2788
// Part Positions
2789
SIDEBAR_POSITON: new RuntimeStateKey<Position>('sideBar.position', StorageScope.WORKSPACE, StorageTarget.MACHINE, Position.LEFT),
2790
PANEL_POSITION: new RuntimeStateKey<Position>('panel.position', StorageScope.WORKSPACE, StorageTarget.MACHINE, Position.BOTTOM),
2791
PANEL_ALIGNMENT: new RuntimeStateKey<PanelAlignment>('panel.alignment', StorageScope.PROFILE, StorageTarget.USER, 'center'),
2792
2793
// Part Visibility
2794
ACTIVITYBAR_HIDDEN: new RuntimeStateKey<boolean>('activityBar.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, false, true),
2795
SIDEBAR_HIDDEN: new RuntimeStateKey<boolean>('sideBar.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2796
EDITOR_HIDDEN: new RuntimeStateKey<boolean>('editor.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, false),
2797
PANEL_HIDDEN: new RuntimeStateKey<boolean>('panel.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, true),
2798
AUXILIARYBAR_HIDDEN: new RuntimeStateKey<boolean>('auxiliaryBar.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, true),
2799
STATUSBAR_HIDDEN: new RuntimeStateKey<boolean>('statusBar.hidden', StorageScope.WORKSPACE, StorageTarget.MACHINE, false, true)
2800
2801
} as const;
2802
2803
interface ILayoutStateChangeEvent<T extends StorageKeyType> {
2804
readonly key: RuntimeStateKey<T>;
2805
readonly value: T;
2806
}
2807
2808
enum WorkbenchLayoutSettings {
2809
AUXILIARYBAR_DEFAULT_VISIBILITY = 'workbench.secondarySideBar.defaultVisibility',
2810
AUXILIARYBAR_FORCE_MAXIMIZED = 'workbench.secondarySideBar.forceMaximized',
2811
ACTIVITY_BAR_VISIBLE = 'workbench.activityBar.visible',
2812
PANEL_POSITION = 'workbench.panel.defaultLocation',
2813
PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized',
2814
ZEN_MODE_CONFIG = 'zenMode',
2815
EDITOR_CENTERED_LAYOUT_AUTO_RESIZE = 'workbench.editor.centeredLayoutAutoResize',
2816
EDITOR_RESTORE_EDITORS = 'workbench.editor.restoreEditors',
2817
}
2818
2819
enum LegacyWorkbenchLayoutSettings {
2820
STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State
2821
SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State
2822
}
2823
2824
interface ILayoutStateLoadConfiguration {
2825
readonly mainContainerDimension: IDimension;
2826
readonly resetLayout: boolean;
2827
}
2828
2829
class LayoutStateModel extends Disposable {
2830
2831
static readonly STORAGE_PREFIX = 'workbench.';
2832
2833
private readonly _onDidChangeState = this._register(new Emitter<ILayoutStateChangeEvent<StorageKeyType>>());
2834
readonly onDidChangeState = this._onDidChangeState.event;
2835
2836
private readonly stateCache = new Map<string, unknown>();
2837
2838
private readonly isNew: {
2839
[StorageScope.WORKSPACE]: boolean;
2840
[StorageScope.PROFILE]: boolean;
2841
[StorageScope.APPLICATION]: boolean;
2842
};
2843
2844
constructor(
2845
private readonly storageService: IStorageService,
2846
private readonly configurationService: IConfigurationService,
2847
private readonly contextService: IWorkspaceContextService,
2848
private readonly environmentService: IBrowserWorkbenchEnvironmentService,
2849
) {
2850
super();
2851
2852
this.isNew = {
2853
[StorageScope.WORKSPACE]: this.storageService.isNew(StorageScope.WORKSPACE),
2854
[StorageScope.PROFILE]: this.storageService.isNew(StorageScope.PROFILE),
2855
[StorageScope.APPLICATION]: this.storageService.isNew(StorageScope.APPLICATION)
2856
};
2857
2858
this._register(this.configurationService.onDidChangeConfiguration(configurationChange => this.updateStateFromLegacySettings(configurationChange)));
2859
}
2860
2861
private updateStateFromLegacySettings(configurationChangeEvent: IConfigurationChangeEvent): void {
2862
if (configurationChangeEvent.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {
2863
this.setRuntimeValueAndFire(LayoutStateKeys.ACTIVITYBAR_HIDDEN, this.isActivityBarHidden());
2864
}
2865
2866
if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)) {
2867
this.setRuntimeValueAndFire(LayoutStateKeys.STATUSBAR_HIDDEN, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE));
2868
}
2869
2870
if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION)) {
2871
this.setRuntimeValueAndFire(LayoutStateKeys.SIDEBAR_POSITON, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left'));
2872
}
2873
}
2874
2875
private updateLegacySettingsFromState<T extends StorageKeyType>(key: RuntimeStateKey<T>, value: T): void {
2876
const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE);
2877
if (key.zenModeIgnore && isZenMode) {
2878
return;
2879
}
2880
2881
if (key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) {
2882
this.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, value ? ActivityBarPosition.HIDDEN : undefined);
2883
} else if (key === LayoutStateKeys.STATUSBAR_HIDDEN) {
2884
this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, !value);
2885
} else if (key === LayoutStateKeys.SIDEBAR_POSITON) {
2886
this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, positionToString(value as Position));
2887
}
2888
}
2889
2890
load(configuration: ILayoutStateLoadConfiguration): void {
2891
let key: keyof typeof LayoutStateKeys;
2892
2893
// Load stored values for all keys unless we explicitly set to reset
2894
if (!configuration.resetLayout) {
2895
for (key in LayoutStateKeys) {
2896
const stateKey = LayoutStateKeys[key] as WorkbenchLayoutStateKey<StorageKeyType>;
2897
const value = this.loadKeyFromStorage(stateKey);
2898
2899
if (value !== undefined) {
2900
this.stateCache.set(stateKey.name, value);
2901
}
2902
}
2903
}
2904
2905
// Apply legacy settings
2906
this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, this.isActivityBarHidden());
2907
this.stateCache.set(LayoutStateKeys.STATUSBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE));
2908
this.stateCache.set(LayoutStateKeys.SIDEBAR_POSITON.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left'));
2909
2910
// Set dynamic defaults: part sizing and side bar visibility
2911
const auxiliaryBarForceMaximized = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED);
2912
const workbenchState = this.contextService.getWorkbenchState();
2913
const mainContainerDimension = configuration.mainContainerDimension;
2914
LayoutStateKeys.SIDEBAR_SIZE.defaultValue = Math.min(300, mainContainerDimension.width / 4);
2915
LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = workbenchState === WorkbenchState.EMPTY || auxiliaryBarForceMaximized === true;
2916
LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = auxiliaryBarForceMaximized ? Math.max(300, mainContainerDimension.width / 2) : Math.min(300, mainContainerDimension.width / 4);
2917
LayoutStateKeys.AUXILIARYBAR_HIDDEN.defaultValue = (() => {
2918
if (isWeb && !this.environmentService.remoteAuthority) {
2919
return true; // not required in web if unsupported
2920
}
2921
2922
if (auxiliaryBarForceMaximized === true) {
2923
return false; // forced to be visible
2924
}
2925
2926
// Unless auxiliary bar visibility is explicitly configured, make
2927
// sure to not force open it in case we know it was empty before.
2928
const configuration = this.configurationService.inspect(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY);
2929
if (configuration.defaultValue !== 'hidden' && !isConfigured(configuration) && this.stateCache.get(LayoutStateKeys.AUXILIARYBAR_EMPTY.name)) {
2930
return true;
2931
}
2932
2933
// New users: Show auxiliary bar even in empty workspaces
2934
// but not if the user explicitly hides it
2935
if (
2936
this.isNew[StorageScope.APPLICATION] &&
2937
configuration.value !== 'hidden'
2938
) {
2939
return false;
2940
}
2941
2942
// Existing users: respect visibility setting
2943
switch (configuration.value) {
2944
case 'hidden':
2945
return true;
2946
case 'visibleInWorkspace':
2947
case 'maximizedInWorkspace':
2948
return workbenchState === WorkbenchState.EMPTY;
2949
default:
2950
return false;
2951
}
2952
})();
2953
LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? isHorizontal(LayoutStateKeys.PANEL_POSITION.defaultValue)) ? mainContainerDimension.height / 3 : mainContainerDimension.width / 4;
2954
LayoutStateKeys.PANEL_POSITION.defaultValue = positionFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_POSITION) ?? 'bottom');
2955
2956
// Apply all defaults
2957
for (key in LayoutStateKeys) {
2958
const stateKey = LayoutStateKeys[key];
2959
if (this.stateCache.get(stateKey.name) === undefined) {
2960
this.stateCache.set(stateKey.name, stateKey.defaultValue);
2961
}
2962
}
2963
2964
// Apply all overrides
2965
this.applyOverrides(configuration);
2966
2967
// Register for runtime key changes
2968
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this._store)(storageChangeEvent => {
2969
let key: keyof typeof LayoutStateKeys;
2970
for (key in LayoutStateKeys) {
2971
const stateKey = LayoutStateKeys[key] as WorkbenchLayoutStateKey<StorageKeyType>;
2972
if (stateKey instanceof RuntimeStateKey && stateKey.scope === StorageScope.PROFILE && stateKey.target === StorageTarget.USER) {
2973
if (`${LayoutStateModel.STORAGE_PREFIX}${stateKey.name}` === storageChangeEvent.key) {
2974
const value = this.loadKeyFromStorage(stateKey) ?? stateKey.defaultValue;
2975
if (this.stateCache.get(stateKey.name) !== value) {
2976
this.stateCache.set(stateKey.name, value);
2977
this._onDidChangeState.fire({ key: stateKey, value });
2978
}
2979
}
2980
}
2981
}
2982
}));
2983
}
2984
2985
private applyOverrides(configuration: ILayoutStateLoadConfiguration): void {
2986
2987
// Auxiliary bar: Maximized settings
2988
if (this.isNew[StorageScope.WORKSPACE]) {
2989
const defaultAuxiliaryBarVisibility = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_DEFAULT_VISIBILITY);
2990
const startupEditor = this.configurationService.getValue<'none' | 'welcomePage' | 'readme' | 'newUntitledFile' | 'welcomePageInEmptyWorkbench' | 'terminal' | 'agentSessionsWelcomePage'>('workbench.startupEditor');
2991
if (startupEditor === 'agentSessionsWelcomePage') {
2992
this.applyAuxiliaryBarHiddenOverride(true);
2993
} else if (
2994
defaultAuxiliaryBarVisibility === 'maximized' ||
2995
(defaultAuxiliaryBarVisibility === 'maximizedInWorkspace' && this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY)
2996
) {
2997
this.applyAuxiliaryBarMaximizedOverride();
2998
}
2999
}
3000
3001
// Both editor and panel should not be hidden on startup unless auxiliary bar is maximized
3002
if (
3003
this.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) &&
3004
this.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN) &&
3005
!this.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_WAS_LAST_MAXIMIZED)
3006
) {
3007
this.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, false);
3008
}
3009
3010
// Restrict auxiliary bar size in case of small window dimensions
3011
if (this.isNew[StorageScope.WORKSPACE] && configuration.mainContainerDimension.width <= DEFAULT_WORKSPACE_WINDOW_DIMENSIONS.width) {
3012
this.setInitializationValue(LayoutStateKeys.SIDEBAR_SIZE, Math.min(300, configuration.mainContainerDimension.width / 4));
3013
this.setInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE, Math.min(300, configuration.mainContainerDimension.width / 4));
3014
}
3015
}
3016
3017
private applyAuxiliaryBarMaximizedOverride(): void {
3018
this.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_VISIBILITY, {
3019
sideBarVisible: !this.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN),
3020
panelVisible: !this.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN),
3021
editorVisible: !this.getRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN),
3022
auxiliaryBarVisible: !this.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN)
3023
});
3024
3025
this.setRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN, true);
3026
this.setRuntimeValue(LayoutStateKeys.PANEL_HIDDEN, true);
3027
this.setRuntimeValue(LayoutStateKeys.EDITOR_HIDDEN, true);
3028
this.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, false);
3029
3030
this.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_LAST_NON_MAXIMIZED_SIZE, this.getInitializationValue(LayoutStateKeys.AUXILIARYBAR_SIZE));
3031
this.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_WAS_LAST_MAXIMIZED, true);
3032
}
3033
3034
private applyAuxiliaryBarHiddenOverride(value: boolean): void {
3035
this.setRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN, value);
3036
}
3037
3038
save(workspace: boolean, global: boolean): void {
3039
let key: keyof typeof LayoutStateKeys;
3040
3041
const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE);
3042
3043
for (key in LayoutStateKeys) {
3044
const stateKey = LayoutStateKeys[key] as WorkbenchLayoutStateKey<StorageKeyType>;
3045
if ((workspace && stateKey.scope === StorageScope.WORKSPACE) ||
3046
(global && stateKey.scope === StorageScope.PROFILE)) {
3047
if (isZenMode && stateKey instanceof RuntimeStateKey && stateKey.zenModeIgnore) {
3048
continue; // Don't write out specific keys while in zen mode
3049
}
3050
3051
this.saveKeyToStorage(stateKey);
3052
}
3053
}
3054
}
3055
3056
getInitializationValue<T extends StorageKeyType>(key: InitializationStateKey<T>): T {
3057
return this.stateCache.get(key.name) as T;
3058
}
3059
3060
setInitializationValue<T extends StorageKeyType>(key: InitializationStateKey<T>, value: T): void {
3061
this.stateCache.set(key.name, value);
3062
}
3063
3064
getRuntimeValue<T extends StorageKeyType>(key: RuntimeStateKey<T>, fallbackToSetting?: boolean): T {
3065
if (fallbackToSetting) {
3066
switch (key) {
3067
case LayoutStateKeys.ACTIVITYBAR_HIDDEN:
3068
this.stateCache.set(key.name, this.isActivityBarHidden());
3069
break;
3070
case LayoutStateKeys.STATUSBAR_HIDDEN:
3071
this.stateCache.set(key.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE));
3072
break;
3073
case LayoutStateKeys.SIDEBAR_POSITON:
3074
this.stateCache.set(key.name, this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left');
3075
break;
3076
}
3077
}
3078
3079
return this.stateCache.get(key.name) as T;
3080
}
3081
3082
setRuntimeValue<T extends StorageKeyType>(key: RuntimeStateKey<T>, value: T): void {
3083
this.stateCache.set(key.name, value);
3084
const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE);
3085
3086
if (key.scope === StorageScope.PROFILE) {
3087
if (!isZenMode || !key.zenModeIgnore) {
3088
this.saveKeyToStorage<T>(key);
3089
this.updateLegacySettingsFromState(key, value);
3090
}
3091
}
3092
}
3093
3094
private isActivityBarHidden(): boolean {
3095
const oldValue = this.configurationService.getValue<boolean | undefined>(WorkbenchLayoutSettings.ACTIVITY_BAR_VISIBLE);
3096
if (oldValue !== undefined) {
3097
return !oldValue;
3098
}
3099
3100
return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.DEFAULT;
3101
}
3102
3103
private setRuntimeValueAndFire<T extends StorageKeyType>(key: RuntimeStateKey<T>, value: T): void {
3104
const previousValue = this.stateCache.get(key.name);
3105
if (previousValue === value) {
3106
return;
3107
}
3108
3109
this.setRuntimeValue(key, value);
3110
this._onDidChangeState.fire({ key, value });
3111
}
3112
3113
private saveKeyToStorage<T extends StorageKeyType>(key: WorkbenchLayoutStateKey<T>): void {
3114
const value = this.stateCache.get(key.name) as T;
3115
this.storageService.store(`${LayoutStateModel.STORAGE_PREFIX}${key.name}`, typeof value === 'object' ? JSON.stringify(value) : value, key.scope, key.target);
3116
}
3117
3118
private loadKeyFromStorage<T extends StorageKeyType>(key: WorkbenchLayoutStateKey<T>): T | undefined {
3119
const value = this.storageService.get(`${LayoutStateModel.STORAGE_PREFIX}${key.name}`, key.scope);
3120
if (value !== undefined) {
3121
this.isNew[key.scope] = false; // remember that we had previous state for this scope
3122
3123
switch (typeof key.defaultValue) {
3124
case 'boolean': return (value === 'true') as T;
3125
case 'number': return parseInt(value) as T;
3126
case 'object': return JSON.parse(value) as T;
3127
}
3128
}
3129
3130
return value as T | undefined;
3131
}
3132
}
3133
3134
//#endregion
3135
3136