Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts
5263 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/sidebarpart.css';
7
import './sidebarActions.js';
8
import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position as SideBarPosition } from '../../../services/layout/browser/layoutService.js';
9
import { SidebarFocusContext, ActiveViewletContext } from '../../../common/contextkeys.js';
10
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
11
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
12
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
13
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
14
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
15
import { contrastBorder } from '../../../../platform/theme/common/colorRegistry.js';
16
import { 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';
17
import { INotificationService } from '../../../../platform/notification/common/notification.js';
18
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
19
import { AnchorAlignment } from '../../../../base/browser/ui/contextview/contextview.js';
20
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
21
import { LayoutPriority } from '../../../../base/browser/ui/grid/grid.js';
22
import { assertReturnsDefined } from '../../../../base/common/types.js';
23
import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';
24
import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js';
25
import { ActivityBarCompositeBar, ActivitybarPart } from '../activitybar/activitybarPart.js';
26
import { ActionsOrientation } from '../../../../base/browser/ui/actionbar/actionbar.js';
27
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
28
import { IPaneCompositeBarOptions } from '../paneCompositeBar.js';
29
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
30
import { Action2, IMenuService, registerAction2 } from '../../../../platform/actions/common/actions.js';
31
import { Separator } from '../../../../base/common/actions.js';
32
import { ToggleActivityBarVisibilityActionId } from '../../actions/layoutActions.js';
33
import { localize2 } from '../../../../nls.js';
34
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
35
import { VisibleViewContainersTracker } from '../visibleViewContainersTracker.js';
36
37
export class SidebarPart extends AbstractPaneCompositePart {
38
39
static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';
40
41
//#region IView
42
43
readonly minimumWidth: number = 170;
44
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
45
readonly minimumHeight: number = 0;
46
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
47
override get snap(): boolean { return true; }
48
49
readonly priority: LayoutPriority = LayoutPriority.Low;
50
51
get preferredWidth(): number | undefined {
52
const viewlet = this.getActivePaneComposite();
53
54
if (!viewlet) {
55
return undefined;
56
}
57
58
const width = viewlet.getOptimalWidth();
59
if (typeof width !== 'number') {
60
return undefined;
61
}
62
63
return Math.max(width, 300);
64
}
65
66
private readonly activityBarPart = this._register(this.instantiationService.createInstance(ActivitybarPart, this));
67
private readonly visibleViewContainersTracker: VisibleViewContainersTracker;
68
69
//#endregion
70
71
constructor(
72
@INotificationService notificationService: INotificationService,
73
@IStorageService storageService: IStorageService,
74
@IContextMenuService contextMenuService: IContextMenuService,
75
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
76
@IKeybindingService keybindingService: IKeybindingService,
77
@IHoverService hoverService: IHoverService,
78
@IInstantiationService instantiationService: IInstantiationService,
79
@IThemeService themeService: IThemeService,
80
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
81
@IContextKeyService contextKeyService: IContextKeyService,
82
@IExtensionService extensionService: IExtensionService,
83
@IConfigurationService private readonly configurationService: IConfigurationService,
84
@IMenuService menuService: IMenuService,
85
) {
86
super(
87
Parts.SIDEBAR_PART,
88
{ hasTitle: true, trailingSeparator: false, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 },
89
SidebarPart.activeViewletSettingsKey,
90
ActiveViewletContext.bindTo(contextKeyService),
91
SidebarFocusContext.bindTo(contextKeyService),
92
'sideBar',
93
'viewlet',
94
SIDE_BAR_TITLE_FOREGROUND,
95
SIDE_BAR_TITLE_BORDER,
96
notificationService,
97
storageService,
98
contextMenuService,
99
layoutService,
100
keybindingService,
101
hoverService,
102
instantiationService,
103
themeService,
104
viewDescriptorService,
105
contextKeyService,
106
extensionService,
107
menuService,
108
);
109
110
// Track visible view containers for auto-hide
111
this.visibleViewContainersTracker = this._register(instantiationService.createInstance(VisibleViewContainersTracker, ViewContainerLocation.Sidebar));
112
this._register(this.visibleViewContainersTracker.onDidChange((e) => this.onDidChangeAutoHideViewContainers(e)));
113
114
this.rememberActivityBarVisiblePosition();
115
this._register(configurationService.onDidChangeConfiguration(e => {
116
if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {
117
this.onDidChangeActivityBarLocation();
118
}
119
if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE)) {
120
this.onDidChangeActivityBarLocation();
121
}
122
}));
123
124
this.registerActions();
125
}
126
127
private onDidChangeAutoHideViewContainers(e: { before: number; after: number }): void {
128
// Only update if auto-hide is enabled and composite bar position is top/bottom
129
const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);
130
const autoHide = this.configurationService.getValue<boolean>(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE);
131
if (autoHide && (activityBarPosition === ActivityBarPosition.TOP || activityBarPosition === ActivityBarPosition.BOTTOM)) {
132
const visibleBefore = e.before > 1;
133
const visibleAfter = e.after > 1;
134
if (visibleBefore !== visibleAfter) {
135
this.onDidChangeActivityBarLocation();
136
}
137
}
138
}
139
140
private onDidChangeActivityBarLocation(): void {
141
this.activityBarPart.hide();
142
143
this.updateCompositeBar();
144
145
const id = this.getActiveComposite()?.getId();
146
if (id) {
147
this.onTitleAreaUpdate(id);
148
}
149
150
if (this.shouldShowActivityBar()) {
151
this.activityBarPart.show();
152
}
153
154
this.rememberActivityBarVisiblePosition();
155
}
156
157
override updateStyles(): void {
158
super.updateStyles();
159
160
const container = assertReturnsDefined(this.getContainer());
161
162
container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND) || '';
163
container.style.color = this.getColor(SIDE_BAR_FOREGROUND) || '';
164
165
const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder);
166
const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT;
167
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '';
168
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : '';
169
container.style.borderRightColor = isPositionLeft ? borderColor || '' : '';
170
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
171
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
172
container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';
173
container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
174
}
175
176
override layout(width: number, height: number, top: number, left: number): void {
177
if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {
178
return;
179
}
180
181
super.layout(width, height, top, left);
182
}
183
184
protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
185
return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT;
186
}
187
188
protected override createCompositeBar(): ActivityBarCompositeBar {
189
return this.instantiationService.createInstance(ActivityBarCompositeBar, this.getCompositeBarOptions(), this.partId, this, false);
190
}
191
192
protected getCompositeBarOptions(): IPaneCompositeBarOptions {
193
return {
194
partContainerClass: 'sidebar',
195
pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey,
196
placeholderViewContainersKey: ActivitybarPart.placeholderViewContainersKey,
197
viewContainersWorkspaceStateKey: ActivitybarPart.viewContainersWorkspaceStateKey,
198
icon: true,
199
orientation: ActionsOrientation.HORIZONTAL,
200
recomputeSizes: true,
201
activityHoverOptions: {
202
position: () => this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW,
203
},
204
fillExtraContextMenuActions: actions => {
205
if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) {
206
const viewsSubmenuAction = this.getViewsSubmenuAction();
207
if (viewsSubmenuAction) {
208
actions.push(new Separator());
209
actions.push(viewsSubmenuAction);
210
}
211
}
212
},
213
compositeSize: 0,
214
iconSize: 16,
215
overflowActionSize: 30,
216
colors: theme => ({
217
activeBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),
218
inactiveBackgroundColor: theme.getColor(SIDE_BAR_BACKGROUND),
219
activeBorderBottomColor: theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER),
220
activeForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND),
221
inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND),
222
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
223
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
224
dragAndDropBorder: theme.getColor(ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER)
225
}),
226
compact: true
227
};
228
}
229
230
protected shouldShowCompositeBar(): boolean {
231
const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);
232
if (activityBarPosition !== ActivityBarPosition.TOP && activityBarPosition !== ActivityBarPosition.BOTTOM) {
233
return false;
234
}
235
236
// Check if auto-hide is enabled and there's only one visible view container
237
const autoHide = this.configurationService.getValue<boolean>(LayoutSettings.ACTIVITY_BAR_AUTO_HIDE);
238
if (autoHide) {
239
// Use visible composite count from the composite bar if available (considers pinned state),
240
// otherwise fall back to the tracker's count (based on active view descriptors).
241
// Note: We access paneCompositeBar directly to avoid circular calls with getVisiblePaneCompositeIds()
242
const visibleCount = this.visibleViewContainersTracker.visibleCount;
243
if (visibleCount <= 1) {
244
return false;
245
}
246
}
247
248
return true;
249
}
250
251
private shouldShowActivityBar(): boolean {
252
if (this.shouldShowCompositeBar()) {
253
return false;
254
}
255
256
return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.HIDDEN;
257
}
258
259
protected getCompositeBarPosition(): CompositeBarPosition {
260
const activityBarPosition = this.configurationService.getValue<ActivityBarPosition>(LayoutSettings.ACTIVITY_BAR_LOCATION);
261
switch (activityBarPosition) {
262
case ActivityBarPosition.TOP: return CompositeBarPosition.TOP;
263
case ActivityBarPosition.BOTTOM: return CompositeBarPosition.BOTTOM;
264
case ActivityBarPosition.HIDDEN:
265
case ActivityBarPosition.DEFAULT: // noop
266
default: return CompositeBarPosition.TITLE;
267
}
268
}
269
270
private rememberActivityBarVisiblePosition(): void {
271
const activityBarPosition = this.configurationService.getValue<string>(LayoutSettings.ACTIVITY_BAR_LOCATION);
272
if (activityBarPosition !== ActivityBarPosition.HIDDEN) {
273
this.storageService.store(LayoutSettings.ACTIVITY_BAR_LOCATION, activityBarPosition, StorageScope.PROFILE, StorageTarget.USER);
274
}
275
}
276
277
private getRememberedActivityBarVisiblePosition(): ActivityBarPosition {
278
const activityBarPosition = this.storageService.get(LayoutSettings.ACTIVITY_BAR_LOCATION, StorageScope.PROFILE);
279
switch (activityBarPosition) {
280
case ActivityBarPosition.TOP: return ActivityBarPosition.TOP;
281
case ActivityBarPosition.BOTTOM: return ActivityBarPosition.BOTTOM;
282
default: return ActivityBarPosition.DEFAULT;
283
}
284
}
285
286
override getPinnedPaneCompositeIds(): string[] {
287
return this.shouldShowCompositeBar() ? super.getPinnedPaneCompositeIds() : this.activityBarPart.getPinnedPaneCompositeIds();
288
}
289
290
override getVisiblePaneCompositeIds(): string[] {
291
return this.shouldShowCompositeBar() ? super.getVisiblePaneCompositeIds() : this.activityBarPart.getVisiblePaneCompositeIds();
292
}
293
294
override getPaneCompositeIds(): string[] {
295
return this.shouldShowCompositeBar() ? super.getPaneCompositeIds() : this.activityBarPart.getPaneCompositeIds();
296
}
297
298
async focusActivityBar(): Promise<void> {
299
if (this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.HIDDEN) {
300
await this.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, this.getRememberedActivityBarVisiblePosition());
301
302
this.onDidChangeActivityBarLocation();
303
}
304
305
if (this.shouldShowCompositeBar()) {
306
this.focusCompositeBar();
307
} else {
308
if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) {
309
this.layoutService.setPartHidden(false, Parts.ACTIVITYBAR_PART);
310
}
311
312
this.activityBarPart.show(true);
313
}
314
}
315
316
private registerActions(): void {
317
const that = this;
318
this._register(registerAction2(class extends Action2 {
319
constructor() {
320
super({
321
id: ToggleActivityBarVisibilityActionId,
322
title: localize2('toggleActivityBar', "Toggle Activity Bar Visibility"),
323
});
324
}
325
run(): Promise<void> {
326
const value = that.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.HIDDEN ? that.getRememberedActivityBarVisiblePosition() : ActivityBarPosition.HIDDEN;
327
return that.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, value);
328
}
329
}));
330
}
331
332
toJSON(): object {
333
return {
334
type: Parts.SIDEBAR_PART
335
};
336
}
337
}
338
339