提交 a0b7054c 编写于 作者: A Alex Dima

Fixes #35225: Detect menu items invoked via keybinding and dispatch keybinding on renderer

上级 57c1e826
......@@ -152,10 +152,20 @@ export abstract class AbstractKeybindingService extends Disposable implements IK
this._currentChord = null;
}
public dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void {
const keybindings = this.resolveUserBinding(userSettingsLabel);
if (keybindings.length === 1) {
this._doDispatch(keybindings[0], target);
}
}
protected _dispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return this._doDispatch(this.resolveKeyboardEvent(e), target);
}
private _doDispatch(keybinding: ResolvedKeybinding, target: IContextKeyServiceTarget): boolean {
let shouldPreventDefault = false;
const keybinding = this.resolveKeyboardEvent(e);
if (keybinding.isChord()) {
console.warn('Unexpected keyboard event mapped to a chord');
return false;
......
......@@ -57,6 +57,8 @@ export interface IKeybindingService {
*/
softDispatch(keyboardEvent: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult | null;
dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void;
/**
* Look up keybindings for a command.
* Use `lookupKeybinding` if you are interested in the preferred keybinding.
......
......@@ -120,8 +120,7 @@ export class MockKeybindingService implements IKeybindingService {
return null;
}
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return false;
dispatchByUserSettingsLabel(userSettingsLabel: string, target: IContextKeyServiceTarget): void {
}
mightProducePrintableCharacter(e: IKeyboardEvent): boolean {
......
......@@ -25,6 +25,7 @@ export interface IMenubarMenu {
export interface IMenubarKeybinding {
label: string;
userSettingsLabel: string;
isNative?: boolean; // Assumed true if missing
}
......
......@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { isMacintosh, language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron';
import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
......@@ -31,6 +31,15 @@ interface IMenuItemClickHandler {
inNoWindow: () => void;
}
type IMenuItemInvocation = (
{ type: 'commandId'; commandId: string; }
| { type: 'keybinding'; userSettingsLabel: string; }
);
interface IMenuItemWithKeybinding {
userSettingsLabel?: string;
}
export class Menubar {
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
......@@ -621,17 +630,49 @@ export class Menubar {
}
}
private static _menuItemIsTriggeredViaKeybinding(event: Electron.Event, userSettingsLabel: string): boolean {
// The event coming in from Electron will inform us only about the modifier keys pressed.
// The strategy here is to check if the modifier keys match those of the keybinding,
// since it is highly unlikely to use modifier keys when clicking with the mouse
if (!userSettingsLabel) {
// There is no keybinding
return false;
}
let ctrlRequired = /ctrl/.test(userSettingsLabel);
let shiftRequired = /shift/.test(userSettingsLabel);
let altRequired = /alt/.test(userSettingsLabel);
let metaRequired = /cmd/.test(userSettingsLabel) || /super/.test(userSettingsLabel);
if (!ctrlRequired && !shiftRequired && !altRequired && !metaRequired) {
// This keybinding does not use any modifier keys, so we cannot use this heuristic
return false;
}
return (
ctrlRequired === event.ctrlKey
&& shiftRequired === event.shiftKey
&& altRequired === event.altKey
&& metaRequired === event.metaKey
);
}
private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem;
private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem {
const label = this.mnemonicLabel(arg1);
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => {
const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem & IMenuItemWithKeybinding, win: Electron.BrowserWindow, event: Electron.Event) => {
const userSettingsLabel = menuItem.userSettingsLabel;
let commandId = arg2;
if (Array.isArray(arg2)) {
commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking
}
this.runActionInRenderer(commandId);
if (isMacintosh && userSettingsLabel && Menubar._menuItemIsTriggeredViaKeybinding(event, userSettingsLabel)) {
this.runActionInRenderer({ type: 'keybinding', userSettingsLabel });
} else {
this.runActionInRenderer({ type: 'commandId', commandId });
}
};
const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0;
const checked = typeof arg4 === 'boolean' ? arg4 : false;
......@@ -704,7 +745,7 @@ export class Menubar {
};
}
private runActionInRenderer(id: string): void {
private runActionInRenderer(invocation: IMenuItemInvocation): void {
// We make sure to not run actions when the window has no focus, this helps
// for https://github.com/Microsoft/vscode/issues/25907 and specifically for
// https://github.com/Microsoft/vscode/issues/11928
......@@ -719,18 +760,21 @@ export class Menubar {
}
if (activeWindow) {
if (!activeWindow.isReady && isMacintosh && id === 'workbench.action.toggleDevTools' && !this.environmentService.isBuilt) {
// prevent this action from running twice on macOS (https://github.com/Microsoft/vscode/issues/62719)
// we already register a keybinding in bootstrap-window.js for opening developer tools in case something
// goes wrong and that keybinding is only removed when the application has loaded (= window ready).
return;
if (invocation.type === 'commandId') {
if (!activeWindow.isReady && isMacintosh && invocation.commandId === 'workbench.action.toggleDevTools' && !this.environmentService.isBuilt) {
// prevent this action from running twice on macOS (https://github.com/Microsoft/vscode/issues/62719)
// we already register a keybinding in bootstrap-window.js for opening developer tools in case something
// goes wrong and that keybinding is only removed when the application has loaded (= window ready).
return;
}
this.windowsMainService.sendToFocused('vscode:runAction', { id: invocation.commandId, from: 'menu' } as IRunActionInWindowRequest);
} else {
this.windowsMainService.sendToFocused('vscode:runKeybinding', { userSettingsLabel: invocation.userSettingsLabel } as IRunKeybindingInWindowRequest);
}
this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest);
}
}
private withKeybinding(commandId: string | undefined, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions {
private withKeybinding(commandId: string | undefined, options: Electron.MenuItemConstructorOptions & IMenuItemWithKeybinding): Electron.MenuItemConstructorOptions {
const binding = typeof commandId === 'string' ? this.keybindings[commandId] : undefined;
// Apply binding if there is one
......@@ -739,6 +783,7 @@ export class Menubar {
// if the binding is native, we can just apply it
if (binding.isNative !== false) {
options.accelerator = binding.label;
options.userSettingsLabel = binding.userSettingsLabel;
}
// the keybinding is not native so we cannot show it as part of the accelerator of
......@@ -768,6 +813,7 @@ export class Menubar {
const originalClick = options.click;
options.click = (item, window, event) => {
console.log(`444444`);
this.reportMenuActionTelemetry(commandId);
if (originalClick) {
originalClick(item, window, event);
......
......@@ -408,6 +408,10 @@ export interface IRunActionInWindowRequest {
args?: any[];
}
export interface IRunKeybindingInWindowRequest {
userSettingsLabel: string;
}
export class ActiveWindowManager implements IDisposable {
private disposables: IDisposable[] = [];
......
......@@ -980,13 +980,13 @@ export class MenubarControl extends Disposable {
// first try to resolve a native accelerator
const electronAccelerator = binding.getElectronAccelerator();
if (electronAccelerator) {
return { label: electronAccelerator };
return { label: electronAccelerator, userSettingsLabel: binding.getUserSettingsLabel() };
}
// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)
const acceleratorLabel = binding.getLabel();
if (acceleratorLabel) {
return { label: acceleratorLabel, isNative: false };
return { label: acceleratorLabel, isNative: false, userSettingsLabel: binding.getUserSettingsLabel() };
}
return null;
......
......@@ -15,7 +15,7 @@ import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IPathData } from 'vs/platform/windows/common/windows';
import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IPathData, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
......@@ -38,6 +38,7 @@ import { AccessibilitySupport, isRootUser, isWindows, isMacintosh } from 'vs/bas
import product from 'vs/platform/node/product';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
const TextInputActions: IAction[] = [
new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && Promise.resolve(true)),
......@@ -71,6 +72,7 @@ export class ElectronWindow extends Themable {
@IWorkbenchThemeService protected themeService: IWorkbenchThemeService,
@INotificationService private notificationService: INotificationService,
@ICommandService private commandService: ICommandService,
@IKeybindingService private keybindingService: IKeybindingService,
@IContextMenuService private contextMenuService: IContextMenuService,
@ITelemetryService private telemetryService: ITelemetryService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
......@@ -133,6 +135,11 @@ export class ElectronWindow extends Themable {
});
});
// Support runKeybinding event
ipc.on('vscode:runKeybinding', (event: any, request: IRunKeybindingInWindowRequest) => {
this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement);
});
// Error reporting from main
ipc.on('vscode:reportError', (event: any, error: string) => {
if (error) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册