/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size, IDimension } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform'; import { pathsToEditors, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; import { Position, Parts, IWorkbenchLayoutService, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { MenuBarVisibility, 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'; import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; import { assertIsDefined } from 'vs/base/common/types'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { URI } from 'vs/base/common/uri'; export enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', STATUSBAR_VISIBLE = 'workbench.statusBar.visible', SIDEBAR_POSITION = 'workbench.sideBar.location', PANEL_POSITION = 'workbench.panel.defaultLocation', ZEN_MODE_RESTORE = 'zenMode.restore', WORKSPACE_FIRST_OPEN = 'workbench.workspaceFirstOpen' } enum Storage { SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', SIDEBAR_SIZE = 'workbench.sidebar.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', 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' } enum Classes { SIDEBAR_HIDDEN = 'nosidebar', EDITOR_HIDDEN = 'noeditorarea', PANEL_HIDDEN = 'nopanel', STATUSBAR_HIDDEN = 'nostatusbar', FULLSCREEN = 'fullscreen', WINDOW_BORDER = 'border' } interface PanelActivityState { id: string; name?: string; pinned: boolean; order: number; visible: boolean; } interface SideBarActivityState { id: string; pinned: boolean; order: number; visible: boolean; } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { _serviceBrand: undefined; //#region Events private readonly _onZenModeChange = this._register(new Emitter()); readonly onZenModeChange = this._onZenModeChange.event; private readonly _onFullscreenChange = this._register(new Emitter()); readonly onFullscreenChange = this._onFullscreenChange.event; private readonly _onCenteredLayoutChange = this._register(new Emitter()); readonly onCenteredLayoutChange = this._onCenteredLayoutChange.event; private readonly _onMaximizeChange = this._register(new Emitter()); readonly onMaximizeChange = this._onMaximizeChange.event; private readonly _onPanelPositionChange = this._register(new Emitter()); readonly onPanelPositionChange = this._onPanelPositionChange.event; private readonly _onPartVisibilityChange = this._register(new Emitter()); readonly onPartVisibilityChange = this._onPartVisibilityChange.event; private readonly _onLayout = this._register(new Emitter()); readonly onLayout = this._onLayout.event; //#endregion private _dimension!: IDimension; get dimension(): IDimension { return this._dimension; } private _container: HTMLElement = document.createElement('div'); get container(): HTMLElement { return this._container; } get offset() { return { top: (() => { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; } return offset; })() }; } private parts: Map = new Map(); private workbenchGrid!: SerializableGrid; private disposed: boolean | undefined; private titleBarPartView!: ISerializableView; private activityBarPartView!: ISerializableView; private sideBarPartView!: ISerializableView; private panelPartView!: ISerializableView; private editorPartView!: ISerializableView; private statusBarPartView!: ISerializableView; private environmentService!: IWorkbenchEnvironmentService; private configurationService!: IConfigurationService; private lifecycleService!: ILifecycleService; private storageService!: IStorageService; private hostService!: IHostService; private editorService!: IEditorService; private editorGroupService!: IEditorGroupsService; private panelService!: IPanelService; private titleService!: ITitleService; private viewletService!: IViewletService; private contextService!: IWorkspaceContextService; private backupFileService!: IBackupFileService; private notificationService!: INotificationService; private themeService!: IThemeService; protected readonly state = { fullscreen: false, maximized: false, hasFocus: false, windowBorder: false, menuBar: { visibility: 'default' 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 | IResourceEditorInputType[] }, panel: { hidden: false, position: Position.BOTTOM, lastNonMaximizedWidth: 300, lastNonMaximizedHeight: 300, panelToRestore: undefined as string | undefined }, statusBar: { hidden: false }, zenMode: { active: false, restore: false, transitionedToFullScreen: false, transitionedToCenteredEditorLayout: false, wasSideBarVisible: false, wasPanelVisible: false, transitionDisposables: new DisposableStore(), setNotificationsFilter: false, editorWidgetSet: new Set() }, }; constructor( protected readonly parent: HTMLElement ) { super(); } protected initLayout(accessor: ServicesAccessor): void { // Services this.environmentService = accessor.get(IWorkbenchEnvironmentService); this.configurationService = accessor.get(IConfigurationService); this.lifecycleService = accessor.get(ILifecycleService); this.hostService = accessor.get(IHostService); this.contextService = accessor.get(IWorkspaceContextService); this.storageService = accessor.get(IStorageService); this.backupFileService = accessor.get(IBackupFileService); this.themeService = accessor.get(IThemeService); // Parts this.editorService = accessor.get(IEditorService); this.editorGroupService = accessor.get(IEditorGroupsService); this.panelService = accessor.get(IPanelService); this.viewletService = accessor.get(IViewletService); this.titleService = accessor.get(ITitleService); this.notificationService = accessor.get(INotificationService); accessor.get(IStatusbarService); // not used, but called to ensure instantiated accessor.get(IActivityBarService); // not used, but called to ensure instantiated // Listeners this.registerLayoutListeners(); // State this.initLayoutState(accessor.get(ILifecycleService), accessor.get(IFileService)); } private registerLayoutListeners(): void { // Restore editor if hidden and it changes // The editor service will always trigger this // on startup so we can ignore the first one let firstTimeEditorActivation = true; const showEditorIfHidden = () => { if (!firstTimeEditorActivation && this.state.editor.hidden) { this.toggleMaximizedPanel(); } firstTimeEditorActivation = false; }; // Restore editor part on any editor change this._register(this.editorService.onDidVisibleEditorsChange(showEditorIfHidden)); this._register(this.editorGroupService.onDidActivateGroup(showEditorIfHidden)); // Revalidate center layout when active editor changes: diff editor quits centered mode. this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.state.editor.centered))); // Configuration changes this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); // Fullscreen changes 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))); // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); // Menubar visibility changes if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); } // Theme changes this._register(this.themeService.onDidColorThemeChange(theme => this.updateStyles())); // Window focus changes this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e))); } private onMenubarToggled(visible: boolean) { if (visible !== this.state.menuBar.toggled) { this.state.menuBar.toggled = visible; if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); this.layout(); } } } private onFullscreenChanged(): void { this.state.fullscreen = isFullscreen(); // Apply as CSS class if (this.state.fullscreen) { addClass(this.container, Classes.FULLSCREEN); } else { removeClass(this.container, Classes.FULLSCREEN); if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { this.toggleZenMode(); } } // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); this.updateWindowBorder(true); this.layout(); // handle title bar when fullscreen changes } this._onFullscreenChange.fire(this.state.fullscreen); } private onWindowFocusChanged(hasFocus: boolean): void { if (this.state.hasFocus === hasFocus) { return; } this.state.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); } // Panel position this.updatePanelPosition(); if (!this.state.zenMode.active) { // Statusbar visibility const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); if (newStatusbarHiddenValue !== this.state.statusBar.hidden) { this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); } // Activitybar visibility const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); if (newActivityBarHiddenValue !== this.state.activityBar.hidden) { this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); } } // Menubar visibility const newMenubarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); } private setSideBarPosition(position: Position): void { const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_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; // Adjust CSS const activityBarContainer = assertIsDefined(activityBar.getContainer()); const sideBarContainer = assertIsDefined(sideBar.getContainer()); removeClass(activityBarContainer, oldPositionValue); removeClass(sideBarContainer, oldPositionValue); addClass(activityBarContainer, newPositionValue); addClass(sideBarContainer, newPositionValue); // Update Styles activityBar.updateStyles(); sideBar.updateStyles(); // Layout if (!wasHidden) { this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; } if (position === Position.LEFT) { this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 0]); this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 1]); } else { this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 4]); this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 4]); } this.layout(); } private updateWindowBorder(skipLayout: boolean = false) { if (isWeb || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { return; } const theme = this.themeService.getColorTheme(); const activeBorder = theme.getColor(WINDOW_ACTIVE_BORDER); const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); let windowBorder = false; if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { windowBorder = true; // If the inactive color is missing, fallback to the active one const borderColor = this.state.hasFocus ? activeBorder : inactiveBorder ?? activeBorder; this.container.style.setProperty('--window-border-color', borderColor?.toString() ?? 'transparent'); } if (windowBorder === this.state.windowBorder) { return; } this.state.windowBorder = windowBorder; toggleClass(this.container, Classes.WINDOW_BORDER, windowBorder); if (!skipLayout) { this.layout(); } } private updateStyles() { this.updateWindowBorder(); } private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { this.applyDefaultLayout(this.environmentService, this.storageService); // Fullscreen this.state.fullscreen = isFullscreen(); // Menubar visibility this.state.menuBar.visibility = getMenuBarVisibility(this.configurationService, this.environmentService); // Activity bar visibility this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); // Sidebar visibility this.state.sideBar.hidden = this.storageService.getBoolean(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); // Sidebar position this.state.sideBar.position = (this.configurationService.getValue(Settings.SIDEBAR_POSITION) === 'right') ? Position.RIGHT : Position.LEFT; // Sidebar viewlet if (!this.state.sideBar.hidden) { // Only restore last viewlet if window was reloaded or we are in development mode let viewletToRestore: string; if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewletService.getDefaultViewletId()); } else { viewletToRestore = this.viewletService.getDefaultViewletId(); } if (viewletToRestore) { this.state.sideBar.viewletToRestore = viewletToRestore; } else { this.state.sideBar.hidden = true; // we hide sidebar if there is no viewlet to restore } } // Editor visibility this.state.editor.hidden = this.storageService.getBoolean(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE, false); // 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); // Panel visibility this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true); // Panel position this.updatePanelPosition(); // Panel to restore if (!this.state.panel.hidden) { let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, Registry.as(PanelExtensions.Panels).getDefaultPanelId()); if (panelToRestore) { this.state.panel.panelToRestore = panelToRestore; } else { this.state.panel.hidden = true; // we hide panel if there is no panel to restore } } // 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; // Window border this.updateWindowBorder(true); } private applyDefaultLayout(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService) { const defaultLayout = environmentService.options?.defaultLayout; if (!defaultLayout) { return; } const firstOpen = storageService.getBoolean(Settings.WORKSPACE_FIRST_OPEN, StorageScope.WORKSPACE); if (!firstOpen) { return; } const { sidebar } = defaultLayout; if (sidebar) { if (sidebar.visible !== undefined) { if (sidebar.visible) { storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); } else { storageService.store(Storage.SIDEBAR_HIDDEN, true, StorageScope.WORKSPACE); } } if (sidebar.containers?.length) { const sidebarState: SideBarActivityState[] = []; let order = -1; for (const container of sidebar.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { let viewletId; switch (container.id) { case 'explorer': viewletId = 'workbench.view.explorer'; break; case 'run': viewletId = 'workbench.view.debug'; break; case 'scm': viewletId = 'workbench.view.scm'; break; case 'search': viewletId = 'workbench.view.search'; break; case 'extensions': viewletId = 'workbench.view.extensions'; break; case 'remote': viewletId = 'workbench.view.remote'; break; default: viewletId = `workbench.view.extension.${container.id}`; } if (container.active) { storageService.store(SidebarPart.activeViewletSettingsKey, viewletId, StorageScope.WORKSPACE); } if (container.order !== undefined || (container.active === undefined && (container).visible !== undefined)) { // {{SQL CARBON EDIT}} strict-null-checks order = container.order ?? (order + 1); const state: SideBarActivityState = { id: viewletId, order: order, pinned: (container.active || (container).visible) ?? true, // {{SQL CARBON EDIT}} strict-null-checks visible: (container.active || (container).visible) ?? true // {{SQL CARBON EDIT}} strict-null-checks }; sidebarState.push(state); } if (container.views !== undefined) { const viewsState: { id: string, isHidden?: boolean, order?: number }[] = []; const viewsWorkspaceState: { [id: string]: { collapsed: boolean, isHidden?: boolean, size?: number } } = {}; for (const view of container.views) { if (view.order !== undefined || view.visible !== undefined) { viewsState.push({ id: view.id, isHidden: view.visible === undefined ? undefined : !view.visible, order: view.order === undefined ? undefined : view.order }); } if (view.collapsed !== undefined) { viewsWorkspaceState[view.id] = { collapsed: view.collapsed, isHidden: view.visible === undefined ? undefined : !view.visible, }; } } storageService.store(`${viewletId}.state.hidden`, JSON.stringify(viewsState), StorageScope.GLOBAL); storageService.store(`${viewletId}.state`, JSON.stringify(viewsWorkspaceState), StorageScope.WORKSPACE); } } if (sidebarState.length) { storageService.store(ActivitybarPart.PINNED_VIEWLETS, JSON.stringify(sidebarState), StorageScope.GLOBAL); } } } const { panel } = defaultLayout; if (panel) { if (panel.visible !== undefined) { if (panel.visible) { storageService.store(Storage.PANEL_HIDDEN, false, StorageScope.WORKSPACE); } else { storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); } } if (panel.containers?.length) { const panelState: PanelActivityState[] = []; let order = -1; for (const container of panel.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { let name; let panelId = container.id; switch (panelId) { case 'terminal': name = 'Terminal'; panelId = 'workbench.panel.terminal'; break; case 'debug': name = 'Debug Console'; panelId = 'workbench.panel.repl'; break; case 'problems': name = 'Problems'; panelId = 'workbench.panel.markers'; break; case 'output': name = 'Output'; panelId = 'workbench.panel.output'; break; case 'comments': name = 'Comments'; panelId = 'workbench.panel.comments'; break; case 'refactor': name = 'Refactor Preview'; panelId = 'refactorPreview'; break; default: continue; } if (container.active) { storageService.store(PanelPart.activePanelSettingsKey, panelId, StorageScope.WORKSPACE); } if (container.order !== undefined || (container.active === undefined && (container).visible !== undefined)) { // {{SQL CARBON EDIT}} strict-null-checks order = container.order ?? (order + 1); const state: PanelActivityState = { id: panelId, name: name, order: order, pinned: (container.active || (container).visible) ?? true, // {{SQL CARBON EDIT}} strict-null-checks visible: (container.active || (container).visible) ?? true // {{SQL CARBON EDIT}} strict-null-checks }; panelState.push(state); } } if (panelState.length) { storageService.store(PanelPart.PINNED_PANELS, JSON.stringify(panelState), StorageScope.GLOBAL); } } } } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditorInputType[] { const initialFilesToOpen = this.getInitialFilesToOpen(); // Only restore editors if we are not instructed to open files initially this.state.editor.restoreEditors = initialFilesToOpen === undefined; // Files to open, diff or create if (initialFilesToOpen !== undefined) { // Files to diff is exclusive return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => { if (filesToDiff?.length === 2) { return [{ leftResource: filesToDiff[0].resource, rightResource: filesToDiff[1].resource, options: { pinned: true }, forceFile: true }]; } // Otherwise: Open/Create files return pathsToEditors(initialFilesToOpen.filesToOpenOrCreate, fileService); }); } // Empty workbench else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.getValue('workbench.startupEditor') === 'newUntitledFile') { if (this.editorGroupService.willRestoreEditors) { return []; // do not open any empty untitled file if we restored editors from previous session } return this.backupFileService.hasBackups().then(hasBackups => { if (hasBackups) { return []; // do not open any empty untitled file if we have backups to restore } return [Object.create(null)]; // open empty untitled file }); } return []; } private _openedDefaultEditors: boolean = false; get openedDefaultEditors() { return this._openedDefaultEditors; } private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined { const defaultLayout = this.environmentService.options?.defaultLayout; if (defaultLayout?.editors?.length && this.storageService.getBoolean(Settings.WORKSPACE_FIRST_OPEN, StorageScope.WORKSPACE)) { this._openedDefaultEditors = true; return { filesToOpenOrCreate: defaultLayout.editors .map(f => { // Support the old path+scheme api until embedders can migrate if ('path' in f && 'scheme' in f) { return { fileUri: URI.file((f as any).path).with({ scheme: (f as any).scheme }) }; } return { fileUri: URI.revive(f.uri), openOnlyIfExists: f.openOnlyIfExists }; }) }; } const configuration = this.environmentService.configuration; if (configuration.filesToOpenOrCreate || configuration.filesToDiff) { return { filesToOpenOrCreate: configuration.filesToOpenOrCreate, filesToDiff: configuration.filesToDiff }; } return undefined; } 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); } protected getPart(key: Parts): Part { const part = this.parts.get(key); if (!part) { throw new Error(`Unknown part ${key}`); } return part; } isRestored(): boolean { return this.lifecycleService.phase >= LifecyclePhase.Restored; } hasFocus(part: Parts): boolean { const activeElement = document.activeElement; if (!activeElement) { return false; } const container = this.getContainer(part); return !!container && isAncestor(activeElement, container); } focusPart(part: Parts): void { switch (part) { case Parts.EDITOR_PART: this.editorGroupService.activeGroup.focus(); break; case Parts.PANEL_PART: const activePanel = this.panelService.getActivePanel(); if (activePanel) { activePanel.focus(); } break; case Parts.SIDEBAR_PART: const activeViewlet = this.viewletService.getActiveViewlet(); if (activeViewlet) { activeViewlet.focus(); } break; default: // Status Bar, Activity Bar and Title Bar simply pass focus to container const container = this.getContainer(part); if (container) { container.focus(); } } } getContainer(part: Parts): HTMLElement | undefined { switch (part) { case Parts.TITLEBAR_PART: return this.getPart(Parts.TITLEBAR_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.EDITOR_PART: return this.getPart(Parts.EDITOR_PART).getContainer(); case Parts.STATUSBAR_PART: return this.getPart(Parts.STATUSBAR_PART).getContainer(); } } isVisible(part: Parts): boolean { switch (part) { case Parts.TITLEBAR_PART: if (getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { return false; } else if (!this.state.fullscreen && !isWeb) { return true; } else if (isMacintosh && isNative) { return false; } else if (this.state.menuBar.visibility === 'visible') { return true; } else if (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default') { return this.state.menuBar.toggled; } return false; case Parts.SIDEBAR_PART: return !this.state.sideBar.hidden; case Parts.PANEL_PART: return !this.state.panel.hidden; case Parts.STATUSBAR_PART: return !this.state.statusBar.hidden; case Parts.ACTIVITYBAR_PART: return !this.state.activityBar.hidden; case Parts.EDITOR_PART: return !this.state.editor.hidden; } return true; // any other part cannot be hidden } focus(): void { this.editorGroupService.activeGroup.focus(); } getDimension(part: Parts): Dimension | undefined { return this.getPart(part).dimension; } getMaximumEditorDimensions(): Dimension { const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + (this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0); const takenHeight = (this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.minimumHeight : 0) + (this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0); const availableWidth = this.dimension.width - takenWidth; const availableHeight = this.dimension.height - takenHeight; return { width: availableWidth, height: availableHeight }; } getWorkbenchContainer(): HTMLElement { return this.parent; } toggleZenMode(skipLayout?: boolean, restoring = false): void { this.state.zenMode.active = !this.state.zenMode.active; this.state.zenMode.transitionDisposables.clear(); const setLineNumbers = (lineNumbers?: LineNumbersType) => { const setEditorLineNumbers = (editor: IEditor) => { // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { const model = editor.getModel(); lineNumbers = this.configurationService.getValue('editor.lineNumbers', { resource: model.uri, overrideIdentifier: model.getModeId() }); } if (!lineNumbers) { lineNumbers = this.configurationService.getValue('editor.lineNumbers'); } editor.updateOptions({ lineNumbers }); }; const editorControlSet = this.state.zenMode.editorWidgetSet; if (!lineNumbers) { // Reset line numbers on all editors visible and non-visible for (const editor of editorControlSet) { setEditorLineNumbers(editor); } editorControlSet.clear(); } else { this.editorService.visibleTextEditorControls.forEach(editorControl => { if (!editorControlSet.has(editorControl)) { editorControlSet.add(editorControl); this.state.zenMode.transitionDisposables.add(editorControl.onDidDispose(() => { editorControlSet.delete(editorControl); })); } setEditorLineNumbers(editorControl); }); } }; // 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; // 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'); toggleFullScreen = !this.state.fullscreen && config.fullScreen; 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.setPanelHidden(true, true); this.setSideBarHidden(true, true); if (config.hideActivityBar) { this.setActivityBarHidden(true, true); } if (config.hideStatusBar) { this.setStatusBarHidden(true, true); } if (config.hideLineNumbers) { setLineNumbers('off'); this.state.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.zenMode.setNotificationsFilter = config.silentNotifications; if (config.silentNotifications) { this.notificationService.setFilter(NotificationsFilter.ERROR); } this.state.zenMode.transitionDisposables.add(this.configurationService.onDidChangeConfiguration(c => { const silentNotificationsKey = 'zenMode.silentNotifications'; if (c.affectsConfiguration(silentNotificationsKey)) { const filter = this.configurationService.getValue(silentNotificationsKey) ? NotificationsFilter.ERROR : NotificationsFilter.OFF; this.notificationService.setFilter(filter); } })); if (config.centerLayout) { this.centerEditorLayout(true, true); } } // Zen Mode Inactive else { if (this.state.zenMode.wasPanelVisible) { this.setPanelHidden(false, true); } if (this.state.zenMode.wasSideBarVisible) { this.setSideBarHidden(false, true); } if (this.state.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; } if (!skipLayout) { this.layout(); } if (toggleFullScreen) { this.hostService.toggleFullScreen(); } // Event this._onZenModeChange.fire(this.state.zenMode.active); // State if (this.state.zenMode.active) { this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE); // 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); } } private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.statusBar.hidden = hidden; // Adjust CSS if (hidden) { addClass(this.container, Classes.STATUSBAR_HIDDEN); } else { removeClass(this.container, Classes.STATUSBAR_HIDDEN); } // Propagate to grid this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } protected createWorkbenchLayout(): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const panelPart = this.getPart(Parts.PANEL_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); const statusBar = this.getPart(Parts.STATUSBAR_PART); // View references for all parts this.titleBarPartView = titleBar; this.sideBarPartView = sideBar; this.activityBarPartView = activityBar; this.editorPartView = editorPart; this.panelPartView = panelPart; this.statusBarPartView = statusBar; const viewMap = { [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, [Parts.TITLEBAR_PART]: this.titleBarPartView, [Parts.EDITOR_PART]: this.editorPartView, [Parts.PANEL_PART]: this.panelPartView, [Parts.SIDEBAR_PART]: this.sideBarPartView, [Parts.STATUSBAR_PART]: this.statusBarPartView }; const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; const workbenchGrid = SerializableGrid.deserialize( this.createGridDescriptor(), { fromJSON }, { proportionalLayout: false } ); this.container.prepend(workbenchGrid.element); this.container.setAttribute('role', 'application'); this.workbenchGrid = workbenchGrid; [titleBar, editorPart, activityBar, panelPart, sideBar, statusBar].forEach((part: Part) => { this._register(part.onDidVisibilityChange((visible) => { this._onPartVisibilityChange.fire(); if (part === sideBar) { this.setSideBarHidden(!visible, true); } else if (part === panelPart) { this.setPanelHidden(!visible, true); } else if (part === editorPart) { this.setEditorHidden(!visible, true); } })); }); 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); 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); this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL); const gridSize = grid.getViewSize(); this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); })); } getClientArea(): Dimension { return getClientArea(this.parent); } layout(): void { if (!this.disposed) { this._dimension = this.getClientArea(); position(this.container, 0, 0, 0, 0, 'relative'); size(this.container, this._dimension.width, this._dimension.height); // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); // Emit as event this._onLayout.fire(this._dimension); } } isEditorLayoutCentered(): boolean { return this.state.editor.centered; } centerEditorLayout(active: boolean, skipLayout?: boolean): void { this.state.editor.centered = active; this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE); let smartActive = active; const activeEditor = this.editorService.activeEditor; if (this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize') && (this.editorGroupService.groups.length > 1 || (activeEditor && activeEditor instanceof SideBySideEditorInput))) { smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group. } // Enter Centered Editor Layout if (this.editorGroupService.isLayoutCentered() !== smartActive) { this.editorGroupService.centerLayout(smartActive); if (!skipLayout) { this.layout(); } } this._onCenteredLayoutChange.fire(this.state.editor.centered); } resizePart(part: Parts, sizeChange: number): void { const sizeChangePxWidth = this.workbenchGrid.width * sizeChange / 100; const sizeChangePxHeight = this.workbenchGrid.height * sizeChange / 100; let viewSize: IViewSize; switch (part) { case Parts.SIDEBAR_PART: viewSize = this.workbenchGrid.getViewSize(this.sideBarPartView); this.workbenchGrid.resizeView(this.sideBarPartView, { width: viewSize.width + sizeChangePxWidth, height: viewSize.height }); break; case Parts.PANEL_PART: viewSize = this.workbenchGrid.getViewSize(this.panelPartView); this.workbenchGrid.resizeView(this.panelPartView, { width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) }); break; case Parts.EDITOR_PART: viewSize = this.workbenchGrid.getViewSize(this.editorPartView); // Single Editor Group if (this.editorGroupService.count === 1) { if (this.isVisible(Parts.SIDEBAR_PART)) { this.workbenchGrid.resizeView(this.editorPartView, { width: viewSize.width + sizeChangePxWidth, height: viewSize.height }); } else if (this.isVisible(Parts.PANEL_PART)) { this.workbenchGrid.resizeView(this.editorPartView, { width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) }); } } else { const activeGroup = this.editorGroupService.activeGroup; const { width, height } = this.editorGroupService.getSize(activeGroup); this.editorGroupService.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); } break; default: return; // Cannot resize other parts } } setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.activityBar.hidden = hidden; // Propagate to grid this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } setEditorHidden(hidden: boolean, skipLayout?: boolean): void { this.state.editor.hidden = hidden; // Adjust CSS if (hidden) { addClass(this.container, Classes.EDITOR_HIDDEN); } else { removeClass(this.container, Classes.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); } 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) { 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 ]); } setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.sideBar.hidden = hidden; // Adjust CSS if (hidden) { addClass(this.container, Classes.SIDEBAR_HIDDEN); } else { removeClass(this.container, Classes.SIDEBAR_HIDDEN); } // If sidebar becomes hidden, also hide the current active Viewlet if any if (hidden && this.viewletService.getActiveViewlet()) { this.viewletService.hideActiveViewlet(); // Pass Focus to Editor or Panel if Sidebar is now hidden const activePanel = this.panelService.getActivePanel(); if (this.hasFocus(Parts.PANEL_PART) && activePanel) { activePanel.focus(); } else { this.focus(); } } // If sidebar becomes visible, show last active Viewlet or default viewlet else if (!hidden && !this.viewletService.getActiveViewlet()) { const viewletToOpen = this.viewletService.getLastActiveViewletId(); if (viewletToOpen) { const viewlet = this.viewletService.openViewlet(viewletToOpen, true); if (!viewlet) { this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true); } } } // 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); } else { this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); } } setPanelHidden(hidden: boolean, skipLayout?: boolean): void { this.state.panel.hidden = hidden; // Adjust CSS if (hidden) { addClass(this.container, Classes.PANEL_HIDDEN); } else { removeClass(this.container, Classes.PANEL_HIDDEN); } // If panel part becomes hidden, also hide the current active panel if any if (hidden && this.panelService.getActivePanel()) { this.panelService.hideActivePanel(); this.editorGroupService.activeGroup.focus(); // Pass focus to editor group if panel part is now hidden } // If panel part becomes visible, show last active panel or default panel else if (!hidden && !this.panelService.getActivePanel()) { const panelToOpen = this.panelService.getLastActivePanelId(); if (panelToOpen) { const focus = !skipLayout; this.panelService.openPanel(panelToOpen, focus); } } // If not maximized and hiding, unmaximize before hiding to allow caching of size if (this.isPanelMaximized() && hidden) { this.toggleMaximizedPanel(); } // Propagate to grid this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); // Remember in settings if (!hidden) { this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); } else { this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); } // The editor and panel cannot be hidden at the same time if (hidden && this.state.editor.hidden) { this.setEditorHidden(false, true); } } toggleMaximizedPanel(): void { const size = this.workbenchGrid.getViewSize(this.panelPartView); 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); } else { this.state.panel.lastNonMaximizedWidth = size.width; this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL); } } 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.editorGroupService.activeGroup.focus(); } } hasWindowBorder(): boolean { return this.state.windowBorder; } getWindowBorderRadius(): string | undefined { return this.state.windowBorder && isMacintosh ? '5px' : undefined; } isPanelMaximized(): boolean { if (!this.workbenchGrid) { return false; } return this.state.editor.hidden; } getSideBarPosition(): Position { return this.state.sideBar.position; } setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void { if (this.state.menuBar.visibility !== visibility) { this.state.menuBar.visibility = visibility; // Layout if (!skipLayout) { this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } } } getMenubarVisibility(): MenuBarVisibility { return this.state.menuBar.visibility; } getPanelPosition(): Position { return this.state.panel.position; } setPanelPosition(position: Position): void { if (this.state.panel.hidden) { this.setPanelHidden(false); } const panelPart = this.getPart(Parts.PANEL_PART); const oldPositionValue = positionToString(this.state.panel.position); const newPositionValue = positionToString(position); this.state.panel.position = position; // Save panel position this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); removeClass(panelContainer, oldPositionValue); addClass(panelContainer, newPositionValue); // Update Styles panelPart.updateStyles(); // Layout const size = this.workbenchGrid.getViewSize(this.panelPartView); const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); // Save last non-maximized size for panel before move if (newPositionValue !== oldPositionValue && !this.state.editor.hidden) { // 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; } else if (positionFromString(oldPositionValue) === Position.BOTTOM) { this.state.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); } else if (position === Position.RIGHT) { this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.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); } // Reset sidebar to original size before shifting the panel this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); this._onPanelPositionChange.fire(newPositionValue); } isWindowMaximized() { return this.state.maximized; } updateWindowMaximizedState(maximized: boolean) { if (this.state.maximized === maximized) { return; } this.state.maximized = maximized; this.updateWindowBorder(); this._onMaximizeChange.fire(maximized); } getVisibleNeighborPart(part: Parts, direction: Direction): Parts | undefined { if (!this.workbenchGrid) { return undefined; } if (!this.isVisible(part)) { return undefined; } const neighborViews = this.workbenchGrid.getNeighborViews(this.getPart(part), direction, false); if (!neighborViews) { return undefined; } for (const neighborView of neighborViews) { const neighborPart = [Parts.ACTIVITYBAR_PART, Parts.EDITOR_PART, Parts.PANEL_PART, Parts.SIDEBAR_PART, Parts.STATUSBAR_PART, Parts.TITLEBAR_PART] .find(partId => this.getPart(partId) === neighborView && this.isVisible(partId)); if (neighborPart !== undefined) { return neighborPart; } } return undefined; } private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] { switch (this.state.panel.position) { case Position.BOTTOM: return [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]; case Position.RIGHT: return [editorNode, panelNode]; case Position.LEFT: return [panelNode, editorNode]; } } 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); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', 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, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, fallbackPanelSize)) : fallbackPanelSize; const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; const activityBarWidth = this.activityBarPartView.minimumWidth; const middleSectionHeight = height - titleBarHeight - statusBarHeight; const editorSectionWidth = width - (this.state.activityBar.hidden ? 0 : activityBarWidth) - (this.state.sideBar.hidden ? 0 : sideBarSize); const activityBarNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.ACTIVITYBAR_PART }, size: activityBarWidth, visible: !this.state.activityBar.hidden }; const sideBarNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.SIDEBAR_PART }, size: sideBarSize, visible: !this.state.sideBar.hidden }; const editorNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.EDITOR_PART }, size: this.state.panel.position === Position.BOTTOM ? middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize), visible: !this.state.editor.hidden }; const panelNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.PANEL_PART }, size: panelSize, visible: !this.state.panel.hidden }; const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth); const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT ? [activityBarNode, sideBarNode, ...editorSectionNode] : [...editorSectionNode, sideBarNode, activityBarNode]; const result: ISerializedGrid = { root: { type: 'branch', size: width, data: [ { type: 'leaf', data: { type: Parts.TITLEBAR_PART }, size: titleBarHeight, visible: this.isVisible(Parts.TITLEBAR_PART) }, { type: 'branch', data: middleSection, size: middleSectionHeight }, { type: 'leaf', data: { type: Parts.STATUSBAR_PART }, size: statusBarHeight, visible: !this.state.statusBar.hidden } ] }, orientation: Orientation.VERTICAL, width, height }; return result; } dispose(): void { super.dispose(); this.disposed = true; } }