From 10deabf1d67b8e9ba23d7646b36bbc486d2f0781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Thu, 16 Jul 2020 10:10:52 +0200 Subject: [PATCH] Make vscode/theia APIs contributed instead of built-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Mäder --- .../browser/plugin-vscode-frontend-module.ts | 4 + .../vscode-main-plugin-api-provider.ts | 35 +++ .../src/node/plugin-vscode-backend-module.ts | 6 +- .../node/plugin-vscode-cli-contribution.ts | 2 +- .../src/node/scanner-vscode.ts | 4 +- ...de-init.ts => vscode-api-node-provider.ts} | 104 ++++----- .../src/node/vscode-plugin-api-provider.ts | 29 +++ .../plugin-ext/src/common/plugin-api-rpc.ts | 10 +- .../src/common/plugin-ext-api-contribution.ts | 11 +- .../plugin-ext/src/common/plugin-protocol.ts | 18 +- .../plugin-ext/src/common/rpc-protocol.ts | 9 + .../src/hosted/browser/hosted-plugin.ts | 62 +++--- .../src/hosted/browser/worker/worker-main.ts | 68 +----- .../src/hosted/node/plugin-host-rpc.ts | 54 +---- .../src/hosted/node/scanners/scanner-theia.ts | 4 +- .../src/main/browser/main-context.ts | 145 ------------- .../browser/plugin-ext-frontend-module.ts | 4 + .../browser/theia-main-plugin-api-provider.ts | 201 ++++++++++++++++++ .../main/node/plugin-ext-backend-module.ts | 13 +- ...> theia-api-script-loader-contribution.ts} | 22 +- .../main/node/theia-plugin-api-provider.ts | 35 +++ .../node/theia-api-node-provider.ts} | 43 ++-- .../plugin-ext/src/plugin/plugin-context.ts | 52 +++-- .../plugin-ext/src/plugin/plugin-manager.ts | 26 +-- .../webworker/theia-api-worker-provider.ts | 55 +++++ packages/plugin-ext/webpack.config.js | 52 +++++ 26 files changed, 626 insertions(+), 442 deletions(-) create mode 100644 packages/plugin-ext-vscode/src/browser/vscode-main-plugin-api-provider.ts rename packages/plugin-ext-vscode/src/node/{plugin-vscode-init.ts => vscode-api-node-provider.ts} (54%) create mode 100644 packages/plugin-ext-vscode/src/node/vscode-plugin-api-provider.ts delete mode 100644 packages/plugin-ext/src/main/browser/main-context.ts create mode 100644 packages/plugin-ext/src/main/browser/theia-main-plugin-api-provider.ts rename packages/plugin-ext/src/main/node/{plugin-service.ts => theia-api-script-loader-contribution.ts} (73%) create mode 100644 packages/plugin-ext/src/main/node/theia-plugin-api-provider.ts rename packages/plugin-ext/src/{hosted/node/scanners/backend-init-theia.ts => plugin/node/theia-api-node-provider.ts} (53%) create mode 100644 packages/plugin-ext/src/plugin/webworker/theia-api-worker-provider.ts create mode 100644 packages/plugin-ext/webpack.config.js diff --git a/packages/plugin-ext-vscode/src/browser/plugin-vscode-frontend-module.ts b/packages/plugin-ext-vscode/src/browser/plugin-vscode-frontend-module.ts index 0bce5c3e799c3..9926fa902417e 100644 --- a/packages/plugin-ext-vscode/src/browser/plugin-vscode-frontend-module.ts +++ b/packages/plugin-ext-vscode/src/browser/plugin-vscode-frontend-module.ts @@ -18,8 +18,12 @@ import { ContainerModule } from 'inversify'; import { CommandContribution } from '@theia/core'; import { PluginVscodeCommandsContribution } from './plugin-vscode-commands-contribution'; import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; +import { VSCodeMainPluginAPIProvider } from './vscode-main-plugin-api-provider'; +import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; export default new ContainerModule(bind => { + bind(VSCodeMainPluginAPIProvider).toSelf().inSingletonScope(); + bind(MainPluginApiProvider).toService(VSCodeMainPluginAPIProvider); bind(PluginVSCodeEnvironment).toSelf().inSingletonScope(); bind(PluginVscodeCommandsContribution).toSelf().inSingletonScope(); bind(CommandContribution).toDynamicValue(context => context.container.get(PluginVscodeCommandsContribution)); diff --git a/packages/plugin-ext-vscode/src/browser/vscode-main-plugin-api-provider.ts b/packages/plugin-ext-vscode/src/browser/vscode-main-plugin-api-provider.ts new file mode 100644 index 0000000000000..12725a9701a96 --- /dev/null +++ b/packages/plugin-ext-vscode/src/browser/vscode-main-plugin-api-provider.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { interfaces, injectable } from 'inversify'; +import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common'; +import { TheiaAPIInitParameters } from '@theia/plugin-ext/lib/plugin/plugin-context'; +import { TheiaMainPluginAPIProvider } from '@theia/plugin-ext/lib/main/browser/theia-main-plugin-api-provider'; + +@injectable() +export class VSCodeMainPluginAPIProvider implements MainPluginApiProvider { + readonly id: string = 'vscode'; + + initialize(rpc: RPCProtocol, container: interfaces.Container): void { + // main API will be set up by theia + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async computeInitParameters?(rpc: RPCProtocol, container: interfaces.Container): Promise { + const theiaMainPluginAPIProvider: TheiaMainPluginAPIProvider = container.get(TheiaMainPluginAPIProvider); + return theiaMainPluginAPIProvider.computeInitParameters!(rpc, container); + } +} diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts index 1b39a16af6a09..d71e0819385a6 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-backend-module.ts @@ -23,13 +23,17 @@ import { PluginVsCodeDirectoryHandler } from './plugin-vscode-directory-handler' import { VsCodePluginScanner } from './scanner-vscode'; import { PluginVsCodeCliContribution } from './plugin-vscode-cli-contribution'; import { CliContribution } from '@theia/core/lib/node'; -import { PluginHostEnvironmentVariable } from '@theia/plugin-ext/lib/common'; +import { PluginHostEnvironmentVariable, ExtPluginApiProvider } from '@theia/plugin-ext/lib/common'; import { PluginVSCodeEnvironment } from '../common/plugin-vscode-environment'; import { PluginVSCodeDeployerParticipant } from './plugin-vscode-deployer-participant'; +import { VSCodePluginApiProvider } from './vscode-plugin-api-provider'; export default new ContainerModule(bind => { bind(PluginVSCodeEnvironment).toSelf().inSingletonScope(); + bind(VSCodePluginApiProvider).toSelf().inSingletonScope(); + bind(Symbol.for(ExtPluginApiProvider)).toService(VSCodePluginApiProvider); + bind(PluginVSCodeDeployerParticipant).toSelf().inSingletonScope(); bind(PluginDeployerParticipant).toService(PluginVSCodeDeployerParticipant); diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts b/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts index d6e7ac85fd18c..7b9dfdd72f690 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts +++ b/packages/plugin-ext-vscode/src/node/plugin-vscode-cli-contribution.ts @@ -18,7 +18,7 @@ import { injectable } from 'inversify'; import { Argv, Arguments } from 'yargs'; import { CliContribution } from '@theia/core/lib/node/cli'; import { PluginHostEnvironmentVariable } from '@theia/plugin-ext/lib/common'; -import { VSCODE_DEFAULT_API_VERSION } from './plugin-vscode-init'; +import { VSCODE_DEFAULT_API_VERSION } from './vscode-api-node-provider'; /** * CLI Contribution allowing to override the VS Code API version which is returned by `vscode.version` API call. */ diff --git a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts index 9ee5bda8a4a2f..df64d5e2858c2 100644 --- a/packages/plugin-ext-vscode/src/node/scanner-vscode.ts +++ b/packages/plugin-ext-vscode/src/node/scanner-vscode.ts @@ -76,9 +76,7 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca getLifecycle(plugin: PluginPackage): PluginLifecycle { return { startMethod: 'activate', - stopMethod: 'deactivate', - - backendInitPath: __dirname + '/plugin-vscode-init.js' + stopMethod: 'deactivate' }; } diff --git a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts b/packages/plugin-ext-vscode/src/node/vscode-api-node-provider.ts similarity index 54% rename from packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts rename to packages/plugin-ext-vscode/src/node/vscode-api-node-provider.ts index c2eabf61d49fd..b78b32cfedd39 100644 --- a/packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts +++ b/packages/plugin-ext-vscode/src/node/vscode-api-node-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2018-2019 Red Hat, Inc. + * Copyright (C) 2018 Red Hat, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,80 +13,58 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - import * as theia from '@theia/plugin'; -import { BackendInitializationFn, PluginAPIFactory, Plugin, emptyPlugin } from '@theia/plugin-ext'; +import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol'; +import { Plugin, emptyPlugin, PluginManager, PluginAPIFactory } from '@theia/plugin-ext/lib/common/plugin-api-rpc'; +import { ExtPluginApiBackendInitializationFn } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; +import { createAPIFactory } from '@theia/plugin-ext/lib/plugin/plugin-context'; +import { KeyValueStorageProxy } from '@theia/plugin-ext/lib/plugin/plugin-storage'; -export const VSCODE_DEFAULT_API_VERSION = '1.44.0'; - -/** Set up en as a default locale for VS Code extensions using vscode-nls */ -process.env['VSCODE_NLS_CONFIG'] = JSON.stringify({ locale: 'en', availableLanguages: {} }); -process.env['VSCODE_PID'] = process.env['THEIA_PARENT_PID']; +/* eslint-disable @typescript-eslint/no-explicit-any */ const pluginsApiImpl = new Map(); -const plugins = new Array(); let defaultApi: typeof theia; let isLoadOverride = false; -let pluginApiFactory: PluginAPIFactory; - -export enum ExtensionKind { - UI = 1, - Workspace = 2 -} - -export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIFactory, plugin: Plugin) => { - const vscode = Object.assign(apiFactory(plugin), { ExtensionKind }); - - // use Theia plugin api instead vscode extensions - (vscode).extensions = { - get all(): any[] { - return vscode.plugins.all.map(p => asExtension(p)); - }, - getExtension(pluginId: string): any | undefined { - return asExtension(vscode.plugins.getPlugin(pluginId)); - }, - get onDidChange(): theia.Event { - return vscode.plugins.onDidChange; - } - }; - - // override the version for vscode to be a VSCode version - (vscode).version = process.env['VSCODE_API_VERSION'] || VSCODE_DEFAULT_API_VERSION; +let apiFactory: PluginAPIFactory; +let plugins: PluginManager; - pluginsApiImpl.set(plugin.model.id, vscode); - plugins.push(plugin); - pluginApiFactory = apiFactory; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const provideApi: ExtPluginApiBackendInitializationFn = (rpc: RPCProtocol, pluginManager: PluginManager, storageProxy: KeyValueStorageProxy, initParams?: any) => { + apiFactory = createAPIFactory(pluginManager, rpc, storageProxy, initParams); + plugins = pluginManager; if (!isLoadOverride) { overrideInternalLoad(); isLoadOverride = true; } + }; function overrideInternalLoad(): void { const module = require('module'); - const vscodeModuleName = 'vscode'; // save original load method const internalLoad = module._load; - // if we try to resolve theia module, return the filename entry to use cache. + // if we try to resolve che module, return the filename entry to use cache. // eslint-disable-next-line @typescript-eslint/no-explicit-any module._load = function (request: string, parent: any, isMain: {}): any { - if (request !== vscodeModuleName) { + if (request !== 'vscode') { return internalLoad.apply(this, arguments); } const plugin = findPlugin(parent.filename); if (plugin) { - const apiImpl = pluginsApiImpl.get(plugin.model.id); + let apiImpl = pluginsApiImpl.get(plugin.model.id); + if (!apiImpl) { + apiImpl = doInitialization(plugin); + pluginsApiImpl.set(plugin.model.id, apiImpl); + } return apiImpl; } if (!defaultApi) { - console.warn(`Could not identify plugin for 'Theia' require call from ${parent.filename}`); - defaultApi = pluginApiFactory(emptyPlugin); + console.warn(`Could not identify plugin for 'Che' require call from ${parent.filename}`); + defaultApi = doInitialization(emptyPlugin); } return defaultApi; @@ -94,17 +72,45 @@ function overrideInternalLoad(): void { } function findPlugin(filePath: string): Plugin | undefined { - return plugins.find(plugin => filePath.startsWith(plugin.pluginFolder)); + return plugins.getAllPlugins().find(plugin => filePath.startsWith(plugin.pluginFolder)); } -function asExtension(plugin: any | undefined): any | undefined { +export default provideApi; + +export const VSCODE_DEFAULT_API_VERSION = '1.44.0'; + +/** Set up en as a default locale for VS Code extensions using vscode-nls */ +process.env['VSCODE_NLS_CONFIG'] = JSON.stringify({ locale: 'en', availableLanguages: {} }); +process.env['VSCODE_PID'] = process.env['THEIA_PARENT_PID']; + +export const doInitialization = (plugin: Plugin): typeof theia => { + const api: typeof theia = apiFactory(plugin); + + // use Theia plugin api instead vscode extensions + (api).extensions = { + get all(): any[] { + return api.plugins.all.map(p => asExtension(p)); + }, + getExtension(pluginId: string): any | undefined { + return asExtension(api.plugins.getPlugin(pluginId)); + }, + get onDidChange(): theia.Event { + return api.plugins.onDidChange; + } + }; + + // override the version for vscode to be a VSCode version + (api).version = process.env['VSCODE_API_VERSION'] || VSCODE_DEFAULT_API_VERSION; + return api; + +}; + +function asExtension(plugin: any | undefined): typeof theia | undefined { if (!plugin) { return plugin; } if (plugin.pluginPath) { plugin.extensionPath = plugin.pluginPath; } - // stub as a local VS Code extension (not running on a remote workspace) - plugin.extensionKind = ExtensionKind.UI; return plugin; } diff --git a/packages/plugin-ext-vscode/src/node/vscode-plugin-api-provider.ts b/packages/plugin-ext-vscode/src/node/vscode-plugin-api-provider.ts new file mode 100644 index 0000000000000..8d8825693b43d --- /dev/null +++ b/packages/plugin-ext-vscode/src/node/vscode-plugin-api-provider.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { injectable } from 'inversify'; +import * as path from 'path'; +import { ExtPluginApiProvider, ExtPluginApi } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; + +@injectable() +export class VSCodePluginApiProvider implements ExtPluginApiProvider { + + provideApi(): ExtPluginApi { + return { + id: 'vscode', + backendInitPath: path.join('@theia/plugin-ext-vscode/lib/node/vscode-api-node-provider.js') + }; + } +} diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index fcfdaeec87f66..6b511ec6ed433 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -18,7 +18,7 @@ import { createProxyIdentifier, ProxyIdentifier, RPCProtocol } from './rpc-protocol'; import * as theia from '@theia/plugin'; -import { PluginLifecycle, PluginModel, PluginMetadata, PluginPackage, IconUrl, PluginJsonValidationContribution } from './plugin-protocol'; +import { PluginLifecycle, PluginModel, PluginMetadata, PluginPackage, IconUrl } from './plugin-protocol'; import { QueryParameters } from './env'; import { TextEditorCursorStyle } from './editor-options'; import { @@ -177,13 +177,7 @@ export const emptyPlugin: Plugin = { }; export interface PluginManagerInitializeParams { - preferences: PreferenceData - globalState: KeysToKeysToAnyValue - workspaceState: KeysToKeysToAnyValue - env: EnvInit - extApi?: ExtPluginApi[] - webview: WebviewInitData - jsonValidation: PluginJsonValidationContribution[] + extApi?: { pluginApi: ExtPluginApi, initParameters?: any }[] } export interface PluginManagerStartParams { diff --git a/packages/plugin-ext/src/common/plugin-ext-api-contribution.ts b/packages/plugin-ext/src/common/plugin-ext-api-contribution.ts index 98f33b4ed3b4d..7515eea50bb42 100644 --- a/packages/plugin-ext/src/common/plugin-ext-api-contribution.ts +++ b/packages/plugin-ext/src/common/plugin-ext-api-contribution.ts @@ -16,6 +16,7 @@ import { RPCProtocol } from './rpc-protocol'; import { PluginManager, Plugin } from './plugin-api-rpc'; import { interfaces } from 'inversify'; +import { KeyValueStorageProxy } from '../plugin/plugin-storage'; export const ExtPluginApiProvider = 'extPluginApi'; /** @@ -33,6 +34,7 @@ export interface ExtPluginApiProvider { * This interface describes scripts for both plugin runtimes: frontend(WebWorker) and backend(NodeJs) */ export interface ExtPluginApi { + readonly id: string; /** * Path to the script which should be loaded to provide api, module should export `provideApi` function with @@ -47,11 +49,13 @@ export interface ExtPluginApi { } export interface ExtPluginApiFrontendInitializationFn { - (rpc: RPCProtocol, plugins: Map): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (rpc: RPCProtocol, pluginManager: PluginManager, storageProxy: KeyValueStorageProxy, plugins: Map, initParams?: any): void; } export interface ExtPluginApiBackendInitializationFn { - (rpc: RPCProtocol, pluginManager: PluginManager): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (rpc: RPCProtocol, pluginManager: PluginManager, storageProxy: KeyValueStorageProxy, initParams?: any): void; } /** @@ -78,5 +82,8 @@ export const MainPluginApiProvider = Symbol('mainPluginApi'); * [initialize](#initialize) will be called once per plugin runtime */ export interface MainPluginApiProvider { + readonly id: string; initialize(rpc: RPCProtocol, container: interfaces.Container): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + computeInitParameters?(rpc: RPCProtocol, container: interfaces.Container): Promise; } diff --git a/packages/plugin-ext/src/common/plugin-protocol.ts b/packages/plugin-ext/src/common/plugin-protocol.ts index bceb8c598bbe3..c6cbcf212d8ee 100644 --- a/packages/plugin-ext/src/common/plugin-protocol.ts +++ b/packages/plugin-ext/src/common/plugin-protocol.ts @@ -17,7 +17,7 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { RPCProtocol } from './rpc-protocol'; import { Disposable } from '@theia/core/lib/common/disposable'; import { LogPart, KeysToAnyValues, KeysToKeysToAnyValue } from './types'; -import { CharacterPair, CommentRule, PluginAPIFactory, Plugin } from './plugin-api-rpc'; +import { CharacterPair, CommentRule, Plugin } from './plugin-api-rpc'; import { ExtPluginApi } from './plugin-ext-api-contribution'; import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import { RecursivePartial } from '@theia/core/lib/common/types'; @@ -652,23 +652,7 @@ export interface PluginLifecycle { * Frontend module name, frontend plugin should expose this name. */ frontendModuleName?: string; - /** - * Path to the script which should do some initialization before frontend plugin is loaded. - */ - frontendInitPath?: string; - /** - * Path to the script which should do some initialization before backend plugin is loaded. - */ - backendInitPath?: string; } - -/** - * The export function of initialization module of backend plugin. - */ -export interface BackendInitializationFn { - (apiFactory: PluginAPIFactory, plugin: Plugin): void; -} - export interface BackendLoadingFn { (rpc: RPCProtocol, plugin: Plugin): void; } diff --git a/packages/plugin-ext/src/common/rpc-protocol.ts b/packages/plugin-ext/src/common/rpc-protocol.ts index e849aa69ea810..b327468277cf9 100644 --- a/packages/plugin-ext/src/common/rpc-protocol.ts +++ b/packages/plugin-ext/src/common/rpc-protocol.ts @@ -47,6 +47,11 @@ export interface RPCProtocol extends Disposable { */ set(identifier: ProxyIdentifier, instance: R): R; + /** + * Get a locally created instance + */ + get(identifier: ProxyIdentifier): R; + } export class ProxyIdentifier { @@ -134,6 +139,10 @@ export class RPCProtocolImpl implements RPCProtocol { return instance; } + get(identifier: ProxyIdentifier): R { + return this.locals.get(identifier.id); + } + private createProxy(proxyId: string): T { const handler = { get: (target: any, name: string) => { diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 885e18d8353c0..ba980990c9575 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -27,8 +27,7 @@ import { injectable, inject, interfaces, named, postConstruct } from 'inversify' import { PluginWorker } from '../../main/browser/plugin-worker'; import { PluginMetadata, getPluginId, HostedPluginServer, DeployedPlugin } from '../../common/plugin-protocol'; import { HostedPluginWatcher } from './hosted-plugin-watcher'; -import { MAIN_RPC_CONTEXT, PluginManagerExt, ConfigStorage, UIKind } from '../../common/plugin-api-rpc'; -import { setUpPluginApi } from '../../main/browser/main-context'; +import { MAIN_RPC_CONTEXT, PluginManagerExt, ConfigStorage } from '../../common/plugin-api-rpc'; import { RPCProtocol, RPCProtocolImpl } from '../../common/rpc-protocol'; import { Disposable, DisposableCollection, @@ -38,10 +37,8 @@ import { import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser/preferences'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { PluginContributionHandler } from '../../main/browser/plugin-contribution-handler'; -import { getQueryParameters } from '../../main/browser/env-main'; -import { MainPluginApiProvider } from '../../common/plugin-ext-api-contribution'; +import { MainPluginApiProvider, ExtPluginApi } from '../../common/plugin-ext-api-contribution'; import { PluginPathsService } from '../../main/common/plugin-paths-protocol'; -import { getPreferences } from '../../main/browser/preference-registry-main'; import { PluginServer } from '../../common/plugin-protocol'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; @@ -59,8 +56,6 @@ import { WidgetManager } from '@theia/core/lib/browser/widget-manager'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import URI from '@theia/core/lib/common/uri'; -import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; -import { environment } from '@theia/application-package/lib/environment'; import { JsonSchemaStore } from '@theia/core/lib/browser/json-schema-store'; export type PluginHost = 'frontend' | string; @@ -436,39 +431,35 @@ export class HostedPluginSupport { this.managers.set(host, manager); toDisconnect.push(Disposable.create(() => this.managers.delete(host))); - const [extApi, globalState, workspaceState, webviewResourceRoot, webviewCspSource, defaultShell, jsonValidation] = await Promise.all([ - this.server.getExtPluginAPI(), - this.pluginServer.getAllStorageValues(undefined), - this.pluginServer.getAllStorageValues({ - workspace: this.workspaceService.workspace, - roots: this.workspaceService.tryGetRoots() - }), - this.webviewEnvironment.resourceRoot(), - this.webviewEnvironment.cspSource(), - this.terminalService.getDefaultShell(), - this.jsonSchemaStore.schemas - ]); + const extApi = await this.server.getExtPluginAPI(); if (toDisconnect.disposed) { return undefined; } + const mainApiProviders = new Map(); + this.mainPluginApiProviders.getContributions().forEach(provider => { + mainApiProviders.set(provider.id, provider); + }); + + const apiInitParams: { + pluginApi: ExtPluginApi, + initParameters: any + }[] = []; + + for (const api of extApi) { + const result = { + pluginApi: api, + initParameters: undefined + }; + const mainProvider = mainApiProviders.get(api.id); + if (mainProvider && mainProvider.computeInitParameters) { + result.initParameters = await mainProvider.computeInitParameters(rpc, this.container); + } + apiInitParams.push(result); + }; + await manager.$init({ - preferences: getPreferences(this.preferenceProviderProvider, this.workspaceService.tryGetRoots()), - globalState, - workspaceState, - env: { - queryParams: getQueryParameters(), - language: navigator.language, - shell: defaultShell, - uiKind: environment.electron.is() ? UIKind.Desktop : UIKind.Web, - appName: FrontendApplicationConfigProvider.get().applicationName - }, - extApi, - webview: { - webviewResourceRoot, - webviewCspSource - }, - jsonValidation + extApi: apiInitParams, }); if (toDisconnect.disposed) { return undefined; @@ -479,7 +470,6 @@ export class HostedPluginSupport { protected initRpc(host: PluginHost, pluginId: string): RPCProtocol { const rpc = host === 'frontend' ? new PluginWorker().rpc : this.createServerRpc(pluginId, host); - setUpPluginApi(rpc, this.container); this.mainPluginApiProviders.getContributions().forEach(p => p.initialize(rpc, this.container)); return rpc; } diff --git a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts index 6c66ee88b7b99..b0a1db0036325 100644 --- a/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts +++ b/packages/plugin-ext/src/hosted/browser/worker/worker-main.ts @@ -17,25 +17,19 @@ import { Emitter } from '@theia/core/lib/common/event'; import { RPCProtocolImpl } from '../../../common/rpc-protocol'; import { PluginManagerExtImpl } from '../../../plugin/plugin-manager'; -import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin } from '../../../common/plugin-api-rpc'; -import { createAPIFactory } from '../../../plugin/plugin-context'; +import { MAIN_RPC_CONTEXT, Plugin } from '../../../common/plugin-api-rpc'; import { getPluginId, PluginMetadata, PluginPackage } from '../../../common/plugin-protocol'; -import * as theia from '@theia/plugin'; import { PreferenceRegistryExtImpl } from '../../../plugin/preference-registry'; import { ExtPluginApi } from '../../../common/plugin-ext-api-contribution'; -import { createDebugExtStub } from './debug-stub'; import { EditorsAndDocumentsExtImpl } from '../../../plugin/editors-and-documents'; import { WorkspaceExtImpl } from '../../../plugin/workspace'; import { MessageRegistryExt } from '../../../plugin/message-registry'; -import { WorkerEnvExtImpl } from './worker-env-ext'; -import { ClipboardExt } from '../../../plugin/clipboard-ext'; -import { KeyValueStorageProxy } from '../../../plugin/plugin-storage'; import { WebviewsExtImpl } from '../../../plugin/webviews'; +import { KeyValueStorageProxy } from '../../../plugin/plugin-storage'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const ctx = self as any; -const pluginsApiImpl = new Map(); const pluginsModulesNames = new Map(); const emitter = new Emitter(); @@ -49,17 +43,12 @@ const rpc = new RPCProtocolImpl({ addEventListener('message', (message: any) => { emitter.fire(message.data); }); -function initialize(contextPath: string, pluginMetadata: PluginMetadata): void { - ctx.importScripts('/context/' + contextPath); -} -const envExt = new WorkerEnvExtImpl(rpc); + const storageProxy = new KeyValueStorageProxy(rpc); const editorsAndDocuments = new EditorsAndDocumentsExtImpl(rpc); const messageRegistryExt = new MessageRegistryExt(rpc); const workspaceExt = new WorkspaceExtImpl(rpc, editorsAndDocuments, messageRegistryExt); const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt); -const debugExt = createDebugExtStub(rpc); -const clipboardExt = new ClipboardExt(rpc); const webviewExt = new WebviewsExtImpl(rpc, workspaceExt); const pluginManager = new PluginManagerExtImpl({ @@ -88,12 +77,6 @@ const pluginManager = new PluginManagerExtImpl({ const pluginModel = plg.model; const pluginLifecycle = plg.lifecycle; if (pluginModel.entryPoint!.frontend) { - let frontendInitPath = pluginLifecycle.frontendInitPath; - if (frontendInitPath) { - initialize(frontendInitPath, plg); - } else { - frontendInitPath = ''; - } const plugin: Plugin = { pluginPath: pluginModel.entryPoint.frontend!, pluginFolder: pluginModel.packagePath, @@ -104,8 +87,6 @@ const pluginManager = new PluginManagerExtImpl({ } }; result.push(plugin); - const apiImpl = apiFactory(plugin); - pluginsApiImpl.set(plugin.model.id, apiImpl); pluginsModulesNames.set(plugin.lifecycle.frontendModuleName!, plugin); } else { foreign.push({ @@ -122,12 +103,13 @@ const pluginManager = new PluginManagerExtImpl({ return [result, foreign]; }, - initExtApi(extApi: ExtPluginApi[]): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initExtApi(extApi: { pluginApi: ExtPluginApi, initParameters?: any }[]): void { for (const api of extApi) { try { - if (api.frontendExtApi) { - ctx.importScripts(api.frontendExtApi.initPath); - ctx[api.frontendExtApi.initVariable][api.frontendExtApi.initFunction](rpc, pluginsModulesNames); + if (api.pluginApi.frontendExtApi) { + ctx.importScripts(api.pluginApi.frontendExtApi.initPath); + ctx[api.pluginApi.frontendExtApi.initVariable][api.pluginApi.frontendExtApi.initFunction](rpc, pluginsModulesNames, api.initParameters); } } catch (e) { @@ -135,39 +117,7 @@ const pluginManager = new PluginManagerExtImpl({ } } } -}, envExt, storageProxy, preferenceRegistryExt, webviewExt, rpc); - -const apiFactory = createAPIFactory( - rpc, - pluginManager, - envExt, - debugExt, - preferenceRegistryExt, - editorsAndDocuments, - workspaceExt, - messageRegistryExt, - clipboardExt, - webviewExt -); -let defaultApi: typeof theia; - -const handler = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get: (target: any, name: string) => { - const plugin = pluginsModulesNames.get(name); - if (plugin) { - const apiImpl = pluginsApiImpl.get(plugin.model.id); - return apiImpl; - } - - if (!defaultApi) { - defaultApi = apiFactory(emptyPlugin); - } - - return defaultApi; - } -}; -ctx['theia'] = new Proxy(Object.create(null), handler); +}, rpc, storageProxy); rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, pluginManager); rpc.set(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT, editorsAndDocuments); diff --git a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts index 4fbc8f817b64b..749a96d907ee3 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts @@ -15,18 +15,15 @@ ********************************************************************************/ import { PluginManagerExtImpl } from '../../plugin/plugin-manager'; -import { MAIN_RPC_CONTEXT, Plugin, PluginAPIFactory } from '../../common/plugin-api-rpc'; +import { MAIN_RPC_CONTEXT, Plugin } from '../../common/plugin-api-rpc'; import { PluginMetadata } from '../../common/plugin-protocol'; -import { createAPIFactory } from '../../plugin/plugin-context'; import { EnvExtImpl } from '../../plugin/env'; import { PreferenceRegistryExtImpl } from '../../plugin/preference-registry'; import { ExtPluginApi } from '../../common/plugin-ext-api-contribution'; -import { DebugExtImpl } from '../../plugin/node/debug/debug'; import { EditorsAndDocumentsExtImpl } from '../../plugin/editors-and-documents'; import { WorkspaceExtImpl } from '../../plugin/workspace'; import { MessageRegistryExt } from '../../plugin/message-registry'; import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext'; -import { ClipboardExt } from '../../plugin/clipboard-ext'; import { loadManifest } from './plugin-manifest-loader'; import { KeyValueStorageProxy } from '../../plugin/plugin-storage'; import { WebviewsExtImpl } from '../../plugin/webviews'; @@ -36,8 +33,6 @@ import { WebviewsExtImpl } from '../../plugin/webviews'; */ export class PluginHostRPC { - private apiFactory: PluginAPIFactory; - private pluginManager: PluginManagerExtImpl; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -47,12 +42,10 @@ export class PluginHostRPC { initialize(): void { const envExt = new EnvNodeExtImpl(this.rpc); const storageProxy = new KeyValueStorageProxy(this.rpc); - const debugExt = new DebugExtImpl(this.rpc); const editorsAndDocumentsExt = new EditorsAndDocumentsExtImpl(this.rpc); const messageRegistryExt = new MessageRegistryExt(this.rpc); const workspaceExt = new WorkspaceExtImpl(this.rpc, editorsAndDocumentsExt, messageRegistryExt); const preferenceRegistryExt = new PreferenceRegistryExtImpl(this.rpc, workspaceExt); - const clipboardExt = new ClipboardExt(this.rpc); const webviewExt = new WebviewsExtImpl(this.rpc, workspaceExt); this.pluginManager = this.createPluginManager(envExt, storageProxy, preferenceRegistryExt, webviewExt, this.rpc); this.rpc.set(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT, this.pluginManager); @@ -61,43 +54,17 @@ export class PluginHostRPC { this.rpc.set(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT, preferenceRegistryExt); this.rpc.set(MAIN_RPC_CONTEXT.STORAGE_EXT, storageProxy); this.rpc.set(MAIN_RPC_CONTEXT.WEBVIEWS_EXT, webviewExt); - - this.apiFactory = createAPIFactory( - this.rpc, - this.pluginManager, - envExt, - debugExt, - preferenceRegistryExt, - editorsAndDocumentsExt, - workspaceExt, - messageRegistryExt, - clipboardExt, - webviewExt - ); } async terminate(): Promise { await this.pluginManager.terminate(); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - initContext(contextPath: string, plugin: Plugin): any { - const { name, version } = plugin.rawModel; - console.log('PLUGIN_HOST(' + process.pid + '): initializing(' + name + '@' + version + ' with ' + contextPath + ')'); - try { - const backendInit = require(contextPath); - backendInit.doInitialization(this.apiFactory, plugin); - } catch (e) { - console.error(e); - } - } - createPluginManager( envExt: EnvExtImpl, storageProxy: KeyValueStorageProxy, preferencesManager: PreferenceRegistryExtImpl, webview: WebviewsExtImpl, // eslint-disable-next-line @typescript-eslint/no-explicit-any rpc: any): PluginManagerExtImpl { const { extensionTestsPath } = process.env; - const self = this; const pluginManager = new PluginManagerExtImpl({ // eslint-disable-next-line @typescript-eslint/no-explicit-any loadPlugin(plugin: Plugin): any { @@ -161,12 +128,6 @@ export class PluginHostRPC { rawModel }); } else { - let backendInitPath = pluginLifecycle.backendInitPath; - // if no init path, try to init as regular Theia plugin - if (!backendInitPath) { - backendInitPath = __dirname + '/scanners/backend-init-theia.js'; - } - const plugin: Plugin = { pluginPath: pluginModel.entryPoint.backend!, pluginFolder: pluginModel.packagePath, @@ -175,8 +136,6 @@ export class PluginHostRPC { rawModel }; - self.initContext(backendInitPath, plugin); - result.push(plugin); } } catch (e) { @@ -185,12 +144,13 @@ export class PluginHostRPC { } return [result, foreign]; }, - initExtApi(extApi: ExtPluginApi[]): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initExtApi(extApi: { pluginApi: ExtPluginApi, initParameters?: any }[]): void { for (const api of extApi) { - if (api.backendInitPath) { + if (api.pluginApi.backendInitPath) { try { - const extApiInit = require(api.backendInitPath); - extApiInit.provideApi(rpc, pluginManager); + const extApiInit = require(api.pluginApi.backendInitPath); + extApiInit.provideApi(rpc, pluginManager, storageProxy, api.initParameters); } catch (e) { console.error(e); } @@ -225,7 +185,7 @@ export class PluginHostRPC { `Path ${extensionTestsPath} does not point to a valid extension test runner.` ); } : undefined - }, envExt, storageProxy, preferencesManager, webview, rpc); + }, rpc, storageProxy); return pluginManager; } } diff --git a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts index c13341be70384..0dc0f94bf0bd2 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts +++ b/packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts @@ -114,9 +114,7 @@ export class TheiaPluginScanner implements PluginScanner { return { startMethod: 'start', stopMethod: 'stop', - frontendModuleName: buildFrontendModuleName(plugin), - - backendInitPath: __dirname + '/backend-init-theia.js' + frontendModuleName: buildFrontendModuleName(plugin) }; } diff --git a/packages/plugin-ext/src/main/browser/main-context.ts b/packages/plugin-ext/src/main/browser/main-context.ts deleted file mode 100644 index e778cacf5b22e..0000000000000 --- a/packages/plugin-ext/src/main/browser/main-context.ts +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************** - * Copyright (C) 2018 Red Hat, Inc. and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { interfaces } from 'inversify'; -import { CommandRegistryMainImpl } from './command-registry-main'; -import { PreferenceRegistryMainImpl } from './preference-registry-main'; -import { QuickOpenMainImpl } from './quick-open-main'; -import { RPCProtocol } from '../../common/rpc-protocol'; -import { PLUGIN_RPC_CONTEXT, LanguagesMainFactory, OutputChannelRegistryFactory } from '../../common/plugin-api-rpc'; -import { MessageRegistryMainImpl } from './message-registry-main'; -import { WindowStateMain } from './window-state-main'; -import { WorkspaceMainImpl } from './workspace-main'; -import { StatusBarMessageRegistryMainImpl } from './status-bar-message-registry-main'; -import { EnvMainImpl } from './env-main'; -import { EditorsAndDocumentsMain } from './editors-and-documents-main'; -import { TerminalServiceMainImpl } from './terminal-main'; -import { DialogsMainImpl } from './dialogs-main'; -import { TreeViewsMainImpl } from './view/tree-views-main'; -import { NotificationMainImpl } from './notification-main'; -import { ConnectionMainImpl } from './connection-main'; -import { WebviewsMainImpl } from './webviews-main'; -import { TasksMainImpl } from './tasks-main'; -import { StorageMainImpl } from './plugin-storage'; -import { LanguagesContributionMainImpl } from './languages-contribution-main'; -import { DebugMainImpl } from './debug/debug-main'; -import { FileSystemMainImpl } from './file-system-main'; -import { ScmMainImpl } from './scm-main'; -import { DecorationsMainImpl } from './decorations/decorations-main'; -import { ClipboardMainImpl } from './clipboard-main'; -import { DocumentsMainImpl } from './documents-main'; -import { TextEditorsMainImpl } from './text-editors-main'; -import { EditorManager } from '@theia/editor/lib/browser'; -import { EditorModelService } from './text-editor-model-service'; -import { OpenerService } from '@theia/core/lib/browser/opener-service'; -import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; -import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service'; -import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; -import { UntitledResourceResolver } from './editor/untitled-resource'; -import { FileResourceResolver } from '@theia/filesystem/lib/browser'; - -export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void { - const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.COMMAND_REGISTRY_MAIN, commandRegistryMain); - - const quickOpenMain = new QuickOpenMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.QUICK_OPEN_MAIN, quickOpenMain); - - const workspaceMain = new WorkspaceMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.WORKSPACE_MAIN, workspaceMain); - - const dialogsMain = new DialogsMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.DIALOGS_MAIN, dialogsMain); - - const messageRegistryMain = new MessageRegistryMainImpl(container); - rpc.set(PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN, messageRegistryMain); - - const preferenceRegistryMain = new PreferenceRegistryMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.PREFERENCE_REGISTRY_MAIN, preferenceRegistryMain); - - const editorsAndDocuments = new EditorsAndDocumentsMain(rpc, container); - - const modelService = container.get(EditorModelService); - const editorManager = container.get(EditorManager); - const openerService = container.get(OpenerService); - const shell = container.get(ApplicationShell); - const untitledResourceResolver = container.get(UntitledResourceResolver); - const fileResourceResolver = container.get(FileResourceResolver); - const documentsMain = new DocumentsMainImpl(editorsAndDocuments, modelService, rpc, editorManager, openerService, shell, untitledResourceResolver, fileResourceResolver); - rpc.set(PLUGIN_RPC_CONTEXT.DOCUMENTS_MAIN, documentsMain); - - const bulkEditService = container.get(MonacoBulkEditService); - const monacoEditorService = container.get(MonacoEditorService); - const editorsMain = new TextEditorsMainImpl(editorsAndDocuments, rpc, bulkEditService, monacoEditorService); - rpc.set(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN, editorsMain); - - // start listening only after all clients are subscribed to events - editorsAndDocuments.listen(); - - const statusBarMessageRegistryMain = new StatusBarMessageRegistryMainImpl(container); - rpc.set(PLUGIN_RPC_CONTEXT.STATUS_BAR_MESSAGE_REGISTRY_MAIN, statusBarMessageRegistryMain); - - const envMain = new EnvMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.ENV_MAIN, envMain); - - const notificationMain = new NotificationMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN, notificationMain); - - const terminalMain = new TerminalServiceMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.TERMINAL_MAIN, terminalMain); - - const treeViewsMain = new TreeViewsMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.TREE_VIEWS_MAIN, treeViewsMain); - - const outputChannelRegistryFactory: OutputChannelRegistryFactory = container.get(OutputChannelRegistryFactory); - const outputChannelRegistryMain = outputChannelRegistryFactory(); - rpc.set(PLUGIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_MAIN, outputChannelRegistryMain); - - const languagesMainFactory: LanguagesMainFactory = container.get(LanguagesMainFactory); - const languagesMain = languagesMainFactory(rpc); - rpc.set(PLUGIN_RPC_CONTEXT.LANGUAGES_MAIN, languagesMain); - - const webviewsMain = new WebviewsMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.WEBVIEWS_MAIN, webviewsMain); - - const storageMain = new StorageMainImpl(container); - rpc.set(PLUGIN_RPC_CONTEXT.STORAGE_MAIN, storageMain); - - const connectionMain = new ConnectionMainImpl(rpc); - rpc.set(PLUGIN_RPC_CONTEXT.CONNECTION_MAIN, connectionMain); - - const tasksMain = new TasksMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.TASKS_MAIN, tasksMain); - - const languagesContribution = new LanguagesContributionMainImpl(rpc, container, connectionMain); - rpc.set(PLUGIN_RPC_CONTEXT.LANGUAGES_CONTRIBUTION_MAIN, languagesContribution); - - const debugMain = new DebugMainImpl(rpc, connectionMain, container); - rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain); - - rpc.set(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_MAIN, new FileSystemMainImpl(rpc, container)); - - const scmMain = new ScmMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.SCM_MAIN, scmMain); - - const decorationsMain = new DecorationsMainImpl(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.DECORATIONS_MAIN, decorationsMain); - - const windowMain = new WindowStateMain(rpc, container); - rpc.set(PLUGIN_RPC_CONTEXT.WINDOW_MAIN, windowMain); - - const clipboardMain = new ClipboardMainImpl(container); - rpc.set(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN, clipboardMain); -} diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts index 864ff8b2753f5..3bf6dea5b7792 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts +++ b/packages/plugin-ext/src/main/browser/plugin-ext-frontend-module.ts @@ -71,9 +71,13 @@ import { WebviewResourceCache } from './webview/webview-resource-cache'; import { PluginIconThemeService, PluginIconThemeFactory, PluginIconThemeDefinition, PluginIconTheme } from './plugin-icon-theme-service'; import { PluginTreeViewNodeLabelProvider } from './view/plugin-tree-view-node-label-provider'; import { WebviewWidgetFactory } from './webview/webview-widget-factory'; +import { TheiaMainPluginAPIProvider } from './theia-main-plugin-api-provider'; export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(TheiaMainPluginAPIProvider).toSelf().inSingletonScope(); + bind(MainPluginApiProvider).toService(TheiaMainPluginAPIProvider); + bind(LanguagesMainImpl).toSelf().inTransientScope(); bind(LanguagesMainFactory).toFactory(context => (rpc: RPCProtocol) => { const child = context.container.createChild(); diff --git a/packages/plugin-ext/src/main/browser/theia-main-plugin-api-provider.ts b/packages/plugin-ext/src/main/browser/theia-main-plugin-api-provider.ts new file mode 100644 index 0000000000000..229b57cac35f7 --- /dev/null +++ b/packages/plugin-ext/src/main/browser/theia-main-plugin-api-provider.ts @@ -0,0 +1,201 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { interfaces, injectable } from 'inversify'; +import { CommandRegistryMainImpl } from './command-registry-main'; +import { PreferenceRegistryMainImpl } from './preference-registry-main'; +import { QuickOpenMainImpl } from './quick-open-main'; +import { RPCProtocol } from '../../common/rpc-protocol'; +import { PLUGIN_RPC_CONTEXT, LanguagesMainFactory, OutputChannelRegistryFactory, UIKind } from '../../common/plugin-api-rpc'; +import { MessageRegistryMainImpl } from './message-registry-main'; +import { WindowStateMain } from './window-state-main'; +import { WorkspaceMainImpl } from './workspace-main'; +import { StatusBarMessageRegistryMainImpl } from './status-bar-message-registry-main'; +import { EnvMainImpl } from './env-main'; +import { EditorsAndDocumentsMain } from './editors-and-documents-main'; +import { TerminalServiceMainImpl } from './terminal-main'; +import { DialogsMainImpl } from './dialogs-main'; +import { TreeViewsMainImpl } from './view/tree-views-main'; +import { NotificationMainImpl } from './notification-main'; +import { ConnectionMainImpl } from './connection-main'; +import { WebviewsMainImpl } from './webviews-main'; +import { TasksMainImpl } from './tasks-main'; +import { StorageMainImpl } from './plugin-storage'; +import { LanguagesContributionMainImpl } from './languages-contribution-main'; +import { DebugMainImpl } from './debug/debug-main'; +import { FileSystemMainImpl } from './file-system-main'; +import { ScmMainImpl } from './scm-main'; +import { DecorationsMainImpl } from './decorations/decorations-main'; +import { ClipboardMainImpl } from './clipboard-main'; +import { DocumentsMainImpl } from './documents-main'; +import { TextEditorsMainImpl } from './text-editors-main'; +import { EditorManager } from '@theia/editor/lib/browser'; +import { EditorModelService } from './text-editor-model-service'; +import { OpenerService } from '@theia/core/lib/browser/opener-service'; +import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell'; +import { MonacoBulkEditService } from '@theia/monaco/lib/browser/monaco-bulk-edit-service'; +import { MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service'; +import { UntitledResourceResolver } from './editor/untitled-resource'; +import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; +import { WorkspaceService } from '@theia/workspace/lib/browser'; +import { FileResourceResolver } from '@theia/filesystem/lib/browser'; +import { MainPluginApiProvider, PluginServer } from '../../common'; +import { TheiaAPIInitParameters } from '../../plugin/plugin-context'; +import { WebviewEnvironment } from './webview/webview-environment'; +import { PreferenceProviderProvider } from '@theia/core/lib/browser/preferences'; +import { getPreferences } from './preference-registry-main'; +import { getQueryParameters } from './env-main'; +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; +import { environment } from '@theia/application-package/lib/environment'; +import { JsonSchemaStore } from '@theia/core/lib/browser/json-schema-store'; + +@injectable() +export class TheiaMainPluginAPIProvider implements MainPluginApiProvider { + readonly id: string = 'theia'; + + initialize(rpc: RPCProtocol, container: interfaces.Container): void { + + const commandRegistryMain = new CommandRegistryMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.COMMAND_REGISTRY_MAIN, commandRegistryMain); + + const quickOpenMain = new QuickOpenMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.QUICK_OPEN_MAIN, quickOpenMain); + + const workspaceMain = new WorkspaceMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.WORKSPACE_MAIN, workspaceMain); + + const dialogsMain = new DialogsMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.DIALOGS_MAIN, dialogsMain); + + const messageRegistryMain = new MessageRegistryMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN, messageRegistryMain); + + const preferenceRegistryMain = new PreferenceRegistryMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.PREFERENCE_REGISTRY_MAIN, preferenceRegistryMain); + + const editorsAndDocuments = new EditorsAndDocumentsMain(rpc, container); + + const modelService = container.get(EditorModelService); + const editorManager = container.get(EditorManager); + const openerService = container.get(OpenerService); + const shell = container.get(ApplicationShell); + const untitledResourceResolver = container.get(UntitledResourceResolver); + const fileResourceResolver = container.get(FileResourceResolver); + const documentsMain = new DocumentsMainImpl(editorsAndDocuments, modelService, rpc, editorManager, openerService, shell, untitledResourceResolver, fileResourceResolver); + rpc.set(PLUGIN_RPC_CONTEXT.DOCUMENTS_MAIN, documentsMain); + + const bulkEditService = container.get(MonacoBulkEditService); + const monacoEditorService = container.get(MonacoEditorService); + const editorsMain = new TextEditorsMainImpl(editorsAndDocuments, rpc, bulkEditService, monacoEditorService); + rpc.set(PLUGIN_RPC_CONTEXT.TEXT_EDITORS_MAIN, editorsMain); + + // start listening only after all clients are subscribed to events + editorsAndDocuments.listen(); + + const statusBarMessageRegistryMain = new StatusBarMessageRegistryMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.STATUS_BAR_MESSAGE_REGISTRY_MAIN, statusBarMessageRegistryMain); + + const envMain = new EnvMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.ENV_MAIN, envMain); + + const notificationMain = new NotificationMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN, notificationMain); + + const terminalMain = new TerminalServiceMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.TERMINAL_MAIN, terminalMain); + + const treeViewsMain = new TreeViewsMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.TREE_VIEWS_MAIN, treeViewsMain); + + const outputChannelRegistryFactory: OutputChannelRegistryFactory = container.get(OutputChannelRegistryFactory); + const outputChannelRegistryMain = outputChannelRegistryFactory(); + rpc.set(PLUGIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_MAIN, outputChannelRegistryMain); + + const languagesMainFactory: LanguagesMainFactory = container.get(LanguagesMainFactory); + const languagesMain = languagesMainFactory(rpc); + rpc.set(PLUGIN_RPC_CONTEXT.LANGUAGES_MAIN, languagesMain); + + const webviewsMain = new WebviewsMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.WEBVIEWS_MAIN, webviewsMain); + + const storageMain = new StorageMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.STORAGE_MAIN, storageMain); + + const connectionMain = new ConnectionMainImpl(rpc); + rpc.set(PLUGIN_RPC_CONTEXT.CONNECTION_MAIN, connectionMain); + + const tasksMain = new TasksMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.TASKS_MAIN, tasksMain); + + const languagesContribution = new LanguagesContributionMainImpl(rpc, container, connectionMain); + rpc.set(PLUGIN_RPC_CONTEXT.LANGUAGES_CONTRIBUTION_MAIN, languagesContribution); + + const debugMain = new DebugMainImpl(rpc, connectionMain, container); + rpc.set(PLUGIN_RPC_CONTEXT.DEBUG_MAIN, debugMain); + + rpc.set(PLUGIN_RPC_CONTEXT.FILE_SYSTEM_MAIN, new FileSystemMainImpl(rpc, container)); + + const scmMain = new ScmMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.SCM_MAIN, scmMain); + + const decorationsMain = new DecorationsMainImpl(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.DECORATIONS_MAIN, decorationsMain); + + const windowMain = new WindowStateMain(rpc, container); + rpc.set(PLUGIN_RPC_CONTEXT.WINDOW_MAIN, windowMain); + + const clipboardMain = new ClipboardMainImpl(container); + rpc.set(PLUGIN_RPC_CONTEXT.CLIPBOARD_MAIN, clipboardMain); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async computeInitParameters?(rpc: RPCProtocol, container: interfaces.Container): Promise { + const pluginServer: PluginServer = container.get(PluginServer); + const webviewEnvironment: WebviewEnvironment = container.get(WebviewEnvironment); + const terminalService: TerminalService = container.get(TerminalService); + const preferenceProviderProvider: PreferenceProviderProvider = container.get(PreferenceProviderProvider); + const workspaceService: WorkspaceService = container.get(WorkspaceService); + const jsonSchemaStore = container.get(JsonSchemaStore); + const [globalState, workspaceState, webviewResourceRoot, webviewCspSource, defaultShell, jsonSchemas] = await Promise.all([ + pluginServer.getAllStorageValues(undefined), + pluginServer.getAllStorageValues({ + workspace: workspaceService.workspace, + roots: workspaceService.tryGetRoots() + }), + webviewEnvironment.resourceRoot(), + webviewEnvironment.cspSource(), + terminalService.getDefaultShell(), + jsonSchemaStore.schemas + ]); + + return { + preferences: getPreferences(preferenceProviderProvider, workspaceService.tryGetRoots()), + globalState, + workspaceState, + env: { + queryParams: getQueryParameters(), + language: navigator.language, + shell: defaultShell, + uiKind: environment.electron.is() ? UIKind.Desktop : UIKind.Web, + appName: FrontendApplicationConfigProvider.get().applicationName + }, + webview: { + webviewResourceRoot, + webviewCspSource + }, + jsonValidation: jsonSchemas + }; + } +} diff --git a/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts b/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts index 418ac7e209fce..3266f735a737c 100644 --- a/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts +++ b/packages/plugin-ext/src/main/node/plugin-ext-backend-module.ts @@ -15,7 +15,6 @@ ********************************************************************************/ import { interfaces } from 'inversify'; -import { PluginApiContribution } from './plugin-service'; import { BackendApplicationContribution, CliContribution } from '@theia/core/lib/node'; import { PluginsKeyValueStorage } from './plugins-key-value-storage'; import { PluginDeployerContribution } from './plugin-deployer-contribution'; @@ -38,8 +37,17 @@ import { WebviewResourceLoaderImpl } from './webview-resource-loader-impl'; import { WebviewResourceLoaderPath } from '../common/webview-protocol'; import { PluginTheiaEnvironment } from '../common/plugin-theia-environment'; import { PluginTheiaDeployerParticipant } from './plugin-theia-deployer-participant'; +import { TheiaApiScriptLoaderContribution } from './theia-api-script-loader-contribution'; +import { TheiaPluginApiProvider } from './theia-plugin-api-provider'; +import { ExtPluginApiProvider } from '../../common/plugin-ext-api-contribution'; export function bindMainBackend(bind: interfaces.Bind): void { + bind(TheiaApiScriptLoaderContribution).toSelf().inSingletonScope(); + bind(BackendApplicationContribution).toService(TheiaApiScriptLoaderContribution); + + bind(TheiaPluginApiProvider).toSelf().inSingletonScope(); + bind(Symbol.for(ExtPluginApiProvider)).toService(TheiaPluginApiProvider); + bind(WebviewResourceLoaderImpl).toSelf().inSingletonScope(); bind(ConnectionHandler).toDynamicValue(ctx => new JsonRpcConnectionHandler(WebviewResourceLoaderPath, () => @@ -47,9 +55,6 @@ export function bindMainBackend(bind: interfaces.Bind): void { ) ).inSingletonScope(); - bind(PluginApiContribution).toSelf().inSingletonScope(); - bind(BackendApplicationContribution).toService(PluginApiContribution); - bindContributionProvider(bind, PluginDeployerParticipant); bind(PluginDeployer).to(PluginDeployerImpl).inSingletonScope(); bind(PluginDeployerContribution).toSelf().inSingletonScope(); diff --git a/packages/plugin-ext/src/main/node/plugin-service.ts b/packages/plugin-ext/src/main/node/theia-api-script-loader-contribution.ts similarity index 73% rename from packages/plugin-ext/src/main/node/plugin-service.ts rename to packages/plugin-ext/src/main/node/theia-api-script-loader-contribution.ts index f98aefde36300..feda95b54cd16 100644 --- a/packages/plugin-ext/src/main/node/plugin-service.ts +++ b/packages/plugin-ext/src/main/node/theia-api-script-loader-contribution.ts @@ -13,26 +13,24 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - -import * as path from 'path'; import connect = require('connect'); import serveStatic = require('serve-static'); const vhost = require('vhost'); import * as express from 'express'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { injectable } from 'inversify'; +import * as path from 'path'; import { WebviewExternalEndpoint } from '../common/webview-protocol'; -import { environment } from '@theia/application-package/lib/environment'; -const pluginPath = (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + './theia/plugins/'; +const pluginPath = path.resolve(__dirname, '../../lib/webworker'); @injectable() -export class PluginApiContribution implements BackendApplicationContribution { +export class TheiaApiScriptLoaderContribution implements BackendApplicationContribution { configure(app: express.Application): void { - app.get('/plugin/:path(*)', (req, res) => { + app.get('/theia/api/:path(*)', (req, res) => { const filePath: string = req.params.path; - res.sendFile(pluginPath + filePath); + res.sendFile(path.resolve(pluginPath, filePath)); }); const webviewApp = connect(); @@ -43,14 +41,10 @@ export class PluginApiContribution implements BackendApplicationContribution { } protected webviewExternalEndpoint(): string { - let endpointPattern; - if (environment.electron.is()) { - endpointPattern = WebviewExternalEndpoint.defaultPattern; - } else { - endpointPattern = process.env[WebviewExternalEndpoint.pattern] || WebviewExternalEndpoint.defaultPattern; - } - return endpointPattern + return (process.env[WebviewExternalEndpoint.pattern] || WebviewExternalEndpoint.defaultPattern) .replace('{{uuid}}', '.+') .replace('{{hostname}}', '.+'); + } + } diff --git a/packages/plugin-ext/src/main/node/theia-plugin-api-provider.ts b/packages/plugin-ext/src/main/node/theia-plugin-api-provider.ts new file mode 100644 index 0000000000000..8f232801d79e4 --- /dev/null +++ b/packages/plugin-ext/src/main/node/theia-plugin-api-provider.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { ExtPluginApiProvider, ExtPluginApi } from '../../common/plugin-ext-api-contribution'; +import { injectable } from 'inversify'; +import * as path from 'path'; + +@injectable() +export class TheiaPluginApiProvider implements ExtPluginApiProvider { + + provideApi(): ExtPluginApi { + return { + id: 'theia', + frontendExtApi: { + initPath: '/theia/api/theia-api-worker-provider.js', + initFunction: 'initializeApi', + initVariable: 'theia_api_provider' + }, + backendInitPath: path.join('@theia/plugin-ext/lib/plugin/node/theia-api-node-provider.js') + }; + } + +} diff --git a/packages/plugin-ext/src/hosted/node/scanners/backend-init-theia.ts b/packages/plugin-ext/src/plugin/node/theia-api-node-provider.ts similarity index 53% rename from packages/plugin-ext/src/hosted/node/scanners/backend-init-theia.ts rename to packages/plugin-ext/src/plugin/node/theia-api-node-provider.ts index fbfb27be1e955..47dcec925d32b 100644 --- a/packages/plugin-ext/src/hosted/node/scanners/backend-init-theia.ts +++ b/packages/plugin-ext/src/plugin/node/theia-api-node-provider.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (C) 2015-2018 Red Hat, Inc. + * Copyright (C) 2018 Red Hat, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -13,24 +13,23 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ - import * as theia from '@theia/plugin'; -import { BackendInitializationFn } from '../../../common/plugin-protocol'; -import { PluginAPIFactory, Plugin, emptyPlugin } from '../../../common/plugin-api-rpc'; +import { RPCProtocol } from '../../common/rpc-protocol'; +import { Plugin, emptyPlugin, PluginManager, PluginAPIFactory } from '../../common/plugin-api-rpc'; +import { ExtPluginApiBackendInitializationFn } from '../../common/plugin-ext-api-contribution'; +import { createAPIFactory } from '../plugin-context'; +import { KeyValueStorageProxy } from '../plugin-storage'; const pluginsApiImpl = new Map(); -const plugins = new Array(); let defaultApi: typeof theia; let isLoadOverride = false; -let pluginApiFactory: PluginAPIFactory; - -export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIFactory, plugin: Plugin) => { +let theiaApiFactory: PluginAPIFactory; +let plugins: PluginManager; - const apiImpl = apiFactory(plugin); - pluginsApiImpl.set(plugin.model.id, apiImpl); - - plugins.push(plugin); - pluginApiFactory = apiFactory; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const provideApi: ExtPluginApiBackendInitializationFn = (rpc: RPCProtocol, pluginManager: PluginManager, storageProxy: KeyValueStorageProxy, initParams?: any) => { + theiaApiFactory = createAPIFactory(pluginManager, rpc, storageProxy, initParams); + plugins = pluginManager; if (!isLoadOverride) { overrideInternalLoad(); @@ -44,22 +43,26 @@ function overrideInternalLoad(): void { // save original load method const internalLoad = module._load; - // if we try to resolve theia module, return the filename entry to use cache. + // if we try to resolve che module, return the filename entry to use cache. // eslint-disable-next-line @typescript-eslint/no-explicit-any module._load = function (request: string, parent: any, isMain: {}): any { - if (request !== '@theia/plugin') { + if (request !== '@eclipse-che/plugin') { return internalLoad.apply(this, arguments); } const plugin = findPlugin(parent.filename); if (plugin) { - const apiImpl = pluginsApiImpl.get(plugin.model.id); + let apiImpl = pluginsApiImpl.get(plugin.model.id); + if (!apiImpl) { + apiImpl = theiaApiFactory(plugin); + pluginsApiImpl.set(plugin.model.id, apiImpl); + } return apiImpl; } if (!defaultApi) { - console.warn(`Could not identify plugin for 'Theia' require call from ${parent.filename}`); - defaultApi = pluginApiFactory(emptyPlugin); + console.warn(`Could not identify plugin for 'Che' require call from ${parent.filename}`); + defaultApi = theiaApiFactory(emptyPlugin); } return defaultApi; @@ -67,5 +70,7 @@ function overrideInternalLoad(): void { } function findPlugin(filePath: string): Plugin | undefined { - return plugins.find(plugin => filePath.startsWith(plugin.pluginFolder)); + return plugins.getAllPlugins().find(plugin => filePath.startsWith(plugin.pluginFolder)); } + +export default provideApi; diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index bd6247dd93c16..f46ea233648dd 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -27,14 +27,16 @@ import { Plugin as InternalPlugin, PluginManager, PluginAPIFactory, - MainMessageType + MainMessageType, + PreferenceData, + EnvInit, + WebviewInitData } from '../common/plugin-api-rpc'; import { RPCProtocol } from '../common/rpc-protocol'; import { MessageRegistryExt } from './message-registry'; import { StatusBarMessageRegistryExt } from './status-bar-message-registry'; import { WindowStateExtImpl } from './window-state'; import { WorkspaceExtImpl } from './workspace'; -import { EnvExtImpl } from './env'; import { QueryParameters } from '../common/env'; import { ConfigurationTarget, @@ -150,19 +152,34 @@ import { DecorationsExtImpl } from './decorations'; import { TextEditorExt } from './text-editor'; import { ClipboardExt } from './clipboard-ext'; import { WebviewsExtImpl } from './webviews'; +import { EnvNodeExtImpl } from './node/env-node-ext'; +import { KeysToKeysToAnyValue } from '../common/types'; +import { KeyValueStorageProxy } from './plugin-storage'; +import { PluginJsonValidationContribution } from '../common/plugin-protocol'; + +export interface TheiaAPIInitParameters { + preferences: PreferenceData + globalState: KeysToKeysToAnyValue + workspaceState: KeysToKeysToAnyValue + env: EnvInit + webview: WebviewInitData + jsonValidation: PluginJsonValidationContribution[] +} export function createAPIFactory( - rpc: RPCProtocol, pluginManager: PluginManager, - envExt: EnvExtImpl, - debugExt: DebugExtImpl, - preferenceRegistryExt: PreferenceRegistryExtImpl, - editorsAndDocumentsExt: EditorsAndDocumentsExtImpl, - workspaceExt: WorkspaceExtImpl, - messageRegistryExt: MessageRegistryExt, - clipboard: ClipboardExt, - webviewExt: WebviewsExtImpl + rpc: RPCProtocol, + storageProxy: KeyValueStorageProxy, + params: TheiaAPIInitParameters ): PluginAPIFactory { + const editorsAndDocumentsExt: EditorsAndDocumentsExtImpl = rpc.get(MAIN_RPC_CONTEXT.EDITORS_AND_DOCUMENTS_EXT); + const workspaceExt: WorkspaceExtImpl = rpc.get(MAIN_RPC_CONTEXT.WORKSPACE_EXT); + const preferenceRegistryExt: PreferenceRegistryExtImpl = rpc.get(MAIN_RPC_CONTEXT.PREFERENCE_REGISTRY_EXT); + const webviewExt: WebviewsExtImpl = rpc.get(MAIN_RPC_CONTEXT.WEBVIEWS_EXT); + const debugExt = new DebugExtImpl(rpc); + const messageRegistryExt = new MessageRegistryExt(rpc); + const envExt: EnvNodeExtImpl = new EnvNodeExtImpl(rpc); + const clipboardExt = new ClipboardExt(rpc); const commandRegistry = rpc.set(MAIN_RPC_CONTEXT.COMMAND_REGISTRY_EXT, new CommandRegistryImpl(rpc)); const quickOpenExt = rpc.set(MAIN_RPC_CONTEXT.QUICK_OPEN_EXT, new QuickOpenExtImpl(rpc)); @@ -184,6 +201,17 @@ export function createAPIFactory( const decorationsExt = rpc.set(MAIN_RPC_CONTEXT.DECORATIONS_EXT, new DecorationsExtImpl(rpc)); rpc.set(MAIN_RPC_CONTEXT.DEBUG_EXT, debugExt); + storageProxy.init(params.globalState, params.workspaceState); + + envExt.setQueryParameters(params.env.queryParams); + envExt.setLanguage(params.env.language); + envExt.setShell(params.env.shell); + envExt.setUIKind(params.env.uiKind); + envExt.setApplicationName(params.env.appName); + webviewExt.init(params.webview); + + preferenceRegistryExt.init(params.preferences); + return function (plugin: InternalPlugin): typeof theia { const commands: typeof theia.commands = { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -517,7 +545,7 @@ export function createAPIFactory( get uriScheme(): string { return envExt.uriScheme; }, get shell(): string { return envExt.shell; }, get uiKind(): theia.UIKind { return envExt.uiKind; }, - clipboard, + clipboard: clipboardExt, getEnvVariable(envVarName: string): PromiseLike { return envExt.getEnvVariable(envVarName); }, diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index a26c0d692c7a3..2e0303e34570c 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -31,13 +31,10 @@ import { PluginMetadata, PluginJsonValidationContribution } from '../common/plug import * as theia from '@theia/plugin'; import { join } from 'path'; import { Deferred } from '@theia/core/lib/common/promise-util'; -import { EnvExtImpl } from './env'; -import { PreferenceRegistryExtImpl } from './preference-registry'; import { Memento, KeyValueStorageProxy } from './plugin-storage'; import { ExtPluginApi } from '../common/plugin-ext-api-contribution'; import { RPCProtocol } from '../common/rpc-protocol'; import { Emitter } from '@theia/core/lib/common/event'; -import { WebviewsExtImpl } from './webviews'; export interface PluginHost { @@ -46,7 +43,8 @@ export interface PluginHost { init(data: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> | [Plugin[], Plugin[]]; - initExtApi(extApi: ExtPluginApi[]): void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + initExtApi(extApi: { pluginApi: ExtPluginApi, initParameters?: any }[]): void; loadTests?(): Promise; } @@ -104,11 +102,8 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { constructor( private readonly host: PluginHost, - private readonly envExt: EnvExtImpl, - private readonly storageProxy: KeyValueStorageProxy, - private readonly preferencesManager: PreferenceRegistryExtImpl, - private readonly webview: WebviewsExtImpl, - private readonly rpc: RPCProtocol + private readonly rpc: RPCProtocol, + private readonly storageProxy: KeyValueStorageProxy ) { this.messageRegistryProxy = this.rpc.getProxy(PLUGIN_RPC_CONTEXT.MESSAGE_REGISTRY_MAIN); this.notificationMain = this.rpc.getProxy(PLUGIN_RPC_CONTEXT.NOTIFICATION_MAIN); @@ -183,22 +178,9 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { } async $init(params: PluginManagerInitializeParams): Promise { - this.storageProxy.init(params.globalState, params.workspaceState); - - this.envExt.setQueryParameters(params.env.queryParams); - this.envExt.setLanguage(params.env.language); - this.envExt.setShell(params.env.shell); - this.envExt.setUIKind(params.env.uiKind); - this.envExt.setApplicationName(params.env.appName); - - this.preferencesManager.init(params.preferences); - if (params.extApi) { this.host.initExtApi(params.extApi); } - - this.webview.init(params.webview); - this.jsonValidation = params.jsonValidation; } async $start(params: PluginManagerStartParams): Promise { diff --git a/packages/plugin-ext/src/plugin/webworker/theia-api-worker-provider.ts b/packages/plugin-ext/src/plugin/webworker/theia-api-worker-provider.ts new file mode 100644 index 0000000000000..09f37782836f2 --- /dev/null +++ b/packages/plugin-ext/src/plugin/webworker/theia-api-worker-provider.ts @@ -0,0 +1,55 @@ +/******************************************************************************** + * Copyright (C) 2018 Red Hat, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ +import { Plugin, emptyPlugin, PluginManager } from '../../common/plugin-api-rpc'; +import { ExtPluginApiFrontendInitializationFn } from '../../common/plugin-ext-api-contribution'; +import { RPCProtocol } from '../../common/rpc-protocol'; +import * as theia from '@theia/plugin'; +import { createAPIFactory } from '../plugin-context'; +import { KeyValueStorageProxy } from '../plugin-storage'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const ctx = self as any; +const pluginsApiImpl = new Map(); +let defaultApi: typeof theia; + +export const initializeApi: ExtPluginApiFrontendInitializationFn = (rpc: RPCProtocol, pluginManager: PluginManager, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + storageProxy: KeyValueStorageProxy, plugins: Map, initParams?: any) => { + const theiaApiFactory = createAPIFactory(pluginManager, rpc, storageProxy, initParams); + const handler = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + get: (target: any, name: string) => { + const plugin = plugins.get(name); + if (plugin) { + let apiImpl = pluginsApiImpl.get(plugin.model.id); + if (!apiImpl) { + apiImpl = theiaApiFactory(plugin); + pluginsApiImpl.set(plugin.model.id, apiImpl); + } + return apiImpl; + } + + if (!defaultApi) { + defaultApi = theiaApiFactory(emptyPlugin); + } + + return defaultApi; + } + }; + + // eslint-disable-next-line no-null/no-null + ctx['theia'] = new Proxy(Object.create(null), handler); +}; diff --git a/packages/plugin-ext/webpack.config.js b/packages/plugin-ext/webpack.config.js new file mode 100644 index 0000000000000..a05c4f2ebfbcd --- /dev/null +++ b/packages/plugin-ext/webpack.config.js @@ -0,0 +1,52 @@ +/********************************************************************* + * Copyright (c) 2018 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ + +const path = require('path'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); + +module.exports = { + entry: './lib/plugin/webworker/theia-api-worker-provider.js', + devtool: 'source-map', + mode: 'production', + node: { + fs: 'empty', + child_process: 'empty', + net: 'empty', + crypto: 'empty' + }, + module: { + rules: [ + { + test: /\.ts$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true + } + } + ], + exclude: /node_modules/ + } + ] + }, + plugins: [ + new CleanWebpackPlugin(['lib/webworker']) + ], + resolve: { + extensions: ['.ts', '.js'] + }, + output: { + filename: 'theia-api-worker-provider.js', + libraryTarget: "var", + library: "theia_api_provider", + path: path.resolve(__dirname, 'lib/webworker') + } +};