提交 056db452 编写于 作者: J Joao Moreno

driver: dispatchKeybindings

上级 f53e87d7
......@@ -360,7 +360,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
this.toDispose.push(dom.addDisposableListener(domNode, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let keyEvent = new StandardKeyboardEvent(e);
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
let shouldPreventDefault = this.dispatchEvent(keyEvent, keyEvent.target);
if (shouldPreventDefault) {
keyEvent.preventDefault();
}
......
......@@ -17,7 +17,7 @@ suite('StandaloneKeybindingService', () => {
class TestStandaloneKeybindingService extends StandaloneKeybindingService {
public testDispatch(e: IKeyboardEvent): void {
super._dispatch(e, null);
super.dispatchEvent(e, null);
}
}
......
......@@ -23,12 +23,14 @@ export interface IDriver {
_serviceBrand: any;
getWindowIds(): TPromise<number[]>;
getElements(windowId: number, selector: string): TPromise<IElement[]>;
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void>;
}
//*END
export interface IDriverChannel extends IChannel {
call(command: 'getWindowIds'): TPromise<number[]>;
call(command: 'getElements', arg: [number, string]): TPromise<IElement[]>;
call(command: 'dispatchKeybinding', arg: [number, string]): TPromise<void>;
call(command: string, arg: any): TPromise<any>;
}
......@@ -40,6 +42,7 @@ export class DriverChannel implements IDriverChannel {
switch (command) {
case 'getWindowIds': return this.driver.getWindowIds();
case 'getElements': return this.driver.getElements(arg[0], arg[1]);
case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg[0], arg[1]);
}
return undefined;
......@@ -59,6 +62,10 @@ export class DriverChannelClient implements IDriver {
getElements(windowId: number, selector: string): TPromise<IElement[]> {
return this.channel.call('getElements', [windowId, selector]);
}
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void> {
return this.channel.call('dispatchKeybinding', [windowId, keybinding]);
}
}
export interface IWindowDriverRegistry {
......@@ -96,10 +103,12 @@ export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry
export interface IWindowDriver {
getElements(selector: string): TPromise<IElement[]>;
dispatchKeybinding(keybinding: string): TPromise<void>;
}
export interface IWindowDriverChannel extends IChannel {
call(command: 'getElements', arg: string): TPromise<IElement[]>;
call(command: 'dispatchKeybinding', arg: string): TPromise<void>;
call(command: string, arg: any): TPromise<any>;
}
......@@ -110,6 +119,7 @@ export class WindowDriverChannel implements IWindowDriverChannel {
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'getElements': return this.driver.getElements(arg);
case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg);
}
return undefined;
......@@ -125,4 +135,8 @@ export class WindowDriverChannelClient implements IWindowDriver {
getElements(selector: string): TPromise<IElement[]> {
return this.channel.call('getElements', selector);
}
dispatchKeybinding(keybinding: string): TPromise<void> {
return this.channel.call('dispatchKeybinding', keybinding);
}
}
\ No newline at end of file
......@@ -9,9 +9,53 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driver';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
import { SimpleKeybinding } from 'vs/base/common/keyCodes';
import { ScanCodeBinding, IMMUTABLE_KEY_CODE_TO_CODE, ScanCodeUtils } from 'vs/workbench/services/keybinding/common/scanCode';
import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class WindowDriver implements IWindowDriver {
constructor(
@IKeybindingService private keybindingService: IKeybindingService
) { }
async dispatchKeybinding(rawKeybinding: string): TPromise<void> {
const [first, second] = KeybindingIO._readUserBinding(rawKeybinding);
this._dispatchKeybinding(first);
if (second) {
this._dispatchKeybinding(second);
}
}
private _dispatchKeybinding(keybinding: SimpleKeybinding | ScanCodeBinding): void {
if (keybinding instanceof ScanCodeBinding) {
throw new Error('ScanCodeBindings not supported');
}
const scanCode = IMMUTABLE_KEY_CODE_TO_CODE[keybinding.keyCode];
const event: IKeyboardEvent = {
ctrlKey: keybinding.ctrlKey,
altKey: keybinding.altKey,
shiftKey: keybinding.shiftKey,
metaKey: keybinding.metaKey,
keyCode: keybinding.keyCode,
code: ScanCodeUtils.toString(scanCode)
};
this.keybindingService.dispatchEvent(event, document.activeElement);
// console.log(keybinding);
// const e = new KeyboardEvent('keydown', event);
// console.log('dispatching', e);
// document.activeElement.dispatchEvent(e);
// document.activeElement.dispatchEvent(new KeyboardEvent('keyup', event));
}
async getElements(selector: string): TPromise<IElement[]> {
const query = document.querySelectorAll(selector);
const result: IElement[] = [];
......@@ -30,8 +74,12 @@ class WindowDriver implements IWindowDriver {
}
}
export async function registerWindowDriver(client: IPCClient, windowId: number): TPromise<IDisposable> {
const windowDriver = new WindowDriver();
export async function registerWindowDriver(
client: IPCClient,
windowId: number,
instantiationService: IInstantiationService
): TPromise<IDisposable> {
const windowDriver = instantiationService.createInstance(WindowDriver);
const windowDriverChannel = new WindowDriverChannel(windowDriver);
client.registerChannel('windowDriver', windowDriverChannel);
......
......@@ -6,7 +6,7 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDriver, DriverChannel, IElement, IWindowDriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel } from 'vs/platform/driver/common/driver';
import { IDriver, DriverChannel, IElement, IWindowDriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IWindowDriver } from 'vs/platform/driver/common/driver';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
......@@ -45,11 +45,19 @@ export class Driver implements IDriver, IWindowDriverRegistry {
}
getElements(windowId: number, selector: string): TPromise<IElement[], any> {
const windowDriver = this.getWindowDriver(windowId);
return windowDriver.getElements(selector);
}
dispatchKeybinding(windowId: number, keybinding: string): TPromise<void> {
const windowDriver = this.getWindowDriver(windowId);
return windowDriver.dispatchKeybinding(keybinding);
}
private getWindowDriver(windowId: number): IWindowDriver {
const router = new WindowRouter(windowId);
const windowDriverChannel = this.windowServer.getChannel<IWindowDriverChannel>('windowDriver', router);
const windowDriver = new WindowDriverChannelClient(windowDriverChannel);
return windowDriver.getElements(selector);
return new WindowDriverChannelClient(windowDriverChannel);
}
}
......
......@@ -114,7 +114,7 @@ export abstract class AbstractKeybindingService implements IKeybindingService {
return this._getResolver().resolve(contextValue, currentChord, firstPart);
}
protected _dispatch(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
let shouldPreventDefault = false;
const keybinding = this.resolveKeyboardEvent(e);
......
......@@ -77,5 +77,10 @@ export interface IKeybindingService {
getKeybindings(): ResolvedKeybindingItem[];
customKeybindingsCount(): number;
/**
* For simulation purposes (eg, smoke test)
*/
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean;
}
......@@ -70,7 +70,7 @@ suite('AbstractKeybindingService', () => {
public testDispatch(kb: number): boolean {
const keybinding = createSimpleKeybinding(kb, OS);
return this._dispatch({
return this.dispatchEvent({
ctrlKey: keybinding.ctrlKey,
shiftKey: keybinding.shiftKey,
altKey: keybinding.altKey,
......
......@@ -120,4 +120,8 @@ export class MockKeybindingService implements IKeybindingService {
public softDispatch(keybinding: IKeyboardEvent, target: IContextKeyServiceTarget): IResolveResult {
return null;
}
dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean {
return false;
}
}
......@@ -95,7 +95,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { DialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService';
import { DialogChannel } from 'vs/platform/dialogs/common/dialogIpc';
import { EventType, addDisposableListener, addClass, getClientArea } from 'vs/base/browser/dom';
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
/**
* Services that we require for the Shell
......@@ -140,7 +139,7 @@ export class WorkbenchShell {
private configuration: IWindowConfiguration;
private workbench: Workbench;
constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, mainProcessClient: IPCClient, configuration: IWindowConfiguration) {
constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, private mainProcessClient: IPCClient, configuration: IWindowConfiguration) {
this.container = container;
this.configuration = configuration;
......@@ -156,11 +155,6 @@ export class WorkbenchShell {
this.toUnbind = [];
this.previousErrorTime = 0;
if (coreServices.environmentService.driverHandle) {
registerWindowDriver(mainProcessClient, configuration.windowId)
.then(disposable => this.toUnbind.push(disposable));
}
}
private createContents(parent: HTMLElement): HTMLElement {
......@@ -195,7 +189,7 @@ export class WorkbenchShell {
private createWorkbench(instantiationService: IInstantiationService, serviceCollection: ServiceCollection, parent: HTMLElement, workbenchContainer: HTMLElement): Workbench {
try {
const workbench = instantiationService.createInstance(Workbench, parent, workbenchContainer, this.configuration, serviceCollection, this.lifecycleService);
const workbench = instantiationService.createInstance(Workbench, parent, workbenchContainer, this.configuration, serviceCollection, this.lifecycleService, this.mainProcessClient);
// Set lifecycle phase to `Restoring`
this.lifecycleService.phase = LifecyclePhase.Restoring;
......
......@@ -106,6 +106,8 @@ import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/no
import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus';
import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
export const EditorsVisibleContext = new RawContextKey<boolean>('editorIsOpen', false);
export const InZenModeContext = new RawContextKey<boolean>('inZenMode', false);
......@@ -231,9 +233,10 @@ export class Workbench implements IPartService {
constructor(
parent: HTMLElement,
container: HTMLElement,
configuration: IWindowConfiguration,
private configuration: IWindowConfiguration,
serviceCollection: ServiceCollection,
private lifecycleService: LifecycleService,
private mainProcessClient: IPCClient,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IStorageService private storageService: IStorageService,
......@@ -318,6 +321,12 @@ export class Workbench implements IPartService {
// Workbench Layout
this.createWorkbenchLayout();
// Driver
if (this.environmentService.driverHandle) {
registerWindowDriver(this.mainProcessClient, this.configuration.windowId, this.instantiationService)
.then(disposable => this.toUnbind.push(disposable));
}
// Restore Parts
return this.restoreParts();
}
......
......@@ -313,7 +313,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
this.toDispose.push(dom.addDisposableListener(windowElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let keyEvent = new StandardKeyboardEvent(e);
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
let shouldPreventDefault = this.dispatchEvent(keyEvent, keyEvent.target);
if (shouldPreventDefault) {
keyEvent.preventDefault();
}
......
......@@ -11,7 +11,7 @@ export class API {
// waitFor calls should not take more than 200 * 100 = 20 seconds to complete, excluding
// the time it takes for the actual retry call to complete
private retryCount: number;
private readonly retryDuration = 100; // in milliseconds
private readonly retryDuration = 1000; // in milliseconds
constructor(
private driver: Driver,
......@@ -21,8 +21,8 @@ export class API {
this.retryCount = (waitTime * 1000) / this.retryDuration;
}
keys(keys: string[]): Promise<void> {
return this.driver.keys(keys);
dispatchKeybinding(keybinding: string): Promise<void> {
return this.driver.dispatchKeybinding(keybinding);
}
waitForTextContent(selector: string, textContent?: string, accept?: (result: string) => boolean): Promise<string> {
......
......@@ -120,7 +120,7 @@ export class Debug extends Viewlet {
// Wait for the keys to be picked up by the editor model such that repl evalutes what just got typed
await this.editor.waitForEditorContents('debug:input', s => s.indexOf(text) >= 0);
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
await this.api.waitForElement(CONSOLE_INPUT_OUTPUT);
await this.api.waitFor(async () => {
const result = await this.getConsoleOutput();
......
......@@ -34,7 +34,7 @@ export class Editor {
await this.api.waitForActiveElement(RENAME_INPUT);
await this.api.setValue(RENAME_INPUT, to);
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
}
async gotoDefinition(term: string, line: number): Promise<void> {
......
......@@ -34,7 +34,7 @@ export class References {
}
async close(): Promise<void> {
await this.api.keys(['Escape', 'NULL']);
await this.api.dispatchKeybinding('escape');
await this.api.waitForElement(References.REFERENCES_WIDGET, element => !element);
}
}
\ No newline at end of file
......@@ -25,7 +25,7 @@ export function setup() {
await app.workbench.quickopen.openQuickOpen('.js');
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.api.keys(['Escape', 'NULL']);
await app.api.dispatchKeybinding('escape');
});
it('quick open respects fuzzy matching', async function () {
......@@ -38,7 +38,7 @@ export function setup() {
await app.workbench.quickopen.openQuickOpen('a.s');
await app.workbench.quickopen.waitForQuickOpenElements(names => expectedNames.every(n => names.some(m => n === m)));
await app.api.keys(['Escape', 'NULL']);
await app.api.dispatchKeybinding('escape');
});
});
}
\ No newline at end of file
......@@ -12,7 +12,7 @@ export class KeybindingsEditor {
constructor(private api: API, private commands: Commands) { }
async updateKeybinding(command: string, keys: string[], ariaLabel: string): Promise<any> {
async updateKeybinding(command: string, keybinding: string, ariaLabel: string): Promise<any> {
await this.commands.runCommand('workbench.action.openGlobalKeybindings');
await this.api.waitForActiveElement(SEARCH_INPUT);
await this.api.setValue(SEARCH_INPUT, command);
......@@ -23,7 +23,8 @@ export class KeybindingsEditor {
await this.api.waitAndClick('div[aria-label="Keybindings"] .monaco-list-row.keybinding-item .action-item .icon.add');
await this.api.waitForElement('.defineKeybindingWidget .monaco-inputbox.synthetic-focus');
await this.api.keys([...keys, 'NULL', 'Enter', 'NULL']);
await this.api.dispatchKeybinding(keybinding);
await this.api.dispatchKeybinding('enter');
await this.api.waitForElement(`div[aria-label="Keybindings"] div[aria-label="Keybinding is ${ariaLabel}."]`);
}
}
\ No newline at end of file
......@@ -32,9 +32,9 @@ export function setup() {
const app = this.app as SpectronApplication;
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.LEFT), 'Activity bar should be positioned on the left.');
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', ['Control', 'u'], 'Control+U');
await app.workbench.keybindingsEditor.updateKeybinding('workbench.action.toggleSidebarPosition', 'ctrl+u', 'Control+U');
await app.api.keys(['Control', 'u', 'NULL']);
await app.api.dispatchKeybinding('ctrl+u');
assert.ok(await app.workbench.activitybar.getActivityBar(ActivityBarPosition.RIGHT), 'Activity bar was not moved to right after toggling its position.');
});
......
......@@ -27,11 +27,10 @@ export class SettingsEditor {
await this.api.waitAndClick(SEARCH_INPUT);
await this.api.waitForActiveElement(SEARCH_INPUT);
await this.api.keys(['ArrowDown', 'NULL']);
await this.api.dispatchKeybinding('down');
await this.api.waitForActiveElement(EDITOR);
await this.api.keys(['ArrowRight', 'NULL']);
await this.api.dispatchKeybinding('right');
await this.editor.waitForTypeInEditor('settings.json', `"${setting}": ${value}`, '.editable-preferences-editor-container');
await this.editors.saveOpenedFile();
}
......
......@@ -36,7 +36,7 @@ export class QuickOpen {
await this.openQuickOpen(fileName);
await this.waitForQuickOpenElements(names => names.some(n => n === fileName));
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
await this.editors.waitForActiveTab(fileName);
await this.editors.waitForEditorFocus(fileName);
}
......@@ -51,16 +51,16 @@ export class QuickOpen {
async submit(text: string): Promise<void> {
await this.api.setValue(QuickOpen.QUICK_OPEN_INPUT, text);
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
await this.waitForQuickOpenClosed();
}
async selectQuickOpenElement(index: number): Promise<void> {
await this.waitForQuickOpenOpened();
for (let from = 0; from < index; from++) {
await this.api.keys(['ArrowDown', 'NULL']);
await this.api.dispatchKeybinding('down');
}
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
await this.waitForQuickOpenClosed();
}
......
......@@ -33,7 +33,7 @@ export class Search extends Viewlet {
await this.api.waitAndClick(INPUT);
await this.api.waitForActiveElement(INPUT);
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
await this.api.waitForElement(`${VIEWLET} .messages[aria-hidden="false"]`);
}
......
......@@ -28,7 +28,7 @@ export class Terminal {
async runCommand(commandText: string): Promise<void> {
// TODO@Tyriar fix this. we should not use type but setValue
// await this.spectron.client.type(commandText);
await this.api.keys(['Enter', 'NULL']);
await this.api.dispatchKeybinding('enter');
}
async waitForTerminalText(fn: (text: string[]) => boolean, timeOutDescription: string = 'Getting Terminal Text'): Promise<string[]> {
......
......@@ -68,32 +68,7 @@ export class Workbench implements Commands {
return;
}
const keys: string = binding.key;
let keysToPress: string[] = [];
const chords = keys.split(' ');
chords.forEach((chord) => {
const keys = chord.split('+');
keys.forEach((key) => keysToPress.push(this.transliterate(key)));
keysToPress.push('NULL');
});
return this.api.keys(keysToPress);
}
/**
* Transliterates key names from keybindings file to WebdriverIO keyboard actions defined in:
* https://w3c.github.io/webdriver/webdriver-spec.html#keyboard-actions
*/
private transliterate(key: string): string {
switch (key) {
case 'ctrl':
return 'Control';
case 'cmd':
return 'Meta';
default:
return key.length === 1 ? key : key.charAt(0).toUpperCase() + key.slice(1);
}
return this.api.dispatchKeybinding(binding.key);
}
}
......@@ -13,7 +13,7 @@ export interface Element {
}
export interface Driver {
keys(keys: string[]): Promise<void>;
dispatchKeybinding(keybinding: string): Promise<void>;
click(selector: string, xoffset?: number, yoffset?: number): Promise<any>;
doubleClick(selector: string): Promise<any>;
move(selector: string): Promise<any>;
......@@ -32,13 +32,8 @@ export class SpectronDriver implements Driver {
private verbose: boolean
) { }
keys(keys: string[]): Promise<void> {
if (this.verbose) {
console.log('- keys:', keys);
}
this.spectronClient.keys(keys);
return Promise.resolve();
dispatchKeybinding(keybinding: string): Promise<void> {
return Promise.reject(new Error('not implemented'));
}
async click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void> {
......@@ -147,12 +142,13 @@ export class CodeDriver implements Driver {
return this._activeWindowId;
}
keys(keys: string[]): Promise<void> {
async dispatchKeybinding(keybinding: string): Promise<void> {
if (this.verbose) {
console.log('- keys:', keys);
console.log('- dispatchKeybinding:', keybinding);
}
throw new Error('Method not implemented.');
const windowId = await this.getWindowId();
await this.driver.dispatchKeybinding(windowId, keybinding);
}
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<any> {
......
......@@ -13,6 +13,7 @@ export interface IDriver {
_serviceBrand: any;
getWindowIds(): Promise<number[]>;
getElements(windowId: number, selector: string): Promise<IElement[]>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
}
export interface IDisposable {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册