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

[plug-in] Implement 'plugins' API #2695

Merged
merged 1 commit into from
Aug 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
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