Path: blob/main/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts
5267 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/sidebarpart.css';6import './sidebarActions.js';7import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position as SideBarPosition } from '../../../services/layout/browser/layoutService.js';8import { SidebarFocusContext, ActiveViewletContext } from '../../../common/contextkeys.js';9import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';10import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';11import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';12import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';13import { IThemeService } from '../../../../platform/theme/common/themeService.js';14import { contrastBorder } from '../../../../platform/theme/common/colorRegistry.js';15import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_TITLE_BORDER, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_TOP_FOREGROUND, ACTIVITY_BAR_TOP_ACTIVE_BORDER, ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND, ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER } from '../../../common/theme.js';16import { INotificationService } from '../../../../platform/notification/common/notification.js';17import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';18import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js';19import { IExtensionService } from '../../../services/extensions/common/extensions.js';20import { LayoutPriority } from '../../../../base/browser/ui/grid/grid.js';21import { assertReturnsDefined } from '../../../../base/common/types.js';22import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';23import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js';24import { ActivityBarCompositeBar, ActivitybarPart } from '../activitybar/activitybarPart.js';25import { ActionsOrientation } from '../../../../base/browser/ui/actionbar/actionbar.js';26import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';27import { IPaneCompositeBarOptions } from '../paneCompositeBar.js';28import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';29import { Action2, IMenuService, registerAction2 } from '../../../../platform/actions/common/actions.js';30import { Separator } from '../../../../base/common/actions.js';31import { ToggleActivityBarVisibilityActionId } from '../../actions/layoutActions.js';32import { localize2 } from '../../../../nls.js';33import { IHoverService } from '../../../../platform/hover/browser/hover.js';34import { VisibleViewContainersTracker } from '../visibleViewContainersTracker.js';3536export class SidebarPart extends AbstractPaneCompositePart {3738static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';3940//#region IView4142readonly minimumWidth: number = 170;43readonly maximumWidth: number = Number.POSITIVE_INFINITY;44readonly minimumHeight: number = 0;45readonly maximumHeight: number = Number.POSITIVE_INFINITY;46override get snap(): boolean { return true; }4748readonly priority: LayoutPriority = LayoutPriority.Low;4950get preferredWidth(): number | undefined {51const viewlet = this.getActivePaneComposite();5253if (!viewlet) {54return undefined;55}5657const width = viewlet.getOptimalWidth();58if (typeof width !== 'number') {59return undefined;60}6162return Math.max(width, 300);63}6465private readonly activityBarPart = this._register(this.instantiationService.createInstance(ActivitybarPart, this));66private readonly visibleViewContainersTracker: VisibleViewContainersTracker;6768//#endregion6970constructor(71@INotificationService notificationService: INotificationService,72@IStorageService storageService: IStorageService,73@IContextMenuService contextMenuService: IContextMenuService,74@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,75@IKeybindingService keybindingService: IKeybindingService,76@IHoverService hoverService: IHoverService,77@IInstantiationService instantiationService: IInstantiationService,78@IThemeService themeService: IThemeService,79@IViewDescriptorService viewDescriptorService: IViewDescriptorService,80@IContextKeyService contextKeyService: IContextKeyService,81@IExtensionService extensionService: IExtensionService,82@IConfigurationService private readonly configurationService: IConfigurationService,83@IMenuService menuService: IMenuService,84) {85super(86Parts.SIDEBAR_PART,87{ hasTitle: true, trailingSeparator: false, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 },88SidebarPart.activeViewletSettingsKey,89ActiveViewletContext.bindTo(contextKeyService),90SidebarFocusContext.bindTo(contextKeyService),91'sideBar',92'viewlet',93SIDE_BAR_TITLE_FOREGROUND,94SIDE_BAR_TITLE_BORDER,95notificationService,96storageService,97contextMenuService,98layoutService,99keybindingService,100hoverService,101instantiationService,102themeService,103viewDescriptorService,104contextKeyService,105extensionService,106menuService,107);108109// Track visible view containers for auto-hide110this.visibleViewContainersTracker = this._register(instantiationService.createInstance(VisibleViewContainersTracker, ViewContainerLocation.Sidebar));111this._register(this.visibleViewContainersTracker.onDidChange((e) => this.onDidChangeAutoHideViewContainers(e)));112113this.rememberActivityBarVisiblePosition();114this._register(configurationService.onDidChangeConfiguration(e => {115if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {116this.onDidChangeActivityBarLocation();117}118if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE)) {119this.onDidChangeActivityBarLocation();120}121}));122123this.registerActions();124}125126private onDidChangeAutoHideViewContainers(e: { before: number; after: number }): void {127// Only update if auto-hide is enabled and composite bar position is top/bottom128const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);129const autoHide = this.configurationService.getValue<boolean>(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE);130if (autoHide && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM)) {131const visibleBefore = e.before > 1;132const visibleAfter = e.after > 1;133if (visibleBefore !== visibleAfter) {134this.onDidChangeActivityBarLocation();135}136}137}138139private onDidChangeActivityBarLocation(): void {140this.activityBarPart.hide();141142this.updateCompositeBar();143144const id = this.getActiveComposite()?.getId();145if (id) {146this.onTitleAreaUpdate(id);147}148149if (this.shouldShowActivityBar()) {150this.activityBarPart.show();151}152153this.rememberActivityBarVisiblePosition();154}155156override updateStyles(): void {157super.updateStyles();158159const container = assertReturnsDefined(this.getContainer());160161container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || '';162container.style.color = this.getColor(SIDE_BAR_FOREGROUND) || '';163164const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder);165const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;166container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '';167container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : '';168container.style.borderRightColor = isPositionLeft ? borderColor || '' : '';169container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';170container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';171container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';172container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';173}174175override layout(width: number, height: number, top: number, left: number): void {176if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {177return;178}179180super.layout(width, height, top, left);181}182183protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {184return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT;185}186187protected override createCompositeBar(): ActivityBarCompositeBar {188return this.instantiationService.createInstance(ActivityBarCompositeBar, this.getCompositeBarOptions(), this.partId, this, false);189}190191protected getCompositeBarOptions(): IPaneCompositeBarOptions {192return {193partContainerClass: 'sidebar',194pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey,195placeholderViewContainersKey: ActivitybarPart.placeholderViewContainersKey,196viewContainersWorkspaceStateKey: ActivitybarPart.viewContainersWorkspaceStateKey,197icon: true,198orientation: ActionsOrientation.HORIZONTAL,199recomputeSizes: true,200activityHoverOptions: {201position: () => this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW,202},203fillExtraContextMenuActions: actions => {204if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) {205const viewsSubmenuAction = this.getViewsSubmenuAction();206if (viewsSubmenuAction) {207actions.push(new Separator());208actions.push(viewsSubmenuAction);209}210}211},212compositeSize: 0,213iconSize: 16,214overflowActionSize: 30,215colors: theme => ({216activeBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),217inactiveBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),218activeBorderBottomColor: theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER),219activeForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND),220inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND),221badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),222badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),223dragAndDropBorder: theme.getColor(ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER)224}),225compact: true226};227}228229protected shouldShowCompositeBar(): boolean {230const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);231if (activityBarPosition !== ActivityBarPosition.TOP && activityBarPosition !== ActivityBarPosition.BOTTOM) {232return false;233}234235// Check if auto-hide is enabled and there's only one visible view container236const autoHide = this.configurationService.getValue<boolean>(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE);237if (autoHide) {238// Use visible composite count from the composite bar if available (considers pinned state),239// otherwise fall back to the tracker's count (based on active view descriptors).240// Note: We access paneCompositeBar directly to avoid circular calls with getVisiblePaneCompositeIds()241const visibleCount = this.visibleViewContainersTracker.visibleCount;242if (visibleCount <= 1) {243return false;244}245}246247return true;248}249250private shouldShowActivityBar(): boolean {251if (this.shouldShowCompositeBar()) {252return false;253}254255return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.HIDDEN;256}257258protected getCompositeBarPosition(): CompositeBarPosition {259const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);260switch (activityBarPosition) {261case ActivityBarPosition.TOP: return CompositeBarPosition.TOP;262case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM;263case ActivityBarPosition.HIDDEN:264case ActivityBarPosition.DEFAULT: // noop265default: return CompositeBarPosition.TITLE;266}267}268269private rememberActivityBarVisiblePosition(): void {270const activityBarPosition = this.configurationService.getValue<string>(LayoutSettings.ACTIVITY_BAR_LOCATION);271if (activityBarPosition !== ActivityBarPosition.HIDDEN) {272this.storageService.store(LayoutSettings.ACTIVITY_BAR_LOCATION, activityBarPosition, StorageScope.PROFILE, StorageTarget.USER);273}274}275276private getRememberedActivityBarVisiblePosition(): ActivityBarPosition {277const activityBarPosition = this.storageService.get(LayoutSettings.ACTIVITY_BAR_LOCATION, StorageScope.PROFILE);278switch (activityBarPosition) {279case ActivityBarPosition.TOP: return ActivityBarPosition.TOP;280case ActivityBarPosition.BOTTOM: return ActivityBarPosition.BOTTOM;281default: return ActivityBarPosition.DEFAULT;282}283}284285override getPinnedPaneCompositeIds(): string[] {286return this.shouldShowCompositeBar() ? super.getPinnedPaneCompositeIds() : this.activityBarPart.getPinnedPaneCompositeIds();287}288289override getVisiblePaneCompositeIds(): string[] {290return this.shouldShowCompositeBar() ? super.getVisiblePaneCompositeIds() : this.activityBarPart.getVisiblePaneCompositeIds();291}292293override getPaneCompositeIds(): string[] {294return this.shouldShowCompositeBar() ? super.getPaneCompositeIds() : this.activityBarPart.getPaneCompositeIds();295}296297async focusActivityBar(): Promise<void> {298if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.HIDDEN) {299await this.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, this.getRememberedActivityBarVisiblePosition());300301this.onDidChangeActivityBarLocation();302}303304if (this.shouldShowCompositeBar()) {305this.focusCompositeBar();306} else {307if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {308this.layoutService.setPartHidden(false, Parts.ACTIVITYBAR_PART);309}310311this.activityBarPart.show(true);312}313}314315private registerActions(): void {316const that = this;317this._register(registerAction2(class extends Action2 {318constructor() {319super({320id: ToggleActivityBarVisibilityActionId,321title: localize2('toggleActivityBar', "Toggle Activity Bar Visibility"),322});323}324run(): Promise<void> {325const value = that.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.HIDDEN ? that.getRememberedActivityBarVisiblePosition() : ActivityBarPosition.HIDDEN;326return that.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, value);327}328}));329}330331toJSON(): object {332return {333type: Parts.SIDEBAR_PART334};335}336}337338339