Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show command shortcuts in toolbar item tooltips. #12660

Merged
merged 2 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions packages/core/src/browser/keybinding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,17 +434,18 @@ export class KeybindingRegistry {
*/
getKeybindingsForCommand(commandId: string): ScopedKeybinding[] {
const result: ScopedKeybinding[] = [];
const disabledBindings: ScopedKeybinding[] = [];
const disabledBindings = new Set<string>();
for (let scope = KeybindingScope.END - 1; scope >= KeybindingScope.DEFAULT; scope--) {
this.keymaps[scope].forEach(binding => {
if (binding.command?.startsWith('-')) {
disabledBindings.push(binding);
}
const command = this.commandRegistry.getCommand(binding.command);
if (command
&& command.id === commandId
&& !disabledBindings.some(disabled => common.Keybinding.equals(disabled, { ...binding, command: '-' + binding.command }, false, true))) {
result.push({ ...binding, scope });
disabledBindings.add(JSON.stringify({ command: binding.command.substring(1), binding: binding.keybinding, context: binding.context, when: binding.when }));
} else {
const command = this.commandRegistry.getCommand(binding.command);
if (command
&& command.id === commandId
&& !disabledBindings.has(JSON.stringify({ command: binding.command, binding: binding.keybinding, context: binding.context, when: binding.when }))) {
result.push({ ...binding, scope });
}
}
});
}
Expand Down Expand Up @@ -491,11 +492,15 @@ export class KeybindingRegistry {
* Only execute if it has no context (global context) or if we're in that context.
*/
protected isEnabled(binding: common.Keybinding, event: KeyboardEvent): boolean {
return this.isEnabledInScope(binding, <HTMLElement>event.target);
}

isEnabledInScope(binding: common.Keybinding, target: HTMLElement | undefined): boolean {
const context = binding.context && this.contexts[binding.context];
if (context && !context.isEnabled(binding)) {
return false;
}
if (binding.when && !this.whenContextService.match(binding.when, <HTMLElement>event.target)) {
if (binding.when && !this.whenContextService.match(binding.when, target)) {
return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { inject, injectable } from 'inversify';
import { inject, injectable, postConstruct } from 'inversify';
import * as React from 'react';
import { ContextKeyService } from '../../context-key-service';
import { CommandRegistry, Disposable, DisposableCollection, MenuCommandExecutor, MenuModelRegistry, MenuPath, nls } from '../../../common';
Expand All @@ -23,6 +23,7 @@ import { LabelIcon, LabelParser } from '../../label-parser';
import { ACTION_ITEM, codicon, ReactWidget, Widget } from '../../widgets';
import { TabBarToolbarRegistry } from './tab-bar-toolbar-registry';
import { AnyToolbarItem, ReactTabBarToolbarItem, TabBarDelegator, TabBarToolbarItem, TAB_BAR_TOOLBAR_CONTEXT_MENU } from './tab-bar-toolbar-types';
import { KeybindingRegistry } from '../..//keybinding';

/**
* Factory for instantiating tab-bar toolbars.
Expand All @@ -45,20 +46,34 @@ export class TabBarToolbar extends ReactWidget {
protected contextKeyListener: Disposable | undefined;
protected toDisposeOnUpdateItems: DisposableCollection = new DisposableCollection();

protected keybindingContextKeys = new Set<string>();

@inject(CommandRegistry) protected readonly commands: CommandRegistry;
@inject(LabelParser) protected readonly labelParser: LabelParser;
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
@inject(MenuCommandExecutor) protected readonly menuCommandExecutor: MenuCommandExecutor;
@inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer;
@inject(TabBarToolbarRegistry) protected readonly toolbarRegistry: TabBarToolbarRegistry;
@inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService;
@inject(KeybindingRegistry) protected readonly keybindings: KeybindingRegistry;

constructor() {
super();
this.addClass(TabBarToolbar.Styles.TAB_BAR_TOOLBAR);
this.hide();
}

@postConstruct()
protected init(): void {
this.toDispose.push(this.keybindings.onKeybindingsChanged(() => this.update()));

this.toDispose.push(this.contextKeyService.onDidChange(e => {
if (e.affects(this.keybindingContextKeys)) {
this.update();
}
}));
}

updateItems(items: Array<TabBarToolbarItem | ReactTabBarToolbarItem>, current: Widget | undefined): void {
this.toDisposeOnUpdateItems.dispose();
this.toDisposeOnUpdateItems = new DisposableCollection();
Expand Down Expand Up @@ -131,12 +146,33 @@ export class TabBarToolbar extends ReactWidget {
}

protected render(): React.ReactNode {
this.keybindingContextKeys.clear();
return <React.Fragment>
{this.renderMore()}
{[...this.inline.values()].map(item => TabBarToolbarItem.is(item) ? this.renderItem(item) : item.render(this.current))}
</React.Fragment>;
}

protected resolveKeybindingForCommand(command: string | undefined): string {
let result = '';
if (command) {
const bindings = this.keybindings.getKeybindingsForCommand(command);
let found = false;
if (bindings && bindings.length > 0) {
bindings.forEach(binding => {
if (binding.when) {
this.contextKeyService.parseKeys(binding.when)?.forEach(key => this.keybindingContextKeys.add(key));
}
if (!found && this.keybindings.isEnabledInScope(binding, this.current?.node)) {
found = true;
result = ` (${this.keybindings.acceleratorFor(binding, '+')})`;
}
});
}
}
return result;
}

protected renderItem(item: AnyToolbarItem): React.ReactNode {
let innerText = '';
const classNames = [];
Expand All @@ -156,7 +192,7 @@ export class TabBarToolbar extends ReactWidget {
iconClass += ` ${ACTION_ITEM}`;
classNames.push(iconClass);
}
const tooltip = item.tooltip || (command && command.label);
const tooltip = `${item.tooltip || (command && command.label) || ''}${this.resolveKeybindingForCommand(command?.id)}`;

const toolbarItemClassNames = this.getToolbarItemClassNames(item);
return <div key={item.id}
Expand Down
28 changes: 26 additions & 2 deletions packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
TooltipAttributes,
TreeSelection,
HoverService,
ApplicationShell
ApplicationShell,
KeybindingRegistry
} from '@theia/core/lib/browser';
import { MenuPath, MenuModelRegistry, ActionMenuNode } from '@theia/core/lib/common/menu';
import * as React from '@theia/core/shared/react';
Expand Down Expand Up @@ -404,6 +405,9 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
@inject(MenuModelRegistry)
protected readonly menus: MenuModelRegistry;

@inject(KeybindingRegistry)
protected readonly keybindings: KeybindingRegistry;

@inject(ContextKeyService)
protected readonly contextKeys: ContextKeyService;

Expand Down Expand Up @@ -441,6 +445,7 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
this.toDispose.push(this.model.onDidChangeWelcomeState(this.update, this));
this.toDispose.push(this.onDidChangeVisibilityEmitter);
this.toDispose.push(this.contextKeyService.onDidChange(() => this.update()));
this.toDispose.push(this.keybindings.onKeybindingsChanged(() => this.update()));
this.treeDragType = `application/vnd.code.tree.${this.id.toLowerCase()}`;
}

Expand Down Expand Up @@ -731,14 +736,33 @@ export class TreeViewWidget extends TreeViewWelcomeWidget {
return { viewId: this.id, itemId: treeNode.id };
}

protected resolveKeybindingForCommand(command: string | undefined): string {
let result = '';
if (command) {
const bindings = this.keybindings.getKeybindingsForCommand(command);
let found = false;
if (bindings && bindings.length > 0) {
bindings.forEach(binding => {
if (!found && this.keybindings.isEnabledInScope(binding, this.node)) {
found = true;
result = ` (${this.keybindings.acceleratorFor(binding, '+')})`;
}
});
}
}
return result;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
protected renderInlineCommand(actionMenuNode: ActionMenuNode, index: number, tabbable: boolean, args: any[]): React.ReactNode {
if (!actionMenuNode.icon || !this.commands.isVisible(actionMenuNode.command, ...args) || !actionMenuNode.when || !this.contextKeys.match(actionMenuNode.when)) {
return false;
}
const className = [TREE_NODE_SEGMENT_CLASS, TREE_NODE_TAIL_CLASS, actionMenuNode.icon, ACTION_ITEM, 'theia-tree-view-inline-action'].join(' ');
const tabIndex = tabbable ? 0 : undefined;
return <div key={index} className={className} title={actionMenuNode.label} tabIndex={tabIndex} onClick={e => {
const titleString = actionMenuNode.label + this.resolveKeybindingForCommand(actionMenuNode.command);

return <div key={index} className={className} title={titleString} tabIndex={tabIndex} onClick={e => {
e.stopPropagation();
this.commands.executeCommand(actionMenuNode.command, ...args);
}} />;
Expand Down
17 changes: 2 additions & 15 deletions packages/toolbar/src/browser/toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export class ToolbarImpl extends TabBarToolbar {
protected isBusyDeferred = new Deferred<void>();

@postConstruct()
protected init(): void {
protected override init(): void {
super.init();
this.doInit();
}

Expand Down Expand Up @@ -312,20 +313,6 @@ export class ToolbarImpl extends TabBarToolbar {
);
}

protected resolveKeybindingForCommand(commandID: string | undefined): string {
if (!commandID) {
return '';
}
const keybindings = this.keybindingRegistry.getKeybindingsForCommand(commandID);
if (keybindings.length > 0) {
const binding = keybindings[0];
const bindingKeySequence = this.keybindingRegistry.resolveKeybinding(binding);
const keyCode = bindingKeySequence[0];
return ` (${this.keybindingRegistry.acceleratorForKeyCode(keyCode, '+')})`;
}
return '';
}

protected handleOnDragStart = (e: React.DragEvent<HTMLDivElement>): void => this.doHandleOnDragStart(e);
protected doHandleOnDragStart(e: React.DragEvent<HTMLDivElement>): void {
const draggedElement = e.currentTarget;
Expand Down
Loading