Skip to content

Commit

Permalink
Add support for "enablement" property in plugin command contributions (
Browse files Browse the repository at this point in the history
…#12483)

Fixes #12426

Contributed on behalf of STMicroelectronics

Signed-off-by: Thomas Mäder <t.s.maeder@gmail.com>
  • Loading branch information
tsmaeder committed May 23, 2023
1 parent 8ac7919 commit 894e890
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 14 deletions.
Expand Up @@ -43,6 +43,7 @@ export class TabBarToolbar extends ReactWidget {
protected more = new Map<string, TabBarToolbarItem>();

protected contextKeyListener: Disposable | undefined;
protected toDisposeOnUpdateItems: DisposableCollection = new DisposableCollection();

@inject(CommandRegistry) protected readonly commands: CommandRegistry;
@inject(LabelParser) protected readonly labelParser: LabelParser;
Expand All @@ -59,11 +60,20 @@ export class TabBarToolbar extends ReactWidget {
}

updateItems(items: Array<TabBarToolbarItem | ReactTabBarToolbarItem>, current: Widget | undefined): void {
this.toDisposeOnUpdateItems.dispose();
this.toDisposeOnUpdateItems = new DisposableCollection();
this.inline.clear();
this.more.clear();

const contextKeys = new Set<string>();
for (const item of items.sort(TabBarToolbarItem.PRIORITY_COMPARATOR).reverse()) {
if ('command' in item) {
this.commands.getAllHandlers(item.command).forEach(handler => {
if (handler.onDidChangeEnabled) {
this.toDisposeOnUpdateItems.push(handler.onDidChangeEnabled(() => this.update()));
}
});
}
if ('render' in item || item.group === undefined || item.group === 'navigation') {
this.inline.set(item.id, item);
} else {
Expand Down Expand Up @@ -149,7 +159,6 @@ export class TabBarToolbar extends ReactWidget {
const tooltip = item.tooltip || (command && command.label);

const toolbarItemClassNames = this.getToolbarItemClassNames(item);
if (item.menuPath && !item.command) { toolbarItemClassNames.push('enabled'); }
return <div key={item.id}
className={toolbarItemClassNames.join(' ')}
onMouseDown={this.onMouseDownEvent}
Expand All @@ -162,10 +171,18 @@ export class TabBarToolbar extends ReactWidget {
</div>;
}

protected isEnabled(item: AnyToolbarItem): boolean {
if (!!item.command) {
return this.commandIsEnabled(item.command) && this.evaluateWhenClause(item.when);
} else {
return !!item.menuPath;
}
}

protected getToolbarItemClassNames(item: AnyToolbarItem): string[] {
const classNames = [TabBarToolbar.Styles.TAB_BAR_TOOLBAR_ITEM];
if (item.command) {
if (this.commandIsEnabled(item.command) && this.evaluateWhenClause(item.when)) {
if (this.isEnabled(item)) {
classNames.push('enabled');
}
if (this.commandIsToggled(item.command)) {
Expand Down Expand Up @@ -254,15 +271,15 @@ export class TabBarToolbar extends ReactWidget {

const item: AnyToolbarItem | undefined = this.inline.get(e.currentTarget.id);

if (!this.evaluateWhenClause(item?.when)) {
if (!item || !this.isEnabled(item)) {
return;
}

if (item?.command && item.menuPath) {
if (item.command && item.menuPath) {
this.menuCommandExecutor.executeCommand(item.menuPath, item.command, this.current);
} else if (item?.command) {
} else if (item.command) {
this.commands.executeCommand(item.command, this.current);
} else if (item?.menuPath) {
} else if (item.menuPath) {
this.renderMoreContextMenu(this.toAnchor(e), item.menuPath);
}
this.update();
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/common/command.ts
Expand Up @@ -122,6 +122,7 @@ export interface CommandHandler {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isEnabled?(...args: any[]): boolean;
onDidChangeEnabled?: Event<void>;
/**
* Test whether menu items for this handler should be visible.
*/
Expand Down
Expand Up @@ -35,6 +35,12 @@ export interface ElectronMenuOptions {
* Defaults to `true`.
*/
readonly showDisabled?: boolean;

/**
* Controls whether to render disabled items as disabled
* Defaults to `true`
*/
readonly honorDisabled?: boolean;
/**
* A DOM context to use when evaluating any `when` clauses
* of menu items registered for this item.
Expand Down Expand Up @@ -108,7 +114,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
const maxWidget = document.getElementsByClassName(MAXIMIZED_CLASS);
if (preference === 'visible' || (preference === 'classic' && maxWidget.length === 0)) {
const menuModel = this.menuProvider.getMenu(MAIN_MENU_BAR);
this._menu = this.fillMenuTemplate([], menuModel, [], { rootMenuPath: MAIN_MENU_BAR });
this._menu = this.fillMenuTemplate([], menuModel, [], { honorDisabled: false, rootMenuPath: MAIN_MENU_BAR });
if (isOSX) {
this._menu.unshift(this.createOSXMenu());
}
Expand All @@ -121,7 +127,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {

createElectronContextMenu(menuPath: MenuPath, args?: any[], context?: HTMLElement, contextKeyService?: ContextMatcher): MenuDto[] {
const menuModel = this.menuProvider.getMenu(menuPath);
return this.fillMenuTemplate([], menuModel, args, { showDisabled: false, context, rootMenuPath: menuPath, contextKeyService });
return this.fillMenuTemplate([], menuModel, args, { showDisabled: true, context, rootMenuPath: menuPath, contextKeyService });
}

protected fillMenuTemplate(parentItems: MenuDto[],
Expand All @@ -130,6 +136,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
options: ElectronMenuOptions
): MenuDto[] {
const showDisabled = options?.showDisabled !== false;
const honorDisabled = options?.honorDisabled !== false;

if (CompoundMenuNode.is(menu) && menu.children.length && this.undefinedOrMatch(options.contextKeyService ?? this.contextKeyService, menu.when, options.context)) {
const role = CompoundMenuNode.getRole(menu);
Expand Down Expand Up @@ -181,7 +188,7 @@ export class ElectronMainMenuFactory extends BrowserMainMenuFactory {
label: node.label,
type: this.commandRegistry.getToggledHandler(commandId, ...args) ? 'checkbox' : 'normal',
checked: this.commandRegistry.isToggled(commandId, ...args),
enabled: true, // https://github.com/eclipse-theia/theia/issues/446
enabled: !honorDisabled || this.commandRegistry.isEnabled(commandId, args), // see https://github.com/eclipse-theia/theia/issues/446
visible: true,
accelerator,
execute: () => this.execute(commandId, args, options.rootMenuPath)
Expand Down
Expand Up @@ -47,6 +47,7 @@ import { ContributedTerminalProfileStore, TerminalProfileStore } from '@theia/te
import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
import { PluginTerminalRegistry } from './plugin-terminal-registry';
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';

@injectable()
export class PluginContributionHandler {
Expand Down Expand Up @@ -122,6 +123,9 @@ export class PluginContributionHandler {
@inject(ContributionProvider) @named(LabelProviderContribution)
protected readonly contributionProvider: ContributionProvider<LabelProviderContribution>;

@inject(ContextKeyService)
protected readonly contextKeyService: ContextKeyService;

protected readonly commandHandlers = new Map<string, CommandHandler['execute'] | undefined>();

protected readonly onDidRegisterCommandHandlerEmitter = new Emitter<string>();
Expand Down Expand Up @@ -399,7 +403,7 @@ export class PluginContributionHandler {
return Disposable.NULL;
}
const toDispose = new DisposableCollection();
for (const { iconUrl, themeIcon, command, category, title, originalTitle } of contribution.commands) {
for (const { iconUrl, themeIcon, command, category, title, originalTitle, enablement } of contribution.commands) {
const reference = iconUrl && this.style.toIconClass(iconUrl);
const icon = themeIcon && ThemeIcon.fromString(themeIcon);
let iconClass;
Expand All @@ -409,12 +413,12 @@ export class PluginContributionHandler {
} else if (icon) {
iconClass = ThemeIcon.asClassName(icon);
}
toDispose.push(this.registerCommand({ id: command, category, label: title, originalLabel: originalTitle, iconClass }));
toDispose.push(this.registerCommand({ id: command, category, label: title, originalLabel: originalTitle, iconClass }, enablement));
}
return toDispose;
}

registerCommand(command: Command): Disposable {
registerCommand(command: Command, enablement?: string): Disposable {
if (this.hasCommand(command.id)) {
console.warn(`command '${command.id}' already registered`);
return Disposable.NULL;
Expand All @@ -429,11 +433,27 @@ export class PluginContributionHandler {
return handler(...args);
},
// Always enabled - a command can be executed programmatically or via the commands palette.
isEnabled(): boolean { return true; },
isEnabled: () => {
if (enablement) {
return this.contextKeyService.match(enablement);
}
return true;
},
// Visibility rules are defined via the `menus` contribution point.
isVisible(): boolean { return true; }
};

if (enablement) {
const contextKeys = this.contextKeyService.parseKeys(enablement);
if (contextKeys && contextKeys.size > 0) {
commandHandler.onDidChangeEnabled = (listener: () => void) => this.contextKeyService.onDidChange(e => {
if (e.affects(contextKeys)) {
listener();
}
});
}
}

const toDispose = new DisposableCollection();
if (this.commands.getCommand(command.id)) {
// overriding built-in command, i.e. `type` by the VSCodeVim extension
Expand Down Expand Up @@ -608,5 +628,4 @@ export class PluginContributionHandler {
}
return { indentAction, appendText: action.appendText, removeText: action.removeText };
}

}

0 comments on commit 894e890

Please sign in to comment.