mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-27 01:25:36 -05:00
1140 lines
37 KiB
TypeScript
1140 lines
37 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
|
import { Event, Emitter } from 'vs/base/common/event';
|
|
import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size } from 'vs/base/browser/dom';
|
|
import { onDidChangeFullscreen, isFullscreen, getZoomFactor } 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 } from 'vs/base/common/platform';
|
|
import { IResourceInput } from 'vs/platform/editor/common/editor';
|
|
import { IUntitledResourceInput, IResourceDiffInput } 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, ILayoutOptions } from 'vs/workbench/services/layout/browser/layoutService';
|
|
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
|
import { IStorageService, StorageScope, IWillSaveStateEvent, 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 { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
|
import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
|
import { IWindowService, IPath, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
|
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
|
|
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
|
import { Sizing, Direction, Grid, View } from 'vs/base/browser/ui/grid/grid';
|
|
import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout';
|
|
import { IDimension } from 'vs/platform/layout/browser/layoutService';
|
|
import { Part } from 'vs/workbench/browser/part';
|
|
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
|
import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService';
|
|
import { coalesce } from 'vs/base/common/arrays';
|
|
|
|
enum Settings {
|
|
MENUBAR_VISIBLE = 'window.menuBarVisibility',
|
|
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'
|
|
}
|
|
|
|
enum Storage {
|
|
SIDEBAR_HIDDEN = 'workbench.sidebar.hidden',
|
|
|
|
PANEL_HIDDEN = 'workbench.panel.hidden',
|
|
PANEL_POSITION = 'workbench.panel.location',
|
|
|
|
ZEN_MODE_ENABLED = 'workbench.zenmode.active',
|
|
CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active',
|
|
}
|
|
|
|
export abstract class Layout extends Disposable implements IWorkbenchLayoutService {
|
|
|
|
_serviceBrand: ServiceIdentifier<any>;
|
|
|
|
private readonly _onTitleBarVisibilityChange: Emitter<void> = this._register(new Emitter<void>());
|
|
get onTitleBarVisibilityChange(): Event<void> { return this._onTitleBarVisibilityChange.event; }
|
|
|
|
private readonly _onZenMode: Emitter<boolean> = this._register(new Emitter<boolean>());
|
|
get onZenModeChange(): Event<boolean> { return this._onZenMode.event; }
|
|
|
|
private readonly _onLayout = this._register(new Emitter<IDimension>());
|
|
get onLayout(): Event<IDimension> { return this._onLayout.event; }
|
|
|
|
private _dimension: IDimension;
|
|
get dimension(): IDimension { return this._dimension; }
|
|
|
|
private _container: HTMLElement = document.createElement('div');
|
|
get container(): HTMLElement { return this._container; }
|
|
|
|
private parts: Map<string, Part> = new Map<string, Part>();
|
|
|
|
private workbenchGrid: Grid<View> | WorkbenchLegacyLayout;
|
|
|
|
private disposed: boolean;
|
|
|
|
private titleBarPartView: View;
|
|
private activityBarPartView: View;
|
|
private sideBarPartView: View;
|
|
private panelPartView: View;
|
|
private editorPartView: View;
|
|
private statusBarPartView: View;
|
|
|
|
private environmentService: IEnvironmentService;
|
|
private configurationService: IConfigurationService;
|
|
private lifecycleService: ILifecycleService;
|
|
private storageService: IStorageService;
|
|
private windowService: IWindowService;
|
|
private editorService: IEditorService;
|
|
private editorGroupService: IEditorGroupsService;
|
|
private panelService: IPanelService;
|
|
private titleService: ITitleService;
|
|
private viewletService: IViewletService;
|
|
private contextService: IWorkspaceContextService;
|
|
private backupFileService: IBackupFileService;
|
|
|
|
protected readonly state = {
|
|
fullscreen: 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<IResourceEditor[]> | IResourceEditor[]
|
|
},
|
|
|
|
panel: {
|
|
hidden: false,
|
|
position: Position.BOTTOM,
|
|
height: 350,
|
|
width: 350,
|
|
panelToRestore: undefined as string | undefined
|
|
},
|
|
|
|
statusBar: {
|
|
hidden: false
|
|
},
|
|
|
|
zenMode: {
|
|
active: false,
|
|
restore: false,
|
|
transitionedToFullScreen: false,
|
|
transitionedToCenteredEditorLayout: false,
|
|
wasSideBarVisible: false,
|
|
wasPanelVisible: false,
|
|
transitionDisposeables: [] as IDisposable[]
|
|
}
|
|
};
|
|
|
|
constructor(
|
|
protected readonly parent: HTMLElement
|
|
) {
|
|
super();
|
|
}
|
|
|
|
protected initLayout(accessor: ServicesAccessor): void {
|
|
|
|
// Services
|
|
this.environmentService = accessor.get(IEnvironmentService);
|
|
this.configurationService = accessor.get(IConfigurationService);
|
|
this.lifecycleService = accessor.get(ILifecycleService);
|
|
this.windowService = accessor.get(IWindowService);
|
|
this.contextService = accessor.get(IWorkspaceContextService);
|
|
this.storageService = accessor.get(IStorageService);
|
|
|
|
// 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);
|
|
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));
|
|
}
|
|
|
|
private registerLayoutListeners(): void {
|
|
|
|
// Storage
|
|
this._register(this.storageService.onWillSaveState(e => this.saveLayoutState(e)));
|
|
|
|
// Restore editor if hidden and it changes
|
|
this._register(this.editorService.onDidVisibleEditorsChange(() => this.setEditorHidden(false)));
|
|
this._register(this.editorGroupService.onDidActivateGroup(() => this.setEditorHidden(false)));
|
|
|
|
// 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) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
|
|
this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible)));
|
|
}
|
|
}
|
|
|
|
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')) {
|
|
this._onTitleBarVisibilityChange.fire();
|
|
this.layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
private onFullscreenChanged(): void {
|
|
this.state.fullscreen = isFullscreen();
|
|
|
|
// Apply as CSS class
|
|
if (this.state.fullscreen) {
|
|
addClass(this.container, 'fullscreen');
|
|
} else {
|
|
removeClass(this.container, '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') {
|
|
this._onTitleBarVisibilityChange.fire();
|
|
this.layout(); // handle title bar when fullscreen changes
|
|
}
|
|
}
|
|
|
|
private doUpdateLayoutConfiguration(skipLayout?: boolean): void {
|
|
|
|
// Sidebar position
|
|
const newSidebarPositionValue = this.configurationService.getValue<string>(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<boolean>(Settings.STATUSBAR_VISIBLE);
|
|
if (newStatusbarHiddenValue !== this.state.statusBar.hidden) {
|
|
this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout);
|
|
}
|
|
|
|
// Activitybar visibility
|
|
const newActivityBarHiddenValue = !this.configurationService.getValue<boolean>(Settings.ACTIVITYBAR_VISIBLE);
|
|
if (newActivityBarHiddenValue !== this.state.activityBar.hidden) {
|
|
this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout);
|
|
}
|
|
}
|
|
|
|
// Menubar visibility
|
|
const newMenubarVisibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);
|
|
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;
|
|
|
|
if (this.state.sideBar.hidden) {
|
|
this.setSideBarHidden(false, true /* Skip Layout */);
|
|
}
|
|
|
|
const newPositionValue = (position === Position.LEFT) ? 'left' : 'right';
|
|
const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right';
|
|
this.state.sideBar.position = position;
|
|
|
|
// Adjust CSS
|
|
removeClass(activityBar.getContainer(), oldPositionValue);
|
|
removeClass(sideBar.getContainer(), oldPositionValue);
|
|
addClass(activityBar.getContainer(), newPositionValue);
|
|
addClass(sideBar.getContainer(), newPositionValue);
|
|
|
|
// Update Styles
|
|
activityBar.updateStyles();
|
|
sideBar.updateStyles();
|
|
|
|
// Layout
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
if (!wasHidden) {
|
|
this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView);
|
|
}
|
|
|
|
this.workbenchGrid.removeView(this.sideBarPartView);
|
|
this.workbenchGrid.removeView(this.activityBarPartView);
|
|
|
|
if (!this.state.panel.hidden && this.state.panel.position === Position.BOTTOM) {
|
|
this.workbenchGrid.removeView(this.panelPartView);
|
|
}
|
|
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
|
|
private initLayoutState(lifecycleService: ILifecycleService): void {
|
|
|
|
// Fullscreen
|
|
this.state.fullscreen = isFullscreen();
|
|
|
|
// Menubar visibility
|
|
this.state.menuBar.visibility = this.configurationService.getValue<MenuBarVisibility>(Settings.MENUBAR_VISIBLE);
|
|
|
|
// Activity bar visibility
|
|
this.state.activityBar.hidden = !this.configurationService.getValue<string>(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<string>(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) {
|
|
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 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();
|
|
|
|
// 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) {
|
|
const panelRegistry = Registry.as<PanelRegistry>(PanelExtensions.Panels);
|
|
|
|
let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, panelRegistry.getDefaultPanelId());
|
|
if (!panelRegistry.hasPanel(panelToRestore)) {
|
|
panelToRestore = panelRegistry.getDefaultPanelId(); // fallback to default if panel is unknown
|
|
}
|
|
|
|
if (panelToRestore) {
|
|
this.state.panel.panelToRestore = panelToRestore;
|
|
} else {
|
|
this.state.panel.hidden = true; // we hide panel if there is no panel to restore
|
|
}
|
|
}
|
|
|
|
// Statusbar visibility
|
|
this.state.statusBar.hidden = !this.configurationService.getValue<string>(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);
|
|
}
|
|
|
|
private resolveEditorsToOpen(): Promise<IResourceEditor[]> | IResourceEditor[] {
|
|
const configuration = this.windowService.getConfiguration();
|
|
const hasInitialFilesToOpen = this.hasInitialFilesToOpen();
|
|
|
|
// Only restore editors if we are not instructed to open files initially
|
|
this.state.editor.restoreEditors = !hasInitialFilesToOpen;
|
|
|
|
// Files to open, diff or create
|
|
if (hasInitialFilesToOpen) {
|
|
|
|
// Files to diff is exclusive
|
|
const filesToDiff = this.toInputs(configuration.filesToDiff, false);
|
|
if (filesToDiff && filesToDiff.length === 2) {
|
|
return [<IResourceDiffInput>{
|
|
leftResource: filesToDiff[0].resource,
|
|
rightResource: filesToDiff[1].resource,
|
|
options: { pinned: true },
|
|
forceFile: true
|
|
}];
|
|
}
|
|
|
|
const filesToCreate = this.toInputs(configuration.filesToCreate, true);
|
|
const filesToOpen = this.toInputs(configuration.filesToOpen, false);
|
|
|
|
// Otherwise: Open/Create files
|
|
return [...filesToOpen, ...filesToCreate];
|
|
}
|
|
|
|
// Empty workbench
|
|
else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.inspect('workbench.startupEditor').value === 'newUntitledFile') {
|
|
const isEmpty = this.editorGroupService.count === 1 && this.editorGroupService.activeGroup.count === 0;
|
|
if (!isEmpty) {
|
|
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 [<IUntitledResourceInput>{}];
|
|
});
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private hasInitialFilesToOpen(): boolean {
|
|
const configuration = this.windowService.getConfiguration();
|
|
|
|
return !!(
|
|
(configuration.filesToCreate && configuration.filesToCreate.length > 0) ||
|
|
(configuration.filesToOpen && configuration.filesToOpen.length > 0) ||
|
|
(configuration.filesToDiff && configuration.filesToDiff.length > 0));
|
|
}
|
|
|
|
private toInputs(paths: IPath[] | undefined, isNew: boolean): Array<IResourceInput | IUntitledResourceInput> {
|
|
if (!paths || !paths.length) {
|
|
return [];
|
|
}
|
|
|
|
return coalesce(paths.map(p => {
|
|
const resource = p.fileUri;
|
|
if (!resource) {
|
|
return undefined;
|
|
}
|
|
|
|
let input: IResourceInput | IUntitledResourceInput;
|
|
if (isNew) {
|
|
input = { filePath: resource.fsPath, options: { pinned: true } } as IUntitledResourceInput;
|
|
} else {
|
|
input = { resource, options: { pinned: true }, forceFile: true } as IResourceInput;
|
|
}
|
|
|
|
if (!isNew && typeof p.lineNumber === 'number') {
|
|
input.options!.selection = {
|
|
startLineNumber: p.lineNumber,
|
|
startColumn: p.columnNumber || 1
|
|
};
|
|
}
|
|
|
|
return input;
|
|
}));
|
|
}
|
|
|
|
private updatePanelPosition() {
|
|
const defaultPanelPosition = this.configurationService.getValue<string>(Settings.PANEL_POSITION);
|
|
const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition);
|
|
|
|
this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM;
|
|
}
|
|
|
|
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 isAncestor(activeElement, container);
|
|
}
|
|
|
|
getContainer(part: Parts): HTMLElement {
|
|
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) {
|
|
return true;
|
|
} else if (isMacintosh) {
|
|
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.workbenchGrid instanceof Grid ? !this.state.editor.hidden : true;
|
|
}
|
|
|
|
return true; // any other part cannot be hidden
|
|
}
|
|
|
|
getTitleBarOffset(): number {
|
|
let offset = 0;
|
|
if (this.isVisible(Parts.TITLEBAR_PART)) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight;
|
|
} else {
|
|
offset = this.workbenchGrid.partLayoutInfo.titlebar.height;
|
|
|
|
if (isMacintosh || this.state.menuBar.visibility === 'hidden') {
|
|
offset /= getZoomFactor();
|
|
}
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
getWorkbenchElement(): HTMLElement {
|
|
return this.container;
|
|
}
|
|
|
|
toggleZenMode(skipLayout?: boolean, restoring = false): void {
|
|
this.state.zenMode.active = !this.state.zenMode.active;
|
|
this.state.zenMode.transitionDisposeables = dispose(this.state.zenMode.transitionDisposeables);
|
|
|
|
const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers }));
|
|
|
|
// 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;
|
|
} = 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.transitionDisposeables.push(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off')));
|
|
}
|
|
|
|
if (config.hideTabs && this.editorGroupService.partOptions.showTabs) {
|
|
this.state.zenMode.transitionDisposeables.push(this.editorGroupService.enforcePartOptions({ showTabs: false }));
|
|
}
|
|
|
|
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(this.configurationService.getValue('editor.lineNumbers'));
|
|
|
|
// Status bar and activity bar visibility come from settings -> update their visibility.
|
|
this.doUpdateLayoutConfiguration(true);
|
|
|
|
this.editorGroupService.activeGroup.focus();
|
|
|
|
toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen;
|
|
}
|
|
|
|
if (!skipLayout) {
|
|
this.layout();
|
|
}
|
|
|
|
if (toggleFullScreen) {
|
|
this.windowService.toggleFullScreen();
|
|
}
|
|
|
|
// Event
|
|
this._onZenMode.fire(this.state.zenMode.active);
|
|
}
|
|
|
|
private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void {
|
|
this.state.statusBar.hidden = hidden;
|
|
|
|
// Adjust CSS
|
|
if (hidden) {
|
|
addClass(this.container, 'nostatusbar');
|
|
} else {
|
|
removeClass(this.container, 'nostatusbar');
|
|
}
|
|
|
|
// Layout
|
|
if (!skipLayout) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
protected createWorkbenchLayout(instantiationService: IInstantiationService): 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);
|
|
|
|
if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) {
|
|
|
|
// Create view wrappers for all parts
|
|
this.titleBarPartView = new View(titleBar);
|
|
this.sideBarPartView = new View(sideBar);
|
|
this.activityBarPartView = new View(activityBar);
|
|
this.editorPartView = new View(editorPart);
|
|
this.panelPartView = new View(panelPart);
|
|
this.statusBarPartView = new View(statusBar);
|
|
|
|
this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false });
|
|
|
|
this.container.prepend(this.workbenchGrid.element);
|
|
} else {
|
|
this.workbenchGrid = instantiationService.createInstance(
|
|
WorkbenchLegacyLayout,
|
|
this.parent,
|
|
this.container,
|
|
{
|
|
titlebar: titleBar,
|
|
activitybar: activityBar,
|
|
editor: editorPart,
|
|
sidebar: sideBar,
|
|
panel: panelPart,
|
|
statusbar: statusBar,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
layout(options?: ILayoutOptions): void {
|
|
if (!this.disposed) {
|
|
this._dimension = getClientArea(this.parent);
|
|
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
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);
|
|
|
|
// Layout grid views
|
|
this.layoutGrid();
|
|
} else {
|
|
this.workbenchGrid.layout(options);
|
|
}
|
|
|
|
// Emit as event
|
|
this._onLayout.fire(this._dimension);
|
|
}
|
|
}
|
|
|
|
private layoutGrid(): void {
|
|
if (!(this.workbenchGrid instanceof Grid)) {
|
|
return;
|
|
}
|
|
|
|
let panelInGrid = this.workbenchGrid.hasView(this.panelPartView);
|
|
let sidebarInGrid = this.workbenchGrid.hasView(this.sideBarPartView);
|
|
let activityBarInGrid = this.workbenchGrid.hasView(this.activityBarPartView);
|
|
let statusBarInGrid = this.workbenchGrid.hasView(this.statusBarPartView);
|
|
let titlebarInGrid = this.workbenchGrid.hasView(this.titleBarPartView);
|
|
|
|
// Add parts to grid
|
|
if (!statusBarInGrid) {
|
|
this.workbenchGrid.addView(this.statusBarPartView, Sizing.Split, this.editorPartView, Direction.Down);
|
|
statusBarInGrid = true;
|
|
}
|
|
|
|
if (!titlebarInGrid && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
|
|
this.workbenchGrid.addView(this.titleBarPartView, Sizing.Split, this.editorPartView, Direction.Up);
|
|
titlebarInGrid = true;
|
|
}
|
|
|
|
if (!activityBarInGrid) {
|
|
this.workbenchGrid.addView(this.activityBarPartView, Sizing.Split, panelInGrid && this.state.sideBar.position === this.state.panel.position ? this.panelPartView : this.editorPartView, this.state.sideBar.position === Position.RIGHT ? Direction.Right : Direction.Left);
|
|
activityBarInGrid = true;
|
|
}
|
|
|
|
if (!sidebarInGrid) {
|
|
this.workbenchGrid.addView(this.sideBarPartView, this.state.sideBar.width !== undefined ? this.state.sideBar.width : Sizing.Split, this.activityBarPartView, this.state.sideBar.position === Position.LEFT ? Direction.Right : Direction.Left);
|
|
sidebarInGrid = true;
|
|
}
|
|
|
|
if (!panelInGrid) {
|
|
this.workbenchGrid.addView(this.panelPartView, this.getPanelDimension(this.state.panel.position) !== undefined ? this.getPanelDimension(this.state.panel.position) : Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right);
|
|
panelInGrid = true;
|
|
}
|
|
|
|
// Hide parts
|
|
if (this.state.panel.hidden) {
|
|
this.panelPartView.hide();
|
|
}
|
|
|
|
if (this.state.statusBar.hidden) {
|
|
this.statusBarPartView.hide();
|
|
}
|
|
|
|
if (!this.isVisible(Parts.TITLEBAR_PART)) {
|
|
this.titleBarPartView.hide();
|
|
}
|
|
|
|
if (this.state.activityBar.hidden) {
|
|
this.activityBarPartView.hide();
|
|
}
|
|
|
|
if (this.state.sideBar.hidden) {
|
|
this.sideBarPartView.hide();
|
|
}
|
|
|
|
if (this.state.editor.hidden) {
|
|
this.editorPartView.hide();
|
|
}
|
|
|
|
// Show visible parts
|
|
if (!this.state.editor.hidden) {
|
|
this.editorPartView.show();
|
|
}
|
|
|
|
if (!this.state.statusBar.hidden) {
|
|
this.statusBarPartView.show();
|
|
}
|
|
|
|
if (this.isVisible(Parts.TITLEBAR_PART)) {
|
|
this.titleBarPartView.show();
|
|
}
|
|
|
|
if (!this.state.activityBar.hidden) {
|
|
this.activityBarPartView.show();
|
|
}
|
|
|
|
if (!this.state.sideBar.hidden) {
|
|
this.sideBarPartView.show();
|
|
}
|
|
|
|
if (!this.state.panel.hidden) {
|
|
this.panelPartView.show();
|
|
}
|
|
}
|
|
|
|
private getPanelDimension(position: Position): number {
|
|
return position === Position.BOTTOM ? this.state.panel.height : this.state.panel.width;
|
|
}
|
|
|
|
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;
|
|
if (this.editorGroupService.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
resizePart(part: Parts, sizeChange: number): void {
|
|
let view: View;
|
|
switch (part) {
|
|
case Parts.SIDEBAR_PART:
|
|
view = this.sideBarPartView;
|
|
case Parts.PANEL_PART:
|
|
view = this.panelPartView;
|
|
case Parts.EDITOR_PART:
|
|
view = this.editorPartView;
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.workbenchGrid.resizeView(view, this.workbenchGrid.getViewSize(view) + sizeChange);
|
|
} else {
|
|
this.workbenchGrid.resizePart(part, sizeChange);
|
|
}
|
|
break;
|
|
default:
|
|
return; // Cannot resize other parts
|
|
}
|
|
}
|
|
|
|
setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void {
|
|
this.state.activityBar.hidden = hidden;
|
|
|
|
// Layout
|
|
if (!skipLayout) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
setEditorHidden(hidden: boolean, skipLayout?: boolean): void {
|
|
if (!(this.workbenchGrid instanceof Grid) || hidden === this.state.editor.hidden) {
|
|
return;
|
|
}
|
|
|
|
this.state.editor.hidden = hidden;
|
|
|
|
// The editor and the panel cannot be hidden at the same time
|
|
if (this.state.editor.hidden && this.state.panel.hidden) {
|
|
this.setPanelHidden(false, true);
|
|
}
|
|
|
|
if (!skipLayout) {
|
|
this.layout();
|
|
}
|
|
}
|
|
|
|
setSideBarHidden(hidden: boolean, skipLayout?: boolean): void {
|
|
this.state.sideBar.hidden = hidden;
|
|
|
|
// Adjust CSS
|
|
if (hidden) {
|
|
addClass(this.container, 'nosidebar');
|
|
} else {
|
|
removeClass(this.container, 'nosidebar');
|
|
}
|
|
|
|
// 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.editorGroupService.activeGroup.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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Layout
|
|
if (!skipLayout) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
setPanelHidden(hidden: boolean, skipLayout?: boolean): void {
|
|
this.state.panel.hidden = hidden;
|
|
|
|
// Adjust CSS
|
|
if (hidden) {
|
|
addClass(this.container, 'nopanel');
|
|
} else {
|
|
removeClass(this.container, 'nopanel');
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Layout
|
|
if (!skipLayout) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
}
|
|
|
|
toggleMaximizedPanel(): void {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
this.workbenchGrid.maximizeViewSize(this.panelPartView);
|
|
} else {
|
|
this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART });
|
|
}
|
|
}
|
|
|
|
isPanelMaximized(): boolean {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
try {
|
|
return this.workbenchGrid.getViewSize2(this.panelPartView).height === this.getPart(Parts.PANEL_PART).maximumHeight;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return this.workbenchGrid.isPanelMaximized();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
const dimensions = getClientArea(this.parent);
|
|
this.workbenchGrid.layout(dimensions.width, dimensions.height);
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getMenubarVisibility(): MenuBarVisibility {
|
|
return this.state.menuBar.visibility;
|
|
}
|
|
|
|
getPanelPosition(): Position {
|
|
return this.state.panel.position;
|
|
}
|
|
|
|
setPanelPosition(position: Position): void {
|
|
const panelPart = this.getPart(Parts.PANEL_PART);
|
|
const wasHidden = this.state.panel.hidden;
|
|
|
|
if (this.state.panel.hidden) {
|
|
this.setPanelHidden(false, true /* Skip Layout */);
|
|
} else {
|
|
this.savePanelDimension();
|
|
}
|
|
|
|
const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right';
|
|
const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right';
|
|
this.state.panel.position = position;
|
|
|
|
function positionToString(position: Position): string {
|
|
switch (position) {
|
|
case Position.LEFT: return 'left';
|
|
case Position.RIGHT: return 'right';
|
|
case Position.BOTTOM: return 'bottom';
|
|
}
|
|
}
|
|
|
|
this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE);
|
|
|
|
// Adjust CSS
|
|
removeClass(panelPart.getContainer(), oldPositionValue);
|
|
addClass(panelPart.getContainer(), newPositionValue);
|
|
|
|
// Update Styles
|
|
panelPart.updateStyles();
|
|
|
|
// Layout
|
|
if (this.workbenchGrid instanceof Grid) {
|
|
if (!wasHidden) {
|
|
this.savePanelDimension();
|
|
}
|
|
|
|
this.workbenchGrid.removeView(this.panelPartView);
|
|
this.layout();
|
|
} else {
|
|
this.workbenchGrid.layout();
|
|
}
|
|
}
|
|
|
|
private savePanelDimension(): void {
|
|
if (!(this.workbenchGrid instanceof Grid)) {
|
|
return;
|
|
}
|
|
|
|
if (this.state.panel.position === Position.BOTTOM) {
|
|
this.state.panel.height = this.workbenchGrid.getViewSize(this.panelPartView);
|
|
} else {
|
|
this.state.panel.width = this.workbenchGrid.getViewSize(this.panelPartView);
|
|
}
|
|
}
|
|
|
|
private saveLayoutState(e: IWillSaveStateEvent): void {
|
|
|
|
// Zen Mode
|
|
if (this.state.zenMode.active) {
|
|
this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE);
|
|
} else {
|
|
this.storageService.remove(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
dispose(): void {
|
|
super.dispose();
|
|
|
|
this.disposed = true;
|
|
}
|
|
}
|