Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/browser/parts/sidebarPart.ts
13395 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 '../../../workbench/browser/parts/sidebar/media/sidebarpart.css';
7
import './media/sidebarPart.css';
8
import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from '../../../workbench/services/layout/browser/layoutService.js';
9
import { SidebarFocusContext, ActiveViewletContext } from '../../../workbench/common/contextkeys.js';
10
import { IStorageService } 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 { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_TITLE_BORDER, SIDE_BAR_FOREGROUND, 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 '../../../workbench/common/theme.js';
16
import { agentsPanelForeground } 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 '../../../workbench/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 '../../../workbench/common/views.js';
24
import { AbstractPaneCompositePart, CompositeBarPosition } from '../../../workbench/browser/parts/paneCompositePart.js';
25
import { ICompositeTitleLabel } from '../../../workbench/browser/parts/compositePart.js';
26
import { Part } from '../../../workbench/browser/part.js';
27
import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';
28
import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js';
29
import { IPaneCompositeBarOptions } from '../../../workbench/browser/parts/paneCompositeBar.js';
30
import { IMenuService } from '../../../platform/actions/common/actions.js';
31
import { Separator } from '../../../base/common/actions.js';
32
import { IHoverService } from '../../../platform/hover/browser/hover.js';
33
import { Extensions } from '../../../workbench/browser/panecomposite.js';
34
import { Menus } from '../menus.js';
35
import { $, append, getWindowId, prepend } from '../../../base/browser/dom.js';
36
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
37
import { isFullscreen, onDidChangeFullscreen } from '../../../base/browser/browser.js';
38
import { mainWindow } from '../../../base/browser/window.js';
39
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
40
import { hasNativeTitlebar, getTitleBarStyle } from '../../../platform/window/common/window.js';
41
import { isMacintosh, isNative, isWeb } from '../../../base/common/platform.js';
42
43
/**
44
* Sidebar part specifically for agent sessions workbench.
45
* This is a simplified version of the SidebarPart for agent session contexts.
46
*/
47
export class SidebarPart extends AbstractPaneCompositePart {
48
49
static readonly activeViewletSettingsKey = 'workbench.agentsession.sidebar.activeviewletid';
50
static readonly pinnedViewContainersKey = 'workbench.agentsession.pinnedViewlets2';
51
static readonly placeholderViewContainersKey = 'workbench.agentsession.placeholderViewlets';
52
static readonly viewContainersWorkspaceStateKey = 'workbench.agentsession.viewletsWorkspaceState';
53
54
/** Visual margin values - sidebar is flush (no card appearance) */
55
static readonly MARGIN_TOP = 0;
56
static readonly MARGIN_BOTTOM = 0;
57
static readonly MARGIN_LEFT = 0;
58
private static readonly FOOTER_ITEM_HEIGHT = 26;
59
private static readonly FOOTER_ITEM_GAP = 4;
60
private static readonly FOOTER_VERTICAL_PADDING = 6;
61
private static readonly FOOTER_BOTTOM_MARGIN = 2;
62
private static readonly FOOTER_BORDER_TOP = 1;
63
64
private footerContainer: HTMLElement | undefined;
65
private sideBarTitleArea: HTMLElement | undefined;
66
private footerToolbar: MenuWorkbenchToolBar | undefined;
67
private previousLayoutDimensions: { width: number; height: number; top: number; left: number } | undefined;
68
69
//#region IView
70
71
// On web the titlebar hosts an additional host filter combo alongside the
72
// sidebar toggle; use a wider minimum so those controls always fit within
73
// the sidebar's rendered area (below this the sidebar snaps closed).
74
readonly minimumWidth: number = isWeb ? 270 : 170;
75
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
76
readonly minimumHeight: number = 0;
77
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
78
override get snap(): boolean { return true; }
79
80
readonly priority: LayoutPriority = LayoutPriority.Low;
81
82
get preferredWidth(): number | undefined {
83
const viewlet = this.getActivePaneComposite();
84
85
if (!viewlet) {
86
return undefined;
87
}
88
89
const width = viewlet.getOptimalWidth();
90
if (typeof width !== 'number') {
91
return undefined;
92
}
93
94
return Math.max(width, 300);
95
}
96
97
//#endregion
98
99
constructor(
100
@INotificationService notificationService: INotificationService,
101
@IStorageService storageService: IStorageService,
102
@IContextMenuService contextMenuService: IContextMenuService,
103
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
104
@IKeybindingService keybindingService: IKeybindingService,
105
@IHoverService hoverService: IHoverService,
106
@IInstantiationService instantiationService: IInstantiationService,
107
@IThemeService themeService: IThemeService,
108
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
109
@IContextKeyService contextKeyService: IContextKeyService,
110
@IExtensionService extensionService: IExtensionService,
111
@IMenuService menuService: IMenuService,
112
@IConfigurationService private readonly configurationService: IConfigurationService,
113
) {
114
super(
115
Parts.SIDEBAR_PART,
116
{ hasTitle: false, trailingSeparator: false, borderWidth: () => 0 },
117
SidebarPart.activeViewletSettingsKey,
118
ActiveViewletContext.bindTo(contextKeyService),
119
SidebarFocusContext.bindTo(contextKeyService),
120
'sideBar',
121
'viewlet',
122
SIDE_BAR_TITLE_FOREGROUND,
123
SIDE_BAR_TITLE_BORDER,
124
ViewContainerLocation.Sidebar,
125
Extensions.Viewlets,
126
Menus.SidebarTitle,
127
notificationService,
128
storageService,
129
contextMenuService,
130
layoutService,
131
keybindingService,
132
hoverService,
133
instantiationService,
134
themeService,
135
viewDescriptorService,
136
contextKeyService,
137
extensionService,
138
menuService,
139
);
140
}
141
142
override create(parent: HTMLElement): void {
143
super.create(parent);
144
this.createFooter(parent);
145
}
146
147
protected override createTitleArea(parent: HTMLElement): HTMLElement | undefined {
148
const titleArea = super.createTitleArea(parent);
149
this.sideBarTitleArea = titleArea;
150
151
if (titleArea) {
152
// Add a drag region so the sidebar title area can be used to move the window,
153
// matching the titlebar's drag behavior.
154
prepend(titleArea, $('div.titlebar-drag-region'));
155
}
156
157
// macOS native: the sidebar spans full height and the traffic lights
158
// overlay the top-left corner. Add a fixed-width spacer inside the
159
// title area to push content horizontally past the traffic lights.
160
if (titleArea && isMacintosh && isNative && !hasNativeTitlebar(this.configurationService, getTitleBarStyle(this.configurationService))) {
161
const spacer = $('div.window-controls-container');
162
spacer.style.width = '70px';
163
spacer.style.height = '100%';
164
spacer.style.flexShrink = '0';
165
spacer.style.order = '-1'; // match global-actions-left order so DOM order is respected
166
prepend(titleArea, spacer);
167
168
// Hide spacer in fullscreen (traffic lights are not shown)
169
const updateSpacerVisibility = () => {
170
spacer.style.display = isFullscreen(mainWindow) ? 'none' : '';
171
};
172
updateSpacerVisibility();
173
this._register(onDidChangeFullscreen(windowId => {
174
if (windowId === getWindowId(mainWindow)) {
175
updateSpacerVisibility();
176
}
177
}));
178
}
179
180
return titleArea;
181
}
182
183
private createFooter(parent: HTMLElement): void {
184
const footer = append(parent, $('.sidebar-footer.sidebar-action-list'));
185
this.footerContainer = footer;
186
187
this.footerToolbar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, footer, Menus.SidebarFooter, {
188
hiddenItemStrategy: HiddenItemStrategy.NoHide,
189
toolbarOptions: { primaryGroup: () => true },
190
telemetrySource: 'sidebarFooter',
191
}));
192
193
this._register(this.footerToolbar.onDidChangeMenuItems(() => {
194
if (this.previousLayoutDimensions) {
195
const { width, height, top, left } = this.previousLayoutDimensions;
196
this.layout(width, height, top, left);
197
}
198
}));
199
}
200
201
private getFooterHeight(): number {
202
const actionCount = this.footerToolbar?.getItemsLength() ?? 0;
203
if (actionCount === 0) {
204
return 0;
205
}
206
207
return SidebarPart.FOOTER_VERTICAL_PADDING * 2
208
+ (actionCount * SidebarPart.FOOTER_ITEM_HEIGHT)
209
+ ((actionCount - 1) * SidebarPart.FOOTER_ITEM_GAP)
210
+ SidebarPart.FOOTER_BOTTOM_MARGIN
211
+ SidebarPart.FOOTER_BORDER_TOP;
212
}
213
214
private updateFooterVisibility(): void {
215
const footer = this.footerContainer;
216
if (!footer) {
217
return;
218
}
219
220
footer.style.display = this.getFooterHeight() > 0 ? '' : 'none';
221
}
222
223
override updateStyles(): void {
224
super.updateStyles();
225
226
const container = assertReturnsDefined(this.getContainer());
227
228
container.style.backgroundColor = 'transparent';
229
container.style.color = this.getColor(SIDE_BAR_FOREGROUND) || '';
230
container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
231
232
// No right border in sessions sidebar
233
container.style.borderRightWidth = '';
234
container.style.borderRightStyle = '';
235
container.style.borderRightColor = '';
236
237
if (this.sideBarTitleArea) {
238
this.sideBarTitleArea.style.backgroundColor = 'transparent';
239
this.sideBarTitleArea.style.color = this.getColor(agentsPanelForeground) || '';
240
}
241
}
242
243
override layout(width: number, height: number, top: number, left: number): void {
244
this.previousLayoutDimensions = { width, height, top, left };
245
246
if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) {
247
return;
248
}
249
250
this.updateFooterVisibility();
251
const footerHeight = Math.min(height, this.getFooterHeight());
252
253
// Layout content with reduced height to account for footer
254
super.layout(
255
width,
256
height - footerHeight,
257
top, left
258
);
259
260
// Restore the full grid-allocated dimensions so that Part.relayout() works correctly.
261
Part.prototype.layout.call(this, width, height, top, left);
262
}
263
264
protected override getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
265
return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT;
266
}
267
268
protected override createTitleLabel(_parent: HTMLElement): ICompositeTitleLabel {
269
// No title label in agent sessions sidebar
270
return {
271
updateTitle: () => { },
272
updateStyles: () => { }
273
};
274
}
275
276
protected getCompositeBarOptions(): IPaneCompositeBarOptions {
277
return {
278
partContainerClass: 'sidebar',
279
pinnedViewContainersKey: SidebarPart.pinnedViewContainersKey,
280
placeholderViewContainersKey: SidebarPart.placeholderViewContainersKey,
281
viewContainersWorkspaceStateKey: SidebarPart.viewContainersWorkspaceStateKey,
282
icon: false,
283
orientation: ActionsOrientation.HORIZONTAL,
284
recomputeSizes: true,
285
activityHoverOptions: {
286
position: () => this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW,
287
},
288
fillExtraContextMenuActions: actions => {
289
if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) {
290
const viewsSubmenuAction = this.getViewsSubmenuAction();
291
if (viewsSubmenuAction) {
292
actions.push(new Separator());
293
actions.push(viewsSubmenuAction);
294
}
295
}
296
},
297
compositeSize: 0,
298
iconSize: 16,
299
overflowActionSize: 30,
300
colors: theme => ({
301
activeBackgroundColor: undefined,
302
inactiveBackgroundColor: undefined,
303
activeBorderBottomColor: theme.getColor(ACTIVITY_BAR_TOP_ACTIVE_BORDER),
304
activeForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_FOREGROUND),
305
inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_TOP_INACTIVE_FOREGROUND),
306
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
307
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
308
dragAndDropBorder: theme.getColor(ACTIVITY_BAR_TOP_DRAG_AND_DROP_BORDER)
309
}),
310
compact: true
311
};
312
}
313
314
protected shouldShowCompositeBar(): boolean {
315
return false;
316
}
317
318
protected getCompositeBarPosition(): CompositeBarPosition {
319
return CompositeBarPosition.TITLE;
320
}
321
322
async focusActivityBar(): Promise<void> {
323
if (this.shouldShowCompositeBar()) {
324
this.focusCompositeBar();
325
}
326
}
327
328
toJSON(): object {
329
return {
330
type: Parts.SIDEBAR_PART
331
};
332
}
333
}
334
335