Skip to content

Commit d1de1c6

Browse files
authored
Merge pull request microsoft#154801 from microsoft/joh/compact-ostrich
Allow to hide menu items from their respective menus
2 parents 2631eaa + e209a09 commit d1de1c6

File tree

20 files changed

+289
-72
lines changed

20 files changed

+289
-72
lines changed

src/vs/base/common/arrays.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArra
4646
return true;
4747
}
4848

49+
/**
50+
* Remove the element at `index` by replacing it with the last element. This is faster than `splice`
51+
* but changes the order of the array
52+
*/
53+
export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {
54+
const last = array.length - 1;
55+
if (index < last) {
56+
array[index] = array[last];
57+
}
58+
array.pop();
59+
}
60+
4961
/**
5062
* Performs a binary search algorithm over a sorted array.
5163
*

src/vs/base/test/common/arrays.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ import * as assert from 'assert';
66
import * as arrays from 'vs/base/common/arrays';
77

88
suite('Arrays', () => {
9+
10+
test('removeFastWithoutKeepingOrder', () => {
11+
const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69];
12+
arrays.removeFastWithoutKeepingOrder(array, 1);
13+
assert.deepStrictEqual(array, [1, 69, 5, 7, 55, 59, 60, 61, 64]);
14+
15+
arrays.removeFastWithoutKeepingOrder(array, 0);
16+
assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60, 61]);
17+
18+
arrays.removeFastWithoutKeepingOrder(array, 7);
19+
assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60]);
20+
});
21+
922
test('findFirst', () => {
1023
const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69];
1124

src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
76
import * as DOM from 'vs/base/browser/dom';
87
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
98
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
@@ -18,6 +17,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1817
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1918
import { INotificationService } from 'vs/platform/notification/common/notification';
2019
import { IThemeService } from 'vs/platform/theme/common/themeService';
20+
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
2121

2222
export interface IDropdownWithPrimaryActionViewItemOptions {
2323
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
@@ -38,15 +38,15 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
3838
dropdownAction: IAction,
3939
dropdownMenuActions: IAction[],
4040
className: string,
41-
private readonly _contextMenuProvider: IContextMenuProvider,
41+
private readonly _contextMenuProvider: IContextMenuService,
4242
private readonly _options: IDropdownWithPrimaryActionViewItemOptions | undefined,
4343
@IKeybindingService _keybindingService: IKeybindingService,
4444
@INotificationService _notificationService: INotificationService,
4545
@IContextKeyService _contextKeyService: IContextKeyService,
4646
@IThemeService _themeService: IThemeService
4747
) {
4848
super(null, primaryAction);
49-
this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService);
49+
this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider);
5050
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
5151
menuAsChild: true,
5252
classNames: ['codicon', 'codicon-chevron-down'],

src/vs/platform/actions/browser/menuEntryActionViewItem.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ export class MenuEntryActionViewItem extends ActionViewItem {
141141
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
142142
@INotificationService protected _notificationService: INotificationService,
143143
@IContextKeyService protected _contextKeyService: IContextKeyService,
144-
@IThemeService protected _themeService: IThemeService
144+
@IThemeService protected _themeService: IThemeService,
145+
@IContextMenuService protected _contextMenuService: IContextMenuService
145146
) {
146147
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: options?.draggable, keybinding: options?.keybinding, hoverDelegate: options?.hoverDelegate });
147148
this._altKey = ModifierKeyEmitter.getInstance();
@@ -202,6 +203,21 @@ export class MenuEntryActionViewItem extends ActionViewItem {
202203
mouseOver = true;
203204
updateAltState();
204205
}));
206+
207+
208+
this._register(addDisposableListener(container, 'contextmenu', event => {
209+
if (!this._menuItemAction.hideActions) {
210+
return;
211+
}
212+
213+
event.preventDefault();
214+
event.stopPropagation();
215+
216+
this._contextMenuService.showContextMenu({
217+
getAnchor: () => container,
218+
getActions: () => this._menuItemAction.hideActions!.asList()
219+
});
220+
}, true));
205221
}
206222

207223
override updateLabel(): void {
@@ -356,7 +372,7 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
356372
) {
357373
super(null, submenuAction);
358374
this._options = options;
359-
this._storageKey = `${submenuAction.item.submenu._debugName}_lastActionId`;
375+
this._storageKey = `${submenuAction.item.submenu.id}_lastActionId`;
360376

361377
// determine default action
362378
let defaultAction: IAction | undefined;

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function isISubmenuItem(item: IMenuItem | ISubmenuItem): item is ISubmenu
4545

4646
export class MenuId {
4747

48-
private static _idPool = 0;
48+
private static readonly _idPool = new Set<string>();
4949

5050
static readonly CommandPalette = new MenuId('CommandPalette');
5151
static readonly DebugBreakpointsContext = new MenuId('DebugBreakpointsContext');
@@ -162,12 +162,15 @@ export class MenuId {
162162
static readonly NewFile = new MenuId('NewFile');
163163
static readonly MergeToolbar = new MenuId('MergeToolbar');
164164

165-
readonly id: number;
166-
readonly _debugName: string;
167165

168-
constructor(debugName: string) {
169-
this.id = MenuId._idPool++;
170-
this._debugName = debugName;
166+
readonly id: string;
167+
168+
constructor(identifier: string) {
169+
if (MenuId._idPool.has(identifier)) {
170+
throw new Error(`Duplicate menu identifier ${identifier}`);
171+
}
172+
MenuId._idPool.add(identifier);
173+
this.id = identifier;
171174
}
172175
}
173176

@@ -350,6 +353,22 @@ export class SubmenuItemAction extends SubmenuAction {
350353
}
351354
}
352355

356+
export class MenuItemActionManageActions {
357+
constructor(
358+
private readonly _hideThis: IAction,
359+
private readonly _toggleAny: IAction[][],
360+
) { }
361+
362+
asList(): IAction[] {
363+
let result: IAction[] = [this._hideThis];
364+
for (const n of this._toggleAny) {
365+
result.push(new Separator());
366+
result = result.concat(n);
367+
}
368+
return result;
369+
}
370+
}
371+
353372
// implements IAction, does NOT extend Action, so that no one
354373
// subscribes to events of Action or modified properties
355374
export class MenuItemAction implements IAction {
@@ -370,6 +389,7 @@ export class MenuItemAction implements IAction {
370389
item: ICommandAction,
371390
alt: ICommandAction | undefined,
372391
options: IMenuActionOptions | undefined,
392+
readonly hideActions: MenuItemActionManageActions | undefined,
373393
@IContextKeyService contextKeyService: IContextKeyService,
374394
@ICommandService private _commandService: ICommandService
375395
) {
@@ -396,7 +416,7 @@ export class MenuItemAction implements IAction {
396416
}
397417

398418
this.item = item;
399-
this.alt = alt ? new MenuItemAction(alt, undefined, options, contextKeyService, _commandService) : undefined;
419+
this.alt = alt ? new MenuItemAction(alt, undefined, options, hideActions, contextKeyService, _commandService) : undefined;
400420
this._options = options;
401421
if (ThemeIcon.isThemeIcon(item.icon)) {
402422
this.class = CSSIcon.asClassName(item.icon);

0 commit comments

Comments
 (0)