Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/views/viewPaneContainer.ts
5292 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 { $, addDisposableListener, Dimension, DragAndDropObserver, EventType, getWindow, isAncestor } from '../../../../base/browser/dom.js';
7
import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js';
8
import { EventType as TouchEventType, Gesture } from '../../../../base/browser/touch.js';
9
import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js';
10
import { IBoundarySashes, Orientation } from '../../../../base/browser/ui/sash/sash.js';
11
import { IPaneViewOptions, PaneView } from '../../../../base/browser/ui/splitview/paneview.js';
12
import { IAction } from '../../../../base/common/actions.js';
13
import { RunOnceScheduler } from '../../../../base/common/async.js';
14
import { Emitter, Event } from '../../../../base/common/event.js';
15
import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
16
import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
17
import { assertReturnsDefined } from '../../../../base/common/types.js';
18
import './media/paneviewlet.css';
19
import * as nls from '../../../../nls.js';
20
import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
21
import { Action2, IAction2Options, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
22
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
23
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
24
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
25
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
26
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
27
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
28
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
29
import { activeContrastBorder, asCssVariable } from '../../../../platform/theme/common/colorRegistry.js';
30
import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js';
31
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
32
import { CompositeDragAndDropObserver, toggleDropEffect } from '../../dnd.js';
33
import { ViewPane } from './viewPane.js';
34
import { IViewletViewOptions } from './viewsViewlet.js';
35
import { Component } from '../../../common/component.js';
36
import { PANEL_SECTION_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_HEADER_FOREGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, SIDE_BAR_SECTION_HEADER_FOREGROUND } from '../../../common/theme.js';
37
import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, ViewContainer, ViewContainerLocation, ViewVisibilityState } from '../../../common/views.js';
38
import { IViewsService } from '../../../services/views/common/viewsService.js';
39
import { FocusedViewContext } from '../../../common/contextkeys.js';
40
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
41
import { isHorizontal, IWorkbenchLayoutService, LayoutSettings } from '../../../services/layout/browser/layoutService.js';
42
import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
43
import { ILogService } from '../../../../platform/log/common/log.js';
44
import { ViewContainerMenuActions } from './viewMenuActions.js';
45
46
export const ViewsSubMenu = new MenuId('Views');
47
MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, {
48
submenu: ViewsSubMenu,
49
title: nls.localize('views', "Views"),
50
order: 1,
51
} satisfies ISubmenuItem);
52
53
export interface IViewPaneContainerOptions extends IPaneViewOptions {
54
mergeViewWithContainerWhenSingleView: boolean;
55
}
56
57
interface IViewPaneItem {
58
pane: ViewPane;
59
disposable: IDisposable;
60
}
61
62
const enum DropDirection {
63
UP,
64
DOWN,
65
LEFT,
66
RIGHT
67
}
68
69
type BoundingRect = { top: number; left: number; bottom: number; right: number };
70
71
class ViewPaneDropOverlay extends Themable {
72
73
private static readonly OVERLAY_ID = 'monaco-pane-drop-overlay';
74
75
private container!: HTMLElement;
76
private overlay!: HTMLElement;
77
78
private _currentDropOperation: DropDirection | undefined;
79
80
// private currentDropOperation: IDropOperation | undefined;
81
private _disposed: boolean | undefined;
82
83
private cleanupOverlayScheduler: RunOnceScheduler;
84
85
get currentDropOperation(): DropDirection | undefined {
86
return this._currentDropOperation;
87
}
88
89
constructor(
90
private paneElement: HTMLElement,
91
private orientation: Orientation | undefined,
92
private bounds: BoundingRect | undefined,
93
protected location: ViewContainerLocation,
94
themeService: IThemeService,
95
) {
96
super(themeService);
97
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
98
99
this.create();
100
}
101
102
get disposed(): boolean {
103
return !!this._disposed;
104
}
105
106
private create(): void {
107
108
// Container
109
this.container = $('div', { id: ViewPaneDropOverlay.OVERLAY_ID });
110
this.container.style.top = '0px';
111
112
// Parent
113
this.paneElement.appendChild(this.container);
114
this.paneElement.classList.add('dragged-over');
115
this._register(toDisposable(() => {
116
this.container.remove();
117
this.paneElement.classList.remove('dragged-over');
118
}));
119
120
// Overlay
121
this.overlay = $('.pane-overlay-indicator');
122
this.container.appendChild(this.overlay);
123
124
// Overlay Event Handling
125
this.registerListeners();
126
127
// Styles
128
this.updateStyles();
129
}
130
131
override updateStyles(): void {
132
133
// Overlay drop background
134
this.overlay.style.backgroundColor = this.getColor(this.location === ViewContainerLocation.Panel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND) || '';
135
136
// Overlay contrast border (if any)
137
const activeContrastBorderColor = this.getColor(activeContrastBorder);
138
this.overlay.style.outlineColor = activeContrastBorderColor || '';
139
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
140
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
141
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
142
143
this.overlay.style.borderColor = activeContrastBorderColor || '';
144
this.overlay.style.borderStyle = 'solid';
145
this.overlay.style.borderWidth = '0px';
146
}
147
148
private registerListeners(): void {
149
this._register(new DragAndDropObserver(this.container, {
150
onDragOver: e => {
151
152
// Position overlay
153
this.positionOverlay(e.offsetX, e.offsetY);
154
155
// Make sure to stop any running cleanup scheduler to remove the overlay
156
if (this.cleanupOverlayScheduler.isScheduled()) {
157
this.cleanupOverlayScheduler.cancel();
158
}
159
},
160
161
onDragLeave: e => this.dispose(),
162
onDragEnd: e => this.dispose(),
163
164
onDrop: e => {
165
// Dispose overlay
166
this.dispose();
167
}
168
}));
169
170
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
171
// Under some circumstances we have seen reports where the drop overlay is not being
172
// cleaned up and as such the editor area remains under the overlay so that you cannot
173
// type into the editor anymore. This seems related to using VMs and DND via host and
174
// guest OS, though some users also saw it without VMs.
175
// To protect against this issue we always destroy the overlay as soon as we detect a
176
// mouse event over it. The delay is used to guarantee we are not interfering with the
177
// actual DROP event that can also trigger a mouse over event.
178
if (!this.cleanupOverlayScheduler.isScheduled()) {
179
this.cleanupOverlayScheduler.schedule();
180
}
181
}));
182
}
183
184
private positionOverlay(mousePosX: number, mousePosY: number): void {
185
const paneWidth = this.paneElement.clientWidth;
186
const paneHeight = this.paneElement.clientHeight;
187
188
const splitWidthThreshold = paneWidth / 2;
189
const splitHeightThreshold = paneHeight / 2;
190
191
let dropDirection: DropDirection | undefined;
192
193
if (this.orientation === Orientation.VERTICAL) {
194
if (mousePosY < splitHeightThreshold) {
195
dropDirection = DropDirection.UP;
196
} else if (mousePosY >= splitHeightThreshold) {
197
dropDirection = DropDirection.DOWN;
198
}
199
} else if (this.orientation === Orientation.HORIZONTAL) {
200
if (mousePosX < splitWidthThreshold) {
201
dropDirection = DropDirection.LEFT;
202
} else if (mousePosX >= splitWidthThreshold) {
203
dropDirection = DropDirection.RIGHT;
204
}
205
}
206
207
// Draw overlay based on split direction
208
switch (dropDirection) {
209
case DropDirection.UP:
210
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' });
211
break;
212
case DropDirection.DOWN:
213
this.doPositionOverlay({ bottom: '0', left: '0', width: '100%', height: '50%' });
214
break;
215
case DropDirection.LEFT:
216
this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' });
217
break;
218
case DropDirection.RIGHT:
219
this.doPositionOverlay({ top: '0', right: '0', width: '50%', height: '100%' });
220
break;
221
default: {
222
// const top = this.bounds?.top || 0;
223
// const left = this.bounds?.bottom || 0;
224
225
let top = '0';
226
let left = '0';
227
let width = '100%';
228
let height = '100%';
229
if (this.bounds) {
230
const boundingRect = this.container.getBoundingClientRect();
231
top = `${this.bounds.top - boundingRect.top}px`;
232
left = `${this.bounds.left - boundingRect.left}px`;
233
height = `${this.bounds.bottom - this.bounds.top}px`;
234
width = `${this.bounds.right - this.bounds.left}px`;
235
}
236
237
this.doPositionOverlay({ top, left, width, height });
238
}
239
}
240
241
if ((this.orientation === Orientation.VERTICAL && paneHeight <= 25) ||
242
(this.orientation === Orientation.HORIZONTAL && paneWidth <= 25)) {
243
this.doUpdateOverlayBorder(dropDirection);
244
} else {
245
this.doUpdateOverlayBorder(undefined);
246
}
247
248
// Make sure the overlay is visible now
249
this.overlay.style.opacity = '1';
250
251
// Enable transition after a timeout to prevent initial animation
252
setTimeout(() => this.overlay.classList.add('overlay-move-transition'), 0);
253
254
// Remember as current split direction
255
this._currentDropOperation = dropDirection;
256
}
257
258
private doUpdateOverlayBorder(direction: DropDirection | undefined): void {
259
this.overlay.style.borderTopWidth = direction === DropDirection.UP ? '2px' : '0px';
260
this.overlay.style.borderLeftWidth = direction === DropDirection.LEFT ? '2px' : '0px';
261
this.overlay.style.borderBottomWidth = direction === DropDirection.DOWN ? '2px' : '0px';
262
this.overlay.style.borderRightWidth = direction === DropDirection.RIGHT ? '2px' : '0px';
263
}
264
265
private doPositionOverlay(options: { top?: string; bottom?: string; left?: string; right?: string; width: string; height: string }): void {
266
267
// Container
268
this.container.style.height = '100%';
269
270
// Overlay
271
this.overlay.style.top = options.top || '';
272
this.overlay.style.left = options.left || '';
273
this.overlay.style.bottom = options.bottom || '';
274
this.overlay.style.right = options.right || '';
275
this.overlay.style.width = options.width;
276
this.overlay.style.height = options.height;
277
}
278
279
280
contains(element: HTMLElement): boolean {
281
return element === this.container || element === this.overlay;
282
}
283
284
override dispose(): void {
285
super.dispose();
286
287
this._disposed = true;
288
}
289
}
290
291
export class ViewPaneContainer<MementoType extends object = object> extends Component<MementoType> implements IViewPaneContainer {
292
293
readonly viewContainer: ViewContainer;
294
private lastFocusedPane: ViewPane | undefined;
295
private lastMergedCollapsedPane: ViewPane | undefined;
296
private paneItems: IViewPaneItem[] = [];
297
private paneview?: PaneView;
298
299
private visible: boolean = false;
300
301
private areExtensionsReady: boolean = false;
302
303
private didLayout = false;
304
private dimension: Dimension | undefined;
305
private _boundarySashes: IBoundarySashes | undefined;
306
307
private readonly visibleViewsCountFromCache: number | undefined;
308
private readonly visibleViewsStorageId: string;
309
protected readonly viewContainerModel: IViewContainerModel;
310
311
private readonly _onTitleAreaUpdate: Emitter<void> = this._register(new Emitter<void>());
312
readonly onTitleAreaUpdate: Event<void> = this._onTitleAreaUpdate.event;
313
314
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
315
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
316
317
private readonly _onDidAddViews = this._register(new Emitter<IView[]>());
318
readonly onDidAddViews = this._onDidAddViews.event;
319
320
private readonly _onDidRemoveViews = this._register(new Emitter<IView[]>());
321
readonly onDidRemoveViews = this._onDidRemoveViews.event;
322
323
private readonly _onDidChangeViewVisibility = this._register(new Emitter<IView>());
324
readonly onDidChangeViewVisibility = this._onDidChangeViewVisibility.event;
325
326
private readonly _onDidFocusView = this._register(new Emitter<IView>());
327
readonly onDidFocusView = this._onDidFocusView.event;
328
329
private readonly _onDidBlurView = this._register(new Emitter<IView>());
330
readonly onDidBlurView = this._onDidBlurView.event;
331
332
get onDidSashChange(): Event<number> {
333
return assertReturnsDefined(this.paneview).onDidSashChange;
334
}
335
336
get panes(): ViewPane[] {
337
return this.paneItems.map(i => i.pane);
338
}
339
340
get views(): IView[] {
341
return this.panes;
342
}
343
344
get length(): number {
345
return this.paneItems.length;
346
}
347
348
private _menuActions?: ViewContainerMenuActions;
349
get menuActions(): ViewContainerMenuActions | undefined {
350
return this._menuActions;
351
}
352
353
constructor(
354
id: string,
355
private options: IViewPaneContainerOptions,
356
@IInstantiationService protected instantiationService: IInstantiationService,
357
@IConfigurationService protected configurationService: IConfigurationService,
358
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
359
@IContextMenuService protected contextMenuService: IContextMenuService,
360
@ITelemetryService protected telemetryService: ITelemetryService,
361
@IExtensionService protected extensionService: IExtensionService,
362
@IThemeService themeService: IThemeService,
363
@IStorageService protected storageService: IStorageService,
364
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
365
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,
366
@ILogService protected readonly logService: ILogService,
367
) {
368
369
super(id, themeService, storageService);
370
371
const container = this.viewDescriptorService.getViewContainerById(id);
372
if (!container) {
373
throw new Error('Could not find container');
374
}
375
376
377
this.viewContainer = container;
378
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
379
this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined);
380
this.viewContainerModel = this.viewDescriptorService.getViewContainerModel(container);
381
}
382
383
create(parent: HTMLElement): void {
384
const options = this.options as IPaneViewOptions;
385
options.orientation = this.orientation;
386
this.paneview = this._register(new PaneView(parent, this.options));
387
388
if (this._boundarySashes) {
389
this.paneview.setBoundarySashes(this._boundarySashes);
390
}
391
392
this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane)));
393
this._register(this.paneview.onDidScroll(_ => this.onDidScrollPane()));
394
this._register(this.paneview.onDidSashReset((index) => this.onDidSashReset(index)));
395
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(getWindow(parent), e))));
396
this._register(Gesture.addTarget(parent));
397
this._register(addDisposableListener(parent, TouchEventType.Contextmenu, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(getWindow(parent), e))));
398
399
this._menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.paneview.element, this.viewContainer));
400
this._register(this._menuActions.onDidChange(() => this.updateTitleArea()));
401
402
let overlay: ViewPaneDropOverlay | undefined;
403
const getOverlayBounds: () => BoundingRect = () => {
404
const fullSize = parent.getBoundingClientRect();
405
const lastPane = this.panes[this.panes.length - 1].element.getBoundingClientRect();
406
const top = this.orientation === Orientation.VERTICAL ? lastPane.bottom : fullSize.top;
407
const left = this.orientation === Orientation.HORIZONTAL ? lastPane.right : fullSize.left;
408
409
return {
410
top,
411
bottom: fullSize.bottom,
412
left,
413
right: fullSize.right,
414
};
415
};
416
417
const inBounds = (bounds: BoundingRect, pos: { x: number; y: number }) => {
418
return pos.x >= bounds.left && pos.x <= bounds.right && pos.y >= bounds.top && pos.y <= bounds.bottom;
419
};
420
421
422
let bounds: BoundingRect;
423
424
if (this.viewDescriptorService.canMoveViews()) {
425
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {
426
onDragEnter: (e) => {
427
bounds = getOverlayBounds();
428
if (overlay?.disposed) {
429
overlay = undefined;
430
}
431
432
if (!overlay && inBounds(bounds, e.eventData)) {
433
const dropData = e.dragAndDropData.getData();
434
if (dropData.type === 'view') {
435
436
const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id);
437
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id);
438
439
if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) {
440
return;
441
}
442
443
overlay = new ViewPaneDropOverlay(parent, undefined, bounds, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
444
}
445
446
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
447
const container = this.viewDescriptorService.getViewContainerById(dropData.id)!;
448
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
449
450
if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) {
451
overlay = new ViewPaneDropOverlay(parent, undefined, bounds, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
452
}
453
}
454
}
455
},
456
onDragOver: (e) => {
457
if (overlay?.disposed) {
458
overlay = undefined;
459
}
460
461
if (overlay && !inBounds(bounds, e.eventData)) {
462
overlay.dispose();
463
overlay = undefined;
464
}
465
466
if (inBounds(bounds, e.eventData)) {
467
toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined);
468
}
469
},
470
onDragLeave: (e) => {
471
overlay?.dispose();
472
overlay = undefined;
473
},
474
onDrop: (e) => {
475
if (overlay) {
476
const dropData = e.dragAndDropData.getData();
477
const viewsToMove: IViewDescriptor[] = [];
478
479
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
480
const container = this.viewDescriptorService.getViewContainerById(dropData.id)!;
481
const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
482
if (!allViews.some(v => !v.canMoveView)) {
483
viewsToMove.push(...allViews);
484
}
485
} else if (dropData.type === 'view') {
486
const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id);
487
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id);
488
if (oldViewContainer !== this.viewContainer && viewDescriptor?.canMoveView) {
489
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer, undefined, 'dnd');
490
}
491
}
492
493
const paneCount = this.panes.length;
494
495
if (viewsToMove.length > 0) {
496
this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd');
497
}
498
499
if (paneCount > 0) {
500
for (const view of viewsToMove) {
501
const paneToMove = this.panes.find(p => p.id === view.id);
502
if (paneToMove) {
503
this.movePane(paneToMove, this.panes[this.panes.length - 1]);
504
}
505
}
506
}
507
}
508
509
overlay?.dispose();
510
overlay = undefined;
511
}
512
}));
513
}
514
515
this._register(this.onDidSashChange(() => this.saveViewSizes()));
516
this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added)));
517
this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidRemoveViewDescriptors(removed)));
518
const addedViews: IAddedViewDescriptorRef[] = this.viewContainerModel.visibleViewDescriptors.map((viewDescriptor, index) => {
519
const size = this.viewContainerModel.getSize(viewDescriptor.id);
520
const collapsed = this.viewContainerModel.isCollapsed(viewDescriptor.id);
521
return ({ viewDescriptor, index, size, collapsed });
522
});
523
if (addedViews.length) {
524
this.onDidAddViewDescriptors(addedViews);
525
}
526
527
// Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609
528
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
529
this.areExtensionsReady = true;
530
if (this.panes.length) {
531
this.updateTitleArea();
532
this.updateViewHeaders();
533
}
534
this._register(this.configurationService.onDidChangeConfiguration(e => {
535
if (e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION)) {
536
this.updateViewHeaders();
537
}
538
}));
539
});
540
541
this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => this._onTitleAreaUpdate.fire()));
542
}
543
544
getTitle(): string {
545
const containerTitle = this.viewContainerModel.title;
546
547
if (this.isViewMergedWithContainer()) {
548
const singleViewPaneContainerTitle = this.paneItems[0].pane.singleViewPaneContainerTitle;
549
if (singleViewPaneContainerTitle) {
550
return singleViewPaneContainerTitle;
551
}
552
553
const paneItemTitle = this.paneItems[0].pane.title;
554
if (containerTitle === paneItemTitle) {
555
return paneItemTitle;
556
}
557
558
return paneItemTitle ? `${containerTitle}: ${paneItemTitle}` : containerTitle;
559
}
560
561
return containerTitle;
562
}
563
564
private showContextMenu(event: StandardMouseEvent): void {
565
for (const paneItem of this.paneItems) {
566
// Do not show context menu if target is coming from inside pane views
567
if (isAncestor(event.target, paneItem.pane.element)) {
568
return;
569
}
570
}
571
572
event.stopPropagation();
573
event.preventDefault();
574
575
this.contextMenuService.showContextMenu({
576
getAnchor: () => event,
577
getActions: () => this.menuActions?.getContextMenuActions() ?? []
578
});
579
}
580
581
getActionsContext(): unknown {
582
if (this.isViewMergedWithContainer()) {
583
return this.panes[0].getActionsContext();
584
}
585
return undefined;
586
}
587
588
getActionViewItem(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined {
589
if (this.isViewMergedWithContainer()) {
590
return this.paneItems[0].pane.createActionViewItem(action, options);
591
}
592
return createActionViewItem(this.instantiationService, action, options);
593
}
594
595
focus(): void {
596
let paneToFocus: ViewPane | undefined = undefined;
597
if (this.lastFocusedPane) {
598
paneToFocus = this.lastFocusedPane;
599
} else if (this.paneItems.length > 0) {
600
for (const { pane } of this.paneItems) {
601
if (pane.isExpanded()) {
602
paneToFocus = pane;
603
break;
604
}
605
}
606
}
607
if (paneToFocus) {
608
paneToFocus.focus();
609
}
610
}
611
612
private get orientation(): Orientation {
613
switch (this.viewDescriptorService.getViewContainerLocation(this.viewContainer)) {
614
case ViewContainerLocation.Sidebar:
615
case ViewContainerLocation.AuxiliaryBar:
616
return Orientation.VERTICAL;
617
case ViewContainerLocation.Panel: {
618
return isHorizontal(this.layoutService.getPanelPosition()) ? Orientation.HORIZONTAL : Orientation.VERTICAL;
619
}
620
}
621
622
return Orientation.VERTICAL;
623
}
624
625
layout(dimension: Dimension): void {
626
if (this.paneview) {
627
if (this.paneview.orientation !== this.orientation) {
628
this.paneview.flipOrientation(dimension.height, dimension.width);
629
}
630
631
this.paneview.layout(dimension.height, dimension.width);
632
}
633
634
this.dimension = dimension;
635
if (this.didLayout) {
636
this.saveViewSizes();
637
} else {
638
this.didLayout = true;
639
this.restoreViewSizes();
640
}
641
}
642
643
setBoundarySashes(sashes: IBoundarySashes): void {
644
this._boundarySashes = sashes;
645
this.paneview?.setBoundarySashes(sashes);
646
}
647
648
getOptimalWidth(): number {
649
const additionalMargin = 16;
650
const optimalWidth = Math.max(...this.panes.map(view => view.getOptimalWidth() || 0));
651
return optimalWidth + additionalMargin;
652
}
653
654
addPanes(panes: { pane: ViewPane; size: number; index?: number; disposable: IDisposable }[]): void {
655
const wasMerged = this.isViewMergedWithContainer();
656
657
for (const { pane, size, index, disposable } of panes) {
658
this.addPane(pane, size, disposable, index);
659
}
660
661
this.updateViewHeaders();
662
if (this.isViewMergedWithContainer() !== wasMerged) {
663
this.updateTitleArea();
664
}
665
666
this._onDidAddViews.fire(panes.map(({ pane }) => pane));
667
}
668
669
setVisible(visible: boolean): void {
670
if (this.visible !== !!visible) {
671
this.visible = visible;
672
673
this._onDidChangeVisibility.fire(visible);
674
}
675
676
this.panes.filter(view => view.isVisible() !== visible)
677
.map((view) => view.setVisible(visible));
678
}
679
680
isVisible(): boolean {
681
return this.visible;
682
}
683
684
protected updateTitleArea(): void {
685
this._onTitleAreaUpdate.fire();
686
}
687
688
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane {
689
return this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.staticArguments || []), options);
690
}
691
692
getView(id: string): ViewPane | undefined {
693
return this.panes.filter(view => view.id === id)[0];
694
}
695
696
private saveViewSizes(): void {
697
// Save size only when the layout has happened
698
if (this.didLayout) {
699
this.viewContainerModel.setSizes(this.panes.map(view => ({ id: view.id, size: this.getPaneSize(view) })));
700
}
701
}
702
703
private restoreViewSizes(): void {
704
// Restore sizes only when the layout has happened
705
if (this.didLayout) {
706
let initialSizes;
707
for (let i = 0; i < this.viewContainerModel.visibleViewDescriptors.length; i++) {
708
const pane = this.panes[i];
709
const viewDescriptor = this.viewContainerModel.visibleViewDescriptors[i];
710
const size = this.viewContainerModel.getSize(viewDescriptor.id);
711
712
if (typeof size === 'number') {
713
this.resizePane(pane, size);
714
} else {
715
initialSizes = initialSizes ? initialSizes : this.computeInitialSizes();
716
this.resizePane(pane, initialSizes.get(pane.id) || 200);
717
}
718
}
719
}
720
}
721
722
private computeInitialSizes(): Map<string, number> {
723
const sizes: Map<string, number> = new Map<string, number>();
724
if (this.dimension) {
725
const totalWeight = this.viewContainerModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0);
726
for (const viewDescriptor of this.viewContainerModel.visibleViewDescriptors) {
727
if (this.orientation === Orientation.VERTICAL) {
728
sizes.set(viewDescriptor.id, this.dimension.height * (viewDescriptor.weight || 20) / totalWeight);
729
} else {
730
sizes.set(viewDescriptor.id, this.dimension.width * (viewDescriptor.weight || 20) / totalWeight);
731
}
732
}
733
}
734
return sizes;
735
}
736
737
protected override saveState(): void {
738
this.panes.forEach((view) => view.saveState());
739
this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE, StorageTarget.MACHINE);
740
}
741
742
private onContextMenu(event: StandardMouseEvent, viewPane: ViewPane): void {
743
event.stopPropagation();
744
event.preventDefault();
745
746
const actions: IAction[] = viewPane.menuActions.getContextMenuActions();
747
748
this.contextMenuService.showContextMenu({
749
getAnchor: () => event,
750
getActions: () => actions
751
});
752
}
753
754
openView(id: string, focus?: boolean): IView | undefined {
755
let view = this.getView(id);
756
if (!view) {
757
this.toggleViewVisibility(id);
758
}
759
view = this.getView(id);
760
if (view) {
761
view.setExpanded(true);
762
if (focus) {
763
view.focus();
764
}
765
}
766
return view;
767
}
768
769
protected onDidAddViewDescriptors(added: IAddedViewDescriptorRef[]): ViewPane[] {
770
const panesToAdd: { pane: ViewPane; size: number; index: number; disposable: IDisposable }[] = [];
771
772
for (const { viewDescriptor, collapsed, index, size } of added) {
773
const pane = this.createView(viewDescriptor,
774
{
775
id: viewDescriptor.id,
776
title: viewDescriptor.name.value,
777
fromExtensionId: (viewDescriptor as Partial<ICustomViewDescriptor>).extensionId,
778
expanded: !collapsed,
779
singleViewPaneContainerTitle: viewDescriptor.singleViewPaneContainerTitle,
780
});
781
782
try {
783
pane.render();
784
} catch (error) {
785
this.logService.error(`Fail to render view ${viewDescriptor.id}`, error);
786
continue;
787
}
788
if (pane.draggableElement) {
789
const contextMenuDisposable = addDisposableListener(pane.draggableElement, 'contextmenu', e => {
790
e.stopPropagation();
791
e.preventDefault();
792
this.onContextMenu(new StandardMouseEvent(getWindow(pane.draggableElement), e), pane);
793
});
794
795
const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => {
796
this.viewContainerModel.setCollapsed(viewDescriptor.id, collapsed);
797
});
798
799
panesToAdd.push({ pane, size: size || pane.minimumSize, index, disposable: combinedDisposable(contextMenuDisposable, collapseDisposable) });
800
}
801
}
802
803
this.addPanes(panesToAdd);
804
this.restoreViewSizes();
805
806
const panes: ViewPane[] = [];
807
for (const { pane } of panesToAdd) {
808
pane.setVisible(this.isVisible());
809
panes.push(pane);
810
}
811
return panes;
812
}
813
814
private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void {
815
removed = removed.sort((a, b) => b.index - a.index);
816
const panesToRemove: ViewPane[] = [];
817
for (const { index } of removed) {
818
const paneItem = this.paneItems[index];
819
if (paneItem) {
820
panesToRemove.push(this.paneItems[index].pane);
821
}
822
}
823
824
if (panesToRemove.length) {
825
this.removePanes(panesToRemove);
826
827
for (const pane of panesToRemove) {
828
pane.setVisible(false);
829
}
830
}
831
}
832
833
toggleViewVisibility(viewId: string): void {
834
// Check if view is active
835
if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) {
836
const visible = !this.viewContainerModel.isVisible(viewId);
837
this.viewContainerModel.setVisible(viewId, visible);
838
}
839
}
840
841
private addPane(pane: ViewPane, size: number, disposable: IDisposable, index = this.paneItems.length - 1): void {
842
const onDidFocus = pane.onDidFocus(() => {
843
this._onDidFocusView.fire(pane);
844
this.lastFocusedPane = pane;
845
});
846
const onDidBlur = pane.onDidBlur(() => this._onDidBlurView.fire(pane));
847
const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => {
848
if (this.isViewMergedWithContainer()) {
849
this.updateTitleArea();
850
}
851
});
852
853
const onDidChangeVisibility = pane.onDidChangeBodyVisibility(() => this._onDidChangeViewVisibility.fire(pane));
854
const onDidChange = pane.onDidChange(() => {
855
if (pane === this.lastFocusedPane && !pane.isExpanded()) {
856
this.lastFocusedPane = undefined;
857
}
858
});
859
860
const isPanel = this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel;
861
pane.style({
862
headerForeground: asCssVariable(isPanel ? PANEL_SECTION_HEADER_FOREGROUND : SIDE_BAR_SECTION_HEADER_FOREGROUND),
863
headerBackground: asCssVariable(isPanel ? PANEL_SECTION_HEADER_BACKGROUND : SIDE_BAR_SECTION_HEADER_BACKGROUND),
864
headerBorder: asCssVariable(isPanel ? PANEL_SECTION_HEADER_BORDER : SIDE_BAR_SECTION_HEADER_BORDER),
865
dropBackground: asCssVariable(isPanel ? PANEL_SECTION_DRAG_AND_DROP_BACKGROUND : SIDE_BAR_DRAG_AND_DROP_BACKGROUND),
866
leftBorder: isPanel ? asCssVariable(PANEL_SECTION_BORDER) : undefined
867
});
868
869
const store = new DisposableStore();
870
store.add(disposable);
871
store.add(combinedDisposable(pane, onDidFocus, onDidBlur, onDidChangeTitleArea, onDidChange, onDidChangeVisibility));
872
const paneItem: IViewPaneItem = { pane, disposable: store };
873
874
this.paneItems.splice(index, 0, paneItem);
875
assertReturnsDefined(this.paneview).addPane(pane, size, index);
876
877
let overlay: ViewPaneDropOverlay | undefined;
878
879
if (this.viewDescriptorService.canMoveViews()) {
880
881
if (pane.draggableElement) {
882
store.add(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, () => { return { type: 'view', id: pane.id }; }, {}));
883
}
884
885
store.add(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, {
886
onDragEnter: (e) => {
887
if (!overlay) {
888
const dropData = e.dragAndDropData.getData();
889
if (dropData.type === 'view' && dropData.id !== pane.id) {
890
891
const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id);
892
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id);
893
894
if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) {
895
return;
896
}
897
898
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
899
}
900
901
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) {
902
const container = this.viewDescriptorService.getViewContainerById(dropData.id)!;
903
const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
904
905
if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) {
906
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService);
907
}
908
}
909
}
910
},
911
onDragOver: (e) => {
912
toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined);
913
},
914
onDragLeave: (e) => {
915
overlay?.dispose();
916
overlay = undefined;
917
},
918
onDrop: (e) => {
919
if (overlay) {
920
const dropData = e.dragAndDropData.getData();
921
const viewsToMove: IViewDescriptor[] = [];
922
let anchorView: IViewDescriptor | undefined;
923
924
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) {
925
const container = this.viewDescriptorService.getViewContainerById(dropData.id)!;
926
const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors;
927
928
if (allViews.length > 0 && !allViews.some(v => !v.canMoveView)) {
929
viewsToMove.push(...allViews);
930
anchorView = allViews[0];
931
}
932
} else if (dropData.type === 'view') {
933
const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id);
934
const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id);
935
if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView && !this.viewContainer.rejectAddedViews) {
936
viewsToMove.push(viewDescriptor);
937
}
938
939
if (viewDescriptor) {
940
anchorView = viewDescriptor;
941
}
942
}
943
944
if (viewsToMove) {
945
this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd');
946
}
947
948
if (anchorView) {
949
if (overlay.currentDropOperation === DropDirection.DOWN ||
950
overlay.currentDropOperation === DropDirection.RIGHT) {
951
952
const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id);
953
let toIndex = this.panes.findIndex(p => p.id === pane.id);
954
955
if (fromIndex >= 0 && toIndex >= 0) {
956
if (fromIndex > toIndex) {
957
toIndex++;
958
}
959
960
if (toIndex < this.panes.length && toIndex !== fromIndex) {
961
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
962
}
963
}
964
}
965
966
if (overlay.currentDropOperation === DropDirection.UP ||
967
overlay.currentDropOperation === DropDirection.LEFT) {
968
const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id);
969
let toIndex = this.panes.findIndex(p => p.id === pane.id);
970
971
if (fromIndex >= 0 && toIndex >= 0) {
972
if (fromIndex < toIndex) {
973
toIndex--;
974
}
975
976
if (toIndex >= 0 && toIndex !== fromIndex) {
977
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
978
}
979
}
980
}
981
982
if (viewsToMove.length > 1) {
983
viewsToMove.slice(1).forEach(view => {
984
let toIndex = this.panes.findIndex(p => p.id === anchorView!.id);
985
const fromIndex = this.panes.findIndex(p => p.id === view.id);
986
if (fromIndex >= 0 && toIndex >= 0) {
987
if (fromIndex > toIndex) {
988
toIndex++;
989
}
990
991
if (toIndex < this.panes.length && toIndex !== fromIndex) {
992
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
993
anchorView = view;
994
}
995
}
996
});
997
}
998
}
999
}
1000
1001
overlay?.dispose();
1002
overlay = undefined;
1003
}
1004
}));
1005
}
1006
}
1007
1008
removePanes(panes: ViewPane[]): void {
1009
const wasMerged = this.isViewMergedWithContainer();
1010
1011
panes.forEach(pane => this.removePane(pane));
1012
1013
this.updateViewHeaders();
1014
if (wasMerged !== this.isViewMergedWithContainer()) {
1015
this.updateTitleArea();
1016
}
1017
1018
this._onDidRemoveViews.fire(panes);
1019
}
1020
1021
private removePane(pane: ViewPane): void {
1022
const index = this.paneItems.findIndex(i => i.pane === pane);
1023
1024
if (index === -1) {
1025
return;
1026
}
1027
1028
if (this.lastFocusedPane === pane) {
1029
this.lastFocusedPane = undefined;
1030
}
1031
1032
assertReturnsDefined(this.paneview).removePane(pane);
1033
const [paneItem] = this.paneItems.splice(index, 1);
1034
paneItem.disposable.dispose();
1035
1036
}
1037
1038
movePane(from: ViewPane, to: ViewPane): void {
1039
const fromIndex = this.paneItems.findIndex(item => item.pane === from);
1040
const toIndex = this.paneItems.findIndex(item => item.pane === to);
1041
1042
const fromViewDescriptor = this.viewContainerModel.visibleViewDescriptors[fromIndex];
1043
const toViewDescriptor = this.viewContainerModel.visibleViewDescriptors[toIndex];
1044
1045
if (fromIndex < 0 || fromIndex >= this.paneItems.length) {
1046
return;
1047
}
1048
1049
if (toIndex < 0 || toIndex >= this.paneItems.length) {
1050
return;
1051
}
1052
1053
const [paneItem] = this.paneItems.splice(fromIndex, 1);
1054
this.paneItems.splice(toIndex, 0, paneItem);
1055
1056
assertReturnsDefined(this.paneview).movePane(from, to);
1057
1058
this.viewContainerModel.move(fromViewDescriptor.id, toViewDescriptor.id);
1059
1060
this.updateTitleArea();
1061
}
1062
1063
resizePane(pane: ViewPane, size: number): void {
1064
assertReturnsDefined(this.paneview).resizePane(pane, size);
1065
}
1066
1067
getPaneSize(pane: ViewPane): number {
1068
return assertReturnsDefined(this.paneview).getPaneSize(pane);
1069
}
1070
1071
private updateViewHeaders(): void {
1072
if (this.isViewMergedWithContainer()) {
1073
if (this.paneItems[0].pane.isExpanded()) {
1074
this.lastMergedCollapsedPane = undefined;
1075
} else {
1076
this.lastMergedCollapsedPane = this.paneItems[0].pane;
1077
this.paneItems[0].pane.setExpanded(true);
1078
}
1079
this.paneItems[0].pane.headerVisible = false;
1080
this.paneItems[0].pane.collapsible = true;
1081
} else {
1082
if (this.paneItems.length === 1) {
1083
this.paneItems[0].pane.headerVisible = true;
1084
if (this.paneItems[0].pane === this.lastMergedCollapsedPane) {
1085
this.paneItems[0].pane.setExpanded(false);
1086
}
1087
this.paneItems[0].pane.collapsible = false;
1088
} else {
1089
this.paneItems.forEach(i => {
1090
i.pane.headerVisible = true;
1091
i.pane.collapsible = true;
1092
if (i.pane === this.lastMergedCollapsedPane) {
1093
i.pane.setExpanded(false);
1094
}
1095
});
1096
}
1097
this.lastMergedCollapsedPane = undefined;
1098
}
1099
}
1100
1101
isViewMergedWithContainer(): boolean {
1102
if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) {
1103
return false;
1104
}
1105
if (!this.areExtensionsReady) {
1106
if (this.visibleViewsCountFromCache === undefined) {
1107
return this.paneItems[0].pane.isExpanded();
1108
}
1109
// Check in cache so that view do not jump. See #29609
1110
return this.visibleViewsCountFromCache === 1;
1111
}
1112
return true;
1113
}
1114
1115
private onDidScrollPane() {
1116
for (const pane of this.panes) {
1117
pane.onDidScrollRoot();
1118
}
1119
}
1120
1121
private onDidSashReset(index: number) {
1122
let firstPane = undefined;
1123
let secondPane = undefined;
1124
1125
// Deal with collapsed views: to be clever, we split the space taken by the nearest uncollapsed views
1126
for (let i = index; i >= 0; i--) {
1127
if (this.paneItems[i].pane?.isVisible() && this.paneItems[i]?.pane.isExpanded()) {
1128
firstPane = this.paneItems[i].pane;
1129
break;
1130
}
1131
}
1132
1133
for (let i = index + 1; i < this.paneItems.length; i++) {
1134
if (this.paneItems[i].pane?.isVisible() && this.paneItems[i]?.pane.isExpanded()) {
1135
secondPane = this.paneItems[i].pane;
1136
break;
1137
}
1138
}
1139
1140
if (firstPane && secondPane) {
1141
const firstPaneSize = this.getPaneSize(firstPane);
1142
const secondPaneSize = this.getPaneSize(secondPane);
1143
1144
// Avoid rounding errors and be consistent when resizing
1145
// The first pane always get half rounded up and the second is half rounded down
1146
const newFirstPaneSize = Math.ceil((firstPaneSize + secondPaneSize) / 2);
1147
const newSecondPaneSize = Math.floor((firstPaneSize + secondPaneSize) / 2);
1148
1149
// Shrink the larger pane first, then grow the smaller pane
1150
// This prevents interfering with other view sizes
1151
if (firstPaneSize > secondPaneSize) {
1152
this.resizePane(firstPane, newFirstPaneSize);
1153
this.resizePane(secondPane, newSecondPaneSize);
1154
} else {
1155
this.resizePane(secondPane, newSecondPaneSize);
1156
this.resizePane(firstPane, newFirstPaneSize);
1157
}
1158
}
1159
}
1160
1161
override dispose(): void {
1162
super.dispose();
1163
this.paneItems.forEach(i => i.disposable.dispose());
1164
if (this.paneview) {
1165
this.paneview.dispose();
1166
}
1167
}
1168
}
1169
1170
export abstract class ViewPaneContainerAction<T extends IViewPaneContainer> extends Action2 {
1171
override readonly desc: Readonly<IAction2Options> & { viewPaneContainerId: string };
1172
constructor(desc: Readonly<IAction2Options> & { viewPaneContainerId: string }) {
1173
super(desc);
1174
this.desc = desc;
1175
}
1176
1177
run(accessor: ServicesAccessor, ...args: unknown[]): unknown {
1178
const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(this.desc.viewPaneContainerId);
1179
if (viewPaneContainer) {
1180
return this.runInViewPaneContainer(accessor, <T>viewPaneContainer, ...args);
1181
}
1182
return undefined;
1183
}
1184
1185
abstract runInViewPaneContainer(accessor: ServicesAccessor, viewPaneContainer: T, ...args: unknown[]): unknown;
1186
}
1187
1188
class MoveViewPosition extends Action2 {
1189
constructor(desc: Readonly<IAction2Options>, private readonly offset: number) {
1190
super(desc);
1191
}
1192
1193
async run(accessor: ServicesAccessor): Promise<void> {
1194
const viewDescriptorService = accessor.get(IViewDescriptorService);
1195
const contextKeyService = accessor.get(IContextKeyService);
1196
1197
const viewId = FocusedViewContext.getValue(contextKeyService);
1198
if (viewId === undefined) {
1199
return;
1200
}
1201
1202
const viewContainer = viewDescriptorService.getViewContainerByViewId(viewId)!;
1203
const model = viewDescriptorService.getViewContainerModel(viewContainer);
1204
1205
const viewDescriptor = model.visibleViewDescriptors.find(vd => vd.id === viewId)!;
1206
const currentIndex = model.visibleViewDescriptors.indexOf(viewDescriptor);
1207
if (currentIndex + this.offset < 0 || currentIndex + this.offset >= model.visibleViewDescriptors.length) {
1208
return;
1209
}
1210
1211
const newPosition = model.visibleViewDescriptors[currentIndex + this.offset];
1212
1213
model.move(viewDescriptor.id, newPosition.id);
1214
}
1215
}
1216
1217
registerAction2(
1218
class MoveViewUp extends MoveViewPosition {
1219
constructor() {
1220
super({
1221
id: 'views.moveViewUp',
1222
title: nls.localize('viewMoveUp', "Move View Up"),
1223
keybinding: {
1224
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KeyK, KeyCode.UpArrow),
1225
weight: KeybindingWeight.WorkbenchContrib + 1,
1226
when: FocusedViewContext.notEqualsTo('')
1227
}
1228
}, -1);
1229
}
1230
}
1231
);
1232
1233
registerAction2(
1234
class MoveViewLeft extends MoveViewPosition {
1235
constructor() {
1236
super({
1237
id: 'views.moveViewLeft',
1238
title: nls.localize('viewMoveLeft', "Move View Left"),
1239
keybinding: {
1240
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KeyK, KeyCode.LeftArrow),
1241
weight: KeybindingWeight.WorkbenchContrib + 1,
1242
when: FocusedViewContext.notEqualsTo('')
1243
}
1244
}, -1);
1245
}
1246
}
1247
);
1248
1249
registerAction2(
1250
class MoveViewDown extends MoveViewPosition {
1251
constructor() {
1252
super({
1253
id: 'views.moveViewDown',
1254
title: nls.localize('viewMoveDown', "Move View Down"),
1255
keybinding: {
1256
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KeyK, KeyCode.DownArrow),
1257
weight: KeybindingWeight.WorkbenchContrib + 1,
1258
when: FocusedViewContext.notEqualsTo('')
1259
}
1260
}, 1);
1261
}
1262
}
1263
);
1264
1265
registerAction2(
1266
class MoveViewRight extends MoveViewPosition {
1267
constructor() {
1268
super({
1269
id: 'views.moveViewRight',
1270
title: nls.localize('viewMoveRight', "Move View Right"),
1271
keybinding: {
1272
primary: KeyChord(KeyMod.CtrlCmd + KeyCode.KeyK, KeyCode.RightArrow),
1273
weight: KeybindingWeight.WorkbenchContrib + 1,
1274
when: FocusedViewContext.notEqualsTo('')
1275
}
1276
}, 1);
1277
}
1278
}
1279
);
1280
1281
1282
registerAction2(class MoveViews extends Action2 {
1283
constructor() {
1284
super({
1285
id: 'vscode.moveViews',
1286
title: nls.localize('viewsMove', "Move Views"),
1287
});
1288
}
1289
1290
async run(accessor: ServicesAccessor, options: { viewIds: string[]; destinationId: string }): Promise<void> {
1291
if (!Array.isArray(options?.viewIds) || typeof options?.destinationId !== 'string') {
1292
return Promise.reject('Invalid arguments');
1293
}
1294
1295
const viewDescriptorService = accessor.get(IViewDescriptorService);
1296
1297
const destination = viewDescriptorService.getViewContainerById(options.destinationId);
1298
if (!destination) {
1299
return;
1300
}
1301
1302
// FYI, don't use `moveViewsToContainer` in 1 shot, because it expects all views to have the same current location
1303
for (const viewId of options.viewIds) {
1304
const viewDescriptor = viewDescriptorService.getViewDescriptorById(viewId);
1305
if (viewDescriptor?.canMoveView) {
1306
viewDescriptorService.moveViewsToContainer([viewDescriptor], destination, ViewVisibilityState.Default, this.desc.id);
1307
}
1308
}
1309
1310
await accessor.get(IViewsService).openViewContainer(destination.id, true);
1311
}
1312
});
1313
1314