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