diff --git a/packages/core/src/browser/menu/browser-menu-plugin.ts b/packages/core/src/browser/menu/browser-menu-plugin.ts index caabc8f5403d8..16d67e0ff0424 100644 --- a/packages/core/src/browser/menu/browser-menu-plugin.ts +++ b/packages/core/src/browser/menu/browser-menu-plugin.ts @@ -212,7 +212,7 @@ export class MenuServices { } export interface MenuWidgetFactory { - createMenuWidget(effectiveMenuPath: MenuPath, menu: Submenu, contextMatcher: ContextMatcher, options: BrowserMenuOptions): MenuWidget; + createMenuWidget(effectiveMenuPath: MenuPath, menu: Submenu, contextMatcher: ContextMatcher, options: BrowserMenuOptions, args?: unknown[]): MenuWidget; } /** @@ -321,11 +321,11 @@ export class DynamicMenuWidget extends MenuWidget { const result: MenuWidget.IItemOptions[] = []; for (const node of nodes) { - const nodePath = [...parentPath, node.id]; + const nodePath = node.effectiveMenuPath || [...parentPath, node.id]; if (node.isVisible(nodePath, contextMatcher, context, ...(this.args || []))) { if (CompoundMenuNode.is(node)) { if (RenderedMenuNode.is(node)) { - const submenu = this.services.menuWidgetFactory.createMenuWidget(nodePath, node, this.contextMatcher, this.options); + const submenu = this.services.menuWidgetFactory.createMenuWidget(nodePath, node, this.contextMatcher, this.options, this.args); if (submenu.items.length > 0) { result.push({ type: 'submenu', submenu }); } diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx index 7b8d7a172aa3d..b5a106f36dc90 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx @@ -23,37 +23,36 @@ import { ACTION_ITEM, codicon } from '../../widgets'; import { ContextMenuRenderer } from '../../context-menu-renderer'; import { TabBarToolbarItem } from './tab-toolbar-item'; import { ContextKeyService, ContextMatcher } from '../../context-key-service'; -import { CommandMenu, CompoundMenuNode, MenuModelRegistry, MenuNode, MenuPath, RenderedMenuNode } from '../../../common/menu'; +import { CommandMenu, CompoundMenuNode, ContextExpressionMatcher, Group, MenuModelRegistry, MenuNode, MenuPath, RenderedMenuNode, Submenu } from '../../../common/menu'; export const TOOLBAR_WRAPPER_ID_SUFFIX = '-as-tabbar-toolbar-item'; abstract class AbstractToolbarMenuWrapper { constructor( - protected readonly effectiveMenuPath: MenuPath, + readonly effectiveMenuPath: MenuPath, protected readonly commandRegistry: CommandRegistry, protected readonly menuRegistry: MenuModelRegistry, protected readonly contextKeyService: ContextKeyService, protected readonly contextMenuRenderer: ContextMenuRenderer) { } - protected abstract menuPath?: MenuPath; - protected abstract menuNode?: MenuNode; + protected abstract menuNode: MenuNode | undefined; protected abstract id: string; protected abstract icon: string | undefined; protected abstract tooltip: string | undefined; protected abstract text: string | undefined; - protected abstract executeCommand(e: React.MouseEvent): void; + protected abstract executeCommand(widget: Widget, e: React.MouseEvent): void; - isEnabled(): boolean { + isEnabled(widget: Widget): boolean { if (CommandMenu.is(this.menuNode)) { - return this.menuNode.isEnabled(this.effectiveMenuPath); + return this.menuNode.isEnabled(this.effectiveMenuPath, widget); } return true; } - isToggled(): boolean { + isToggled(widget: Widget): boolean { if (CommandMenu.is(this.menuNode) && this.menuNode.isToggled) { - return !!this.menuNode.isToggled(this.effectiveMenuPath); + return !!this.menuNode.isToggled(this.effectiveMenuPath, widget); } return false; } @@ -61,9 +60,7 @@ abstract class AbstractToolbarMenuWrapper { return this.renderMenuItem(widget); } - toMenuNode?(): MenuNode | undefined { - return this.menuNode; - } + abstract toMenuNode(): MenuNode | undefined; /** * Presents the menu to popup on the `event` that is the clicking of @@ -78,7 +75,7 @@ abstract class AbstractToolbarMenuWrapper { const anchor = toAnchor(event); this.contextMenuRenderer.render({ - menuPath: menuPath, + menuPath: this.effectiveMenuPath, menu: this.menuNode as CompoundMenuNode, args: [widget], anchor, @@ -102,9 +99,9 @@ abstract class AbstractToolbarMenuWrapper { return
this.executeCommand(e)} + onClick={e => this.executeCommand(widget, e)} /> -
this.showPopupMenu(widget, this.menuPath!, event, contextMatcher)} > +
this.showPopupMenu(widget, this.effectiveMenuPath!, event, contextMatcher)} >
; @@ -112,41 +109,67 @@ abstract class AbstractToolbarMenuWrapper { return
this.executeCommand(e)} + onClick={e => this.executeCommand(widget, e)} />
; } } } -export class ToolbarMenuNodeWrapper extends AbstractToolbarMenuWrapper implements TabBarToolbarItem { +export class SubmenuAsToolbarItemWrapper extends AbstractToolbarMenuWrapper implements TabBarToolbarItem { constructor( effectiveMenuPath: MenuPath, commandRegistry: CommandRegistry, menuRegistry: MenuModelRegistry, contextKeyService: ContextKeyService, contextMenuRenderer: ContextMenuRenderer, - protected readonly menuNode: MenuNode & RenderedMenuNode, - readonly group: string | undefined, - readonly menuPath?: MenuPath) { + protected readonly menuNode: Submenu, + readonly group: string | undefined) { super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer); } + priority?: number | undefined; - executeCommand(e: React.MouseEvent): void { - if (CommandMenu.is(this.menuNode)) { - this.menuNode.run(this.effectiveMenuPath); - } + executeCommand(widget: Widget, e: React.MouseEvent): void { } isVisible(widget: Widget): boolean { - const menuNodeVisible = this.menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node); - if (CommandMenu.is(this.menuNode)) { - return menuNodeVisible; - } else if (CompoundMenuNode.is(this.menuNode)) { - return menuNodeVisible && !MenuModelRegistry.isEmpty(this.menuNode); - } else { - return menuNodeVisible; - } + const menuNodeVisible = this.menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget); + return menuNodeVisible && !MenuModelRegistry.isEmpty(this.menuNode); + } + + get id(): string { return this.menuNode.id + TOOLBAR_WRAPPER_ID_SUFFIX; } + get icon(): string | undefined { return this.menuNode.icon; } + get tooltip(): string | undefined { return this.menuNode.label; } + get text(): string | undefined { + return (this.group === NAVIGATION || this.group === undefined) ? undefined : this.menuNode.label; + } + get onDidChange(): Event | undefined { + return this.menuNode.onDidChange; + } + + override toMenuNode(): Group | undefined { + return new ToolbarItemAsSubmenuWrapper(this.menuNode!, this.effectiveMenuPath); + }; +} + +export class CommandMenuAsToolbarItemWrapper extends AbstractToolbarMenuWrapper implements TabBarToolbarItem { + constructor( + effectiveMenuPath: MenuPath, + commandRegistry: CommandRegistry, + menuRegistry: MenuModelRegistry, + contextKeyService: ContextKeyService, + contextMenuRenderer: ContextMenuRenderer, + protected readonly menuNode: CommandMenu, + readonly group: string | undefined) { + super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer); + } + + isVisible(widget: Widget): boolean { + return this.menuNode.isVisible(this.effectiveMenuPath, this.contextKeyService, widget.node, widget); + } + + executeCommand(widget: Widget, e: React.MouseEvent): void { + this.menuNode.run(this.effectiveMenuPath, widget); } get id(): string { return this.menuNode.id + TOOLBAR_WRAPPER_ID_SUFFIX; } @@ -158,9 +181,13 @@ export class ToolbarMenuNodeWrapper extends AbstractToolbarMenuWrapper implement get onDidChange(): Event | undefined { return this.menuNode.onDidChange; } + + override toMenuNode(): MenuNode | undefined { + return new ToolbarItemAsCommandMenuWrapper(this.menuNode, this.effectiveMenuPath); + } } -export class ToolbarSubmenuWrapper extends AbstractToolbarMenuWrapper implements TabBarToolbarItem { +export class ToolbarActionWrapper extends AbstractToolbarMenuWrapper implements TabBarToolbarItem { constructor( effectiveMenuPath: MenuPath, commandRegistry: CommandRegistry, @@ -176,7 +203,7 @@ export class ToolbarSubmenuWrapper extends AbstractToolbarMenuWrapper implements return this.toolbarItem.command ? this.commandRegistry.isEnabled(this.toolbarItem.command, widget) : !!this.toolbarItem.menuPath; } - protected executeCommand(e: React.MouseEvent, widget?: Widget): void { + protected executeCommand(widget: Widget, e: React.MouseEvent): void { e.preventDefault(); e.stopPropagation(); @@ -232,8 +259,80 @@ export class ToolbarSubmenuWrapper extends AbstractToolbarMenuWrapper implements return this.toolbarItem.menuPath!; } - get menuNode(): MenuNode | undefined { + get menuNode(): CompoundMenuNode | undefined { return this.menuRegistry.getMenu(this.menuPath); } + + override toMenuNode(): MenuNode | undefined { + return new ToolbarItemAsSubmenuWrapper(this.menuNode!, this.effectiveMenuPath); + } } +/** + * This class wraps a menu node, but replaces the effective menu path. Command parameters need to be mapped + * for commands contributed by extension and this mapping is keyed by the menu path + */ +abstract class AbstractMenuNodeAsToolbarItemWrapper { + constructor(protected readonly menuNode: T, readonly effectiveMenuPath: MenuPath) { } + + get label(): string | undefined { + if (RenderedMenuNode.is(this.menuNode)) { + return this.menuNode.label; + } + }; + /** + * Icon classes for the menu node. If present, these will produce an icon to the left of the label in browser-style menus. + */ + get icon(): string | undefined { + if (RenderedMenuNode.is(this.menuNode)) { + return this.menuNode.label; + } + } + get id(): string { + return this.menuNode.id; + } + get sortString(): string { + return this.menuNode.sortString; + } + + isVisible(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher, context: K | undefined, ...args: unknown[]): boolean { + return this.menuNode!.isVisible(this.effectiveMenuPath, contextMatcher, context, args); + } +} + +/** + * Wrapper form submenu nodes + */ +class ToolbarItemAsSubmenuWrapper extends AbstractMenuNodeAsToolbarItemWrapper implements Group { + + get contextKeyOverlays(): Record | undefined { + return this.menuNode.contextKeyOverlays; + } + isEmpty(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher, context: T | undefined, ...args: unknown[]): boolean { + return this.menuNode.isEmpty(this.effectiveMenuPath, contextMatcher, context, args); + } + get children(): MenuNode[] { + return this.menuNode.children; + } + +} +/** + * Wrapper for command menus + */ +class ToolbarItemAsCommandMenuWrapper extends AbstractMenuNodeAsToolbarItemWrapper implements CommandMenu { + + isEnabled(effectiveMenuPath: MenuPath, ...args: unknown[]): boolean { + return this.menuNode.isEnabled(this.effectiveMenuPath, ...args); + } + isToggled(effectiveMenuPath: MenuPath, ...args: unknown[]): boolean { + return this.menuNode.isToggled(this.effectiveMenuPath, ...args); + } + run(effectiveMenuPath: MenuPath, ...args: unknown[]): Promise { + return this.menuNode.run(this.effectiveMenuPath, args); + } + + override get label(): string { + return super.label!; + } + +} diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts index 2c08c8b2fe59f..7193c1a8d3e0b 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts @@ -22,7 +22,7 @@ import { ContextKeyService } from '../../context-key-service'; import { FrontendApplicationContribution } from '../../frontend-application-contribution'; import { Widget } from '../../widgets'; import { ReactTabBarToolbarAction, RenderedToolbarAction } from './tab-bar-toolbar-types'; -import { ToolbarMenuNodeWrapper, ToolbarSubmenuWrapper } from './tab-bar-toolbar-menu-adapters'; +import { CommandMenuAsToolbarItemWrapper, SubmenuAsToolbarItemWrapper, ToolbarActionWrapper } from './tab-bar-toolbar-menu-adapters'; import { KeybindingRegistry } from '../../keybinding'; import { LabelParser } from '../../label-parser'; import { ContextMenuRenderer } from '../../context-menu-renderer'; @@ -90,7 +90,7 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution { return this.doRegisterItem(new ReactToolbarItemImpl(this.commandRegistry, this.contextKeyService, item)); } else { if (item.menuPath) { - return this.doRegisterItem(new ToolbarSubmenuWrapper(item.menuPath, + return this.doRegisterItem(new ToolbarActionWrapper(item.menuPath, this.commandRegistry, this.menuRegistry, this.contextKeyService, this.contextMenuRenderer, item)); } else { const wrapper = new RenderedToolbarItemImpl(this.commandRegistry, this.contextKeyService, this.keybindingRegistry, this.labelParser, item); @@ -146,13 +146,19 @@ export class TabBarToolbarRegistry implements FrontendApplicationContribution { for (const grandchild of child.children) { if (grandchild.isVisible([...delegate.menuPath, child.id, grandchild.id], this.contextKeyService, widget.node) && RenderedMenuNode.is(grandchild)) { - result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry, this.menuRegistry, - this.contextKeyService, this.contextMenuRenderer, grandchild, child.id, delegate.menuPath)); + if (CommandMenu.is(grandchild)) { + result.push(new CommandMenuAsToolbarItemWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry, + this.menuRegistry, this.contextKeyService, this.contextMenuRenderer, grandchild, child.id)); + } else if (CompoundMenuNode.is(grandchild)) { + result.push(new SubmenuAsToolbarItemWrapper([...delegate.menuPath, child.id, grandchild.id], this.commandRegistry, this.menuRegistry, + this.contextKeyService, this.contextMenuRenderer, grandchild, child.id)); + } + } } } else if (CommandMenu.is(child)) { - result.push(new ToolbarMenuNodeWrapper([...delegate.menuPath, child.id], this.commandRegistry, this.menuRegistry, - this.contextKeyService, this.contextMenuRenderer, child, undefined, delegate.menuPath)); + result.push(new CommandMenuAsToolbarItemWrapper([...delegate.menuPath, child.id], this.commandRegistry, this.menuRegistry, + this.contextKeyService, this.contextMenuRenderer, child, undefined)); } } } diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx index 0071a8eeb1e2c..1354f069c90ff 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx @@ -22,7 +22,7 @@ import { Anchor, ContextMenuAccess, ContextMenuRenderer } from '../../context-me import { LabelParser } from '../../label-parser'; import { codicon, ReactWidget, Widget } from '../../widgets'; import { TabBarToolbarRegistry } from './tab-bar-toolbar-registry'; -import { TabBarDelegator, TabBarToolbarAction } from './tab-bar-toolbar-types'; +import { TAB_BAR_TOOLBAR_CONTEXT_MENU, TabBarDelegator, TabBarToolbarAction } from './tab-bar-toolbar-types'; import { KeybindingRegistry } from '../..//keybinding'; import { TabBarToolbarItem } from './tab-toolbar-item'; import { GroupImpl, MenuModelRegistry } from '../../../common/menu'; @@ -173,7 +173,7 @@ export class TabBarToolbar extends ReactWidget { } return this.contextMenuRenderer.render({ menu: MenuModelRegistry.removeSingleRootNodes(menu), - menuPath: ['contextMenu'], + menuPath: TAB_BAR_TOOLBAR_CONTEXT_MENU, args: [this.current], anchor, context: this.current?.node || this.node, diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx b/packages/core/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx index 13f40dd092df2..3ae912527e002 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-toolbar-item.tsx @@ -28,8 +28,8 @@ import { ActionMenuNode, GroupImpl, MenuNode } from '../../../common/menu'; export interface TabBarToolbarItem { id: string; isVisible(widget: Widget): boolean; - isEnabled(widget?: Widget): boolean; - isToggled(): boolean; + isEnabled(widget: Widget): boolean; + isToggled(widget: Widget): boolean; render(widget?: Widget): React.ReactNode; onDidChange?: Event; group?: string; diff --git a/packages/core/src/browser/tree/tree-widget.tsx b/packages/core/src/browser/tree/tree-widget.tsx index bc327354ee5f9..1919295b29b18 100644 --- a/packages/core/src/browser/tree/tree-widget.tsx +++ b/packages/core/src/browser/tree/tree-widget.tsx @@ -1442,9 +1442,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { if (contextMenuPath) { const { x, y } = event.nativeEvent; const args = this.toContextMenuArgs(node); + const target = event.currentTarget; setTimeout(() => this.contextMenuRenderer.render({ menuPath: contextMenuPath, - context: event.currentTarget, + context: target, anchor: { x, y }, args }), 10); diff --git a/packages/core/src/browser/view-container.ts b/packages/core/src/browser/view-container.ts index 86d40a6613d41..9e94d2303b5b5 100644 --- a/packages/core/src/browser/view-container.ts +++ b/packages/core/src/browser/view-container.ts @@ -23,7 +23,7 @@ import { import { Event as CommonEvent, Emitter } from '../common/event'; import { Disposable, DisposableCollection } from '../common/disposable'; import { CommandRegistry } from '../common/command'; -import { MenuModelRegistry, MenuPath, MenuAction, SubmenuImpl, ActionMenuNode, MenuNode, RenderedMenuNode } from '../common/menu'; +import { MenuModelRegistry, MenuPath, MenuAction, SubmenuImpl, ActionMenuNode, Submenu } from '../common/menu'; import { ApplicationShell, StatefulWidget, SplitPositionHandler, SplitPositionOptions, SIDE_PANEL_TOOLBAR_CONTEXT_MENU } from './shell'; import { MAIN_AREA_ID, BOTTOM_AREA_ID } from './shell/theia-dock-panel'; import { FrontendApplicationStateService } from './frontend-application-state'; @@ -40,7 +40,7 @@ import { ElementExt } from '@lumino/domutils'; import { TabBarDecoratorService } from './shell/tab-bar-decorator'; import { ContextKeyService } from './context-key-service'; import { KeybindingRegistry } from './keybinding'; -import { ToolbarMenuNodeWrapper } from './shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters'; +import { SubmenuAsToolbarItemWrapper } from './shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters'; import { TheiaSplitPanel } from './shell/theia-split-panel'; export interface ViewContainerTitleOptions { @@ -94,7 +94,7 @@ export namespace DynamicToolbarWidget { } } -class PartsMenuToolbarItem extends ToolbarMenuNodeWrapper { +class PartsMenuToolbarItem extends SubmenuAsToolbarItemWrapper { constructor( protected readonly target: () => Widget | undefined, effectiveMenuPath: MenuPath, @@ -102,11 +102,11 @@ class PartsMenuToolbarItem extends ToolbarMenuNodeWrapper { menuRegistry: MenuModelRegistry, contextKeyService: ContextKeyService, contextMenuRenderer: ContextMenuRenderer, - menuNode: MenuNode & RenderedMenuNode, + menuNode: Submenu, group: string | undefined, menuPath?: MenuPath, ) { - super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer, menuNode, group, menuPath); + super(effectiveMenuPath, commandRegistry, menuRegistry, contextKeyService, contextMenuRenderer, menuNode, group); } override isVisible(widget: Widget): boolean { diff --git a/packages/core/src/common/menu/menu-types.ts b/packages/core/src/common/menu/menu-types.ts index 2af2f35989174..700a855f56919 100644 --- a/packages/core/src/common/menu/menu-types.ts +++ b/packages/core/src/common/menu/menu-types.ts @@ -39,6 +39,8 @@ export interface MenuNode { * Menu nodes are sorted in ascending order based on their `sortString`. */ readonly sortString: string; + + readonly effectiveMenuPath?: string[]; // override the path to be passed as the effectiveMenuPath isVisible(effectiveMenuPath: MenuPath, contextMatcher: ContextExpressionMatcher, context: T | undefined, ...args: unknown[]): boolean; onDidChange?: Event; } diff --git a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts index f1555854627b1..cdf388fd712c4 100644 --- a/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts +++ b/packages/core/src/electron-browser/menu/electron-main-menu-factory.ts @@ -175,7 +175,9 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory { const showDisabled = options?.showDisabled !== false; const honorDisabled = options?.honorDisabled !== false; - if (CompoundMenuNode.is(menu) && menu.children.length && menu.isVisible(menuPath, contextMatcher, options.context, ...args)) { + const effectivePath = menu.effectiveMenuPath || menuPath; + + if (CompoundMenuNode.is(menu) && menu.children.length && menu.isVisible(effectivePath, contextMatcher, options.context, ...args)) { if (Group.is(menu) && menu.id === 'inline') { return parentItems; } @@ -186,7 +188,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory { } const children = menu.children; const myItems: MenuDto[] = []; - children.forEach(child => this.fillMenuTemplate(myItems, [...menuPath, child.id], child, args, contextMatcher, options, false)); + children.forEach(child => this.fillMenuTemplate(myItems, [...effectivePath, child.id], child, args, contextMatcher, options, false)); if (myItems.length === 0) { return parentItems; } @@ -200,12 +202,12 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory { parentItems.push({ type: 'separator' }); } } else if (CommandMenu.is(menu)) { - if (!menu.isVisible(menuPath, contextMatcher, options.context, ...args)) { + if (!menu.isVisible(effectivePath, contextMatcher, options.context, ...args)) { return parentItems; } // We should omit rendering context-menu items which are disabled. - if (!showDisabled && !menu.isEnabled(menuPath, ...args)) { + if (!showDisabled && !menu.isEnabled(effectivePath, ...args)) { return parentItems; } @@ -214,15 +216,15 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory { const menuItem: MenuDto = { id: menu.id, label: menu.label, - type: menu.isToggled(menuPath, ...args) ? 'checkbox' : 'normal', - checked: menu.isToggled(menuPath, ...args), - enabled: !honorDisabled || menu.isEnabled(menuPath, ...args), // see https://github.com/eclipse-theia/theia/issues/446 + type: menu.isToggled(effectivePath, ...args) ? 'checkbox' : 'normal', + checked: menu.isToggled(effectivePath, ...args), + enabled: !honorDisabled || menu.isEnabled(effectivePath, ...args), // see https://github.com/eclipse-theia/theia/issues/446 visible: true, accelerator, execute: async () => { const wasToggled = menuItem.checked; - await menu.run(menuPath, ...args); - const isToggled = menu.isToggled(menuPath, ...args); + await menu.run(effectivePath, ...args); + const isToggled = menu.isToggled(effectivePath, ...args); if (isToggled !== wasToggled) { menuItem.type = isToggled ? 'checkbox' : 'normal'; menuItem.checked = isToggled; diff --git a/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts b/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts index 64d6d1aceac19..473d36f25b5a0 100644 --- a/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts +++ b/packages/plugin-ext/src/main/browser/menus/menus-contribution-handler.ts @@ -120,14 +120,14 @@ export class MenusContributionPointHandler { return false; } - return this.commandRegistry.isVisible(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(contributionPoint)(...args)); + return this.commandRegistry.isVisible(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(effectiveMenuPath)(...args)); }, icon: icon, label: label, isEnabled: (effeciveMenuPath: MenuPath, ...args: any[]): boolean => - this.commandRegistry.isEnabled(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(contributionPoint)(...args)), + this.commandRegistry.isEnabled(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(effeciveMenuPath)(...args)), run: (effeciveMenuPath: MenuPath, ...args: any[]): Promise => - this.commandRegistry.executeCommand(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(contributionPoint)(...args)), + this.commandRegistry.executeCommand(command, ...this.pluginMenuCommandAdapter.getArgumentAdapter(effeciveMenuPath)(...args)), isToggled: (effectiveMenuPath: MenuPath) => false, getAccelerator: (context: HTMLElement | undefined): string[] => { const bindings = this.keybindingRegistry.getKeybindingsForCommand(command); diff --git a/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts b/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts index ae804c8e722a4..6fad62a7140fa 100644 --- a/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts +++ b/packages/plugin-ext/src/main/browser/menus/plugin-menu-command-adapter.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** -import { SelectionService, UriSelection } from '@theia/core'; +import { MenuPath, SelectionService, UriSelection } from '@theia/core'; import { ResourceContextKey } from '@theia/core/lib/browser/resource-context-key'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { URI as CodeUri } from '@theia/core/shared/vscode-uri'; @@ -29,7 +29,7 @@ import { ScmCommandArg, TimelineCommandArg, TreeViewItemReference } from '../../ import { TestItemReference, TestMessageArg } from '../../../common/test-types'; import { PluginScmProvider, PluginScmResource, PluginScmResourceGroup } from '../scm-main'; import { TreeViewWidget } from '../view/tree-view-widget'; -import { CodeEditorWidgetUtil, ContributionPoint } from './vscode-theia-menu-mappings'; +import { CodeEditorWidgetUtil, codeToTheiaMappings, ContributionPoint } from './vscode-theia-menu-mappings'; import { TestItem, TestMessage } from '@theia/test/lib/browser/test-service'; export type ArgumentAdapter = (...args: unknown[]) => unknown[]; @@ -85,8 +85,27 @@ export class PluginMenuCommandAdapter { }); } - getArgumentAdapter(contributionPoint: string): ArgumentAdapter { - return this.argumentAdapters.get(contributionPoint) || identity; + getArgumentAdapter(menuPath: MenuPath): ArgumentAdapter { + for (const [contributionPoint, menuPaths] of codeToTheiaMappings) { + for (const theiaPath of menuPaths) { + if (this.isPrefixOf(theiaPath, menuPath)) { + return this.argumentAdapters.get(contributionPoint) || identity; + } + } + } + return identity; + } + + private isPrefixOf(candidate: string[], menuPath: MenuPath): boolean { + if (candidate.length > menuPath.length) { + return false; + } + for (let i = 0; i < candidate.length; i++) { + if (candidate[i] !== menuPath[i]) { + return false; + } + } + return true; } /* eslint-disable @typescript-eslint/no-explicit-any */