Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/paneCompositePart.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/paneCompositePart.css';
7
import { Event } from '../../../base/common/event.js';
8
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
9
import { IProgressIndicator } from '../../../platform/progress/common/progress.js';
10
import { Extensions, PaneComposite, PaneCompositeDescriptor, PaneCompositeRegistry } from '../panecomposite.js';
11
import { IPaneComposite } from '../../common/panecomposite.js';
12
import { IViewDescriptorService, ViewContainerLocation } from '../../common/views.js';
13
import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
14
import { IView } from '../../../base/browser/ui/grid/grid.js';
15
import { IWorkbenchLayoutService, Parts } from '../../services/layout/browser/layoutService.js';
16
import { CompositePart, ICompositePartOptions, ICompositeTitleLabel } from './compositePart.js';
17
import { IPaneCompositeBarOptions, PaneCompositeBar } from './paneCompositeBar.js';
18
import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow } from '../../../base/browser/dom.js';
19
import { Registry } from '../../../platform/registry/common/platform.js';
20
import { INotificationService } from '../../../platform/notification/common/notification.js';
21
import { IStorageService } from '../../../platform/storage/common/storage.js';
22
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
23
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
24
import { IThemeService } from '../../../platform/theme/common/themeService.js';
25
import { IContextKey, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
26
import { IExtensionService } from '../../services/extensions/common/extensions.js';
27
import { IComposite } from '../../common/composite.js';
28
import { localize } from '../../../nls.js';
29
import { CompositeDragAndDropObserver, toggleDropEffect } from '../dnd.js';
30
import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../common/theme.js';
31
import { IMenuService, MenuId } from '../../../platform/actions/common/actions.js';
32
import { ActionsOrientation } from '../../../base/browser/ui/actionbar/actionbar.js';
33
import { Gesture, EventType as GestureEventType } from '../../../base/browser/touch.js';
34
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
35
import { IAction, SubmenuAction } from '../../../base/common/actions.js';
36
import { Composite } from '../composite.js';
37
import { ViewsSubMenu } from './views/viewPaneContainer.js';
38
import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js';
39
import { IHoverService } from '../../../platform/hover/browser/hover.js';
40
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
41
import { DeferredPromise } from '../../../base/common/async.js';
42
43
export enum CompositeBarPosition {
44
TOP,
45
TITLE,
46
BOTTOM
47
}
48
49
export interface IPaneCompositePart extends IView {
50
51
readonly partId: Parts.PANEL_PART | Parts.AUXILIARYBAR_PART | Parts.SIDEBAR_PART;
52
53
readonly onDidPaneCompositeOpen: Event<IPaneComposite>;
54
readonly onDidPaneCompositeClose: Event<IPaneComposite>;
55
56
/**
57
* Opens a viewlet with the given identifier and pass keyboard focus to it if specified.
58
*/
59
openPaneComposite(id: string | undefined, focus?: boolean): Promise<IPaneComposite | undefined>;
60
61
/**
62
* Returns the current active viewlet if any.
63
*/
64
getActivePaneComposite(): IPaneComposite | undefined;
65
66
/**
67
* Returns the viewlet by id.
68
*/
69
getPaneComposite(id: string): PaneCompositeDescriptor | undefined;
70
71
/**
72
* Returns all enabled viewlets
73
*/
74
getPaneComposites(): PaneCompositeDescriptor[];
75
76
/**
77
* Returns the progress indicator for the side bar.
78
*/
79
getProgressIndicator(id: string): IProgressIndicator | undefined;
80
81
/**
82
* Hide the active viewlet.
83
*/
84
hideActivePaneComposite(): void;
85
86
/**
87
* Return the last active viewlet id.
88
*/
89
getLastActivePaneCompositeId(): string;
90
91
/**
92
* Returns id of pinned view containers following the visual order.
93
*/
94
getPinnedPaneCompositeIds(): string[];
95
96
/**
97
* Returns id of visible view containers following the visual order.
98
*/
99
getVisiblePaneCompositeIds(): string[];
100
101
/**
102
* Returns id of all view containers following the visual order.
103
*/
104
getPaneCompositeIds(): string[];
105
}
106
107
export abstract class AbstractPaneCompositePart extends CompositePart<PaneComposite> implements IPaneCompositePart {
108
109
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
110
111
get snap(): boolean {
112
// Always allow snapping closed
113
// Only allow dragging open if the panel contains view containers
114
return this.layoutService.isVisible(this.partId) || !!this.paneCompositeBar.value?.getVisiblePaneCompositeIds().length;
115
}
116
117
get onDidPaneCompositeOpen(): Event<IPaneComposite> { return Event.map(this.onDidCompositeOpen.event, compositeEvent => <IPaneComposite>compositeEvent.composite); }
118
readonly onDidPaneCompositeClose = this.onDidCompositeClose.event as Event<IPaneComposite>;
119
120
private readonly location: ViewContainerLocation;
121
private titleContainer: HTMLElement | undefined;
122
private headerFooterCompositeBarContainer: HTMLElement | undefined;
123
protected readonly headerFooterCompositeBarDispoables = this._register(new DisposableStore());
124
private paneCompositeBarContainer: HTMLElement | undefined;
125
private readonly paneCompositeBar = this._register(new MutableDisposable<PaneCompositeBar>());
126
private compositeBarPosition: CompositeBarPosition | undefined = undefined;
127
private emptyPaneMessageElement: HTMLElement | undefined;
128
129
private readonly globalActionsMenuId: MenuId;
130
private globalToolBar: MenuWorkbenchToolBar | undefined;
131
132
private blockOpening: DeferredPromise<PaneComposite | undefined> | undefined = undefined;
133
protected contentDimension: Dimension | undefined;
134
135
constructor(
136
readonly partId: Parts.PANEL_PART | Parts.AUXILIARYBAR_PART | Parts.SIDEBAR_PART,
137
partOptions: ICompositePartOptions,
138
activePaneCompositeSettingsKey: string,
139
private readonly activePaneContextKey: IContextKey<string>,
140
private paneFocusContextKey: IContextKey<boolean>,
141
nameForTelemetry: string,
142
compositeCSSClass: string,
143
titleForegroundColor: string | undefined,
144
titleBorderColor: string | undefined,
145
@INotificationService notificationService: INotificationService,
146
@IStorageService storageService: IStorageService,
147
@IContextMenuService contextMenuService: IContextMenuService,
148
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
149
@IKeybindingService keybindingService: IKeybindingService,
150
@IHoverService hoverService: IHoverService,
151
@IInstantiationService instantiationService: IInstantiationService,
152
@IThemeService themeService: IThemeService,
153
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
154
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
155
@IExtensionService private readonly extensionService: IExtensionService,
156
@IMenuService protected readonly menuService: IMenuService,
157
) {
158
let location = ViewContainerLocation.Sidebar;
159
let registryId = Extensions.Viewlets;
160
let globalActionsMenuId = MenuId.SidebarTitle;
161
if (partId === Parts.PANEL_PART) {
162
location = ViewContainerLocation.Panel;
163
registryId = Extensions.Panels;
164
globalActionsMenuId = MenuId.PanelTitle;
165
} else if (partId === Parts.AUXILIARYBAR_PART) {
166
location = ViewContainerLocation.AuxiliaryBar;
167
registryId = Extensions.Auxiliary;
168
globalActionsMenuId = MenuId.AuxiliaryBarTitle;
169
}
170
super(
171
notificationService,
172
storageService,
173
contextMenuService,
174
layoutService,
175
keybindingService,
176
hoverService,
177
instantiationService,
178
themeService,
179
Registry.as<PaneCompositeRegistry>(registryId),
180
activePaneCompositeSettingsKey,
181
viewDescriptorService.getDefaultViewContainer(location)?.id || '',
182
nameForTelemetry,
183
compositeCSSClass,
184
titleForegroundColor,
185
titleBorderColor,
186
partId,
187
partOptions
188
);
189
190
this.location = location;
191
this.globalActionsMenuId = globalActionsMenuId;
192
this.registerListeners();
193
}
194
195
private registerListeners(): void {
196
this._register(this.onDidPaneCompositeOpen(composite => this.onDidOpen(composite)));
197
this._register(this.onDidPaneCompositeClose(this.onDidClose, this));
198
199
this._register(this.registry.onDidDeregister((viewletDescriptor: PaneCompositeDescriptor) => {
200
201
const activeContainers = this.viewDescriptorService.getViewContainersByLocation(this.location)
202
.filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0);
203
204
if (activeContainers.length) {
205
if (this.getActiveComposite()?.getId() === viewletDescriptor.id) {
206
const defaultViewletId = this.viewDescriptorService.getDefaultViewContainer(this.location)?.id;
207
const containerToOpen = activeContainers.filter(c => c.id === defaultViewletId)[0] || activeContainers[0];
208
this.doOpenPaneComposite(containerToOpen.id);
209
}
210
} else {
211
this.layoutService.setPartHidden(true, this.partId);
212
}
213
214
this.removeComposite(viewletDescriptor.id);
215
}));
216
217
this._register(this.extensionService.onDidRegisterExtensions(() => {
218
this.layoutCompositeBar();
219
}));
220
}
221
222
private onDidOpen(composite: IComposite): void {
223
this.activePaneContextKey.set(composite.getId());
224
}
225
226
private onDidClose(composite: IComposite): void {
227
const id = composite.getId();
228
if (this.activePaneContextKey.get() === id) {
229
this.activePaneContextKey.reset();
230
}
231
}
232
233
protected override showComposite(composite: Composite): void {
234
super.showComposite(composite);
235
this.layoutCompositeBar();
236
this.layoutEmptyMessage();
237
}
238
239
protected override hideActiveComposite(): Composite | undefined {
240
const composite = super.hideActiveComposite();
241
this.layoutCompositeBar();
242
this.layoutEmptyMessage();
243
return composite;
244
}
245
246
override create(parent: HTMLElement): void {
247
this.element = parent;
248
this.element.classList.add('pane-composite-part');
249
250
super.create(parent);
251
252
if (this.contentArea) {
253
this.createEmptyPaneMessage(this.contentArea);
254
}
255
256
this.updateCompositeBar();
257
258
const focusTracker = this._register(trackFocus(parent));
259
this._register(focusTracker.onDidFocus(() => this.paneFocusContextKey.set(true)));
260
this._register(focusTracker.onDidBlur(() => this.paneFocusContextKey.set(false)));
261
}
262
263
private createEmptyPaneMessage(parent: HTMLElement): void {
264
this.emptyPaneMessageElement = $('.empty-pane-message-area');
265
266
const messageElement = $('.empty-pane-message');
267
messageElement.textContent = localize('pane.emptyMessage', "Drag a view here to display.");
268
269
this.emptyPaneMessageElement.appendChild(messageElement);
270
parent.appendChild(this.emptyPaneMessageElement);
271
272
const setDropBackgroundFeedback = (visible: boolean) => {
273
const updateActivityBarBackground = !this.getActiveComposite() || !visible;
274
const backgroundColor = visible ? this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND)?.toString() || '' : '';
275
276
if (this.titleContainer && updateActivityBarBackground) {
277
this.titleContainer.style.backgroundColor = backgroundColor;
278
}
279
if (this.headerFooterCompositeBarContainer && updateActivityBarBackground) {
280
this.headerFooterCompositeBarContainer.style.backgroundColor = backgroundColor;
281
}
282
283
this.emptyPaneMessageElement!.style.backgroundColor = backgroundColor;
284
};
285
286
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
287
onDragOver: (e) => {
288
EventHelper.stop(e.eventData, true);
289
if (this.paneCompositeBar.value) {
290
const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData);
291
toggleDropEffect(e.eventData.dataTransfer, 'move', validDropTarget);
292
}
293
},
294
onDragEnter: (e) => {
295
EventHelper.stop(e.eventData, true);
296
if (this.paneCompositeBar.value) {
297
const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData);
298
setDropBackgroundFeedback(validDropTarget);
299
}
300
},
301
onDragLeave: (e) => {
302
EventHelper.stop(e.eventData, true);
303
setDropBackgroundFeedback(false);
304
},
305
onDragEnd: (e) => {
306
EventHelper.stop(e.eventData, true);
307
setDropBackgroundFeedback(false);
308
},
309
onDrop: (e) => {
310
EventHelper.stop(e.eventData, true);
311
setDropBackgroundFeedback(false);
312
if (this.paneCompositeBar.value) {
313
this.paneCompositeBar.value.dndHandler.drop(e.dragAndDropData, undefined, e.eventData);
314
} else {
315
// Allow opening views/composites if the composite bar is hidden
316
const dragData = e.dragAndDropData.getData();
317
318
if (dragData.type === 'composite') {
319
const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!;
320
this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.location, undefined, 'dnd');
321
this.openPaneComposite(currentContainer.id, true);
322
}
323
324
else if (dragData.type === 'view') {
325
const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!;
326
if (viewToMove.canMoveView) {
327
this.viewDescriptorService.moveViewToLocation(viewToMove, this.location, 'dnd');
328
329
const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!;
330
331
this.openPaneComposite(newContainer.id, true).then(composite => {
332
composite?.openView(viewToMove.id, true);
333
});
334
}
335
}
336
}
337
},
338
}));
339
}
340
341
protected override createTitleArea(parent: HTMLElement): HTMLElement {
342
const titleArea = super.createTitleArea(parent);
343
344
this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => {
345
this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e));
346
}));
347
this._register(Gesture.addTarget(titleArea));
348
this._register(addDisposableListener(titleArea, GestureEventType.Contextmenu, e => {
349
this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e));
350
}));
351
352
const globalTitleActionsContainer = titleArea.appendChild($('.global-actions'));
353
354
// Global Actions Toolbar
355
this.globalToolBar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar,
356
globalTitleActionsContainer,
357
this.globalActionsMenuId,
358
{
359
actionViewItemProvider: (action, options) => this.actionViewItemProvider(action, options),
360
orientation: ActionsOrientation.HORIZONTAL,
361
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
362
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(),
363
toggleMenuTitle: localize('moreActions', "More Actions..."),
364
hoverDelegate: this.toolbarHoverDelegate,
365
hiddenItemStrategy: HiddenItemStrategy.NoHide,
366
highlightToggledItems: true,
367
telemetrySource: this.nameForTelemetry
368
}
369
));
370
371
return titleArea;
372
}
373
374
protected override createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
375
this.titleContainer = parent;
376
377
const titleLabel = super.createTitleLabel(parent);
378
this.titleLabelElement!.draggable = true;
379
const draggedItemProvider = (): { type: 'view' | 'composite'; id: string } => {
380
const activeViewlet = this.getActivePaneComposite()!;
381
return { type: 'composite', id: activeViewlet.getId() };
382
};
383
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {}));
384
385
return titleLabel;
386
}
387
388
protected updateCompositeBar(updateCompositeBarOption: boolean = false): void {
389
const wasCompositeBarVisible = this.compositeBarPosition !== undefined;
390
const isCompositeBarVisible = this.shouldShowCompositeBar();
391
const previousPosition = this.compositeBarPosition;
392
const newPosition = isCompositeBarVisible ? this.getCompositeBarPosition() : undefined;
393
394
// Only update if the visibility or position has changed or if the composite bar options should be updated
395
if (!updateCompositeBarOption && previousPosition === newPosition) {
396
return;
397
}
398
399
// Remove old composite bar
400
if (wasCompositeBarVisible) {
401
const previousCompositeBarContainer = previousPosition === CompositeBarPosition.TITLE ? this.titleContainer : this.headerFooterCompositeBarContainer;
402
if (!this.paneCompositeBarContainer || !this.paneCompositeBar.value || !previousCompositeBarContainer) {
403
throw new Error('Composite bar containers should exist when removing the previous composite bar');
404
}
405
406
this.paneCompositeBarContainer.remove();
407
this.paneCompositeBarContainer = undefined;
408
this.paneCompositeBar.value = undefined;
409
410
previousCompositeBarContainer.classList.remove('has-composite-bar');
411
412
if (previousPosition === CompositeBarPosition.TOP) {
413
this.removeFooterHeaderArea(true);
414
} else if (previousPosition === CompositeBarPosition.BOTTOM) {
415
this.removeFooterHeaderArea(false);
416
}
417
}
418
419
// Create new composite bar
420
let newCompositeBarContainer;
421
switch (newPosition) {
422
case CompositeBarPosition.TOP: newCompositeBarContainer = this.createHeaderArea(); break;
423
case CompositeBarPosition.TITLE: newCompositeBarContainer = this.titleContainer; break;
424
case CompositeBarPosition.BOTTOM: newCompositeBarContainer = this.createFooterArea(); break;
425
}
426
if (isCompositeBarVisible) {
427
428
if (this.paneCompositeBarContainer || this.paneCompositeBar.value || !newCompositeBarContainer) {
429
throw new Error('Invalid composite bar state when creating the new composite bar');
430
}
431
432
newCompositeBarContainer.classList.add('has-composite-bar');
433
this.paneCompositeBarContainer = prepend(newCompositeBarContainer, $('.composite-bar-container'));
434
this.paneCompositeBar.value = this.createCompositeBar();
435
this.paneCompositeBar.value.create(this.paneCompositeBarContainer);
436
437
if (newPosition === CompositeBarPosition.TOP) {
438
this.setHeaderArea(newCompositeBarContainer);
439
} else if (newPosition === CompositeBarPosition.BOTTOM) {
440
this.setFooterArea(newCompositeBarContainer);
441
}
442
}
443
444
this.compositeBarPosition = newPosition;
445
446
if (updateCompositeBarOption) {
447
this.layoutCompositeBar();
448
}
449
}
450
451
protected override createHeaderArea(): HTMLElement {
452
const headerArea = super.createHeaderArea();
453
454
return this.createHeaderFooterCompositeBarArea(headerArea);
455
}
456
457
protected override createFooterArea(): HTMLElement {
458
const footerArea = super.createFooterArea();
459
460
return this.createHeaderFooterCompositeBarArea(footerArea);
461
}
462
463
protected createHeaderFooterCompositeBarArea(area: HTMLElement): HTMLElement {
464
if (this.headerFooterCompositeBarContainer) {
465
// A pane composite part has either a header or a footer, but not both
466
throw new Error('Header or Footer composite bar already exists');
467
}
468
this.headerFooterCompositeBarContainer = area;
469
470
this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, EventType.CONTEXT_MENU, e => {
471
this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e));
472
}));
473
this.headerFooterCompositeBarDispoables.add(Gesture.addTarget(area));
474
this.headerFooterCompositeBarDispoables.add(addDisposableListener(area, GestureEventType.Contextmenu, e => {
475
this.onCompositeBarAreaContextMenu(new StandardMouseEvent(getWindow(area), e));
476
}));
477
478
return area;
479
}
480
481
private removeFooterHeaderArea(header: boolean): void {
482
this.headerFooterCompositeBarContainer = undefined;
483
this.headerFooterCompositeBarDispoables.clear();
484
if (header) {
485
this.removeHeaderArea();
486
} else {
487
this.removeFooterArea();
488
}
489
}
490
491
protected createCompositeBar(): PaneCompositeBar {
492
return this.instantiationService.createInstance(PaneCompositeBar, this.getCompositeBarOptions(), this.partId, this);
493
}
494
495
protected override onTitleAreaUpdate(compositeId: string): void {
496
super.onTitleAreaUpdate(compositeId);
497
498
// If title actions change, relayout the composite bar
499
this.layoutCompositeBar();
500
}
501
502
async openPaneComposite(id?: string, focus?: boolean): Promise<PaneComposite | undefined> {
503
if (typeof id === 'string' && this.getPaneComposite(id)) {
504
return this.doOpenPaneComposite(id, focus);
505
}
506
507
await this.extensionService.whenInstalledExtensionsRegistered();
508
509
if (typeof id === 'string' && this.getPaneComposite(id)) {
510
return this.doOpenPaneComposite(id, focus);
511
}
512
513
return undefined;
514
}
515
516
private async doOpenPaneComposite(id: string, focus?: boolean): Promise<PaneComposite | undefined> {
517
if (this.blockOpening) {
518
// Workaround against a potential race condition when calling
519
// `setPartHidden` we may end up in `openPaneComposite` again.
520
// But we still want to return the result of the original call,
521
// so we return the promise of the original call.
522
return this.blockOpening.p;
523
}
524
525
let blockOpening: DeferredPromise<PaneComposite | undefined> | undefined;
526
if (!this.layoutService.isVisible(this.partId)) {
527
try {
528
blockOpening = this.blockOpening = new DeferredPromise<PaneComposite | undefined>();
529
this.layoutService.setPartHidden(false, this.partId);
530
} finally {
531
this.blockOpening = undefined;
532
}
533
}
534
535
try {
536
const result = this.openComposite(id, focus) as PaneComposite | undefined;
537
blockOpening?.complete(result);
538
539
return result;
540
} catch (error) {
541
blockOpening?.error(error);
542
throw error;
543
}
544
}
545
546
getPaneComposite(id: string): PaneCompositeDescriptor | undefined {
547
return (this.registry as PaneCompositeRegistry).getPaneComposite(id);
548
}
549
550
getPaneComposites(): PaneCompositeDescriptor[] {
551
return (this.registry as PaneCompositeRegistry).getPaneComposites()
552
.sort((v1, v2) => {
553
if (typeof v1.order !== 'number') {
554
return 1;
555
}
556
557
if (typeof v2.order !== 'number') {
558
return -1;
559
}
560
561
return v1.order - v2.order;
562
});
563
}
564
565
getPinnedPaneCompositeIds(): string[] {
566
return this.paneCompositeBar.value?.getPinnedPaneCompositeIds() ?? [];
567
}
568
569
getVisiblePaneCompositeIds(): string[] {
570
return this.paneCompositeBar.value?.getVisiblePaneCompositeIds() ?? [];
571
}
572
573
getPaneCompositeIds(): string[] {
574
return this.paneCompositeBar.value?.getPaneCompositeIds() ?? [];
575
}
576
577
getActivePaneComposite(): IPaneComposite | undefined {
578
return <IPaneComposite>this.getActiveComposite();
579
}
580
581
getLastActivePaneCompositeId(): string {
582
return this.getLastActiveCompositeId();
583
}
584
585
hideActivePaneComposite(): void {
586
if (this.layoutService.isVisible(this.partId)) {
587
this.layoutService.setPartHidden(true, this.partId);
588
}
589
590
this.hideActiveComposite();
591
}
592
593
protected focusCompositeBar(): void {
594
this.paneCompositeBar.value?.focus();
595
}
596
597
override layout(width: number, height: number, top: number, left: number): void {
598
if (!this.layoutService.isVisible(this.partId)) {
599
return;
600
}
601
602
this.contentDimension = new Dimension(width, height);
603
604
// Layout contents
605
super.layout(this.contentDimension.width, this.contentDimension.height, top, left);
606
607
// Layout composite bar
608
this.layoutCompositeBar();
609
610
// Add empty pane message
611
this.layoutEmptyMessage();
612
}
613
614
private layoutCompositeBar(): void {
615
if (this.contentDimension && this.dimension && this.paneCompositeBar.value) {
616
const padding = this.compositeBarPosition === CompositeBarPosition.TITLE ? 16 : 8;
617
const borderWidth = this.partId === Parts.PANEL_PART ? 0 : 1;
618
let availableWidth = this.contentDimension.width - padding - borderWidth;
619
availableWidth = Math.max(AbstractPaneCompositePart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth());
620
this.paneCompositeBar.value.layout(availableWidth, this.dimension.height);
621
}
622
}
623
624
private layoutEmptyMessage(): void {
625
const visible = !this.getActiveComposite();
626
this.element.classList.toggle('empty', visible);
627
if (visible) {
628
this.titleLabel?.updateTitle('', '');
629
}
630
}
631
632
protected getToolbarWidth(): number {
633
if (!this.toolBar || this.compositeBarPosition !== CompositeBarPosition.TITLE) {
634
return 0;
635
}
636
637
const activePane = this.getActivePaneComposite();
638
if (!activePane) {
639
return 0;
640
}
641
642
// Each toolbar item has 4px margin
643
const toolBarWidth = this.toolBar.getItemsWidth() + this.toolBar.getItemsLength() * 4;
644
const globalToolBarWidth = this.globalToolBar ? this.globalToolBar.getItemsWidth() + this.globalToolBar.getItemsLength() * 4 : 0;
645
return toolBarWidth + globalToolBarWidth + 8; // 8px padding left
646
}
647
648
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
649
if (this.shouldShowCompositeBar() && this.getCompositeBarPosition() === CompositeBarPosition.TITLE) {
650
return this.onCompositeBarContextMenu(event);
651
} else {
652
const activePaneComposite = this.getActivePaneComposite() as PaneComposite;
653
const activePaneCompositeActions = activePaneComposite ? activePaneComposite.getContextMenuActions() : [];
654
if (activePaneCompositeActions.length) {
655
this.contextMenuService.showContextMenu({
656
getAnchor: () => event,
657
getActions: () => activePaneCompositeActions,
658
getActionViewItem: (action, options) => this.actionViewItemProvider(action, options),
659
actionRunner: activePaneComposite.getActionRunner(),
660
skipTelemetry: true
661
});
662
}
663
}
664
}
665
666
private onCompositeBarAreaContextMenu(event: StandardMouseEvent): void {
667
return this.onCompositeBarContextMenu(event);
668
}
669
670
private onCompositeBarContextMenu(event: StandardMouseEvent): void {
671
if (this.paneCompositeBar.value) {
672
const actions: IAction[] = [...this.paneCompositeBar.value.getContextMenuActions()];
673
if (actions.length) {
674
this.contextMenuService.showContextMenu({
675
getAnchor: () => event,
676
getActions: () => actions,
677
skipTelemetry: true
678
});
679
}
680
}
681
}
682
683
protected getViewsSubmenuAction(): SubmenuAction | undefined {
684
const viewPaneContainer = (this.getActivePaneComposite() as PaneComposite)?.getViewPaneContainer();
685
if (viewPaneContainer) {
686
const disposables = new DisposableStore();
687
const scopedContextKeyService = disposables.add(this.contextKeyService.createScoped(this.element));
688
scopedContextKeyService.createKey('viewContainer', viewPaneContainer.viewContainer.id);
689
const menu = this.menuService.getMenuActions(ViewsSubMenu, scopedContextKeyService, { shouldForwardArgs: true, renderShortTitle: true });
690
const viewsActions = getActionBarActions(menu, () => true).primary;
691
disposables.dispose();
692
return viewsActions.length > 1 && viewsActions.some(a => a.enabled) ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined;
693
}
694
return undefined;
695
}
696
697
protected abstract shouldShowCompositeBar(): boolean;
698
protected abstract getCompositeBarOptions(): IPaneCompositeBarOptions;
699
protected abstract getCompositeBarPosition(): CompositeBarPosition;
700
}
701
702