diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 9957a797e02..a09f623d72e 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -13,14 +13,14 @@ import { IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor' import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { StartupKind, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; +import { getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -50,49 +50,130 @@ import { Schemas } from 'vs/base/common/network'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart, AUXILIARYBAR_ENABLED } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; +import { mixin } from 'vs/base/common/objects'; -type PanelAlignment = 'left' | 'center' | 'right' | 'justified'; - -export enum Settings { - ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', - STATUSBAR_VISIBLE = 'workbench.statusBar.visible', - - SIDEBAR_POSITION = 'workbench.sideBar.location', - PANEL_POSITION = 'workbench.panel.defaultLocation', +enum WorkbenchLayoutSettings { PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', - PANEL_ALIGNMENT = 'workbench.experimental.panel.alignment', - - ZEN_MODE_RESTORE = 'zenMode.restore', + ZEN_MODE_CONFIG = 'zenMode', + ZEN_MODE_SILENT_NOTIFICATIONS = 'zenMode.silentNotifications', + EDITOR_CENTERED_LAYOUT_AUTO_RESIZE = 'workbench.editor.centeredLayoutAutoResize', } -enum Storage { - SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', - SIDEBAR_SIZE = 'workbench.sidebar.size', - - AUXILIARYBAR_HIDDEN = 'workbench.auxiliarybar.hidden', - AUXILIARYBAR_SIZE = 'workbench.auxiliarybar.size', - - PANEL_HIDDEN = 'workbench.panel.hidden', - PANEL_POSITION = 'workbench.panel.location', - PANEL_SIZE = 'workbench.panel.size', - PANEL_DIMENSION = 'workbench.panel.dimension', - PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', - PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', - PANEL_LAST_IS_MAXIMIZED = 'workbench.panel.lastIsMaximized', - - EDITOR_HIDDEN = 'workbench.editor.hidden', - - ZEN_MODE_ENABLED = 'workbench.zenmode.active', - CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', - - GRID_LAYOUT = 'workbench.grid.layout', - GRID_WIDTH = 'workbench.grid.width', - GRID_HEIGHT = 'workbench.grid.height', - - MENU_VISIBILITY = 'window.menuBarVisibility' +enum LegacyWorkbenchLayoutSettings { + PANEL_POSITION = 'workbench.panel.defaultLocation', // Deprecated to UI State + ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', // Deprecated to UI State + STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State + SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State + PANEL_ALIGNMENT = 'workbench.experimental.panel.alignment', // Deprecated to UI State } -enum Classes { +enum WorkbenchLayoutStorageKeys { + WORKSPACE_STATE_RUNTIME = 'workbench.state.runtime.workspace', + GLOBAL_STATE_RUNTIME = 'workbench.state.runtime.global', + WORKSPACE_STATE_INITIALIZATION = 'workbench.state.initialization.workspace', + GLOBAL_STATE_INITIALIZATION = 'workbench.state.initialization.global', +} + +interface IWorkbenchLayoutWorkspaceInitializationState { + activityBar: { hidden: boolean } + sideBar: { hidden: boolean } + editor: { hidden: boolean } + panel: { + hidden: boolean, + position: Position, + } + auxiliaryBar: { hidden: boolean } + statusBar: { hidden: boolean } +} + +interface IWorkbenchLayoutWorkspaceRuntimeState { + editor: { + centered: boolean, + }, + zenMode: { + active: boolean, + transitionedToFullScreen: boolean, + transitionedToCenteredEditorLayout: boolean, + wasVisible: { + activityBar: boolean, + statusBar: boolean, + sideBar: boolean, + panel: boolean, + auxiliaryBar: boolean, + }, + } +} + +interface IWorkbenchLayoutGlobalInitializationState { + window: { + width: number, + height: number, + }, + sideBar: { + size: number, + position: Position + }, + panel: { + size: number, + alignment: PanelAlignment + }, + auxiliaryBar: { size: number } +} + +interface IWorkbenchLayoutGlobalRuntimeState { + panel: { + lastNonMaximizedWidth: number, + lastNonMaximizedHeight: number, + wasLastMaximized: boolean, + } +} + +interface IWorkbenchLayoutWindowRuntimeState { + fullscreen: boolean, + maximized: boolean, + hasFocus: boolean, + windowBorder: boolean, + menuBar: { + toggled: boolean + }, + zenMode: { + transitionDisposables: DisposableStore + } +} + +interface IWorkbenchLayoutWindowInitializationState { + views: { + defaults: string[] | undefined + containerToRestore: { + sideBar?: string, + panel?: string, + auxiliaryBar?: string, + } + }, + editor: { + restoreEditors: boolean, + editorsToOpen: Promise | IUntypedEditorInput[] + } +} + +interface IWorkbenchLayoutInitializationState { + window: IWorkbenchLayoutWindowInitializationState, + global: IWorkbenchLayoutGlobalInitializationState, + workspace: IWorkbenchLayoutWorkspaceInitializationState, +} + +interface IWorkbenchLayoutRuntimeState { + window: IWorkbenchLayoutWindowRuntimeState, + global: IWorkbenchLayoutGlobalRuntimeState, + workspace: IWorkbenchLayoutWorkspaceRuntimeState +} + +interface IWorkbenchLayoutState { + runtime: IWorkbenchLayoutRuntimeState, + initialization: IWorkbenchLayoutInitializationState, +} + +enum WorkbenchLayoutClasses { SIDEBAR_HIDDEN = 'nosidebar', EDITOR_HIDDEN = 'noeditorarea', PANEL_HIDDEN = 'nopanel', @@ -102,6 +183,8 @@ enum Classes { WINDOW_BORDER = 'border' } +interface IInitialFilesToOpen { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } + export abstract class Layout extends Disposable implements IWorkbenchLayoutService { declare readonly _serviceBrand: undefined; @@ -154,6 +237,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly parts = new Map(); + private _initialized: boolean = false; private workbenchGrid!: SerializableGrid; private disposed: boolean | undefined; @@ -184,71 +268,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private statusBarService!: IStatusbarService; private logService!: ILogService; - protected readonly state = { - fullscreen: false, - maximized: false, - hasFocus: false, - windowBorder: false, - - menuBar: { - visibility: 'classic' as MenuBarVisibility, - toggled: false - }, - - activityBar: { - hidden: false - }, - - sideBar: { - hidden: false, - position: Position.LEFT, - width: 300, - viewletToRestore: undefined as string | undefined - }, - - editor: { - hidden: false, - centered: false, - restoreCentered: false, - restoreEditors: false, - editorsToOpen: [] as Promise | IUntypedEditorInput[] - }, - - panel: { - hidden: false, - position: Position.BOTTOM, - lastNonMaximizedWidth: 300, - lastNonMaximizedHeight: 300, - wasLastMaximized: false, - panelToRestore: undefined as string | undefined, - alignment: 'center' as PanelAlignment - }, - - auxiliaryBar: { - hidden: false, - panelToRestore: undefined as string | undefined - }, - - statusBar: { - hidden: false - }, - - views: { - defaults: undefined as (string[] | undefined) - }, - - zenMode: { - active: false, - restore: false, - transitionedToFullScreen: false, - transitionedToCenteredEditorLayout: false, - wasSideBarVisible: false, - wasPanelVisible: false, - wasAuxiliaryBarPartVisible: false, - transitionDisposables: new DisposableStore(), - setNotificationsFilter: false, - } - }; + private state!: IWorkbenchLayoutState; constructor( protected readonly parent: HTMLElement @@ -290,7 +310,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore editor if hidden const showEditorIfHidden = () => { - if (this.state.editor.hidden) { + if (!this.isVisible(Parts.EDITOR_PART)) { this.toggleMaximizedPanel(); } }; @@ -303,7 +323,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); // Revalidate center layout when active editor changes: diff editor quits centered mode. - this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.state.editor.centered))); + this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.state.runtime.workspace.editor.centered))); // Configuration changes this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); @@ -312,8 +332,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); // Group changes - this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.state.editor.centered))); - this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.state.editor.centered))); + this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.state.runtime.workspace.editor.centered))); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.state.runtime.workspace.editor.centered))); // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); @@ -331,16 +351,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private onMenubarToggled(visible: boolean) { - if (visible !== this.state.menuBar.toggled) { - this.state.menuBar.toggled = visible; + if (visible !== this.state.runtime.window.menuBar.toggled) { + this.state.runtime.window.menuBar.toggled = visible; + const menuBarVisibility = getMenuBarVisibility(this.configurationService); // The menu bar toggles the title bar in web because it does not need to be shown for window controls only - if (isWeb && this.state.menuBar.visibility === 'toggle') { + if (isWeb && menuBarVisibility === 'toggle') { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } // The menu bar toggles the title bar in full screen for toggle and classic settings - else if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'classic')) { + else if (this.state.runtime.window.fullscreen && (menuBarVisibility === 'toggle' || menuBarVisibility === 'classic')) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } @@ -352,21 +373,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private onFullscreenChanged(): void { - this.state.fullscreen = isFullscreen(); + this.state.runtime.window.fullscreen = isFullscreen(); // Apply as CSS class - if (this.state.fullscreen) { - this.container.classList.add(Classes.FULLSCREEN); + if (this.state.runtime.window.fullscreen) { + this.container.classList.add(WorkbenchLayoutClasses.FULLSCREEN); } else { - this.container.classList.remove(Classes.FULLSCREEN); + this.container.classList.remove(WorkbenchLayoutClasses.FULLSCREEN); - if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { + if (this.state.runtime.workspace.zenMode.transitionedToFullScreen && this.state.runtime.workspace.zenMode.active) { this.toggleZenMode(); } } // Change edge snapping accordingly - this.workbenchGrid.edgeSnapping = this.state.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.window.fullscreen; // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService) === 'custom') { @@ -376,69 +397,67 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.updateWindowBorder(true); } - this._onDidChangeFullscreen.fire(this.state.fullscreen); + this._onDidChangeFullscreen.fire(this.state.runtime.window.fullscreen); } private onWindowFocusChanged(hasFocus: boolean): void { - if (this.state.hasFocus === hasFocus) { + if (this.state.runtime.window.hasFocus === hasFocus) { return; } - this.state.hasFocus = hasFocus; + this.state.runtime.window.hasFocus = hasFocus; this.updateWindowBorder(); } private doUpdateLayoutConfiguration(skipLayout?: boolean): void { // Sidebar position - const newSidebarPositionValue = this.configurationService.getValue(Settings.SIDEBAR_POSITION); - const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; - if (newSidebarPosition !== this.getSideBarPosition()) { - this.setSideBarPosition(newSidebarPosition); - } + // const newSidebarPositionValue = this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION); + // const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; + // if (newSidebarPosition !== this.getSideBarPosition()) { + // this.setSideBarPosition(newSidebarPosition); + // } // Panel position - this.updatePanelPosition(); + // this.updatePanelPosition(); // Panel alignment - const newPanelAlignmentValue = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; - if (newPanelAlignmentValue !== this.state.panel.alignment) { - this.setPanelAlignment(newPanelAlignmentValue, skipLayout); - } + // const newPanelAlignmentValue = this.configurationService.getValue(LegacyWorkbenchLayoutSettings.PANEL_ALIGNMENT) ?? 'center'; + // if (newPanelAlignmentValue !== this.getPanelAlignment()) { + // this.setPanelAlignment(newPanelAlignmentValue, skipLayout); + // } - if (!this.state.zenMode.active) { + // if (!this.state.runtime.workspace.zenMode.active) { - // Statusbar visibility - const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); - if (newStatusbarHiddenValue !== this.state.statusBar.hidden) { - this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); - } + // // Statusbar visibility + // const newStatusbarHiddenValue = !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE); + // if (newStatusbarHiddenValue !== this.isVisible(Parts.STATUSBAR_PART)) { + // this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); + // } - // Activitybar visibility - const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); - if (newActivityBarHiddenValue !== this.state.activityBar.hidden) { - this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); - } - } + // // Activitybar visibility + // const newActivityBarHiddenValue = !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE); + // if (newActivityBarHiddenValue !== this.isVisible(Parts.ACTIVITYBAR_PART)) { + // this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); + // } + // } // Menubar visibility - const newMenubarVisibility = getMenuBarVisibility(this.configurationService); - this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); + this.updateMenubarVisibility(!!skipLayout); // Centered Layout - this.centerEditorLayout(this.state.editor.centered, skipLayout); + this.centerEditorLayout(this.state.runtime.workspace.editor.centered, skipLayout); } private setSideBarPosition(position: Position): void { const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); const auxiliaryBar = this.getPart(Parts.AUXILIARYBAR_PART); - const wasHidden = this.state.sideBar.hidden; const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; - const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right'; - this.state.sideBar.position = position; + const oldPositionValue = (this.getSideBarPosition() === Position.LEFT) ? 'left' : 'right'; + const panelAlignment = this.getPanelAlignment(); // Adjust CSS const activityBarContainer = assertIsDefined(activityBar.getContainer()); @@ -458,14 +477,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi sideBar.updateStyles(); auxiliaryBar.updateStyles(); - // Layout - if (!wasHidden) { - this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; - } - // Move activity bar, side bar, and side panel - this.adjustPartPositions(position, this.state.panel.alignment); - // this.layout(); + this.adjustPartPositions(position, panelAlignment); } private updateWindowBorder(skipLayout: boolean = false) { @@ -479,21 +492,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); let windowBorder = false; - if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { + if (!this.state.runtime.window.fullscreen && !this.state.runtime.window.maximized && (activeBorder || inactiveBorder)) { windowBorder = true; // If the inactive color is missing, fallback to the active one - const borderColor = this.state.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; + const borderColor = this.state.runtime.window.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; this.container.style.setProperty('--window-border-color', borderColor?.toString() ?? 'transparent'); } - if (windowBorder === this.state.windowBorder) { + if (windowBorder === this.state.runtime.window.windowBorder) { return; } - this.state.windowBorder = windowBorder; + this.state.runtime.window.windowBorder = windowBorder; - this.container.classList.toggle(Classes.WINDOW_BORDER, windowBorder); + this.container.classList.toggle(WorkbenchLayoutClasses.WINDOW_BORDER, windowBorder); if (!skipLayout) { this.layout(); @@ -505,134 +518,204 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { + const workbenchDimensions = this.getClientArea(); - // Default Layout - this.applyDefaultLayout(this.environmentService, this.storageService); + // Window Initialization State + const initialFilesToOpen = this.getInitialFilesToOpen(); + const windowInitializationState: IWorkbenchLayoutWindowInitializationState = { + editor: { + restoreEditors: this.shouldRestoreEditors(this.contextService, initialFilesToOpen), + editorsToOpen: this.resolveEditorsToOpen(fileService, this.contextService, initialFilesToOpen), + }, + views: { + defaults: this.getDefaultLayoutViews(this.environmentService, this.storageService), + containerToRestore: {} + }, + }; - // Fullscreen - this.state.fullscreen = isFullscreen(); + // Window Runtime State + const windowRuntimeState: IWorkbenchLayoutWindowRuntimeState = { + fullscreen: isFullscreen(), + hasFocus: this.hostService.hasFocus, + maximized: false, + windowBorder: false, + menuBar: { + toggled: false, + }, + zenMode: { + transitionDisposables: new DisposableStore(), + } + }; - // Menubar visibility - this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService); + // Workspace Initialization State + const defaultWorkspaceInitializationState: IWorkbenchLayoutWorkspaceInitializationState = { + activityBar: { hidden: false }, + auxiliaryBar: { hidden: true }, + editor: { hidden: false }, + panel: { + hidden: true, + position: Position.BOTTOM + }, + sideBar: { hidden: false }, + statusBar: { hidden: false }, + }; - // Activity bar visibility - this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); + const workspaceInitializationStateSerialized = this.storageService.get(WorkbenchLayoutStorageKeys.WORKSPACE_STATE_INITIALIZATION, StorageScope.WORKSPACE, '{}'); + const workspaceInitializationState: IWorkbenchLayoutWorkspaceInitializationState = mixin(JSON.parse(workspaceInitializationStateSerialized), defaultWorkspaceInitializationState); - // Sidebar visibility - this.state.sideBar.hidden = this.storageService.getBoolean(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); + // Workspace Runtime State + const defaultWorkspaceRuntimeState: IWorkbenchLayoutWorkspaceRuntimeState = { + editor: { + centered: false, + }, + zenMode: { + active: false, + transitionedToCenteredEditorLayout: false, + transitionedToFullScreen: false, + wasVisible: { + auxiliaryBar: false, + panel: false, + sideBar: false, + activityBar: true, + statusBar: true, + }, + } + }; - // Sidebar position - this.state.sideBar.position = (this.configurationService.getValue(Settings.SIDEBAR_POSITION) === 'right') ? Position.RIGHT : Position.LEFT; + const workspaceRuntimeStateSerialized = this.storageService.get(WorkbenchLayoutStorageKeys.WORKSPACE_STATE_RUNTIME, StorageScope.WORKSPACE, '{}'); + const workspaceRuntimeState: IWorkbenchLayoutWorkspaceRuntimeState = mixin(JSON.parse(workspaceRuntimeStateSerialized), defaultWorkspaceRuntimeState); - // Sidebar viewlet - if (!this.state.sideBar.hidden) { + // Global Initialization State + const defaultGlobalInitializationState: IWorkbenchLayoutGlobalInitializationState = { + window: { + height: workbenchDimensions.height, + width: workbenchDimensions.width, + }, + auxiliaryBar: { + size: Math.min(300, workbenchDimensions.width / 4) + }, + panel: { + size: workspaceInitializationState.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4, + alignment: 'center', + }, + sideBar: { + position: Position.LEFT, + size: Math.min(300, workbenchDimensions.width / 4) + } + }; + const globalInitializationStateSerialized = this.storageService.get(WorkbenchLayoutStorageKeys.GLOBAL_STATE_INITIALIZATION, StorageScope.GLOBAL, '{}'); + const globalInitializationState: IWorkbenchLayoutGlobalInitializationState = mixin(JSON.parse(globalInitializationStateSerialized), defaultGlobalInitializationState); + + // Global Runtime State + const defaultGlobalRuntimeState: IWorkbenchLayoutGlobalRuntimeState = { + panel: { + lastNonMaximizedHeight: 300, + lastNonMaximizedWidth: 300, + wasLastMaximized: false, + } + }; + + const globalStateSerialized = this.storageService.get(WorkbenchLayoutStorageKeys.GLOBAL_STATE_RUNTIME, StorageScope.GLOBAL, '{}'); + const globalRuntimeState: IWorkbenchLayoutGlobalRuntimeState = mixin(JSON.parse(globalStateSerialized), defaultGlobalRuntimeState); + + this.state = { + initialization: { + window: windowInitializationState, + global: globalInitializationState, + workspace: workspaceInitializationState, + }, + runtime: { + window: windowRuntimeState, + global: globalRuntimeState, + workspace: workspaceRuntimeState, + } + }; + + // Sidebar View Container To Restore + if (this.isVisible(Parts.SIDEBAR_PART)) { // Only restore last viewlet if window was reloaded or we are in development mode - let viewletToRestore: string | undefined; + let viewContainerToRestore: string | undefined; if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { - viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); + viewContainerToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id); } else { - viewletToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; + viewContainerToRestore = this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id; } - if (viewletToRestore) { - this.state.sideBar.viewletToRestore = viewletToRestore; + if (viewContainerToRestore) { + this.state.initialization.window.views.containerToRestore.sideBar = viewContainerToRestore; } else { - this.state.sideBar.hidden = true; // we hide sidebar if there is no viewlet to restore + this.state.initialization.workspace.sideBar.hidden = true; } } - // Editor visibility - this.state.editor.hidden = this.storageService.getBoolean(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE, false); + // Panel View Container To Restore + if (this.isVisible(Parts.PANEL_PART)) { + let viewContainerToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id); - // Editor centered layout - this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false); - - // Editors to open - this.state.editor.editorsToOpen = this.resolveEditorsToOpen(fileService, this.contextService); - - // Panel visibility - this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true); - - // Whether or not the panel was last maximized - this.state.panel.wasLastMaximized = this.storageService.getBoolean(Storage.PANEL_LAST_IS_MAXIMIZED, StorageScope.WORKSPACE, false); - - // Panel position - this.updatePanelPosition(); - - this.state.panel.alignment = this.configurationService.getValue(Settings.PANEL_ALIGNMENT) ?? 'center'; - - // Panel to restore - if (!this.state.panel.hidden) { - let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id); - - if (panelToRestore) { - this.state.panel.panelToRestore = panelToRestore; + if (viewContainerToRestore) { + this.state.initialization.window.views.containerToRestore.panel = viewContainerToRestore; } else { - this.state.panel.hidden = true; // we hide panel if there is no panel to restore + this.state.initialization.workspace.panel.hidden = true; } } - // Auxiliary Bar visibility - this.state.auxiliaryBar.hidden = !this.configurationService.getValue(AUXILIARYBAR_ENABLED) || this.storageService.getBoolean(Storage.AUXILIARYBAR_HIDDEN, StorageScope.WORKSPACE, true); - // Auxiliary Panel to restore - if (!this.state.auxiliaryBar.hidden) { - let auxiliaryPanelToRestore = this.storageService.get(AuxiliaryBarPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id); + if (this.configurationService.getValue(AUXILIARYBAR_ENABLED) && this.isVisible(Parts.AUXILIARYBAR_PART)) { + let viewContainerToRestore = this.storageService.get(AuxiliaryBarPart.activePanelSettingsKey, StorageScope.WORKSPACE, this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id); - if (auxiliaryPanelToRestore) { - this.state.auxiliaryBar.panelToRestore = auxiliaryPanelToRestore; + if (viewContainerToRestore) { + this.state.initialization.window.views.containerToRestore.auxiliaryBar = viewContainerToRestore; } else { - this.state.auxiliaryBar.hidden = true; // we hide panel if there is no auxiliary panel to restore + this.state.initialization.workspace.auxiliaryBar.hidden = true; } } - // Panel size before maximized - this.state.panel.lastNonMaximizedHeight = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, StorageScope.GLOBAL, 300); - this.state.panel.lastNonMaximizedWidth = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, StorageScope.GLOBAL, 300); - - // Statusbar visibility - this.state.statusBar.hidden = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); - - // Zen mode enablement - this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); - - this.state.hasFocus = this.hostService.hasFocus; + // Hide Auxiliary Bar if disabled + if (!this.configurationService.getValue(AUXILIARYBAR_ENABLED)) { + this.state.initialization.workspace.auxiliaryBar.hidden = true; + } // Window border this.updateWindowBorder(true); } - private applyDefaultLayout(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService) { + private getDefaultLayoutViews(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService): string[] | undefined { const defaultLayout = environmentService.options?.defaultLayout; if (!defaultLayout) { - return; + return undefined; } if (!defaultLayout.force && !storageService.isNew(StorageScope.WORKSPACE)) { - return; + return undefined; } const { views } = defaultLayout; if (views?.length) { - this.state.views.defaults = views.map(view => view.id); + return views.map(view => view.id); } + + return undefined; } - private resolveEditorsToOpen(fileService: IFileService, contextService: IWorkspaceContextService): Promise | IUntypedEditorInput[] { - const initialFilesToOpen = this.getInitialFilesToOpen(); - + private shouldRestoreEditors(contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): boolean { // Restore editors based on a set of rules: // - never when running in web on `tmp` scheme // - not when we have files to open, unless: // - always when `window.restoreWindows: preserve` if (isWeb && getVirtualWorkspaceScheme(contextService.getWorkspace()) === Schemas.tmp) { - this.state.editor.restoreEditors = false; + return false; } else { const forceRestoreEditors = this.configurationService.getValue('window.restoreWindows') === 'preserve'; - this.state.editor.restoreEditors = !!forceRestoreEditors || initialFilesToOpen === undefined; + return !!forceRestoreEditors || initialFilesToOpen === undefined; } + } + protected willRestoreEditors(): boolean { + return this.state.initialization.window.editor.restoreEditors; + } + + private resolveEditorsToOpen(fileService: IFileService, contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise | IUntypedEditorInput[] { // Files to open, diff or create if (initialFilesToOpen) { @@ -674,7 +757,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private _openedDefaultEditors: boolean = false; get openedDefaultEditors() { return this._openedDefaultEditors; } - private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[]; } | undefined { + private getInitialFilesToOpen(): IInitialFilesToOpen | undefined { // Check for editors from `defaultLayout` options first const defaultLayout = this.environmentService.options?.defaultLayout; @@ -742,10 +825,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // not need to await the editors from having // fully loaded. let editors: IUntypedEditorInput[]; - if (Array.isArray(this.state.editor.editorsToOpen)) { - editors = this.state.editor.editorsToOpen; + if (Array.isArray(this.state.initialization.window.editor.editorsToOpen)) { + editors = this.state.initialization.window.editor.editorsToOpen; } else { - editors = await this.state.editor.editorsToOpen; + editors = await this.state.initialization.window.editor.editorsToOpen; } let openEditorsPromise: Promise | undefined = undefined; @@ -770,7 +853,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restore default views (only when `IDefaultLayout` is provided) const restoreDefaultViewsPromise = (async () => { - if (this.state.views.defaults?.length) { + if (this.state.initialization.window.views.defaults?.length) { mark('code/willOpenDefaultViews'); const locationsRestored: { id: string; order: number; }[] = []; @@ -795,7 +878,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; }; - const defaultViews = [...this.state.views.defaults].reverse().map((v, index) => ({ id: v, order: index })); + const defaultViews = [...this.state.initialization.window.views.defaults].reverse().map((v, index) => ({ id: v, order: index })); let i = defaultViews.length; while (i) { @@ -820,17 +903,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // If we opened a view in the sidebar, stop any restore there if (locationsRestored[ViewContainerLocation.Sidebar]) { - this.state.sideBar.viewletToRestore = locationsRestored[ViewContainerLocation.Sidebar].id; + this.state.initialization.window.views.containerToRestore.sideBar = locationsRestored[ViewContainerLocation.Sidebar].id; } // If we opened a view in the panel, stop any restore there if (locationsRestored[ViewContainerLocation.Panel]) { - this.state.panel.panelToRestore = locationsRestored[ViewContainerLocation.Panel].id; + this.state.initialization.window.views.containerToRestore.panel = locationsRestored[ViewContainerLocation.Panel].id; } // If we opened a view in the auxiliary bar, stop any restore there if (locationsRestored[ViewContainerLocation.AuxiliaryBar]) { - this.state.auxiliaryBar.panelToRestore = locationsRestored[ViewContainerLocation.AuxiliaryBar].id; + this.state.initialization.window.views.containerToRestore.auxiliaryBar = locationsRestored[ViewContainerLocation.AuxiliaryBar].id; } mark('code/didOpenDefaultViews'); @@ -844,13 +927,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that sidebar already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.sideBar.viewletToRestore) { + if (!this.state.initialization.window.views.containerToRestore.sideBar) { return; } mark('code/willRestoreViewlet'); - const viewlet = await this.paneCompositeService.openPaneComposite(this.state.sideBar.viewletToRestore, ViewContainerLocation.Sidebar); + const viewlet = await this.paneCompositeService.openPaneComposite(this.state.initialization.window.views.containerToRestore.sideBar, ViewContainerLocation.Sidebar); if (!viewlet) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Sidebar)?.id, ViewContainerLocation.Sidebar); // fallback to default viewlet as needed } @@ -864,13 +947,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that panel already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.panel.panelToRestore) { + if (!this.state.initialization.window.views.containerToRestore.panel) { return; } mark('code/willRestorePanel'); - const panel = await this.paneCompositeService.openPaneComposite(this.state.panel.panelToRestore, ViewContainerLocation.Panel); + const panel = await this.paneCompositeService.openPaneComposite(this.state.initialization.window.views.containerToRestore.panel, ViewContainerLocation.Panel); if (!panel) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.Panel)?.id, ViewContainerLocation.Panel); // fallback to default panel as needed } @@ -884,13 +967,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Restoring views could mean that panel already // restored, as such we need to test again await restoreDefaultViewsPromise; - if (!this.state.auxiliaryBar.panelToRestore) { + if (!this.state.initialization.window.views.containerToRestore.auxiliaryBar) { return; } mark('code/willRestoreAuxiliaryBar'); - const panel = await this.paneCompositeService.openPaneComposite(this.state.auxiliaryBar.panelToRestore, ViewContainerLocation.AuxiliaryBar); + const panel = await this.paneCompositeService.openPaneComposite(this.state.initialization.window.views.containerToRestore.auxiliaryBar, ViewContainerLocation.AuxiliaryBar); if (!panel) { await this.paneCompositeService.openPaneComposite(this.viewDescriptorService.getDefaultViewContainer(ViewContainerLocation.AuxiliaryBar)?.id, ViewContainerLocation.AuxiliaryBar); // fallback to default panel as needed } @@ -899,12 +982,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi })()); // Restore Zen Mode - if (this.state.zenMode.restore) { + if (this.state.runtime.workspace.zenMode.active && getZenModeConfiguration(this.configurationService).restore) { this.toggleZenMode(false, true); } // Restore Editor Center Mode - if (this.state.editor.restoreCentered) { + if (this.state.runtime.workspace.editor.centered) { this.centerEditorLayout(true, true); } @@ -920,13 +1003,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi }); } - private updatePanelPosition() { - const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); - const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - - this.state.panel.position = positionFromString(panelPosition || defaultPanelPosition); - } - registerPart(part: Part): void { this.parts.set(part.getId(), part); } @@ -987,77 +1063,89 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getContainer(part: Parts): HTMLElement | undefined { - switch (part) { - case Parts.TITLEBAR_PART: - return this.getPart(Parts.TITLEBAR_PART).getContainer(); - case Parts.BANNER_PART: - return this.getPart(Parts.BANNER_PART).getContainer(); - case Parts.ACTIVITYBAR_PART: - return this.getPart(Parts.ACTIVITYBAR_PART).getContainer(); - case Parts.SIDEBAR_PART: - return this.getPart(Parts.SIDEBAR_PART).getContainer(); - case Parts.PANEL_PART: - return this.getPart(Parts.PANEL_PART).getContainer(); - case Parts.AUXILIARYBAR_PART: - return this.getPart(Parts.AUXILIARYBAR_PART).getContainer(); - case Parts.EDITOR_PART: - return this.getPart(Parts.EDITOR_PART).getContainer(); - case Parts.STATUSBAR_PART: - return this.getPart(Parts.STATUSBAR_PART).getContainer(); + if (!this.parts.get(part)) { + return undefined; } + + return this.getPart(part).getContainer(); } isVisible(part: Parts): boolean { + if (this._initialized) { + switch (part) { + case Parts.TITLEBAR_PART: + return this.workbenchGrid.isViewVisible(this.titleBarPartView); + case Parts.SIDEBAR_PART: + return this.workbenchGrid.isViewVisible(this.sideBarPartView); + case Parts.PANEL_PART: + return this.workbenchGrid.isViewVisible(this.panelPartView); + case Parts.AUXILIARYBAR_PART: + return this.workbenchGrid.isViewVisible(this.auxiliaryBarPartView); + case Parts.STATUSBAR_PART: + return this.workbenchGrid.isViewVisible(this.statusBarPartView); + case Parts.ACTIVITYBAR_PART: + return this.workbenchGrid.isViewVisible(this.activityBarPartView); + case Parts.EDITOR_PART: + return this.workbenchGrid.isViewVisible(this.editorPartView); + default: + return false; // any other part cannot be hidden + } + } + switch (part) { case Parts.TITLEBAR_PART: - // Using the native title bar, don't ever show the custom one - if (getTitleBarStyle(this.configurationService) === 'native') { - return false; - } - - // macOS desktop does not need a title bar when full screen - if (isMacintosh && isNative) { - return !this.state.fullscreen; - } - - // non-fullscreen native must show the title bar - if (isNative && !this.state.fullscreen) { - return true; - } - - // remaining behavior is based on menubar visibility - switch (this.state.menuBar.visibility) { - case 'classic': - return !this.state.fullscreen || this.state.menuBar.toggled; - case 'compact': - case 'hidden': - return false; - case 'toggle': - return this.state.menuBar.toggled; - case 'visible': - return true; - default: - return isWeb ? false : !this.state.fullscreen || this.state.menuBar.toggled; - } + return this.shouldShowTitleBar(); case Parts.SIDEBAR_PART: - return !this.state.sideBar.hidden; + return !this.state.initialization.workspace.sideBar.hidden; case Parts.PANEL_PART: - return !this.state.panel.hidden; + return !this.state.initialization.workspace.panel.hidden; case Parts.AUXILIARYBAR_PART: - return !!this.configurationService.getValue(AUXILIARYBAR_ENABLED) && !this.state.auxiliaryBar.hidden; + return !this.state.initialization.workspace.auxiliaryBar.hidden; case Parts.STATUSBAR_PART: - return !this.state.statusBar.hidden; + return !this.state.initialization.workspace.statusBar.hidden; case Parts.ACTIVITYBAR_PART: - return !this.state.activityBar.hidden; + return !this.state.initialization.workspace.activityBar.hidden; case Parts.EDITOR_PART: - return !this.state.editor.hidden; + return !this.state.initialization.workspace.editor.hidden; default: - return true; // any other part cannot be hidden + return false; // any other part cannot be hidden + } + } + + private shouldShowTitleBar(): boolean { + // Using the native title bar, don't ever show the custom one + if (getTitleBarStyle(this.configurationService) === 'native') { + return false; + } + + // macOS desktop does not need a title bar when full screen + if (isMacintosh && isNative) { + return !this.state.runtime.window.fullscreen; + } + + // non-fullscreen native must show the title bar + if (isNative && !this.state.runtime.window.fullscreen) { + return true; + } + + // remaining behavior is based on menubar visibility + switch (getMenuBarVisibility(this.configurationService)) { + case 'classic': + return !this.state.runtime.window.fullscreen || this.state.runtime.window.menuBar.toggled; + case 'compact': + case 'hidden': + return false; + case 'toggle': + return this.state.runtime.window.menuBar.toggled; + case 'visible': + return true; + default: + return isWeb ? false : !this.state.runtime.window.fullscreen || this.state.runtime.window.menuBar.toggled; } } focus(): void { - this.editorGroupService.activeGroup.focus(); + this.focusPart(Parts.EDITOR_PART); } getDimension(part: Parts): Dimension | undefined { @@ -1065,7 +1153,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getMaximumEditorDimensions(): Dimension { - const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT; + const panelPosition = this.getPanelPosition(); + const isColumn = panelPosition === Position.RIGHT || panelPosition === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + @@ -1084,8 +1173,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } toggleZenMode(skipLayout?: boolean, restoring = false): void { - this.state.zenMode.active = !this.state.zenMode.active; - this.state.zenMode.transitionDisposables.clear(); + this.state.runtime.workspace.zenMode.active = !this.state.runtime.workspace.zenMode.active; + this.state.runtime.window.zenMode.transitionDisposables.clear(); const setLineNumbers = (lineNumbers?: LineNumbersType) => { const setEditorLineNumbers = (editor: IEditor) => { @@ -1116,26 +1205,20 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) let toggleFullScreen = false; + const config = getZenModeConfiguration(this.configurationService); // Zen Mode Active - if (this.state.zenMode.active) { - const config: { - fullScreen: boolean; - centerLayout: boolean; - hideTabs: boolean; - hideActivityBar: boolean; - hideStatusBar: boolean; - hideLineNumbers: boolean; - silentNotifications: boolean; - } = this.configurationService.getValue('zenMode'); + if (this.state.runtime.workspace.zenMode.active) { - toggleFullScreen = !this.state.fullscreen && config.fullScreen && !isIOS; + toggleFullScreen = !this.state.runtime.window.fullscreen && config.fullScreen && !isIOS; - this.state.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; - this.state.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; - this.state.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); - this.state.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); - this.state.zenMode.wasAuxiliaryBarPartVisible = this.isVisible(Parts.AUXILIARYBAR_PART); + this.state.runtime.workspace.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; + this.state.runtime.workspace.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; + this.state.runtime.workspace.zenMode.wasVisible.sideBar = this.isVisible(Parts.SIDEBAR_PART); + this.state.runtime.workspace.zenMode.wasVisible.panel = this.isVisible(Parts.PANEL_PART); + this.state.runtime.workspace.zenMode.wasVisible.auxiliaryBar = this.isVisible(Parts.AUXILIARYBAR_PART); + this.state.runtime.workspace.zenMode.wasVisible.activityBar = this.isVisible(Parts.AUXILIARYBAR_PART); + this.state.runtime.workspace.zenMode.wasVisible.statusBar = this.isVisible(Parts.STATUSBAR_PART); this.setPanelHidden(true, true); this.setAuxiliaryBarHidden(true, true); @@ -1151,21 +1234,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.hideLineNumbers) { setLineNumbers('off'); - this.state.zenMode.transitionDisposables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); + this.state.runtime.window.zenMode.transitionDisposables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); } if (config.hideTabs && this.editorGroupService.partOptions.showTabs) { - this.state.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); + this.state.runtime.window.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } - this.state.zenMode.setNotificationsFilter = config.silentNotifications; if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } - this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { - const silentNotificationsKey = 'zenMode.silentNotifications'; - if (e.affectsConfiguration(silentNotificationsKey)) { - const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; + this.state.runtime.window.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS)) { + const filter = this.configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_SILENT_NOTIFICATIONS) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; this.notificationService.setFilter(filter); } })); @@ -1177,33 +1258,38 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen Mode Inactive else { - if (this.state.zenMode.wasPanelVisible) { + if (this.state.runtime.workspace.zenMode.wasVisible.panel) { this.setPanelHidden(false, true); } - if (this.state.zenMode.wasAuxiliaryBarPartVisible) { + if (this.state.runtime.workspace.zenMode.wasVisible.auxiliaryBar) { this.setAuxiliaryBarHidden(false, true); } - if (this.state.zenMode.wasSideBarVisible) { + if (this.state.runtime.workspace.zenMode.wasVisible.sideBar) { this.setSideBarHidden(false, true); } - if (this.state.zenMode.transitionedToCenteredEditorLayout) { + if (this.state.runtime.workspace.zenMode.wasVisible.activityBar) { + this.setActivityBarHidden(false, true); + } + + if (this.state.runtime.workspace.zenMode.wasVisible.statusBar) { + this.setStatusBarHidden(false, true); + } + + if (this.state.runtime.workspace.zenMode.transitionedToCenteredEditorLayout) { this.centerEditorLayout(false, true); } setLineNumbers(); - // Status bar and activity bar visibility come from settings -> update their visibility. - this.doUpdateLayoutConfiguration(true); - this.focus(); - if (this.state.zenMode.setNotificationsFilter) { - this.notificationService.setFilter(NotificationsFilter.OFF); - } - toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen; + // Clear notifications filter + this.notificationService.setFilter(NotificationsFilter.OFF); + + toggleFullScreen = this.state.runtime.workspace.zenMode.transitionedToFullScreen && this.state.runtime.window.fullscreen; } if (!skipLayout) { @@ -1215,33 +1301,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Event - this._onDidChangeZenMode.fire(this.state.zenMode.active); - - // State - if (this.state.zenMode.active) { - this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE, StorageTarget.USER); - - // Exit zen mode on shutdown unless configured to keep - this.state.zenMode.transitionDisposables.add(this.storageService.onWillSaveState(e => { - if (e.reason === WillSaveStateReason.SHUTDOWN && this.state.zenMode.active) { - if (!this.configurationService.getValue(Settings.ZEN_MODE_RESTORE)) { - this.toggleZenMode(true); // We will not restore zen mode, need to clear all zen mode state changes - } - } - })); - } else { - this.storageService.remove(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE); - } + this._onDidChangeZenMode.fire(this.state.runtime.workspace.zenMode.active); } private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.statusBar.hidden = hidden; - // Adjust CSS if (hidden) { - this.container.classList.add(Classes.STATUSBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.STATUSBAR_HIDDEN); } else { - this.container.classList.remove(Classes.STATUSBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.STATUSBAR_HIDDEN); } // Propagate to grid @@ -1289,7 +1357,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.container.prepend(workbenchGrid.element); this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; - this.workbenchGrid.edgeSnapping = this.state.fullscreen; + this.workbenchGrid.edgeSnapping = this.state.runtime.window.fullscreen; for (const part of [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar, auxiliaryBarPart]) { this._register(part.onDidVisibilityChange((visible) => { @@ -1307,38 +1375,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } this._register(this.storageService.onWillSaveState(() => { - const grid = this.workbenchGrid as SerializableGrid; - - const sideBarSize = this.state.sideBar.hidden - ? grid.getViewCachedVisibleSize(this.sideBarPartView) - : grid.getViewSize(this.sideBarPartView).width; - - this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const auxiliaryBarSize = this.state.auxiliaryBar.hidden - ? grid.getViewCachedVisibleSize(this.auxiliaryBarPartView) - : grid.getViewSize(this.auxiliaryBarPartView).width; - - this.storageService.store(Storage.AUXILIARYBAR_SIZE, auxiliaryBarSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const panelSize = this.state.panel.hidden - ? grid.getViewCachedVisibleSize(this.panelPartView) - : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); - - this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL, StorageTarget.MACHINE); - - // Remember last panel size for both dimensions - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL, StorageTarget.MACHINE); - - const gridSize = grid.getViewSize(); - this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL, StorageTarget.MACHINE); - this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL, StorageTarget.MACHINE); + // save all serializable state + this.saveState(true, true); })); } - getClientArea(): Dimension { + private saveState(global: boolean, workspace: boolean) { + // implement save state + } + + private getClientArea(): Dimension { return getClientArea(this.parent); } @@ -1352,6 +1398,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); + this._initialized = true; // Emit as event this._onDidLayout.fire(this._dimension); @@ -1359,13 +1406,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isEditorLayoutCentered(): boolean { - return this.state.editor.centered; + return this.state.runtime.workspace.editor.centered; } centerEditorLayout(active: boolean, skipLayout?: boolean): void { - this.state.editor.centered = active; - - this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE, StorageTarget.USER); + this.state.runtime.workspace.editor.centered = active; let smartActive = active; const activeEditor = this.editorService.activeEditor; @@ -1394,7 +1439,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - this._onDidChangeCenteredLayout.fire(this.state.editor.centered); + this._onDidChangeCenteredLayout.fire(this.state.runtime.workspace.editor.centered); } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { @@ -1467,8 +1512,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.activityBar.hidden = hidden; - // Propagate to grid this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } @@ -1478,49 +1521,39 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.editor.hidden = hidden; - // Adjust CSS if (hidden) { - this.container.classList.add(Classes.EDITOR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.EDITOR_HIDDEN); } else { - this.container.classList.remove(Classes.EDITOR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.EDITOR_HIDDEN); } // Propagate to grid this.workbenchGrid.setViewVisible(this.editorPartView, !hidden); - // Remember in settings - if (hidden) { - this.storageService.store(Storage.EDITOR_HIDDEN, true, StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE); - } - // The editor and panel cannot be hidden at the same time - if (hidden && this.state.panel.hidden) { + if (hidden && !this.isVisible(Parts.PANEL_PART)) { this.setPanelHidden(false, true); } } getLayoutClasses(): string[] { return coalesce([ - this.state.sideBar.hidden ? Classes.SIDEBAR_HIDDEN : undefined, - this.state.editor.hidden ? Classes.EDITOR_HIDDEN : undefined, - this.state.panel.hidden ? Classes.PANEL_HIDDEN : undefined, - this.state.statusBar.hidden ? Classes.STATUSBAR_HIDDEN : undefined, - this.state.fullscreen ? Classes.FULLSCREEN : undefined + !this.isVisible(Parts.SIDEBAR_PART) ? WorkbenchLayoutClasses.SIDEBAR_HIDDEN : undefined, + !this.isVisible(Parts.EDITOR_PART) ? WorkbenchLayoutClasses.EDITOR_HIDDEN : undefined, + !this.isVisible(Parts.PANEL_PART) ? WorkbenchLayoutClasses.PANEL_HIDDEN : undefined, + !this.isVisible(Parts.AUXILIARYBAR_PART) ? WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN : undefined, + !this.isVisible(Parts.STATUSBAR_PART) ? WorkbenchLayoutClasses.STATUSBAR_HIDDEN : undefined, + this.state.runtime.window.fullscreen ? WorkbenchLayoutClasses.FULLSCREEN : undefined ]); } private setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.state.sideBar.hidden = hidden; - // Adjust CSS if (hidden) { - this.container.classList.add(Classes.SIDEBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.SIDEBAR_HIDDEN); } else { - this.container.classList.remove(Classes.SIDEBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.SIDEBAR_HIDDEN); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1549,14 +1582,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); - - // Remember in settings - const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; - if (hidden !== defaultHidden) { - this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); - } } private hasViews(id: string): boolean { @@ -1577,8 +1602,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Move activity bar, side bar, and side panel const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); - const preMoveSideBarSize = this.state.sideBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; - const preMoveAuxiliaryBarSize = this.state.auxiliaryBar.hidden ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; + const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; + const preMoveAuxiliaryBarSize = !this.isVisible(Parts.AUXILIARYBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; if (sideBarPosition === Position.LEFT) { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); @@ -1616,33 +1641,30 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setPanelAlignment(alignment: PanelAlignment, skipLayout?: boolean): void { - this.state.panel.alignment = alignment; - // Panel alignment only applies to a panel in the bottom position - if (this.state.panel.position !== Position.BOTTOM) { + if (this.getPanelPosition() !== Position.BOTTOM) { return; } - this.adjustPartPositions(this.state.sideBar.position, alignment); + this.adjustPartPositions(this.getSideBarPosition(), alignment); } private setPanelHidden(hidden: boolean, skipLayout?: boolean): void { - const wasHidden = this.state.panel.hidden; - this.state.panel.hidden = hidden; - // Return if not initialized fully #105480 if (!this.workbenchGrid) { return; } + const wasHidden = !this.isVisible(Parts.PANEL_PART); + const isPanelMaximized = this.isPanelMaximized(); const panelOpensMaximized = this.panelOpensMaximized(); // Adjust CSS if (hidden) { - this.container.classList.add(Classes.PANEL_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.PANEL_HIDDEN); } else { - this.container.classList.remove(Classes.PANEL_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.PANEL_HIDDEN); } // If panel part becomes hidden, also hide the current active panel if any @@ -1690,22 +1712,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { // If in process of hiding, remember whether the panel is maximized or not - this.state.panel.wasLastMaximized = isPanelMaximized; - } - // Remember in settings - if (!hidden) { - this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } - else { - this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); - - // Remember this setting only when panel is hiding - if (this.state.panel.wasLastMaximized) { - this.storageService.store(Storage.PANEL_LAST_IS_MAXIMIZED, true, StorageScope.WORKSPACE, StorageTarget.USER); - } - else { - this.storageService.remove(Storage.PANEL_LAST_IS_MAXIMIZED, StorageScope.WORKSPACE); - } + this.state.runtime.global.panel.wasLastMaximized = isPanelMaximized; } if (focusEditor) { @@ -1715,21 +1722,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { const size = this.workbenchGrid.getViewSize(this.panelPartView); + const panelPosition = this.getPanelPosition(); if (!this.isPanelMaximized()) { - if (!this.state.panel.hidden) { - if (this.state.panel.position === Position.BOTTOM) { - this.state.panel.lastNonMaximizedHeight = size.height; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL, StorageTarget.MACHINE); + if (this.isVisible(Parts.PANEL_PART)) { + if (panelPosition === Position.BOTTOM) { + this.state.runtime.global.panel.lastNonMaximizedHeight = size.height; } else { - this.state.panel.lastNonMaximizedWidth = size.width; - this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL, StorageTarget.MACHINE); + this.state.runtime.global.panel.lastNonMaximizedHeight = size.height; } } this.setEditorHidden(true); } else { this.setEditorHidden(false); - this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height }); + this.workbenchGrid.resizeView(this.panelPartView, { + width: panelPosition === Position.BOTTOM ? size.width : this.state.runtime.global.panel.lastNonMaximizedWidth, + height: panelPosition === Position.BOTTOM ? this.state.runtime.global.panel.lastNonMaximizedHeight : size.height + }); } } @@ -1737,8 +1746,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi * Returns whether or not the panel opens maximized */ private panelOpensMaximized() { - const panelOpensMaximized = panelOpensMaximizedFromString(this.configurationService.getValue(Settings.PANEL_OPENS_MAXIMIZED)); - const panelLastIsMaximized = this.state.panel.wasLastMaximized; + const panelOpensMaximized = panelOpensMaximizedFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_OPENS_MAXIMIZED)); + const panelLastIsMaximized = this.state.runtime.global.panel.wasLastMaximized; return panelOpensMaximized === PanelOpensMaximizedOptions.ALWAYS || (panelOpensMaximized === PanelOpensMaximizedOptions.REMEMBER_LAST && panelLastIsMaximized); } @@ -1748,13 +1757,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return; } - this.state.auxiliaryBar.hidden = hidden; - // Adjust CSS if (hidden) { - this.container.classList.add(Classes.AUXILIARYBAR_HIDDEN); + this.container.classList.add(WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN); } else { - this.container.classList.remove(Classes.AUXILIARYBAR_HIDDEN); + this.container.classList.remove(WorkbenchLayoutClasses.AUXILIARYBAR_HIDDEN); } // If auxiliary bar becomes hidden, also hide the current active pane composite if any @@ -1790,14 +1797,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.auxiliaryBarPartView, !hidden); - - // Remember in settings - const defaultHidden = true; - if (hidden !== defaultHidden) { - this.storageService.store(Storage.AUXILIARYBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE, StorageTarget.USER); - } else { - this.storageService.remove(Storage.AUXILIARYBAR_HIDDEN, StorageScope.WORKSPACE); - } } setPartHidden(hidden: boolean, part: Parts): void { @@ -1818,38 +1817,55 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } hasWindowBorder(): boolean { - return this.state.windowBorder; + return this.state.runtime.window.windowBorder; } getWindowBorderWidth(): number { - return this.state.windowBorder ? 2 : 0; + return this.state.runtime.window.windowBorder ? 2 : 0; } getWindowBorderRadius(): string | undefined { - return this.state.windowBorder && isMacintosh ? '5px' : undefined; + return this.state.runtime.window.windowBorder && isMacintosh ? '5px' : undefined; } isPanelMaximized(): boolean { - return this.state.editor.hidden; + return !this.isVisible(Parts.EDITOR_PART); } getSideBarPosition(): Position { - return this.state.sideBar.position; - } + if (this._initialized) { + if (this.workbenchGrid.getNeighborViews(this.sideBarPartView, Direction.Left).some(view => view === this.activityBarPartView)) { + return Position.LEFT; + } - setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void { - if (this.state.menuBar.visibility !== visibility) { - this.state.menuBar.visibility = visibility; - - // Layout - if (!skipLayout && this.workbenchGrid) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + if (this.workbenchGrid.getNeighborViews(this.sideBarPartView, Direction.Right).some(view => view === this.activityBarPartView)) { + return Position.RIGHT; } } + + return this.state.initialization.global.sideBar.position; } - getMenubarVisibility(): MenuBarVisibility { - return this.state.menuBar.visibility; + private getPanelAlignment(): PanelAlignment { + if (this._initialized) { + const viewsAbove = this.workbenchGrid.getNeighborViews(this.panelPartView, Direction.Up); + const numViewsAbove = viewsAbove.length; + switch (numViewsAbove) { + case 1: return 'center'; + case 2: return viewsAbove[0] === this.editorPartView ? 'right' : 'left'; + case 3: return 'justified'; + } + } + + return this.state.initialization.global.panel.alignment; + } + + updateMenubarVisibility(skipLayout: boolean): void { + // Layout + const shouldShowTitleBar = this.shouldShowTitleBar(); + if (!skipLayout && this.workbenchGrid && shouldShowTitleBar !== this.isVisible(Parts.TITLEBAR_PART)) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, shouldShowTitleBar); + } } toggleMenuBar(): void { @@ -1865,25 +1881,37 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi newVisibilityValue = 'classic'; } - this.configurationService.updateValue(Storage.MENU_VISIBILITY, newVisibilityValue); + this.configurationService.updateValue('window.menuBarVisibility', newVisibilityValue); } getPanelPosition(): Position { - return this.state.panel.position; + if (this._initialized) { + if (this.workbenchGrid.getNeighborViews(this.panelPartView, Direction.Up).some(view => view === this.editorPartView)) { + return Position.BOTTOM; + } + + if (this.workbenchGrid.getNeighborViews(this.panelPartView, Direction.Right).some(view => view === this.editorPartView)) { + return Position.LEFT; + } + + if (this.workbenchGrid.getNeighborViews(this.panelPartView, Direction.Left).some(view => view === this.editorPartView)) { + return Position.RIGHT; + } + + return Position.BOTTOM; + } + + return this.state.initialization.workspace.panel.position; } setPanelPosition(position: Position): void { - if (this.state.panel.hidden) { + if (!this.isVisible(Parts.PANEL_PART)) { this.setPanelHidden(false); } const panelPart = this.getPart(Parts.PANEL_PART); - const oldPositionValue = positionToString(this.state.panel.position); + const oldPositionValue = positionToString(this.getPanelPosition()); const newPositionValue = positionToString(position); - this.state.panel.position = position; - - // Save panel position - this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE, StorageTarget.USER); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); @@ -1897,25 +1925,27 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const size = this.workbenchGrid.getViewSize(this.panelPartView); const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + const editorHidden = !this.isVisible(Parts.EDITOR_PART); + // Save last non-maximized size for panel before move - if (newPositionValue !== oldPositionValue && !this.state.editor.hidden) { + if (newPositionValue !== oldPositionValue && !editorHidden) { // Save the current size of the panel for the new orthogonal direction // If moving down, save the width of the panel // Otherwise, save the height of the panel if (position === Position.BOTTOM) { - this.state.panel.lastNonMaximizedWidth = size.width; + this.state.runtime.global.panel.lastNonMaximizedWidth = size.width; } else if (positionFromString(oldPositionValue) === Position.BOTTOM) { - this.state.panel.lastNonMaximizedHeight = size.height; + this.state.runtime.global.panel.lastNonMaximizedHeight = size.height; } } if (position === Position.BOTTOM) { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.state.runtime.global.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); } else if (position === Position.RIGHT) { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.state.runtime.global.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); } else { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left); + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.state.runtime.global.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left); } // Reset sidebar to original size before shifting the panel @@ -1925,15 +1955,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } isWindowMaximized() { - return this.state.maximized; + return this.state.runtime.window.maximized; } updateWindowMaximizedState(maximized: boolean) { - if (this.state.maximized === maximized) { + if (this.state.runtime.window.maximized === maximized) { return; } - this.state.maximized = maximized; + this.state.runtime.window.maximized = maximized; this.updateWindowBorder(); this._onDidChangeWindowMaximized.fire(maximized); @@ -1977,23 +2007,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const result = [nodes.editor]; nodes.editor.size = availableWidth; if (nodes.sideBar) { - if (this.state.sideBar.position === Position.LEFT) { + if (this.state.initialization.global.sideBar.position === Position.LEFT) { result.splice(0, 0, nodes.sideBar); } else { result.push(nodes.sideBar); } - nodes.editor.size -= this.state.sideBar.hidden ? 0 : nodes.sideBar.size; + nodes.editor.size -= this.state.initialization.workspace.sideBar.hidden ? 0 : nodes.sideBar.size; } if (nodes.auxiliaryBar) { - if (this.state.sideBar.position === Position.RIGHT) { + if (this.state.initialization.global.sideBar.position === Position.RIGHT) { result.splice(0, 0, nodes.auxiliaryBar); } else { result.push(nodes.auxiliaryBar); } - nodes.editor.size -= this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; + nodes.editor.size -= this.state.initialization.workspace.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; } return { @@ -2004,22 +2034,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private arrangeMiddleSectionNodes(nodes: { editor: ISerializedNode, panel: ISerializedNode, activityBar: ISerializedNode, sideBar: ISerializedNode, auxiliaryBar: ISerializedNode }, availableWidth: number, availableHeight: number): ISerializedNode[] { - const activityBarSize = this.state.activityBar.hidden ? 0 : nodes.activityBar.size; - const sideBarSize = this.state.sideBar.hidden ? 0 : nodes.sideBar.size; - const auxiliaryBarSize = this.state.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; - const panelSize = this.state.panel.hidden ? 0 : nodes.panel.size; + const activityBarSize = this.state.initialization.workspace.activityBar.hidden ? 0 : nodes.activityBar.size; + const sideBarSize = this.state.initialization.workspace.sideBar.hidden ? 0 : nodes.sideBar.size; + const auxiliaryBarSize = this.state.initialization.workspace.auxiliaryBar.hidden ? 0 : nodes.auxiliaryBar.size; + const panelSize = this.state.initialization.workspace.panel.hidden ? 0 : nodes.panel.size; const result = [] as ISerializedNode[]; - if (this.state.panel.position !== Position.BOTTOM) { + if (this.state.initialization.workspace.panel.position !== Position.BOTTOM) { result.push(nodes.editor); nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; - if (this.state.panel.position === Position.RIGHT) { + if (this.state.initialization.workspace.panel.position === Position.RIGHT) { result.push(nodes.panel); } else { result.splice(0, 0, nodes.panel); } - if (this.state.sideBar.position === Position.LEFT) { + if (this.state.initialization.global.sideBar.position === Position.LEFT) { result.push(nodes.auxiliaryBar); result.splice(0, 0, nodes.sideBar); result.splice(0, 0, nodes.activityBar); @@ -2029,8 +2059,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi result.push(nodes.activityBar); } } else { - const panelAlignment = this.state.panel.alignment; - const sideBarPosition = this.state.sideBar.position; + const panelAlignment = this.state.initialization.global.panel.alignment; + const sideBarPosition = this.state.initialization.global.sideBar.position; const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); @@ -2072,14 +2102,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private createGridDescriptor(): ISerializedGrid { - const workbenchDimensions = this.getClientArea(); - const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); - const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); - const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)); - const auxiliaryBarPartSize = this.storageService.getNumber(Storage.AUXILIARYBAR_SIZE, StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300)); - const panelDimension = positionFromString(this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom')); - const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; - const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, fallbackPanelSize) : fallbackPanelSize; + const width = this.state.initialization.global.window.width; + const height = this.state.initialization.global.window.height; + const sideBarSize = this.state.initialization.global.sideBar.size; + const auxiliaryBarPartSize = this.state.initialization.global.auxiliaryBar.size; + const panelSize = this.state.initialization.global.panel.size; const titleBarHeight = this.titleBarPartView.minimumHeight; const bannerHeight = this.bannerPartView.minimumHeight; @@ -2091,14 +2118,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.ACTIVITYBAR_PART }, size: activityBarWidth, - visible: !this.state.activityBar.hidden + visible: !this.state.initialization.workspace.activityBar.hidden }; const sideBarNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.SIDEBAR_PART }, size: sideBarSize, - visible: !this.state.sideBar.hidden + visible: !this.state.initialization.workspace.sideBar.hidden }; const auxiliaryBarNode: ISerializedLeafNode = { @@ -2112,14 +2139,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.EDITOR_PART }, size: 0, // Update based on sibling sizes - visible: !this.state.editor.hidden + visible: !this.state.initialization.workspace.editor.hidden }; const panelNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.PANEL_PART }, size: panelSize, - visible: !this.state.panel.hidden + visible: !this.state.initialization.workspace.panel.hidden }; @@ -2157,7 +2184,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi type: 'leaf', data: { type: Parts.STATUSBAR_PART }, size: statusBarHeight, - visible: !this.state.statusBar.hidden + visible: !this.state.initialization.workspace.statusBar.hidden } ] }, @@ -2175,3 +2202,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.disposed = true; } } + +type ZenModeConfiguration = { + centerLayout: boolean; + fullScreen: boolean; + hideActivityBar: boolean; + hideLineNumbers: boolean; + hideStatusBar: boolean; + hideTabs: boolean; + restore: boolean; + silentNotifications: boolean; +}; + +function getZenModeConfiguration(configurationService: IConfigurationService): ZenModeConfiguration { + return configurationService.getValue(WorkbenchLayoutSettings.ZEN_MODE_CONFIG); +} diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index ed8bd49ae03..b3a4c15ddf8 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -333,11 +333,11 @@ export class Workbench extends Layout { [ { id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] }, { id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] }, - { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 - { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, - { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, - { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.state.panel.position)] }, - { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.state.sideBar.position === Position.LEFT ? 'right' : 'left'] }, + { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 + { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, + { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } }, + { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, + { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 5d9a5c8ba58..619b761efa3 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -5,7 +5,6 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { Dimension } from 'vs/base/browser/dom'; @@ -36,6 +35,8 @@ export const enum PanelOpensMaximizedOptions { REMEMBER_LAST } +export type PanelAlignment = 'left' | 'center' | 'right' | 'justified'; + export function positionToString(position: Position): string { switch (position) { case Position.LEFT: return 'left'; @@ -195,11 +196,6 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ getSideBarPosition(): Position; - /** - * Gets the current menubar visibility. - */ - getMenubarVisibility(): MenuBarVisibility; - /** * Toggles the menu bar visibility. */