Skip to content

Commit

Permalink
eclipse-theia#2561 implement 'Plugin' API
Browse files Browse the repository at this point in the history
Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
  • Loading branch information
evidolob authored and marcdumais-work committed Oct 12, 2018
1 parent 1e1a773 commit ac94047
Show file tree
Hide file tree
Showing 15 changed files with 501 additions and 236 deletions.
16 changes: 13 additions & 3 deletions packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { BackendInitializationFn, createAPI, PluginMetadata } from '@theia/plugin-ext';
import { BackendInitializationFn, createAPI, PluginMetadata, PluginManager } from '@theia/plugin-ext';

export const doInitialization: BackendInitializationFn = (rpc: any, pluginMetadata: PluginMetadata) => {
export const doInitialization: BackendInitializationFn = (rpc: any, manager: PluginManager, pluginMetadata: PluginMetadata) => {
const module = require('module');
const vscodeModuleName = 'vscode';
const vscode = createAPI(rpc);
const vscode = createAPI(rpc, manager);

// register the commands that are in the package.json file
const contributes: any = pluginMetadata.source.contributes;
Expand All @@ -37,6 +37,16 @@ export const doInitialization: BackendInitializationFn = (rpc: any, pluginMetada
}
};

// use Theia plugin api instead vscode extensions
(<any>vscode).extensions = {
get all(): any[] {
return vscode.plugins.all;
},
getExtension(pluginId: string): any | undefined {
return vscode.plugins.getPlugin(pluginId);
}
};

// add theia into global goal as 'vscode'
const g = global as any;
g[vscodeModuleName] = vscode;
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext-vscode/src/node/scanner-vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class VsCodePluginScanner implements PluginScanner {

getModel(plugin: PluginPackage): PluginModel {
return {
id: `${plugin.publisher}.${plugin.name}`,
name: plugin.name,
publisher: plugin.publisher,
version: plugin.version,
Expand Down
30 changes: 24 additions & 6 deletions packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { createProxyIdentifier, ProxyIdentifier } from './rpc-protocol';
import * as theia from '@theia/plugin';
import { PluginLifecycle, PluginModel, PluginMetadata } from '../common/plugin-protocol';
import { PluginLifecycle, PluginModel, PluginMetadata, PluginPackage } from '../common/plugin-protocol';
import { QueryParameters } from '../common/env';
import { TextEditorCursorStyle } from '../common/editor-options';
import { TextEditorLineNumbersStyle, EndOfLine, OverviewRulerLane, IndentAction } from '../plugin/types-impl';
Expand All @@ -33,18 +33,36 @@ import {
MarkerData
} from './model';

export interface HostedPluginManagerExt {
$initialize(contextPath: string, pluginMetadata: PluginMetadata): void;
$loadPlugin(contextPath: string, plugin: Plugin): void;
$stopPlugin(contextPath: string): PromiseLike<void>;
export interface PluginInitData {
plugins: PluginMetadata[];
}

export interface Plugin {
pluginPath: string;
initPath: string;
model: PluginModel;
rawModel: PluginPackage;
lifecycle: PluginLifecycle;
}

export interface PluginAPI {

}

export interface PluginManager {
getAllPlugins(): Plugin[];
getPluginById(pluginId: string): Plugin | undefined;
getPluginExport(pluginId: string): PluginAPI | undefined;
isRunning(pluginId: string): boolean;
activatePlugin(pluginId: string): PromiseLike<void>;
}

export interface PluginManagerExt {
$stopPlugin(contextPath: string): PromiseLike<void>;

$init(pluginInit: PluginInitData): PromiseLike<void>;
}

export interface CommandRegistryMain {
$registerCommand(command: theia.Command): void;

Expand Down Expand Up @@ -609,7 +627,7 @@ export const PLUGIN_RPC_CONTEXT = {
};

export const MAIN_RPC_CONTEXT = {
HOSTED_PLUGIN_MANAGER_EXT: createProxyIdentifier<HostedPluginManagerExt>('HostedPluginManagerExt'),
HOSTED_PLUGIN_MANAGER_EXT: createProxyIdentifier<PluginManagerExt>('PluginManagerExt'),
COMMAND_REGISTRY_EXT: createProxyIdentifier<CommandRegistryExt>('CommandRegistryExt'),
QUICK_OPEN_EXT: createProxyIdentifier<QuickOpenExt>('QuickOpenExt'),
WINDOW_STATE_EXT: createProxyIdentifier<WindowStateExt>('WindowStateExt'),
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
import { RPCProtocol } from '../api/rpc-protocol';
import { Disposable } from '@theia/core/lib/common/disposable';
import { LogPart } from './types';
import { CharacterPair, CommentRule } from '../api/plugin-api';
import { CharacterPair, CommentRule, PluginManager } from '../api/plugin-api';

export const hostedServicePath = '/services/hostedPlugin';

Expand Down Expand Up @@ -253,6 +253,7 @@ export interface PluginDeployerDirectoryHandler {
* This interface describes a plugin model object, which is populated from package.json.
*/
export interface PluginModel {
id: string;
name: string;
publisher: string;
version: string;
Expand Down Expand Up @@ -360,7 +361,7 @@ export interface PluginLifecycle {
* The export function of initialization module of backend plugin.
*/
export interface BackendInitializationFn {
(rpc: RPCProtocol, pluginMetadata: PluginMetadata): void;
(rpc: RPCProtocol, manager: PluginManager, pluginMetadata: PluginMetadata): void;
}

export interface BackendLoadingFn {
Expand Down
105 changes: 38 additions & 67 deletions packages/plugin-ext/src/hosted/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { injectable, inject, interfaces } from 'inversify';
import { PluginWorker } from '../../main/browser/plugin-worker';
import { HostedPluginServer, PluginMetadata } from '../../common/plugin-protocol';
import { HostedPluginWatcher } from './hosted-plugin-watcher';
import { MAIN_RPC_CONTEXT, Plugin } from '../../api/plugin-api';
import { MAIN_RPC_CONTEXT } from '../../api/plugin-api';
import { setUpPluginApi } from '../../main/browser/main-context';
import { RPCProtocol, RPCProtocolImpl } from '../../api/rpc-protocol';
import { ILogger } from '@theia/core';
Expand All @@ -43,9 +43,6 @@ export class HostedPluginSupport {

private theiaReadyPromise: Promise<any>;

private backendApiInitialized = false;
private frontendApiInitialized = false;

constructor(
@inject(PreferenceServiceImpl) private readonly preferenceServiceImpl: PreferenceServiceImpl
) {
Expand All @@ -58,81 +55,55 @@ export class HostedPluginSupport {
}

public initPlugins(): void {
this.server.getHostedPlugin().then((pluginMetadata: any) => {
if (pluginMetadata) {
this.loadPlugin(pluginMetadata, this.container);
const backendMetadata = this.server.getDeployedBackendMetadata();
const frontendMetadata = this.server.getDeployedFrontendMetadata();
Promise.all([backendMetadata, frontendMetadata, this.server.getHostedPlugin()]).then(metadata => {
const plugins = [...metadata['0'], ...metadata['1']];
if (metadata['2']) {
plugins.push(metadata['2']!);
}
this.loadPlugins(plugins, this.container);
});

const backendMetadata = this.server.getDeployedBackendMetadata();
}

backendMetadata.then((pluginMetadata: PluginMetadata[]) => {
pluginMetadata.forEach(metadata => this.loadPlugin(metadata, this.container));
});
loadPlugins(pluginsMetadata: PluginMetadata[], container: interfaces.Container): void {
const [frontend, backend] = this.initContributions(pluginsMetadata);
this.theiaReadyPromise.then(() => {
if (frontend) {
this.worker = new PluginWorker();
const hostedExtManager = this.worker.rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$init({ plugins: pluginsMetadata });
setUpPluginApi(this.worker.rpc, container);
}

this.server.getDeployedFrontendMetadata().then((pluginMetadata: PluginMetadata[]) => {
pluginMetadata.forEach(metadata => this.loadPlugin(metadata, this.container));
if (backend) {
const rpc = this.createServerRpc();
const hostedExtManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
hostedExtManager.$init({ plugins: pluginsMetadata });
setUpPluginApi(rpc, container);
}
});

}

public loadPlugin(pluginMetadata: PluginMetadata, container: interfaces.Container): void {
const pluginModel = pluginMetadata.model;
const pluginLifecycle = pluginMetadata.lifecycle;
this.logger.info('Ask to load the plugin with model ', pluginModel, ' and lifecycle', pluginLifecycle);
if (pluginMetadata.model.contributes) {
this.contributionHandler.handleContributions(pluginMetadata.model.contributes);
}
if (pluginModel.entryPoint!.frontend) {
this.logger.info(`Loading frontend hosted plugin: ${pluginModel.name}`);
this.worker = new PluginWorker();
private initContributions(pluginsMetadata: PluginMetadata[]): [boolean, boolean] {
const result: [boolean, boolean] = [false, false];
for (const plugin of pluginsMetadata) {
if (plugin.model.entryPoint.frontend) {
result[0] = true;
}

this.theiaReadyPromise.then(() => {
const hostedExtManager = this.worker.rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.frontend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
let frontendInitPath = pluginLifecycle.frontendInitPath;
if (frontendInitPath) {
hostedExtManager.$initialize(frontendInitPath, pluginMetadata);
} else {
frontendInitPath = '';
}
// we should create only one instance of the plugin api per connection
if (!this.frontendApiInitialized) {
setUpPluginApi(this.worker.rpc, container);
this.frontendApiInitialized = true;
}
hostedExtManager.$loadPlugin(frontendInitPath, plugin);
});
}
if (pluginModel.entryPoint!.backend) {
this.logger.info(`Loading backend hosted plugin: ${pluginModel.name}`);
const rpc = this.createServerRpc();
if (plugin.model.entryPoint.backend) {
result[1] = true;
}

this.theiaReadyPromise.then(() => {
const hostedExtManager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT);
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.backend!,
model: pluginModel,
lifecycle: pluginLifecycle
};
let backendInitPath = pluginLifecycle.backendInitPath;
if (backendInitPath) {
hostedExtManager.$initialize(backendInitPath, pluginMetadata);
} else {
backendInitPath = '';
}
// we should create only one instance of the plugin api per connection
if (!this.backendApiInitialized) {
setUpPluginApi(rpc, container);
this.backendApiInitialized = true;
}
hostedExtManager.$loadPlugin(backendInitPath, plugin);
});
if (plugin.model.contributes) {
this.contributionHandler.handleContributions(plugin.model.contributes);
}
}

return result;
}

private createServerRpc(): RPCProtocol {
Expand Down
81 changes: 48 additions & 33 deletions packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@

import { Emitter } from '@theia/core/lib/common/event';
import { RPCProtocolImpl } from '../../../api/rpc-protocol';
import { HostedPluginManagerExtImpl } from '../../plugin/hosted-plugin-manager';
import { PluginManagerExtImpl } from '../../../plugin/plugin-manager';
import { MAIN_RPC_CONTEXT, Plugin } from '../../../api/plugin-api';
import { createAPI, startPlugin } from '../../../plugin/plugin-context';
import { createAPI } from '../../../plugin/plugin-context';
import { getPluginId, PluginMetadata } from '../../../common/plugin-protocol';
import { Disposable } from '@theia/core/src/common';

// tslint:disable-next-line:no-any
const ctx = self as any;
const plugins = new Map<string, any>();

const emitter = new Emitter();
const rpc = new RPCProtocolImpl({
Expand All @@ -32,18 +31,17 @@ const rpc = new RPCProtocolImpl({
ctx.postMessage(m);
}
});
// tslint:disable-next-line:no-any
addEventListener('message', (message: any) => {
emitter.fire(message.data);
});
function initialize(contextPath: string, pluginMetadata: PluginMetadata): void {
ctx.importScripts('/context/' + contextPath);
}

const theia = createAPI(rpc);
ctx['theia'] = theia;

rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, new HostedPluginManagerExtImpl({
initialize(contextPath: string, pluginMetadata: PluginMetadata): void {
ctx.importScripts('/context/' + contextPath);
},
loadPlugin(contextPath: string, plugin: Plugin): void {
const pluginManager = new PluginManagerExtImpl({
// tslint:disable-next-line:no-any
loadPlugin(contextPath: string, plugin: Plugin): any {
if (isElectron()) {
ctx.importScripts(plugin.pluginPath);
} else {
Expand All @@ -55,32 +53,49 @@ rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, new HostedPluginManagerExtIm
console.error(`WebWorker: Cannot start plugin "${plugin.model.name}". Frontend plugin not found: "${plugin.lifecycle.frontendModuleName}"`);
return;
}
startPlugin(plugin, ctx[plugin.lifecycle.frontendModuleName], plugins);
return ctx[plugin.lifecycle.frontendModuleName];
}
},
stopPlugins(contextPath: string, pluginIds: string[]): void {
pluginIds.forEach(pluginId => {
const pluginData = plugins.get(pluginId);
if (pluginData) {
// call stop method
if (pluginData.stopPluginMethod) {
pluginData.stopPluginMethod();
init(rawPluginData: PluginMetadata[]): [Plugin[], Plugin[]] {
const result: Plugin[] = [];
const foreign: Plugin[] = [];
for (const plg of rawPluginData) {
const pluginModel = plg.model;
const pluginLifecycle = plg.lifecycle;
if (pluginModel.entryPoint!.frontend) {
let frontendInitPath = pluginLifecycle.frontendInitPath;
if (frontendInitPath) {
initialize(frontendInitPath, plg);
} else {
frontendInitPath = '';
}

// dispose any objects
const pluginContext = pluginData.pluginContext;
if (pluginContext) {
pluginContext.subscriptions.forEach((element: Disposable) => {
element.dispose();
});
}

// delete entry
plugins.delete(pluginId);
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.frontend!,
initPath: frontendInitPath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
};
result.push(plugin);
} else {
foreign.push({
pluginPath: pluginModel.entryPoint.backend!,
initPath: pluginLifecycle.backendInitPath!,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
});
}
});
}

return [result, foreign];
}
}));
});

const theia = createAPI(rpc, pluginManager);
ctx['theia'] = theia;

rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, pluginManager);

function isElectron() {
if (typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
Expand Down
Loading

0 comments on commit ac94047

Please sign in to comment.