diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 7211ba6846ee81..ee1b8e31c09358 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -8,7 +8,7 @@ import { MenuId, MenuRegistry, registerAction2, Action2 } from '../../../platfor import { Categories } from '../../../platform/action/common/actionCommonCategories.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { alert } from '../../../base/browser/ui/aria/aria.js'; -import { EditorActionsLocation, EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts, Position, ZenModeSettings, positionToString } from '../../services/layout/browser/layoutService.js'; +import { EditorActionsLocation, EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts, Position, SecondarySideBarLocation, ZenModeSettings, auxiliaryBarPositionFromConfiguration, positionToString } from '../../services/layout/browser/layoutService.js'; import { ServicesAccessor, IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { KeyMod, KeyCode } from '../../../base/common/keyCodes.js'; import { isWindows, isLinux, isWeb, isMacintosh, isNative } from '../../../base/common/platform.js'; @@ -96,6 +96,23 @@ registerAction2(class extends Action2 { // --- Set Sidebar Position const sidebarPositionConfigurationKey = 'workbench.sideBar.location'; +const secondarySideBarPositionConfigurationKey = LayoutSettings.SECONDARY_SIDE_BAR_LOCATION; + +const SecondarySideBarLeftContext = ContextKeyExpr.or( + ContextKeyExpr.equals(`config.${secondarySideBarPositionConfigurationKey}`, SecondarySideBarLocation.LEFT), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${secondarySideBarPositionConfigurationKey}`, SecondarySideBarLocation.OPPOSITE), + ContextKeyExpr.equals(`config.${sidebarPositionConfigurationKey}`, 'right') + ) +) ?? ContextKeyExpr.false(); + +const SecondarySideBarRightContext = ContextKeyExpr.or( + ContextKeyExpr.equals(`config.${secondarySideBarPositionConfigurationKey}`, SecondarySideBarLocation.RIGHT), + ContextKeyExpr.and( + ContextKeyExpr.equals(`config.${secondarySideBarPositionConfigurationKey}`, SecondarySideBarLocation.OPPOSITE), + ContextKeyExpr.equals(`config.${sidebarPositionConfigurationKey}`, 'left') + ) +) ?? ContextKeyExpr.false(); class MoveSidebarPositionAction extends Action2 { constructor(id: string, title: ICommandActionTitle, private readonly position: Position) { @@ -136,6 +153,46 @@ class MoveSidebarLeftAction extends MoveSidebarPositionAction { registerAction2(MoveSidebarRightAction); registerAction2(MoveSidebarLeftAction); +class MoveSecondarySideBarPositionAction extends Action2 { + constructor(id: string, title: ICommandActionTitle, private readonly position: Position) { + super({ + id, + title, + f1: false + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + + const configuredPosition = configurationService.getValue(secondarySideBarPositionConfigurationKey); + const position = this.position === Position.LEFT ? SecondarySideBarLocation.LEFT : SecondarySideBarLocation.RIGHT; + + if (configuredPosition !== position) { + return configurationService.updateValue(secondarySideBarPositionConfigurationKey, position); + } + } +} + +class MoveSecondarySideBarRightAction extends MoveSecondarySideBarPositionAction { + static readonly ID = 'workbench.action.moveSecondarySideBarRight'; + + constructor() { + super(MoveSecondarySideBarRightAction.ID, localize2('moveSecondarySideBarRight', "Move Secondary Side Bar Right"), Position.RIGHT); + } +} + +class MoveSecondarySideBarLeftAction extends MoveSecondarySideBarPositionAction { + static readonly ID = 'workbench.action.moveSecondarySideBarLeft'; + + constructor() { + super(MoveSecondarySideBarLeftAction.ID, localize2('moveSecondarySideBarLeft', "Move Secondary Side Bar Left"), Position.LEFT); + } +} + +registerAction2(MoveSecondarySideBarRightAction); +registerAction2(MoveSecondarySideBarLeftAction); + // --- Toggle Sidebar Position export class ToggleSidebarPositionAction extends Action2 { @@ -170,6 +227,46 @@ export class ToggleSidebarPositionAction extends Action2 { registerAction2(ToggleSidebarPositionAction); +export class ToggleSecondarySideBarPositionAction extends Action2 { + + static readonly ID = 'workbench.action.toggleSecondarySideBarPosition'; + static readonly LABEL = localize('toggleSecondarySideBarPosition', "Toggle Secondary Side Bar Position"); + + static getLabel(layoutService: IWorkbenchLayoutService, configurationService: IConfigurationService): string { + const position = auxiliaryBarPositionFromConfiguration( + layoutService.getSideBarPosition(), + configurationService.getValue(secondarySideBarPositionConfigurationKey) + ); + + return position === Position.LEFT ? localize('moveSecondarySideBarRight', "Move Secondary Side Bar Right") : localize('moveSecondarySideBarLeft', "Move Secondary Side Bar Left"); + } + + constructor() { + super({ + id: ToggleSecondarySideBarPositionAction.ID, + title: localize2('toggleSecondarySideBarPosition', "Toggle Secondary Side Bar Position"), + category: Categories.View, + f1: true, + precondition: IsSessionsWindowContext.negate() + }); + } + + run(accessor: ServicesAccessor): Promise { + const layoutService = accessor.get(IWorkbenchLayoutService); + const configurationService = accessor.get(IConfigurationService); + + const position = auxiliaryBarPositionFromConfiguration( + layoutService.getSideBarPosition(), + configurationService.getValue(secondarySideBarPositionConfigurationKey) + ); + const newPositionValue = (position === Position.LEFT) ? SecondarySideBarLocation.RIGHT : SecondarySideBarLocation.LEFT; + + return configurationService.updateValue(secondarySideBarPositionConfigurationKey, newPositionValue); + } +} + +registerAction2(ToggleSecondarySideBarPositionAction); + const configureLayoutIcon = registerIcon('configure-layout-icon', Codicon.layout, localize('cofigureLayoutIcon', 'Icon represents workbench layout configuration.')); MenuRegistry.appendMenuItem(MenuId.LayoutControlMenu, { submenu: MenuId.LayoutControlMenuSubmenu, @@ -210,10 +307,10 @@ MenuRegistry.appendMenuItems([{ item: { group: '3_workbench_layout_move', command: { - id: ToggleSidebarPositionAction.ID, + id: MoveSecondarySideBarLeftAction.ID, title: localize('move second sidebar left', "Move Secondary Side Bar Left") }, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), + when: ContextKeyExpr.and(SecondarySideBarRightContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 } }, { @@ -221,10 +318,10 @@ MenuRegistry.appendMenuItems([{ item: { group: '3_workbench_layout_move', command: { - id: ToggleSidebarPositionAction.ID, + id: MoveSecondarySideBarRightAction.ID, title: localize('move second sidebar right', "Move Secondary Side Bar Right") }, - when: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), + when: ContextKeyExpr.and(SecondarySideBarLeftContext, ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar))), order: 1 } }]); @@ -1365,7 +1462,7 @@ if (!isMacintosh || !isNative) { ToggleVisibilityActions.push(...[ CreateToggleLayoutItem(ToggleActivityBarVisibilityActionId, ContextKeyExpr.notEquals('config.workbench.activityBar.location', 'hidden'), localize('activityBar', "Activity Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: activityBarLeftIcon, iconB: activityBarRightIcon }), CreateToggleLayoutItem(ToggleSidebarVisibilityAction.ID, SideBarVisibleContext, localize('sideBar', "Primary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelLeftIcon, iconB: panelRightIcon }), - CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), + CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "Secondary Side Bar"), { whenA: SecondarySideBarRightContext, iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon), ]); @@ -1399,6 +1496,13 @@ for (const { active } of [...ToggleVisibilityActions, ...MoveSideBarActions, ... LayoutContextKeySet.add(key); } } +for (const { visualIcon } of [...ToggleVisibilityActions, ...MoveSideBarActions, ...AlignPanelActions, ...QuickInputActions, ...MiscLayoutOptions]) { + if (visualIcon && isContextualLayoutVisualIcon(visualIcon)) { + for (const key of visualIcon.whenA.keys()) { + LayoutContextKeySet.add(key); + } + } +} /** * Matches the title bar's `editorActionsEnabled` getter: true when editor diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index ea2da133d8609a..63f2c6a05721e0 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -11,7 +11,7 @@ import { isWindows, isLinux, isMacintosh, isWeb, isIOS } from '../../base/common import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from '../common/editor.js'; import { SidebarPart } from './parts/sidebar/sidebarPart.js'; import { PanelPart } from './parts/panel/panelPart.js'; -import { Position, Parts, PartOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, partOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar, isHorizontal, isMultiWindowPart, IPartVisibilityChangeEvent } from '../services/layout/browser/layoutService.js'; +import { Position, Parts, PartOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, partOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings, MULTI_WINDOW_PARTS, SINGLE_WINDOW_PARTS, ZenModeSettings, EditorTabsMode, EditorActionsLocation, shouldShowCustomTitleBar, isHorizontal, isMultiWindowPart, IPartVisibilityChangeEvent, auxiliaryBarPositionFromConfiguration, SecondarySideBarLocation } from '../services/layout/browser/layoutService.js'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from '../../platform/workspace/common/workspace.js'; import { IStorageService, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js'; import { IConfigurationChangeEvent, IConfigurationService, isConfigured } from '../../platform/configuration/common/configuration.js'; @@ -438,6 +438,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Auxiliary Sidebar + if (e.affectsConfiguration(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION) && this.workbenchGrid) { + this.updateAuxiliaryBarPositionClasses(); + this.getPart(Parts.AUXILIARYBAR_PART).updateStyles(); + this.adjustPartPositions(this.getSideBarPosition(), this.getPanelAlignment(), this.getPanelPosition()); + } + if (e.affectsConfiguration(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED)) { const forceMaximized = this.configurationService.getValue(WorkbenchLayoutSettings.AUXILIARYBAR_FORCE_MAXIMIZED); if (forceMaximized === true && this.mainPartEditorService.visibleEditors.length === 0) { @@ -611,6 +617,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + private getAuxiliaryBarPosition(sideBarPosition = this.getSideBarPosition()): Position { + return auxiliaryBarPositionFromConfiguration( + sideBarPosition, + this.configurationService.getValue(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION) + ); + } + + private updateAuxiliaryBarPositionClasses(position = this.getAuxiliaryBarPosition()): void { + const auxiliaryBar = this.getPart(Parts.AUXILIARYBAR_PART); + const auxiliaryBarContainer = assertReturnsDefined(auxiliaryBar.getContainer()); + + auxiliaryBarContainer.classList.remove('left', 'right'); + auxiliaryBarContainer.classList.add(positionToString(position)); + } + private setSideBarPosition(position: Position): void { const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); @@ -625,15 +646,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Adjust CSS const activityBarContainer = assertReturnsDefined(activityBar.getContainer()); const sideBarContainer = assertReturnsDefined(sideBar.getContainer()); - const auxiliaryBarContainer = assertReturnsDefined(auxiliaryBar.getContainer()); activityBarContainer.classList.remove(oldPositionValue); sideBarContainer.classList.remove(oldPositionValue); activityBarContainer.classList.add(newPositionValue); sideBarContainer.classList.add(newPositionValue); - // Auxiliary Bar has opposite values - auxiliaryBarContainer.classList.remove(newPositionValue); - auxiliaryBarContainer.classList.add(oldPositionValue); + this.updateAuxiliaryBarPositionClasses(this.getAuxiliaryBarPosition(position)); // Update Styles activityBar.updateStyles(); @@ -1939,8 +1957,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Move activity bar and side bars const isPanelVertical = !isHorizontal(panelPosition); - const sideBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); - const auxiliaryBarSiblingToEditor = isPanelVertical || !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const auxiliaryBarPosition = this.getAuxiliaryBarPosition(sideBarPosition); + const isPartSiblingToEditor = (position: Position) => isPanelVertical || !(panelAlignment === 'center' || (position === Position.LEFT && panelAlignment === 'right') || (position === Position.RIGHT && panelAlignment === 'left')); + const sideBarSiblingToEditor = isPartSiblingToEditor(sideBarPosition); + const auxiliaryBarSiblingToEditor = isPartSiblingToEditor(auxiliaryBarPosition); const preMovePanelWidth = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.panelPartView).width; const preMovePanelHeight = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumHeight) : this.workbenchGrid.getViewSize(this.panelPartView).height; const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; @@ -1951,19 +1971,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (sideBarPosition === Position.LEFT) { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Left : Direction.Right); - if (auxiliaryBarSiblingToEditor) { - this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Right); - } else { - this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, -1]); - } } else { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, -1]); this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Right : Direction.Left); - if (auxiliaryBarSiblingToEditor) { - this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Left); - } else { - this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 0]); - } + } + + if (auxiliaryBarSiblingToEditor) { + this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, auxiliaryBarPosition === Position.LEFT ? Direction.Left : Direction.Right); + } else if (auxiliaryBarPosition === sideBarPosition) { + this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.sideBarPartView, auxiliaryBarPosition === Position.LEFT ? Direction.Right : Direction.Left); + } else { + this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, auxiliaryBarPosition === Position.LEFT ? [2, 0] : [2, -1]); } // Maintain focus after moving parts @@ -2483,26 +2501,34 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const result = [nodes.editor]; nodes.editor.size = availableWidth; + const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); + const auxiliaryBarPosition = this.getAuxiliaryBarPosition(sideBarPosition); + const leftNodes: ISerializedNode[] = []; + const rightNodes: ISerializedNode[] = []; + if (nodes.sideBar) { - if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) { - result.splice(0, 0, nodes.sideBar); + if (sideBarPosition === Position.LEFT) { + leftNodes.push(nodes.sideBar); } else { - result.push(nodes.sideBar); + rightNodes.push(nodes.sideBar); } nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_HIDDEN) ? 0 : nodes.sideBar.size; } if (nodes.auxiliaryBar) { - if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.RIGHT) { - result.splice(0, 0, nodes.auxiliaryBar); + if (auxiliaryBarPosition === Position.LEFT) { + leftNodes.push(nodes.auxiliaryBar); } else { - result.push(nodes.auxiliaryBar); + rightNodes.splice(0, 0, nodes.auxiliaryBar); } nodes.editor.size -= this.stateModel.getRuntimeValue(LayoutStateKeys.AUXILIARYBAR_HIDDEN) ? 0 : nodes.auxiliaryBar.size; } + result.splice(0, 0, ...leftNodes); + result.push(...rightNodes); + return { type: 'branch', data: result, @@ -2519,30 +2545,38 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const panelPostion = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION); const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); + const auxiliaryBarPosition = this.getAuxiliaryBarPosition(sideBarPosition); const result = [] as ISerializedNode[]; if (!isHorizontal(panelPostion)) { - result.push(nodes.editor); + const centerNodes = [nodes.editor]; nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; if (panelPostion === Position.RIGHT) { - result.push(nodes.panel); + centerNodes.push(nodes.panel); } else { - result.splice(0, 0, nodes.panel); + centerNodes.splice(0, 0, nodes.panel); } + const leftEdgeNodes = [] as ISerializedNode[]; + const rightEdgeNodes = [] as ISerializedNode[]; if (sideBarPosition === Position.LEFT) { - result.push(nodes.auxiliaryBar); - result.splice(0, 0, nodes.sideBar); - result.splice(0, 0, nodes.activityBar); + leftEdgeNodes.push(nodes.activityBar, nodes.sideBar); + } else { + rightEdgeNodes.push(nodes.sideBar, nodes.activityBar); + } + + if (auxiliaryBarPosition === Position.LEFT) { + leftEdgeNodes.push(nodes.auxiliaryBar); } else { - result.splice(0, 0, nodes.auxiliaryBar); - result.push(nodes.sideBar); - result.push(nodes.activityBar); + rightEdgeNodes.splice(0, 0, nodes.auxiliaryBar); } + + result.push(...leftEdgeNodes, ...centerNodes, ...rightEdgeNodes); } else { const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); - const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); - const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const isPartNextToEditor = (position: Position) => !(panelAlignment === 'center' || (position === Position.LEFT && panelAlignment === 'right') || (position === Position.RIGHT && panelAlignment === 'left')); + const sideBarNextToEditor = isPartNextToEditor(sideBarPosition); + const auxiliaryBarNextToEditor = isPartNextToEditor(auxiliaryBarPosition); const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize); @@ -2560,27 +2594,30 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi visible: data.some(node => node.visible) }); - if (!sideBarNextToEditor) { - if (sideBarPosition === Position.LEFT) { - result.splice(0, 0, nodes.sideBar); - } else { - result.push(nodes.sideBar); + const leftEdgeNodes = [] as ISerializedNode[]; + const rightEdgeNodes = [] as ISerializedNode[]; + if (sideBarPosition === Position.LEFT) { + leftEdgeNodes.push(nodes.activityBar); + if (!sideBarNextToEditor) { + leftEdgeNodes.push(nodes.sideBar); + } + } else { + if (!sideBarNextToEditor) { + rightEdgeNodes.push(nodes.sideBar); } + rightEdgeNodes.push(nodes.activityBar); } if (!auxiliaryBarNextToEditor) { - if (sideBarPosition === Position.RIGHT) { - result.splice(0, 0, nodes.auxiliaryBar); + if (auxiliaryBarPosition === Position.LEFT) { + leftEdgeNodes.push(nodes.auxiliaryBar); } else { - result.push(nodes.auxiliaryBar); + rightEdgeNodes.splice(0, 0, nodes.auxiliaryBar); } } - if (sideBarPosition === Position.LEFT) { - result.splice(0, 0, nodes.activityBar); - } else { - result.push(nodes.activityBar); - } + result.splice(0, 0, ...leftEdgeNodes); + result.push(...rightEdgeNodes); } return result; diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index c89c0e4eee8f1f..0b798919603c91 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -17,13 +17,13 @@ import { ActiveAuxiliaryContext, AuxiliaryBarFocusContext } from '../../../commo import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_TOP_ACTIVE_BORDER, ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_TOP_FOREGROUND, ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_TITLE_BORDER, SIDE_BAR_FOREGROUND } from '../../../common/theme.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from '../../../services/layout/browser/layoutService.js'; +import { ActivityBarPosition, auxiliaryBarPositionFromConfiguration, IWorkbenchLayoutService, LayoutSettings, Parts, Position, SecondarySideBarLocation } from '../../../services/layout/browser/layoutService.js'; import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { IAction, Separator, SubmenuAction, toAction } from '../../../../base/common/actions.js'; import { ToggleAuxiliaryBarAction } from './auxiliaryBarActions.js'; import { assertReturnsDefined } from '../../../../base/common/types.js'; import { LayoutPriority } from '../../../../base/browser/ui/splitview/splitview.js'; -import { ToggleSidebarPositionAction } from '../../actions/layoutActions.js'; +import { ToggleSecondarySideBarPositionAction } from '../../actions/layoutActions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js'; import { ActionsOrientation } from '../../../../base/browser/ui/actionbar/actionbar.js'; @@ -138,6 +138,9 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) { this.configuration = this.resolveConfiguration(); this.onDidChangeActivityBarLocation(); + } else if (e.affectsConfiguration(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION)) { + this.updateStyles(); + this.updateCompositeBar(true); } else if (e.affectsConfiguration('workbench.secondarySideBar.showLabels')) { this.configuration = this.resolveConfiguration(); this.updateCompositeBar(true); @@ -168,6 +171,13 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { return { position, canShowLabels, showLabels }; } + private getAuxiliaryBarPosition(): Position { + return auxiliaryBarPositionFromConfiguration( + this.layoutService.getSideBarPosition(), + this.configurationService.getValue(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION) + ); + } + private onDidChangeActivityBarLocation(): void { this.updateCompositeBar(); @@ -183,7 +193,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { const container = assertReturnsDefined(this.getContainer()); container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || ''; const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); - const isPositionLeft = this.layoutService.getSideBarPosition() === Position.RIGHT; + const isPositionLeft = this.getAuxiliaryBarPosition() === Position.LEFT; container.style.color = this.getColor(SIDE_BAR_FOREGROUND) || ''; @@ -230,7 +240,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { } private fillExtraContextMenuActions(actions: IAction[]): void { - const currentPositionRight = this.layoutService.getSideBarPosition() === Position.LEFT; + const currentPositionRight = this.getAuxiliaryBarPosition() === Position.RIGHT; if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) { const viewsSubmenuAction = this.getViewsSubmenuAction(); @@ -253,7 +263,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { actions.push(...[ new Separator(), new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), - toAction({ id: ToggleSidebarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSidebarPositionAction.ID) }), + toAction({ id: ToggleSecondarySideBarPositionAction.ID, label: currentPositionRight ? localize('move second side bar left', "Move Secondary Side Bar Left") : localize('move second side bar right', "Move Secondary Side Bar Right"), run: () => this.commandService.executeCommand(ToggleSecondarySideBarPositionAction.ID) }), toggleShowLabelsAction, toAction({ id: ToggleAuxiliaryBarAction.ID, label: localize('hide second side bar', "Hide Secondary Side Bar"), run: () => this.commandService.executeCommand(ToggleAuxiliaryBarAction.ID) }) ]); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 673af6745d1a5d..2163d39fd973c6 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -24,7 +24,7 @@ import { EditorDropTarget } from './editorDropTarget.js'; import { Color } from '../../../../base/common/color.js'; import { CenteredViewLayout, CenteredViewState } from '../../../../base/browser/ui/centered/centeredViewLayout.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; -import { Parts, IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js'; +import { Parts, IWorkbenchLayoutService, Position, SecondarySideBarLocation, auxiliaryBarPositionFromConfiguration, LayoutSettings } from '../../../services/layout/browser/layoutService.js'; import { DeepPartial, assertType } from '../../../../base/common/types.js'; import { CompositeDragAndDropObserver } from '../../dnd.js'; import { DeferredPromise, Promises } from '../../../../base/common/async.js'; @@ -1133,9 +1133,14 @@ export class EditorPart extends Part implements IEditorPart, let lastOpenHorizontalPosition: Position | undefined; let lastOpenVerticalPosition: Position | undefined; const openPartAtPosition = (position: Position) => { + const auxiliaryBarPosition = auxiliaryBarPositionFromConfiguration( + this.layoutService.getSideBarPosition(), + this.configurationService.getValue(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION) + ); + if (!this.layoutService.isVisible(Parts.PANEL_PART) && position === this.layoutService.getPanelPosition()) { this.layoutService.setPartHidden(false, Parts.PANEL_PART); - } else if (!this.layoutService.isVisible(Parts.AUXILIARYBAR_PART) && position === (this.layoutService.getSideBarPosition() === Position.RIGHT ? Position.LEFT : Position.RIGHT)) { + } else if (!this.layoutService.isVisible(Parts.AUXILIARYBAR_PART) && position === auxiliaryBarPosition) { this.layoutService.setPartHidden(false, Parts.AUXILIARYBAR_PART); } }; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index eeddba006261bb..08d45f1c5f88d5 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -13,7 +13,7 @@ import { ConfigurationKeyValuePairs, ConfigurationMigrationWorkbenchContribution import { WorkbenchPhase, registerWorkbenchContribution2 } from '../common/contributions.js'; import { NotificationsPosition, NotificationsSettings } from '../common/notifications.js'; import { CustomEditorLabelService } from '../services/editor/common/customEditorLabelService.js'; -import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings } from '../services/layout/browser/layoutService.js'; +import { ActivityBarPosition, EditorActionsLocation, EditorTabsMode, LayoutSettings, SecondarySideBarLocation } from '../services/layout/browser/layoutService.js'; import { defaultWindowTitle, defaultWindowTitleSeparator } from './parts/titlebar/windowTitle.js'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -567,7 +567,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'right'], 'default': 'left', - 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench. The secondary side bar will show on the opposite side of the workbench."), + 'description': localize('sideBarLocation', "Controls the location of the primary side bar and activity bar. They can either show on the left or right of the workbench."), agentsWindow: { default: 'left', readOnly: true }, }, 'workbench.panel.showLabels': { @@ -608,6 +608,18 @@ const registry = Registry.as(ConfigurationExtensions.Con ], agentsWindow: { default: 'visibleInWorkspace', readOnly: true }, }, + [LayoutSettings.SECONDARY_SIDE_BAR_LOCATION]: { + 'type': 'string', + 'enum': [SecondarySideBarLocation.OPPOSITE, SecondarySideBarLocation.LEFT, SecondarySideBarLocation.RIGHT], + 'default': SecondarySideBarLocation.OPPOSITE, + 'description': localize('secondarySideBarLocation', "Controls the location of the secondary side bar. By default, it shows on the opposite side of the primary side bar."), + 'enumDescriptions': [ + localize('workbench.secondarySideBar.location.opposite', "Show the secondary side bar on the opposite side of the primary side bar."), + localize('workbench.secondarySideBar.location.left', "Show the secondary side bar on the left."), + localize('workbench.secondarySideBar.location.right', "Show the secondary side bar on the right.") + ], + agentsWindow: { default: SecondarySideBarLocation.OPPOSITE, readOnly: true }, + }, 'workbench.secondarySideBar.forceMaximized': { 'type': 'boolean', 'default': false, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 10e2c3edace791..bd9d3f44dabe7e 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -15,7 +15,7 @@ import { isWindows, isLinux, isWeb, isNative, isMacintosh } from '../../base/com import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../common/contributions.js'; import { IEditorFactoryRegistry, EditorExtensions } from '../common/editor.js'; import { getSingletonServiceDescriptors } from '../../platform/instantiation/common/extensions.js'; -import { Position, Parts, IWorkbenchLayoutService, positionToString } from '../services/layout/browser/layoutService.js'; +import { Position, Parts, IWorkbenchLayoutService, positionToString, auxiliaryBarPositionFromConfiguration, LayoutSettings, SecondarySideBarLocation } from '../services/layout/browser/layoutService.js'; import { IStorageService, WillSaveStateReason, StorageScope, StorageTarget } from '../../platform/storage/common/storage.js'; import { IConfigurationChangeEvent, IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js'; @@ -343,6 +343,11 @@ export class Workbench extends Layout { this.restoreFontInfo(storageService, configurationService); // Create Parts + const auxiliaryBarPosition = auxiliaryBarPositionFromConfiguration( + this.getSideBarPosition(), + configurationService.getValue(LayoutSettings.SECONDARY_SIDE_BAR_LOCATION) + ); + for (const { id, role, classes, options } of [ { id: Parts.TITLEBAR_PART, role: 'none', classes: ['titlebar'] }, { id: Parts.BANNER_PART, role: 'banner', classes: ['banner'] }, @@ -350,7 +355,7 @@ export class Workbench extends Layout { { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } }, { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, - { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, + { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', positionToString(auxiliaryBarPosition)] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] } ]) { const partContainer = this.createPart(id, role, classes); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 89b7bdf6dab48f..c51c3162269062 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -49,7 +49,8 @@ export const enum LayoutSettings { EDITOR_ACTIONS_LOCATION = 'workbench.editor.editorActionsLocation', COMMAND_CENTER = 'window.commandCenter', LAYOUT_ACTIONS = 'workbench.layoutControl.enabled', - SHADOWS = 'workbench.shadows' + SHADOWS = 'workbench.shadows', + SECONDARY_SIDE_BAR_LOCATION = 'workbench.secondarySideBar.location' } export const enum ActivityBarPosition { @@ -71,6 +72,12 @@ export const enum EditorActionsLocation { HIDDEN = 'hidden' } +export const enum SecondarySideBarLocation { + OPPOSITE = 'opposite', + LEFT = 'left', + RIGHT = 'right' +} + export const enum Position { LEFT, RIGHT, @@ -111,6 +118,19 @@ export function positionFromString(str: string): Position { return positionsByString[str]; } +export function auxiliaryBarPositionFromConfiguration(sideBarPosition: Position, secondarySideBarLocation: SecondarySideBarLocation | undefined): Position { + switch (secondarySideBarLocation) { + case SecondarySideBarLocation.OPPOSITE: + return sideBarPosition === Position.LEFT ? Position.RIGHT : Position.LEFT; + case SecondarySideBarLocation.LEFT: + return Position.LEFT; + case SecondarySideBarLocation.RIGHT: + return Position.RIGHT; + default: + return sideBarPosition === Position.LEFT ? Position.RIGHT : Position.LEFT; + } +} + function partOpensMaximizedSettingToString(setting: PartOpensMaximizedOptions): string { switch (setting) { case PartOpensMaximizedOptions.ALWAYS: return 'always';