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