-
Notifications
You must be signed in to change notification settings - Fork 27.9k
/
toolbar.ts
257 lines (219 loc) · 8.61 KB
/
toolbar.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { addDisposableListener } from 'vs/base/browser/dom';
import { IToolBarOptions, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IAction, Separator, SubmenuAction, toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { coalesceInPlace } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuActionOptions, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export const enum HiddenItemStrategy {
Ignore = 0,
RenderInSecondaryGroup = 1
}
export type IWorkbenchToolBarOptions = IToolBarOptions & {
/**
* Items of the primary group can be hidden. When this happens the item can
* - move in to the secondary popup-menu, or
* - not be shown at all
*/
hiddenItemStrategy?: HiddenItemStrategy;
/**
* Optional menu id which items are used for the context menu of the toolbar.
*/
contextMenu?: MenuId;
/**
* Optional options how menu actions are created and invoked
*/
menuOptions?: IMenuActionOptions;
/**
* When set the `workbenchActionExecuted` is automatically send for each invoked action. The `from` property
* of the event will the passed `telemetrySource`-value
*/
telemetrySource?: string;
/** This is controlled by the WorkbenchToolBar */
allowContextMenu?: never;
};
/**
* The `WorkbenchToolBar` does
* - support hiding of menu items
* - lookup keybindings for each actions automatically
* - send `workbenchActionExecuted`-events for each action
*
* See {@link MenuWorkbenchToolBar} for a toolbar that is backed by a menu.
*/
export class WorkbenchToolBar extends ToolBar {
private readonly _sessionDisposables = this._store.add(new DisposableStore());
constructor(
container: HTMLElement,
private _options: IWorkbenchToolBarOptions | undefined,
@IMenuService private readonly _menuService: IMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(container, _contextMenuService, {
// defaults
getKeyBinding: (action) => keybindingService.lookupKeybinding(action.id) ?? undefined,
// options (override defaults)
..._options,
// mandatory (overide options)
allowContextMenu: true,
});
// telemetry logic
if (_options?.telemetrySource) {
this._store.add(this.actionBar.onDidRun(e => telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>(
'workbenchActionExecuted',
{ id: e.action.id, from: _options!.telemetrySource! })
));
}
}
override setActions(_primary: readonly IAction[], _secondary: readonly IAction[] = []): void {
this._sessionDisposables.clear();
const primary = _primary.slice();
const secondary = _secondary.slice();
const toggleActions: IAction[] = [];
// move all hidden items to secondary group or ignore them
let shouldPrependSeparator = secondary.length > 0;
for (let i = 0; i < primary.length; i++) {
const action = primary[i];
if (!(action instanceof MenuItemAction)) {
// console.warn(`Action ${action.id}/${action.label} is not a MenuItemAction`);
continue;
}
if (!action.hideActions) {
continue;
}
// collect all toggle actions
toggleActions.push(action.hideActions.toggle);
// hidden items move into overflow or ignore
if (action.hideActions.isHidden) {
primary[i] = undefined!;
if (this._options?.hiddenItemStrategy !== HiddenItemStrategy.Ignore) {
if (shouldPrependSeparator) {
shouldPrependSeparator = false;
secondary.unshift(new Separator());
}
secondary.unshift(action);
}
}
}
coalesceInPlace(primary);
super.setActions(primary, secondary);
// add context menu for toggle actions
if (toggleActions.length > 0) {
this._sessionDisposables.add(addDisposableListener(this.getElement(), 'contextmenu', e => {
const action = this.getItemAction(<HTMLElement>e.target);
if (!(action)) {
return;
}
e.preventDefault();
e.stopPropagation();
let actions = toggleActions;
// add "hide foo" actions
let hideAction: IAction;
if (action instanceof MenuItemAction && action.hideActions) {
hideAction = action.hideActions.hide;
} else {
hideAction = toAction({
id: 'label',
label: localize('hide', "Hide"),
enabled: false,
run() { }
});
}
actions = [hideAction, new Separator(), ...toggleActions];
// add context menu actions (iff appicable)
if (this._options?.contextMenu) {
const menu = this._menuService.createMenu(this._options.contextMenu, this._contextKeyService);
const contextMenuActions: IAction[] = [];
createAndFillInContextMenuActions(menu, { ...this._options?.menuOptions, renderShortTitle: true, }, contextMenuActions);
menu.dispose();
if (contextMenuActions.length > 0) {
actions = [...actions, new Separator(), ...contextMenuActions];
}
}
this._contextMenuService.showContextMenu({
getAnchor: () => e,
getActions: () => actions,
});
}));
}
}
}
// ---- MenuWorkbenchToolBar -------------------------------------------------
export interface IToolBarRenderOptions {
/**
* Determines what groups are considered primary. Defaults to `navigation`. Items of the primary
* group are rendered with buttons and the rest is rendered in the secondary popup-menu.
*/
primaryGroup?: string | ((actionGroup: string) => boolean);
/**
* Limits the number of items that make it in the primary group. The rest overflows into the
* secondary menu.
*/
primaryMaxCount?: number;
/**
* Inlinse submenus with just a single item
*/
shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean;
/**
* Should the primary group allow for separators.
*/
useSeparatorsInPrimaryActions?: boolean;
}
export interface IMenuWorkbenchToolBarOptions extends IWorkbenchToolBarOptions {
/**
* Optional options to configure how the toolbar renderes items.
*/
toolbarOptions?: IToolBarRenderOptions;
}
/**
* A {@link WorkbenchToolBar workbench toolbar} that is purely driven from a {@link MenuId menu}-identifier.
*
* *Note* that Manual updates via `setActions` are NOT supported.
*/
export class MenuWorkbenchToolBar extends WorkbenchToolBar {
constructor(
container: HTMLElement,
menuId: MenuId,
options: IMenuWorkbenchToolBarOptions | undefined,
@IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(container, options, menuService, contextKeyService, contextMenuService, keybindingService, telemetryService);
// update logic
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService));
const updateToolbar = () => {
const primary: IAction[] = [];
const secondary: IAction[] = [];
createAndFillInActionBarActions(
menu,
options?.menuOptions,
{ primary, secondary },
options?.toolbarOptions?.primaryGroup, options?.toolbarOptions?.primaryMaxCount, options?.toolbarOptions?.shouldInlineSubmenu, options?.toolbarOptions?.useSeparatorsInPrimaryActions
);
super.setActions(primary, secondary);
};
this._store.add(menu.onDidChange(updateToolbar));
updateToolbar();
}
/**
* @deprecated The WorkbenchToolBar does not support this method because it works with menus.
*/
override setActions(): void {
throw new BugIndicatingError('This toolbar is populated from a menu.');
}
}