diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index a5f2338647d44..e9353c86faab8 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -26,6 +26,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { isDark } from 'vs/platform/theme/common/theme'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { assertType } from 'vs/base/common/types'; export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): IDisposable { const groups = menu.getActions(options); @@ -129,6 +130,23 @@ export interface IMenuEntryActionViewItemOptions { hoverDelegate?: IHoverDelegate; } +function registerConfigureMenu(contextMenuService: IContextMenuService, item: BaseActionViewItem, action: MenuItemAction | SubmenuItemAction): IDisposable { + assertType(item.element); + return addDisposableListener(item.element, 'contextmenu', event => { + if (!action.hideActions) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + + contextMenuService.showContextMenu({ + getAnchor: () => item.element!, + getActions: () => action.hideActions!.asList() + }); + }, true); +} + export class MenuEntryActionViewItem extends ActionViewItem { private _wantsAltCommand: boolean = false; @@ -204,20 +222,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { updateAltState(); })); - - this._register(addDisposableListener(container, 'contextmenu', event => { - if (!this._menuItemAction.hideActions) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - - this._contextMenuService.showContextMenu({ - getAnchor: () => container, - getActions: () => this._menuItemAction.hideActions!.asList() - }); - }, true)); + this._register(registerConfigureMenu(this._contextMenuService, this, this._menuItemAction)); } override updateLabel(): void { @@ -308,7 +313,7 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { constructor( action: SubmenuItemAction, options: IDropdownMenuActionViewItemOptions | undefined, - @IContextMenuService contextMenuService: IContextMenuService, + @IContextMenuService protected _contextMenuService: IContextMenuService, @IThemeService protected _themeService: IThemeService ) { const dropdownOptions = Object.assign({}, options ?? Object.create(null), { @@ -316,32 +321,35 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem { classNames: options?.classNames ?? (ThemeIcon.isThemeIcon(action.item.icon) ? ThemeIcon.asClassName(action.item.icon) : undefined), }); - super(action, { getActions: () => action.actions }, contextMenuService, dropdownOptions); + super(action, { getActions: () => action.actions }, _contextMenuService, dropdownOptions); } override render(container: HTMLElement): void { super.render(container); - if (this.element) { - container.classList.add('menu-entry'); - const { icon } = (this._action).item; - if (icon && !ThemeIcon.isThemeIcon(icon)) { - this.element.classList.add('icon'); - const setBackgroundImage = () => { - if (this.element) { - this.element.style.backgroundImage = ( - isDark(this._themeService.getColorTheme().type) - ? asCSSUrl(icon.dark) - : asCSSUrl(icon.light) - ); - } - }; + assertType(this.element); + + container.classList.add('menu-entry'); + const action = this._action; + const { icon } = action.item; + if (icon && !ThemeIcon.isThemeIcon(icon)) { + this.element.classList.add('icon'); + const setBackgroundImage = () => { + if (this.element) { + this.element.style.backgroundImage = ( + isDark(this._themeService.getColorTheme().type) + ? asCSSUrl(icon.dark) + : asCSSUrl(icon.light) + ); + } + }; + setBackgroundImage(); + this._register(this._themeService.onDidColorThemeChange(() => { + // refresh when the theme changes in case we go between dark <-> light setBackgroundImage(); - this._register(this._themeService.onDidColorThemeChange(() => { - // refresh when the theme changes in case we go between dark <-> light - setBackgroundImage(); - })); - } + })); } + + this._register(registerConfigureMenu(this._contextMenuService, this, action)); } } @@ -461,6 +469,8 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem { event.stopPropagation(); } })); + + this._register(registerConfigureMenu(this._contextMenuService, this, (this.action))); } override focus(fromRight?: boolean): void { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 461df3221c1a3..79fbc7424c325 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -35,11 +35,11 @@ export interface ISubmenuItem { rememberDefaultAction?: boolean; // for dropdown menu: if true the last executed action is remembered as the default action } -export function isIMenuItem(item: IMenuItem | ISubmenuItem): item is IMenuItem { +export function isIMenuItem(item: any): item is IMenuItem { return (item as IMenuItem).command !== undefined; } -export function isISubmenuItem(item: IMenuItem | ISubmenuItem): item is ISubmenuItem { +export function isISubmenuItem(item: any): item is ISubmenuItem { return (item as ISubmenuItem).submenu !== undefined; } @@ -350,6 +350,7 @@ export class SubmenuItemAction extends SubmenuAction { constructor( readonly item: ISubmenuItem, + readonly hideActions: MenuItemActionManageActions, private readonly _menuService: IMenuService, private readonly _contextKeyService: IContextKeyService, private readonly _options?: IMenuActionOptions diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index 5d2092cc1c5bc..fce79d84ad9aa 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -6,7 +6,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuItemActionManageActions, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuService, isIMenuItem, isISubmenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuItemActionManageActions, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandAction, ILocalizedString } from 'vs/platform/action/common/action'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -251,18 +251,17 @@ class Menu implements IMenu { for (const item of items) { if (this._contextKeyService.contextMatchesRules(item.when)) { let action: MenuItemAction | SubmenuItemAction | undefined; - if (isIMenuItem(item)) { + const isMenuItem = isIMenuItem(item); + const hideActions = new MenuItemActionManageActions(new HideMenuItemAction(this._id, isMenuItem ? item.command : item, this._hiddenStates), allToggleActions); + + if (isMenuItem) { if (!this._hiddenStates.isHidden(this._id, item.command.id)) { - action = new MenuItemAction( - item.command, item.alt, options, - new MenuItemActionManageActions(new HideMenuItemAction(this._id, item.command, this._hiddenStates), allToggleActions), - this._contextKeyService, this._commandService - ); + action = new MenuItemAction(item.command, item.alt, options, hideActions, this._contextKeyService, this._commandService); } // add toggle commmand toggleActions.push(new ToggleMenuItemAction(this._id, item.command, this._hiddenStates)); } else { - action = new SubmenuItemAction(item, this._menuService, this._contextKeyService, options); + action = new SubmenuItemAction(item, hideActions, this._menuService, this._contextKeyService, options); if (action.actions.length === 0) { action.dispose(); action = undefined; @@ -397,10 +396,11 @@ class HideMenuItemAction implements IAction { run: () => void; - constructor(id: MenuId, command: ICommandAction, hiddenStates: PersistedMenuHideState) { - this.id = `hide/${id.id}/${command.id}`; + constructor(menu: MenuId, command: ICommandAction | ISubmenuItem, hiddenStates: PersistedMenuHideState) { + const id = isISubmenuItem(command) ? command.submenu.id : command.id; + this.id = `hide/${menu.id}/${id}`; this.label = localize('hide.label', 'Hide \'{0}\'', typeof command.title === 'string' ? command.title : command.title.value); - this.run = () => { hiddenStates.updateHidden(id, command.id, true); }; + this.run = () => { hiddenStates.updateHidden(menu, id, true); }; } dispose(): void {