Skip to content

Commit 1a5ffab

Browse files
authored
Merge pull request microsoft#88823 from sbatten/movableViews
Add movable property to views and add toggle view commands
2 parents dd3b65f + afe0caf commit 1a5ffab

21 files changed

Lines changed: 790 additions & 618 deletions

File tree

src/vs/platform/actions/common/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export const enum MenuId {
104104
TunnelTitle,
105105
ViewItemContext,
106106
ViewTitle,
107+
ViewTitleContext,
107108
CommentThreadTitle,
108109
CommentThreadActions,
109110
CommentTitle,

src/vs/workbench/api/browser/viewsExtensionPoint.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections';
88
import { IJSONSchema } from 'vs/base/common/jsonSchema';
99
import * as resources from 'vs/base/common/resources';
1010
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
11-
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views';
11+
import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
1212
import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView';
1313
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1414
import { coalesce, } from 'vs/base/common/arrays';
@@ -325,8 +325,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
325325
@IThemeService themeService: IThemeService,
326326
@IContextMenuService contextMenuService: IContextMenuService,
327327
@IExtensionService extensionService: IExtensionService,
328+
@IViewDescriptorService viewDescriptorService: IViewDescriptorService
328329
) {
329-
super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
330+
super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
330331
}
331332
}
332333

src/vs/workbench/browser/parts/activitybar/activitybarPart.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
125125
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
126126
return actions;
127127
},
128+
getContextMenuActionsForComposite: () => [],
128129
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
129130
hidePart: () => this.layoutService.setSideBarHidden(true),
130131
compositeSize: 50,

src/vs/workbench/browser/parts/compositeBar.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface ICompositeBarOptions {
4040
getCompositePinnedAction: (compositeId: string) => Action;
4141
getOnCompositeClickAction: (compositeId: string) => Action;
4242
getContextMenuActions: () => Action[];
43+
getContextMenuActionsForComposite: (compositeId: string) => Action[];
4344
openComposite: (compositeId: string) => Promise<any>;
4445
getDefaultCompositeId: () => string;
4546
hidePart: () => void;
@@ -100,7 +101,14 @@ export class CompositeBar extends Widget implements ICompositeBar {
100101
return this.compositeOverflowActionViewItem;
101102
}
102103
const item = this.model.findItem(action.id);
103-
return item && this.instantiationService.createInstance(CompositeActionViewItem, action as ActivityAction, item.pinnedAction, () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, this);
104+
return item && this.instantiationService.createInstance(
105+
CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
106+
(compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId),
107+
() => this.getContextMenuActions() as Action[],
108+
this.options.colors,
109+
this.options.icon,
110+
this
111+
);
104112
},
105113
orientation: this.options.orientation,
106114
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),

src/vs/workbench/browser/parts/compositeBarActions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
463463
constructor(
464464
private compositeActivityAction: ActivityAction,
465465
private toggleCompositePinnedAction: Action,
466+
private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray<Action>,
466467
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
467468
colors: (theme: ITheme) => ICompositeBarColors,
468469
icon: boolean,
@@ -596,6 +597,12 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
596597

597598
private showContextMenu(container: HTMLElement): void {
598599
const actions: Action[] = [this.toggleCompositePinnedAction];
600+
601+
const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id);
602+
if (compositeContextMenuActions.length) {
603+
actions.push(...compositeContextMenuActions);
604+
}
605+
599606
if ((<any>this.compositeActivityAction.activity).extensionId) {
600607
actions.push(new Separator());
601608
actions.push(CompositeActionViewItem.manageExtensionAction);

src/vs/workbench/browser/parts/panel/panelPart.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
3434
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
3535
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
3636
import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views';
37+
import { MenuId } from 'vs/platform/actions/common/actions';
38+
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
3739

3840
interface ICachedPanel {
3941
id: string;
@@ -137,6 +139,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
137139
.map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)),
138140
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
139141
] as Action[],
142+
getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[],
140143
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
141144
hidePart: () => this.layoutService.setPanelHidden(true),
142145
compositeSize: 0,
@@ -160,6 +163,20 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
160163
this.onDidRegisterPanels([...this.getPanels()]);
161164
}
162165

166+
private getContextMenuActionsForComposite(compositeId: string): readonly IAction[] {
167+
const result: IAction[] = [];
168+
const container = this.getViewContainer(compositeId);
169+
if (container) {
170+
const viewDescriptors = this.viewDescriptorService.getViewDescriptors(container);
171+
if (viewDescriptors.allViewDescriptors.length === 1) {
172+
const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewDescriptors.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext);
173+
result.push(...viewMenuActions.getContextMenuActions());
174+
viewMenuActions.dispose();
175+
}
176+
}
177+
return result;
178+
}
179+
163180
private onDidRegisterPanels(panels: PanelDescriptor[]): void {
164181
for (const panel of panels) {
165182
const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0];
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 { IAction } from 'vs/base/common/actions';
7+
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
8+
import { Emitter, Event } from 'vs/base/common/event';
9+
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
10+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
11+
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
12+
13+
export class ViewMenuActions extends Disposable {
14+
15+
private primaryActions: IAction[] = [];
16+
private readonly titleActionsDisposable = this._register(new MutableDisposable());
17+
private secondaryActions: IAction[] = [];
18+
private contextMenuActions: IAction[] = [];
19+
20+
private _onDidChangeTitle = this._register(new Emitter<void>());
21+
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
22+
23+
constructor(
24+
viewId: string,
25+
menuId: MenuId,
26+
contextMenuId: MenuId,
27+
@IContextKeyService private readonly contextKeyService: IContextKeyService,
28+
@IMenuService private readonly menuService: IMenuService,
29+
) {
30+
super();
31+
32+
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
33+
scopedContextKeyService.createKey('view', viewId);
34+
35+
const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService));
36+
const updateActions = () => {
37+
this.primaryActions = [];
38+
this.secondaryActions = [];
39+
this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions });
40+
this._onDidChangeTitle.fire();
41+
};
42+
this._register(menu.onDidChange(updateActions));
43+
updateActions();
44+
45+
const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService));
46+
const updateContextMenuActions = () => {
47+
this.contextMenuActions = [];
48+
this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, undefined, { primary: [], secondary: this.contextMenuActions });
49+
};
50+
this._register(contextMenu.onDidChange(updateContextMenuActions));
51+
updateContextMenuActions();
52+
53+
this._register(toDisposable(() => {
54+
this.primaryActions = [];
55+
this.secondaryActions = [];
56+
this.contextMenuActions = [];
57+
}));
58+
}
59+
60+
getPrimaryActions(): IAction[] {
61+
return this.primaryActions;
62+
}
63+
64+
getSecondaryActions(): IAction[] {
65+
return this.secondaryActions;
66+
}
67+
68+
getContextMenuActions(): IAction[] {
69+
return this.contextMenuActions;
70+
}
71+
}

src/vs/workbench/browser/parts/views/viewPaneContainer.ts

Lines changed: 25 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
1010
import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler';
1111
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER } from 'vs/workbench/common/theme';
1212
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom';
13-
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
13+
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
1414
import { firstIndex } from 'vs/base/common/arrays';
1515
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
1616
import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle
2525
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2626
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
2727
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
28-
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer } from 'vs/workbench/common/views';
28+
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views';
2929
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
3030
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
3131
import { assertIsDefined } from 'vs/base/common/types';
@@ -36,8 +36,9 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
3636
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
3737
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
3838
import { Component } from 'vs/workbench/common/component';
39-
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
40-
import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
39+
import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
40+
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
41+
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
4142

4243
export interface IPaneColors extends IColorMapping {
4344
dropBackground?: ColorIdentifier;
@@ -76,7 +77,7 @@ export abstract class ViewPane extends Pane implements IView {
7677
readonly id: string;
7778
title: string;
7879

79-
private readonly menuActions?: ViewMenuActions;
80+
private readonly menuActions: ViewMenuActions;
8081

8182
protected actionRunner?: IActionRunner;
8283
protected toolbar?: ToolBar;
@@ -101,10 +102,8 @@ export abstract class ViewPane extends Pane implements IView {
101102
this.showActionsAlways = !!options.showActionsAlways;
102103
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
103104

104-
if (options.titleMenuId !== undefined) {
105-
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId));
106-
this._register(this.menuActions.onDidChangeTitle(() => this.updateActions()));
107-
}
105+
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
106+
this._register(this.menuActions.onDidChangeTitle(() => this.updateActions()));
108107
}
109108

110109
setVisible(visible: boolean): void {
@@ -225,6 +224,10 @@ export abstract class ViewPane extends Pane implements IView {
225224
return this.menuActions ? this.menuActions.getSecondaryActions() : [];
226225
}
227226

227+
getContextMenuActions(): IAction[] {
228+
return this.menuActions ? this.menuActions.getContextMenuActions() : [];
229+
}
230+
228231
getActionViewItem(action: IAction): IActionViewItem | undefined {
229232
if (action instanceof MenuItemAction) {
230233
return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action);
@@ -307,7 +310,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
307310
@IExtensionService protected extensionService: IExtensionService,
308311
@IThemeService protected themeService: IThemeService,
309312
@IStorageService protected storageService: IStorageService,
310-
@IWorkspaceContextService protected contextService: IWorkspaceContextService
313+
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
314+
@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService
311315
) {
312316

313317
super(id, themeService, storageService);
@@ -390,13 +394,22 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
390394

391395
getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] {
392396
const result: IAction[] = [];
397+
398+
if (!viewDescriptor && this.isViewMergedWithContainer()) {
399+
viewDescriptor = this.viewDescriptorService.getViewDescriptor(this.panes[0].id) || undefined;
400+
}
401+
393402
if (viewDescriptor) {
394403
result.push(<IAction>{
395404
id: `${viewDescriptor.id}.removeView`,
396405
label: nls.localize('hideView', "Hide"),
397406
enabled: viewDescriptor.canToggleVisibility,
398-
run: () => this.toggleViewVisibility(viewDescriptor.id)
407+
run: () => this.toggleViewVisibility(viewDescriptor!.id)
399408
});
409+
const view = this.getView(viewDescriptor.id);
410+
if (view) {
411+
result.push(...view.getContextMenuActions());
412+
}
400413
}
401414

402415
const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => (<IAction>{
@@ -412,6 +425,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
412425
}
413426

414427
result.push(...viewToggleActions);
428+
415429
return result;
416430
}
417431

@@ -653,7 +667,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
653667
this.viewsModel.setVisible(viewId, visible);
654668
}
655669

656-
657670
private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void {
658671
const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane);
659672
const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => {
@@ -771,50 +784,3 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
771784
}
772785
}
773786
}
774-
775-
776-
class ViewMenuActions extends Disposable {
777-
778-
private primaryActions: IAction[] = [];
779-
private readonly titleActionsDisposable = this._register(new MutableDisposable());
780-
private secondaryActions: IAction[] = [];
781-
782-
private _onDidChangeTitle = this._register(new Emitter<void>());
783-
readonly onDidChangeTitle: Event<void> = this._onDidChangeTitle.event;
784-
785-
constructor(
786-
viewId: string,
787-
menuId: MenuId,
788-
@IContextKeyService private readonly contextKeyService: IContextKeyService,
789-
@IMenuService private readonly menuService: IMenuService,
790-
) {
791-
super();
792-
793-
const scopedContextKeyService = this._register(this.contextKeyService.createScoped());
794-
scopedContextKeyService.createKey('view', viewId);
795-
796-
const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService));
797-
const updateActions = () => {
798-
this.primaryActions = [];
799-
this.secondaryActions = [];
800-
this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions });
801-
this._onDidChangeTitle.fire();
802-
};
803-
804-
this._register(menu.onDidChange(updateActions));
805-
updateActions();
806-
807-
this._register(toDisposable(() => {
808-
this.primaryActions = [];
809-
this.secondaryActions = [];
810-
}));
811-
}
812-
813-
getPrimaryActions(): IAction[] {
814-
return this.primaryActions;
815-
}
816-
817-
getSecondaryActions(): IAction[] {
818-
return this.secondaryActions;
819-
}
820-
}

0 commit comments

Comments
 (0)