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