Skip to content

Commit

Permalink
eclipse-che/che#9063 implement first prototype of Command API
Browse files Browse the repository at this point in the history
Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
  • Loading branch information
evidolob committed Mar 15, 2018
1 parent 40a4eba commit 4cccac0
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 27 deletions.
7 changes: 6 additions & 1 deletion packages/isolated-extension-api/compile.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
"extends": "../../configs/base.tsconfig",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib"
"outDir": "lib",
"lib": [
"es6",
"dom",
"webworker"
]
},
"include": [
"src"
Expand Down
58 changes: 58 additions & 0 deletions packages/isolated-extension-api/src/api/extension-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
import { createProxyIdentifier, ProxyIdentifier } from './rpc-protocol';
import * as theia from 'theia';
/**
* A command handler is an implementation of a command.
*
* A command can have multiple handlers
* but they should be active in different contexts,
* otherwise first active will be executed.
*/
export interface CommandHandler {
/**
* Execute this handler.
*/
execute(...args: any[]): any;
/**
* Test whether this handler is enabled (active).
*/
isEnabled?(...args: any[]): boolean;
/**
* Test whether menu items for this handler should be visible.
*/
isVisible?(...args: any[]): boolean;
}

export interface CommandRegistryMain {
/**
* Register the given command and handler if present.
*
* Throw if a command is already registered for the given command identifier.
*/
registerCommand(command: theia.Command): void;

unregisterCommand(id: string): void;
executeCommand<T>(id: string, args: any[]): PromiseLike<T>;
getCommands(): PromiseLike<string[]>;
}

export interface CommandRegistryExt {
executeContributedCommand<T>(id: string): PromiseLike<T>;
}

export const EXTENSION_RPC_CONTEXT = {
COMMAND_REGISTRY_MAIN: <ProxyIdentifier<CommandRegistryMain>>createProxyIdentifier<CommandRegistryMain>("CommandRegistryMain")
};

export const MAIN_RPC_CONTEXT = {
COMMAND_REGISTRY_EXT: createProxyIdentifier<CommandRegistryExt>("CommandRegistryExt")
};
21 changes: 14 additions & 7 deletions packages/isolated-extension-api/src/api/rpc-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export interface RPCProtocol {
*/
getProxy<T>(proxyId: ProxyIdentifier<T>): T;

/**
* Register manually created instance.
*/
set<T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R;

}

export class ProxyIdentifier<T> {
Expand All @@ -33,15 +38,12 @@ export class ProxyIdentifier<T> {
}
}

export function createMainProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
return new ProxyIdentifier(true, 'm' + identifier);
}

export function createExtensionProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
return new ProxyIdentifier(false, 'e' + identifier);
export function createProxyIdentifier<T>(identifier: string): ProxyIdentifier<T> {
return new ProxyIdentifier(false, identifier);
}

export class RPCProtocolImpl implements RPCProtocol {

private isDisposed: boolean;
private readonly locals: { [id: string]: any; };
private readonly proxies: { [id: string]: any; };
Expand All @@ -66,10 +68,15 @@ export class RPCProtocolImpl implements RPCProtocol {
return this.proxies[proxyId.id];
}

set<T, R extends T>(identifier: ProxyIdentifier<T>, instance: R): R {
this.locals[identifier.id] = instance;
return instance;
}

private createProxy<T>(proxyId: string): T {
const handler = {
get: (target: any, name: string) => {
if (!target[name] && name.charCodeAt(0) === 36 /* CharCode.DollarSign */) {
if (!target[name] /*&& name.charCodeAt(0) === 36 */ /* CharCode.DollarSign */) {
target[name] = (...myArgs: any[]) =>
this.remoteCall(proxyId, name, myArgs);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
import { CommandRegistryExt, MAIN_RPC_CONTEXT, CommandRegistryMain } from '../api/extension-api';
import { injectable, inject } from "inversify";
import { CommandRegistry } from '@theia/core/lib/common/command';
import { ExtensionWorker } from './extension-worker';
import * as theia from 'theia';
import { Disposable } from '@theia/core/lib/common/disposable';

@injectable()
export class CommandRegistryMainImpl implements CommandRegistryMain {
private proxy: CommandRegistryExt;
private disposables = new Map<string, Disposable>();

constructor( @inject(CommandRegistry) private readonly delegate: CommandRegistry,
@inject(ExtensionWorker) worker: ExtensionWorker) {
this.proxy = worker.rpc.getProxy(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT);
}

registerCommand(command: theia.Command): void {
this.disposables.set(
command.id,
this.delegate.registerCommand(command, {
execute: (...args: any[]) => {
this.proxy.executeContributedCommand(command.id);
},
isEnabled() { return true; },
isVisible() { return true; }
}));
}
unregisterCommand(id: string): void {
const dis = this.disposables.get(id);
if (dis) {
dis.dispose();
this.disposables.delete(id);
}
}
executeCommand<T>(id: string, args: any[]): PromiseLike<T> {
throw new Error("Method not implemented.");
}
getCommands(): PromiseLike<string[]> {
throw new Error("Method not implemented.");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@
import { ContainerModule } from "inversify";
import { FrontendApplicationContribution, FrontendApplication } from "@theia/core/lib/browser";
import { MaybePromise } from "@theia/core/lib/common";
import { ExtensionWorker } from './extension-worker';
import { CommandRegistryMainImpl } from './command-registry-main';
import { EXTENSION_RPC_CONTEXT } from '../api/extension-api';

export default new ContainerModule(bind => {
bind(ExtensionWorker).toSelf().inSingletonScope();
bind(CommandRegistryMainImpl).toSelf().inSingletonScope();
// bind(FrontendApplicationContribution).toService(ExtensionWorker);
bind(FrontendApplicationContribution).toDynamicValue(ctx => ({
onStart(app: FrontendApplication): MaybePromise<void> {

const worker: Worker = new (require('../worker/worker-main'));
worker.addEventListener('message', message => {
console.log('message is', message);
});
const worker = ctx.container.get(ExtensionWorker);
const commandRegistryMain = ctx.container.get(CommandRegistryMainImpl);
worker.rpc.set(EXTENSION_RPC_CONTEXT.COMMAND_REGISTRY_MAIN, commandRegistryMain);
}
}));
});
33 changes: 33 additions & 0 deletions packages/isolated-extension-api/src/browser/extension-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2015-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
import { injectable } from "inversify";
import { RPCProtocolImpl, RPCProtocol } from '../api/rpc-protocol';
import { Emitter } from '@theia/core/lib/common/event';

@injectable()
export class ExtensionWorker {

private worker: Worker;
public readonly rpc: RPCProtocol;
constructor() {
const emmitter = new Emitter();
this.worker = new (require('../worker/worker-main'));
this.worker.onmessage = (message) => {
emmitter.fire(message.data);
};

this.rpc = new RPCProtocolImpl({
onMessage: emmitter.event,
send: (m: {}) => this.worker.postMessage(m)
});

}
}
52 changes: 52 additions & 0 deletions packages/isolated-extension-api/src/extension/comand-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
import { CommandRegistryExt, EXTENSION_RPC_CONTEXT as Ext, CommandRegistryMain } from '../api/extension-api';
import { Disposable } from '@theia/core/lib/common/disposable';
import { RPCProtocol } from '../api/rpc-protocol';
import * as theia from 'theia';

export type Handler = <T>(...args: any[]) => T | PromiseLike<T>;

export class CommandRegistryImpl implements CommandRegistryExt {

private proxy: CommandRegistryMain;
private commands = new Map<string, Handler>();

constructor(rpc: RPCProtocol) {
this.proxy = rpc.getProxy(Ext.COMMAND_REGISTRY_MAIN);
}
registerCommand(command: theia.Command, handler: Handler): Disposable {
if (this.commands.has(command.id)) {
throw new Error(`Command ${command.id} already exist`);
}
this.commands.set(command.id, handler);
this.proxy.registerCommand(command);

return Disposable.create(() => {
this.proxy.unregisterCommand(command.id);
});

}

dispose(): void {
throw new Error("Method not implemented.");
}

executeContributedCommand<T>(id: string): PromiseLike<T> {
const handler = this.commands.get(id);
if (handler) {
return Promise.resolve(handler());
} else {
return Promise.reject(new Error(`Command ${id} doesn't exist`));
}
}

}
29 changes: 29 additions & 0 deletions packages/isolated-extension-api/src/extension/extension-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
import { MAIN_RPC_CONTEXT } from '../api/extension-api';
import { Disposable } from '@theia/core/lib/common/disposable';
import { RPCProtocol } from '../api/rpc-protocol';
import * as theia from 'theia';
import { CommandRegistryImpl } from './comand-registry';

export function createAPI(rpc: RPCProtocol): typeof theia {
const commandRegistryExt = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc));

const commands: typeof theia.commands = {
registerCommand(command: theia.Command, callback: <T>(...args: any[]) => T | Thenable<T>): Disposable {
return commandRegistryExt.registerCommand(command, callback);
}
};
return <typeof theia>{
commands
};

}
36 changes: 36 additions & 0 deletions packages/isolated-extension-api/src/theia.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
//import { Disposable } from '@theia/core/lib/common/disposable';

declare module 'theia' {
/**
* A command is a unique identifier of a function
* which can be executed by a user via a keyboard shortcut,
* a menu action or directly.
*/
export interface Command {
/**
* A unique identifier of this command.
*/
id: string;
/**
* A label of this command.
*/
label?: string;
/**
* An icon class of this command.
*/
iconClass?: string;
}
export namespace commands {
export function registerCommand(command: Command, callback: (...args: any[]) => any): Disposable
}
}
21 changes: 7 additions & 14 deletions packages/isolated-extension-api/src/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,11 @@
* Red Hat, Inc. - initial API and implementation
*/

import { RPCProtocolImpl, createExtensionProxyIdentifier } from '../api/rpc-protocol';
import { RPCProtocolImpl } from '../api/rpc-protocol';
import { Emitter } from '@theia/core/lib/common/event';
import { Disposable } from '@theia/core/lib/common/disposable';
export interface MainThreadCommandsShape extends Disposable {
$registerCommand(id: string): void;
$unregisterCommand(id: string): void;
$executeCommand<T>(id: string, args: any[]): Thenable<T>;
$getCommands(): Thenable<string[]>;
}
import { createAPI } from '../extension/extension-context';

const ctx: Worker = self as any;
const ctx: DedicatedWorkerGlobalScope = self as any;

const emmitter = new Emitter();
const rpc = new RPCProtocolImpl({
Expand All @@ -32,8 +26,7 @@ addEventListener('message', (message: any) => {
emmitter.fire(message.data);
});

const ExtHostCommands = createExtensionProxyIdentifier<MainThreadCommandsShape>("MainThreadCommands");

const proxy = rpc.getProxy(ExtHostCommands);

proxy.$registerCommand('foo');
const api = createAPI(rpc);
api.commands.registerCommand({ id: 'fooBar', label: 'Command From Extension' }, () => {
console.log("Hello from WebWorker Command");
});

0 comments on commit 4cccac0

Please sign in to comment.