Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/paneCompositeBar.ts
5303 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 { localize } from '../../../nls.js';
7
import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';
8
import { IActivityService } from '../../services/activity/common/activity.js';
9
import { IWorkbenchLayoutService, Parts } from '../../services/layout/browser/layoutService.js';
10
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
11
import { IDisposable, DisposableStore, Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
12
import { IColorTheme } from '../../../platform/theme/common/themeService.js';
13
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from './compositeBar.js';
14
import { Dimension, isMouseEvent } from '../../../base/browser/dom.js';
15
import { createCSSRule } from '../../../base/browser/domStylesheets.js';
16
import { asCSSUrl } from '../../../base/browser/cssValue.js';
17
import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js';
18
import { IExtensionService } from '../../services/extensions/common/extensions.js';
19
import { URI, UriComponents } from '../../../base/common/uri.js';
20
import { ToggleCompositePinnedAction, ICompositeBarColors, IActivityHoverOptions, ToggleCompositeBadgeAction, CompositeBarAction, ICompositeBar, ICompositeBarActionItem } from './compositeBarActions.js';
21
import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation } from '../../common/views.js';
22
import { IContextKeyService, ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js';
23
import { isString } from '../../../base/common/types.js';
24
import { IWorkbenchEnvironmentService } from '../../services/environment/common/environmentService.js';
25
import { isNative } from '../../../base/common/platform.js';
26
import { Before2D, ICompositeDragAndDrop } from '../dnd.js';
27
import { ThemeIcon } from '../../../base/common/themables.js';
28
import { IAction, Separator, SubmenuAction, toAction } from '../../../base/common/actions.js';
29
import { StringSHA1 } from '../../../base/common/hash.js';
30
import { GestureEvent } from '../../../base/browser/touch.js';
31
import { IPaneCompositePart } from './paneCompositePart.js';
32
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
33
import { IViewsService } from '../../services/views/common/viewsService.js';
34
35
interface IPlaceholderViewContainer {
36
readonly id: string;
37
readonly name?: string;
38
readonly iconUrl?: UriComponents;
39
readonly themeIcon?: ThemeIcon;
40
readonly isBuiltin?: boolean;
41
readonly views?: { when?: string }[];
42
// TODO @sandy081: Remove this after a while. Migrated to visible in IViewContainerWorkspaceState
43
readonly visible?: boolean;
44
}
45
46
interface IPinnedViewContainer {
47
readonly id: string;
48
readonly pinned: boolean;
49
readonly order?: number;
50
// TODO @sandy081: Remove this after a while. Migrated to visible in IViewContainerWorkspaceState
51
readonly visible: boolean;
52
}
53
54
interface IViewContainerWorkspaceState {
55
readonly id: string;
56
readonly visible: boolean;
57
}
58
59
interface ICachedViewContainer {
60
readonly id: string;
61
name?: string;
62
icon?: URI | ThemeIcon;
63
readonly pinned: boolean;
64
readonly order?: number;
65
visible: boolean;
66
isBuiltin?: boolean;
67
views?: { when?: string }[];
68
}
69
70
export interface IPaneCompositeBarOptions {
71
readonly partContainerClass: string;
72
readonly pinnedViewContainersKey: string;
73
readonly placeholderViewContainersKey: string;
74
readonly viewContainersWorkspaceStateKey: string;
75
readonly icon: boolean;
76
readonly compact?: boolean;
77
readonly iconSize: number;
78
readonly recomputeSizes: boolean;
79
readonly orientation: ActionsOrientation;
80
readonly compositeSize: number;
81
readonly overflowActionSize: number;
82
readonly preventLoopNavigation?: boolean;
83
readonly activityHoverOptions: IActivityHoverOptions;
84
readonly fillExtraContextMenuActions: (actions: IAction[], e?: MouseEvent | GestureEvent) => void;
85
readonly colors: (theme: IColorTheme) => ICompositeBarColors;
86
}
87
88
export class PaneCompositeBar extends Disposable {
89
90
private readonly viewContainerDisposables = this._register(new DisposableMap<string, IDisposable>());
91
92
private readonly compositeBar: CompositeBar;
93
readonly dndHandler: ICompositeDragAndDrop;
94
private readonly compositeActions = new Map<string, { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction }>();
95
96
private hasExtensionsRegistered: boolean = false;
97
98
constructor(
99
private readonly location: ViewContainerLocation,
100
protected readonly options: IPaneCompositeBarOptions,
101
protected readonly part: Parts,
102
private readonly paneCompositePart: IPaneCompositePart,
103
@IInstantiationService protected readonly instantiationService: IInstantiationService,
104
@IStorageService private readonly storageService: IStorageService,
105
@IExtensionService private readonly extensionService: IExtensionService,
106
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
107
@IViewsService private readonly viewService: IViewsService,
108
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
109
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
110
@IWorkbenchLayoutService protected readonly layoutService: IWorkbenchLayoutService,
111
) {
112
super();
113
114
this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, this.options.orientation,
115
async (id: string, focus?: boolean) => { return await this.paneCompositePart.openPaneComposite(id, focus) ?? null; },
116
(from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, this.options.orientation === ActionsOrientation.VERTICAL ? before?.verticallyBefore : before?.horizontallyBefore),
117
() => this.compositeBar.getCompositeBarItems(),
118
);
119
120
const cachedItems = this.cachedViewContainers
121
.map(container => ({
122
id: container.id,
123
name: container.name,
124
visible: !this.shouldBeHidden(container.id, container),
125
order: container.order,
126
pinned: container.pinned,
127
}));
128
this.compositeBar = this.createCompositeBar(cachedItems);
129
this.onDidRegisterViewContainers(this.getViewContainers());
130
this.registerListeners();
131
}
132
133
private createCompositeBar(cachedItems: ICompositeBarItem[]) {
134
return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, {
135
icon: this.options.icon,
136
compact: this.options.compact,
137
orientation: this.options.orientation,
138
activityHoverOptions: this.options.activityHoverOptions,
139
preventLoopNavigation: this.options.preventLoopNavigation,
140
openComposite: async (compositeId, preserveFocus) => {
141
return (await this.paneCompositePart.openPaneComposite(compositeId, !preserveFocus)) ?? null;
142
},
143
getActivityAction: compositeId => this.getCompositeActions(compositeId).activityAction,
144
getCompositePinnedAction: compositeId => this.getCompositeActions(compositeId).pinnedAction,
145
getCompositeBadgeAction: compositeId => this.getCompositeActions(compositeId).badgeAction,
146
getOnCompositeClickAction: compositeId => this.getCompositeActions(compositeId).activityAction,
147
fillExtraContextMenuActions: (actions, e) => this.options.fillExtraContextMenuActions(actions, e),
148
getContextMenuActionsForComposite: compositeId => this.getContextMenuActionsForComposite(compositeId),
149
getDefaultCompositeId: () => this.viewDescriptorService.getDefaultViewContainer(this.location)?.id,
150
dndHandler: this.dndHandler,
151
compositeSize: this.options.compositeSize,
152
overflowActionSize: this.options.overflowActionSize,
153
colors: theme => this.options.colors(theme),
154
}));
155
}
156
157
private getContextMenuActionsForComposite(compositeId: string): IAction[] {
158
const actions: IAction[] = [new Separator()];
159
160
const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!;
161
const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!;
162
const currentLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer);
163
164
// Move View Container
165
const moveActions = [];
166
for (const location of [ViewContainerLocation.Sidebar, ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel]) {
167
if (currentLocation !== location) {
168
moveActions.push(this.createMoveAction(viewContainer, location, defaultLocation));
169
}
170
}
171
172
actions.push(new SubmenuAction('moveToMenu', localize('moveToMenu', "Move To"), moveActions));
173
174
// Reset Location
175
if (defaultLocation !== currentLocation) {
176
actions.push(toAction({
177
id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {
178
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction');
179
this.viewService.openViewContainer(viewContainer.id, true);
180
}
181
}));
182
} else {
183
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
184
if (viewContainerModel.allViewDescriptors.length === 1) {
185
const viewToReset = viewContainerModel.allViewDescriptors[0];
186
const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!;
187
if (defaultContainer !== viewContainer) {
188
actions.push(toAction({
189
id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => {
190
this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction');
191
this.viewService.openViewContainer(viewContainer.id, true);
192
}
193
}));
194
}
195
}
196
}
197
198
return actions;
199
}
200
201
private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction {
202
return toAction({
203
id: `moveViewContainerTo${newLocation}`,
204
label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Secondary Side Bar"),
205
run: () => {
206
let index: number | undefined;
207
if (newLocation !== defaultLocation) {
208
index = this.viewDescriptorService.getViewContainersByLocation(newLocation).length; // move to the end of the location
209
} else {
210
index = undefined; // restore default location
211
}
212
this.viewDescriptorService.moveViewContainerToLocation(viewContainer, newLocation, index);
213
this.viewService.openViewContainer(viewContainer.id, true);
214
}
215
});
216
}
217
218
private registerListeners(): void {
219
220
// View Container Changes
221
this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed)));
222
this._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeViewContainerLocation(viewContainer, from, to)));
223
224
// View Container Visibility Changes
225
this._register(this.paneCompositePart.onDidPaneCompositeOpen(e => this.onDidChangeViewContainerVisibility(e.getId(), true)));
226
this._register(this.paneCompositePart.onDidPaneCompositeClose(e => this.onDidChangeViewContainerVisibility(e.getId(), false)));
227
228
// Extension registration
229
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
230
if (this._store.isDisposed) {
231
return;
232
}
233
this.onDidRegisterExtensions();
234
this._register(this.compositeBar.onDidChange(() => {
235
this.updateCompositeBarItemsFromStorage(true);
236
this.saveCachedViewContainers();
237
}));
238
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, this.options.pinnedViewContainersKey, this._store)(() => this.updateCompositeBarItemsFromStorage(false)));
239
});
240
}
241
242
private onDidChangeViewContainers(added: readonly { container: ViewContainer; location: ViewContainerLocation }[], removed: readonly { container: ViewContainer; location: ViewContainerLocation }[]) {
243
removed.filter(({ location }) => location === this.location).forEach(({ container }) => this.onDidDeregisterViewContainer(container));
244
this.onDidRegisterViewContainers(added.filter(({ location }) => location === this.location).map(({ container }) => container));
245
}
246
247
private onDidChangeViewContainerLocation(container: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation) {
248
if (from === this.location) {
249
this.onDidDeregisterViewContainer(container);
250
}
251
252
if (to === this.location) {
253
this.onDidRegisterViewContainers([container]);
254
}
255
}
256
257
private onDidChangeViewContainerVisibility(id: string, visible: boolean) {
258
if (visible) {
259
// Activate view container action on opening of a view container
260
this.onDidViewContainerVisible(id);
261
} else {
262
// Deactivate view container action on close
263
this.compositeBar.deactivateComposite(id);
264
}
265
}
266
267
private onDidRegisterExtensions(): void {
268
this.hasExtensionsRegistered = true;
269
270
// show/hide/remove composites
271
for (const { id } of this.cachedViewContainers) {
272
const viewContainer = this.getViewContainer(id);
273
if (viewContainer) {
274
this.showOrHideViewContainer(viewContainer);
275
} else {
276
if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) {
277
this.removeComposite(id);
278
} else {
279
this.hideComposite(id);
280
}
281
}
282
}
283
284
this.saveCachedViewContainers();
285
}
286
287
private onDidViewContainerVisible(id: string): void {
288
const viewContainer = this.getViewContainer(id);
289
if (viewContainer) {
290
291
// Update the composite bar by adding
292
this.addComposite(viewContainer);
293
this.compositeBar.activateComposite(viewContainer.id);
294
295
if (this.shouldBeHidden(viewContainer)) {
296
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
297
if (viewContainerModel.activeViewDescriptors.length === 0) {
298
// Update the composite bar by hiding
299
this.hideComposite(viewContainer.id);
300
}
301
}
302
}
303
}
304
305
create(parent: HTMLElement): HTMLElement {
306
return this.compositeBar.create(parent);
307
}
308
309
private getCompositeActions(compositeId: string): { activityAction: ViewContainerActivityAction; pinnedAction: ToggleCompositePinnedAction; badgeAction: ToggleCompositeBadgeAction } {
310
let compositeActions = this.compositeActions.get(compositeId);
311
if (!compositeActions) {
312
const viewContainer = this.getViewContainer(compositeId);
313
if (viewContainer) {
314
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
315
compositeActions = {
316
activityAction: this._register(this.instantiationService.createInstance(ViewContainerActivityAction, this.toCompositeBarActionItemFrom(viewContainerModel), this.part, this.paneCompositePart)),
317
pinnedAction: this._register(new ToggleCompositePinnedAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar)),
318
badgeAction: this._register(new ToggleCompositeBadgeAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar))
319
};
320
} else {
321
const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0];
322
compositeActions = {
323
activityAction: this._register(this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, this.toCompositeBarActionItem(compositeId, cachedComposite?.name ?? compositeId, cachedComposite?.icon, undefined), this.part, this.paneCompositePart)),
324
pinnedAction: this._register(new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)),
325
badgeAction: this._register(new PlaceHolderToggleCompositeBadgeAction(compositeId, this.compositeBar))
326
};
327
}
328
329
this.compositeActions.set(compositeId, compositeActions);
330
}
331
332
return compositeActions;
333
}
334
335
private onDidRegisterViewContainers(viewContainers: readonly ViewContainer[]): void {
336
for (const viewContainer of viewContainers) {
337
this.addComposite(viewContainer);
338
339
// Pin it by default if it is new
340
const cachedViewContainer = this.cachedViewContainers.filter(({ id }) => id === viewContainer.id)[0];
341
if (!cachedViewContainer) {
342
this.compositeBar.pin(viewContainer.id);
343
}
344
345
// Active
346
const visibleViewContainer = this.paneCompositePart.getActivePaneComposite();
347
if (visibleViewContainer?.getId() === viewContainer.id) {
348
this.compositeBar.activateComposite(viewContainer.id);
349
}
350
351
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
352
this.updateCompositeBarActionItem(viewContainer, viewContainerModel);
353
this.showOrHideViewContainer(viewContainer);
354
355
const disposables = new DisposableStore();
356
disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateCompositeBarActionItem(viewContainer, viewContainerModel)));
357
disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer)));
358
359
this.viewContainerDisposables.set(viewContainer.id, disposables);
360
}
361
}
362
363
private onDidDeregisterViewContainer(viewContainer: ViewContainer): void {
364
this.viewContainerDisposables.deleteAndDispose(viewContainer.id);
365
this.removeComposite(viewContainer.id);
366
}
367
368
private updateCompositeBarActionItem(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void {
369
const compositeBarActionItem = this.toCompositeBarActionItemFrom(viewContainerModel);
370
const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id);
371
activityAction.updateCompositeBarActionItem(compositeBarActionItem);
372
373
if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {
374
pinnedAction.setActivity(compositeBarActionItem);
375
}
376
377
if (this.options.recomputeSizes) {
378
this.compositeBar.recomputeSizes();
379
}
380
381
this.saveCachedViewContainers();
382
}
383
384
private toCompositeBarActionItemFrom(viewContainerModel: IViewContainerModel): ICompositeBarActionItem {
385
return this.toCompositeBarActionItem(viewContainerModel.viewContainer.id, viewContainerModel.title, viewContainerModel.icon, viewContainerModel.keybindingId);
386
}
387
388
private toCompositeBarActionItem(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): ICompositeBarActionItem {
389
let classNames: string[] | undefined = undefined;
390
let iconUrl: URI | undefined = undefined;
391
if (this.options.icon) {
392
if (URI.isUri(icon)) {
393
iconUrl = icon;
394
const cssUrl = asCSSUrl(icon);
395
const hash = new StringSHA1();
396
hash.update(cssUrl);
397
const iconId = `activity-${id.replace(/\./g, '-')}-${hash.digest()}`;
398
const iconClass = `.monaco-workbench .${this.options.partContainerClass} .monaco-action-bar .action-label.${iconId}`;
399
classNames = [iconId, 'uri-icon'];
400
createCSSRule(iconClass, `
401
mask: ${cssUrl} no-repeat 50% 50%;
402
mask-size: var(--activity-bar-icon-size, ${this.options.iconSize}px);
403
-webkit-mask: ${cssUrl} no-repeat 50% 50%;
404
-webkit-mask-size: var(--activity-bar-icon-size, ${this.options.iconSize}px);
405
mask-origin: padding;
406
-webkit-mask-origin: padding;
407
`);
408
} else if (ThemeIcon.isThemeIcon(icon)) {
409
classNames = ThemeIcon.asClassNameArray(icon);
410
}
411
}
412
413
return { id, name, classNames, iconUrl, keybindingId };
414
}
415
416
private showOrHideViewContainer(viewContainer: ViewContainer): void {
417
if (this.shouldBeHidden(viewContainer)) {
418
this.hideComposite(viewContainer.id);
419
} else {
420
this.addComposite(viewContainer);
421
422
// Activate if this is the active pane composite
423
const activePaneComposite = this.paneCompositePart.getActivePaneComposite();
424
if (activePaneComposite?.getId() === viewContainer.id) {
425
this.compositeBar.activateComposite(viewContainer.id);
426
}
427
}
428
}
429
430
private shouldBeHidden(viewContainerOrId: string | ViewContainer, cachedViewContainer?: ICachedViewContainer): boolean {
431
const viewContainer = isString(viewContainerOrId) ? this.getViewContainer(viewContainerOrId) : viewContainerOrId;
432
const viewContainerId = isString(viewContainerOrId) ? viewContainerOrId : viewContainerOrId.id;
433
434
if (viewContainer) {
435
if (viewContainer.hideIfEmpty) {
436
if (this.viewService.isViewContainerActive(viewContainerId)) {
437
return false;
438
}
439
} else {
440
return false;
441
}
442
}
443
444
// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window
445
if (!this.hasExtensionsRegistered && !(this.part === Parts.SIDEBAR_PART && this.environmentService.remoteAuthority && isNative)) {
446
cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);
447
448
// Show builtin ViewContainer if not registered yet
449
if (!viewContainer && cachedViewContainer?.isBuiltin && cachedViewContainer?.visible) {
450
return false;
451
}
452
453
if (cachedViewContainer?.views?.length) {
454
return cachedViewContainer.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)));
455
}
456
}
457
458
return true;
459
}
460
461
private addComposite(viewContainer: ViewContainer): void {
462
this.compositeBar.addComposite({ id: viewContainer.id, name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value, order: viewContainer.order, requestedIndex: viewContainer.requestedIndex });
463
}
464
465
private hideComposite(compositeId: string): void {
466
this.compositeBar.hideComposite(compositeId);
467
468
const compositeActions = this.compositeActions.get(compositeId);
469
if (compositeActions) {
470
compositeActions.activityAction.dispose();
471
compositeActions.pinnedAction.dispose();
472
this.compositeActions.delete(compositeId);
473
}
474
}
475
476
private removeComposite(compositeId: string): void {
477
this.compositeBar.removeComposite(compositeId);
478
479
const compositeActions = this.compositeActions.get(compositeId);
480
if (compositeActions) {
481
compositeActions.activityAction.dispose();
482
compositeActions.pinnedAction.dispose();
483
this.compositeActions.delete(compositeId);
484
}
485
}
486
487
getPinnedPaneCompositeIds(): string[] {
488
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);
489
return this.getViewContainers()
490
.filter(v => this.compositeBar.isPinned(v.id))
491
.sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id))
492
.map(v => v.id);
493
}
494
495
getVisiblePaneCompositeIds(): string[] {
496
return this.compositeBar.getVisibleComposites()
497
.filter(v => this.paneCompositePart.getActivePaneComposite()?.getId() === v.id || this.compositeBar.isPinned(v.id))
498
.map(v => v.id);
499
}
500
501
getPaneCompositeIds(): string[] {
502
return this.compositeBar.getVisibleComposites()
503
.map(v => v.id);
504
}
505
506
getContextMenuActions(): IAction[] {
507
return this.compositeBar.getContextMenuActions();
508
}
509
510
focus(index?: number): void {
511
this.compositeBar.focus(index);
512
}
513
514
layout(width: number, height: number): void {
515
this.compositeBar.layout(new Dimension(width, height));
516
}
517
518
private getViewContainer(id: string): ViewContainer | undefined {
519
const viewContainer = this.viewDescriptorService.getViewContainerById(id);
520
return viewContainer && this.viewDescriptorService.getViewContainerLocation(viewContainer) === this.location ? viewContainer : undefined;
521
}
522
523
private getViewContainers(): readonly ViewContainer[] {
524
return this.viewDescriptorService.getViewContainersByLocation(this.location);
525
}
526
527
private updateCompositeBarItemsFromStorage(retainExisting: boolean): void {
528
if (this.pinnedViewContainersValue === this.getStoredPinnedViewContainersValue()) {
529
return;
530
}
531
532
this._placeholderViewContainersValue = undefined;
533
this._pinnedViewContainersValue = undefined;
534
this._cachedViewContainers = undefined;
535
536
const newCompositeItems: ICompositeBarItem[] = [];
537
const compositeItems = this.compositeBar.getCompositeBarItems();
538
539
for (const cachedViewContainer of this.cachedViewContainers) {
540
newCompositeItems.push({
541
id: cachedViewContainer.id,
542
name: cachedViewContainer.name,
543
order: cachedViewContainer.order,
544
pinned: cachedViewContainer.pinned,
545
visible: cachedViewContainer.visible && !!this.getViewContainer(cachedViewContainer.id),
546
});
547
}
548
549
for (const viewContainer of this.getViewContainers()) {
550
// Add missing view containers
551
if (!newCompositeItems.some(({ id }) => id === viewContainer.id)) {
552
const index = compositeItems.findIndex(({ id }) => id === viewContainer.id);
553
if (index !== -1) {
554
const compositeItem = compositeItems[index];
555
newCompositeItems.splice(index, 0, {
556
id: viewContainer.id,
557
name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,
558
order: compositeItem.order,
559
pinned: compositeItem.pinned,
560
visible: compositeItem.visible,
561
});
562
} else {
563
newCompositeItems.push({
564
id: viewContainer.id,
565
name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value,
566
order: viewContainer.order,
567
pinned: true,
568
visible: !this.shouldBeHidden(viewContainer),
569
});
570
}
571
}
572
}
573
574
if (retainExisting) {
575
for (const compositeItem of compositeItems) {
576
const newCompositeItem = newCompositeItems.find(({ id }) => id === compositeItem.id);
577
if (!newCompositeItem) {
578
newCompositeItems.push(compositeItem);
579
}
580
}
581
}
582
583
this.compositeBar.setCompositeBarItems(newCompositeItems);
584
}
585
586
private saveCachedViewContainers(): void {
587
const state: ICachedViewContainer[] = [];
588
589
const compositeItems = this.compositeBar.getCompositeBarItems();
590
for (const compositeItem of compositeItems) {
591
const viewContainer = this.getViewContainer(compositeItem.id);
592
if (viewContainer) {
593
const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer);
594
const views: { when: string | undefined }[] = [];
595
for (const { when } of viewContainerModel.allViewDescriptors) {
596
views.push({ when: when ? when.serialize() : undefined });
597
}
598
state.push({
599
id: compositeItem.id,
600
name: viewContainerModel.title,
601
icon: URI.isUri(viewContainerModel.icon) && this.environmentService.remoteAuthority ? undefined : viewContainerModel.icon, // Do not cache uri icons with remote connection
602
views,
603
pinned: compositeItem.pinned,
604
order: compositeItem.order,
605
visible: compositeItem.visible,
606
isBuiltin: !viewContainer.extensionId
607
});
608
} else {
609
state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false });
610
}
611
}
612
613
this.storeCachedViewContainersState(state);
614
}
615
616
private _cachedViewContainers: ICachedViewContainer[] | undefined = undefined;
617
private get cachedViewContainers(): ICachedViewContainer[] {
618
if (this._cachedViewContainers === undefined) {
619
this._cachedViewContainers = this.getPinnedViewContainers();
620
for (const placeholderViewContainer of this.getPlaceholderViewContainers()) {
621
const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === placeholderViewContainer.id);
622
if (cachedViewContainer) {
623
cachedViewContainer.visible = placeholderViewContainer.visible ?? cachedViewContainer.visible;
624
cachedViewContainer.name = placeholderViewContainer.name;
625
cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon :
626
placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined;
627
if (URI.isUri(cachedViewContainer.icon) && this.environmentService.remoteAuthority) {
628
cachedViewContainer.icon = undefined; // Do not cache uri icons with remote connection
629
}
630
cachedViewContainer.views = placeholderViewContainer.views;
631
cachedViewContainer.isBuiltin = placeholderViewContainer.isBuiltin;
632
}
633
}
634
for (const viewContainerWorkspaceState of this.getViewContainersWorkspaceState()) {
635
const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === viewContainerWorkspaceState.id);
636
if (cachedViewContainer) {
637
cachedViewContainer.visible = viewContainerWorkspaceState.visible ?? cachedViewContainer.visible;
638
}
639
}
640
}
641
642
return this._cachedViewContainers;
643
}
644
645
private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void {
646
const pinnedViewContainers = this.getPinnedViewContainers();
647
this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, order }) => ({
648
id,
649
pinned,
650
visible: Boolean(pinnedViewContainers.find(({ id: pinnedId }) => pinnedId === id)?.visible),
651
order
652
} satisfies IPinnedViewContainer)));
653
654
this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => ({
655
id,
656
iconUrl: URI.isUri(icon) ? icon : undefined,
657
themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined,
658
name,
659
isBuiltin,
660
views
661
} satisfies IPlaceholderViewContainer)));
662
663
this.setViewContainersWorkspaceState(cachedViewContainers.map(({ id, visible }) => ({
664
id,
665
visible,
666
} satisfies IViewContainerWorkspaceState)));
667
}
668
669
private getPinnedViewContainers(): IPinnedViewContainer[] {
670
return JSON.parse(this.pinnedViewContainersValue);
671
}
672
673
private setPinnedViewContainers(pinnedViewContainers: IPinnedViewContainer[]): void {
674
this.pinnedViewContainersValue = JSON.stringify(pinnedViewContainers);
675
}
676
677
private _pinnedViewContainersValue: string | undefined;
678
private get pinnedViewContainersValue(): string {
679
if (!this._pinnedViewContainersValue) {
680
this._pinnedViewContainersValue = this.getStoredPinnedViewContainersValue();
681
}
682
683
return this._pinnedViewContainersValue;
684
}
685
686
private set pinnedViewContainersValue(pinnedViewContainersValue: string) {
687
if (this.pinnedViewContainersValue !== pinnedViewContainersValue) {
688
this._pinnedViewContainersValue = pinnedViewContainersValue;
689
this.setStoredPinnedViewContainersValue(pinnedViewContainersValue);
690
}
691
}
692
693
private getStoredPinnedViewContainersValue(): string {
694
return this.storageService.get(this.options.pinnedViewContainersKey, StorageScope.PROFILE, '[]');
695
}
696
697
private setStoredPinnedViewContainersValue(value: string): void {
698
this.storageService.store(this.options.pinnedViewContainersKey, value, StorageScope.PROFILE, StorageTarget.USER);
699
}
700
701
private getPlaceholderViewContainers(): IPlaceholderViewContainer[] {
702
return JSON.parse(this.placeholderViewContainersValue);
703
}
704
705
private setPlaceholderViewContainers(placeholderViewContainers: IPlaceholderViewContainer[]): void {
706
this.placeholderViewContainersValue = JSON.stringify(placeholderViewContainers);
707
}
708
709
private _placeholderViewContainersValue: string | undefined;
710
private get placeholderViewContainersValue(): string {
711
if (!this._placeholderViewContainersValue) {
712
this._placeholderViewContainersValue = this.getStoredPlaceholderViewContainersValue();
713
}
714
715
return this._placeholderViewContainersValue;
716
}
717
718
private set placeholderViewContainersValue(placeholderViewContainesValue: string) {
719
if (this.placeholderViewContainersValue !== placeholderViewContainesValue) {
720
this._placeholderViewContainersValue = placeholderViewContainesValue;
721
this.setStoredPlaceholderViewContainersValue(placeholderViewContainesValue);
722
}
723
}
724
725
private getStoredPlaceholderViewContainersValue(): string {
726
return this.storageService.get(this.options.placeholderViewContainersKey, StorageScope.PROFILE, '[]');
727
}
728
729
private setStoredPlaceholderViewContainersValue(value: string): void {
730
this.storageService.store(this.options.placeholderViewContainersKey, value, StorageScope.PROFILE, StorageTarget.MACHINE);
731
}
732
733
private getViewContainersWorkspaceState(): IViewContainerWorkspaceState[] {
734
return JSON.parse(this.viewContainersWorkspaceStateValue);
735
}
736
737
private setViewContainersWorkspaceState(viewContainersWorkspaceState: IViewContainerWorkspaceState[]): void {
738
this.viewContainersWorkspaceStateValue = JSON.stringify(viewContainersWorkspaceState);
739
}
740
741
private _viewContainersWorkspaceStateValue: string | undefined;
742
private get viewContainersWorkspaceStateValue(): string {
743
if (!this._viewContainersWorkspaceStateValue) {
744
this._viewContainersWorkspaceStateValue = this.getStoredViewContainersWorkspaceStateValue();
745
}
746
747
return this._viewContainersWorkspaceStateValue;
748
}
749
750
private set viewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue: string) {
751
if (this.viewContainersWorkspaceStateValue !== viewContainersWorkspaceStateValue) {
752
this._viewContainersWorkspaceStateValue = viewContainersWorkspaceStateValue;
753
this.setStoredViewContainersWorkspaceStateValue(viewContainersWorkspaceStateValue);
754
}
755
}
756
757
private getStoredViewContainersWorkspaceStateValue(): string {
758
return this.storageService.get(this.options.viewContainersWorkspaceStateKey, StorageScope.WORKSPACE, '[]');
759
}
760
761
private setStoredViewContainersWorkspaceStateValue(value: string): void {
762
this.storageService.store(this.options.viewContainersWorkspaceStateKey, value, StorageScope.WORKSPACE, StorageTarget.MACHINE);
763
}
764
}
765
766
class ViewContainerActivityAction extends CompositeBarAction {
767
768
private static readonly preventDoubleClickDelay = 300;
769
770
private lastRun = 0;
771
772
constructor(
773
compositeBarActionItem: ICompositeBarActionItem,
774
private readonly part: Parts,
775
private readonly paneCompositePart: IPaneCompositePart,
776
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
777
@IConfigurationService private readonly configurationService: IConfigurationService,
778
@IActivityService private readonly activityService: IActivityService,
779
) {
780
super(compositeBarActionItem);
781
this.updateActivity();
782
this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => {
783
if (!isString(viewContainerOrAction) && viewContainerOrAction.id === this.compositeBarActionItem.id) {
784
this.updateActivity();
785
}
786
}));
787
}
788
789
updateCompositeBarActionItem(compositeBarActionItem: ICompositeBarActionItem): void {
790
this.compositeBarActionItem = compositeBarActionItem;
791
}
792
793
private updateActivity(): void {
794
this.activities = this.activityService.getViewContainerActivities(this.compositeBarActionItem.id);
795
}
796
797
override async run(event: { preserveFocus: boolean }): Promise<void> {
798
if (isMouseEvent(event) && event.button === 2) {
799
return; // do not run on right click
800
}
801
802
// prevent accident trigger on a doubleclick (to help nervous people)
803
const now = Date.now();
804
if (now > this.lastRun /* https://github.com/microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewContainerActivityAction.preventDoubleClickDelay) {
805
return;
806
}
807
this.lastRun = now;
808
809
const focus = (event && 'preserveFocus' in event) ? !event.preserveFocus : true;
810
811
if (this.part === Parts.ACTIVITYBAR_PART) {
812
const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
813
const activeViewlet = this.paneCompositePart.getActivePaneComposite();
814
const focusBehavior = this.configurationService.getValue<string>('workbench.activityBar.iconClickBehavior');
815
816
if (sideBarVisible && activeViewlet?.getId() === this.compositeBarActionItem.id) {
817
switch (focusBehavior) {
818
case 'focus':
819
this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);
820
break;
821
case 'toggle':
822
default:
823
// Hide sidebar if selected viewlet already visible
824
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
825
break;
826
}
827
828
return;
829
}
830
}
831
832
await this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus);
833
return this.activate();
834
}
835
}
836
837
class PlaceHolderViewContainerActivityAction extends ViewContainerActivityAction { }
838
839
class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {
840
841
constructor(id: string, compositeBar: ICompositeBar) {
842
super({ id, name: id, classNames: undefined }, compositeBar);
843
}
844
845
setActivity(activity: ICompositeBarActionItem): void {
846
this.label = activity.name;
847
}
848
}
849
850
class PlaceHolderToggleCompositeBadgeAction extends ToggleCompositeBadgeAction {
851
852
constructor(id: string, compositeBar: ICompositeBar) {
853
super({ id, name: id, classNames: undefined }, compositeBar);
854
}
855
856
setCompositeBarActionItem(actionItem: ICompositeBarActionItem): void {
857
this.label = actionItem.name;
858
}
859
}
860
861