diff --git a/CHANGELOG.md b/CHANGELOG.md index ff769bfaf7fa7..831e401847e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Breaking changes: - [editor] computation of resource context keys moved to core [#4531](https://github.com/theia-ide/theia/pull/4531) +- [plugin] support multiple windows per a backend [#4509](https://github.com/theia-ide/theia/issues/4509) + - Some plugin bindings are scoped per a connection now. Clients, who contribute/rebind these bindings, will need to scope them per a connection as well. ## v0.4.0 - [application-manager] added support for pre-load HTML templates diff --git a/packages/plugin-ext/src/hosted/node-electron/plugin-ext-hosted-electron-backend-module.ts b/packages/plugin-ext/src/hosted/node-electron/plugin-ext-hosted-electron-backend-module.ts index 9e8e6aed9dbe3..5adc5fecd1664 100644 --- a/packages/plugin-ext/src/hosted/node-electron/plugin-ext-hosted-electron-backend-module.ts +++ b/packages/plugin-ext/src/hosted/node-electron/plugin-ext-hosted-electron-backend-module.ts @@ -16,13 +16,18 @@ import { HostedInstanceManager, ElectronNodeHostedPluginRunner } from '../node/hosted-instance-manager'; import { interfaces } from 'inversify'; +import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module'; import { bindCommonHostedBackend } from '../node/plugin-ext-hosted-backend-module'; import { PluginScanner } from '../../common/plugin-protocol'; import { TheiaPluginScannerElectron } from './scanner-theia-electron'; +const hostedBackendConnectionModule = ConnectionContainerModule.create(({ bind }) => { + bind(HostedInstanceManager).to(ElectronNodeHostedPluginRunner); +}); + export function bindElectronBackend(bind: interfaces.Bind): void { bindCommonHostedBackend(bind); + bind(ConnectionContainerModule).toConstantValue(hostedBackendConnectionModule); - bind(HostedInstanceManager).to(ElectronNodeHostedPluginRunner); bind(PluginScanner).to(TheiaPluginScannerElectron).inSingletonScope(); } diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts new file mode 100644 index 0000000000000..52fb1bc5e6e2a --- /dev/null +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-deployer-handler.ts @@ -0,0 +1,69 @@ +/******************************************************************************** + * Copyright (C) 2019 RedHat 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, inject } from 'inversify'; +import { ILogger } from '@theia/core'; +import { PluginDeployerHandler, PluginDeployerEntry, PluginMetadata } from '../../common/plugin-protocol'; +import { HostedPluginReader } from './plugin-reader'; + +@injectable() +export class HostedPluginDeployerHandler implements PluginDeployerHandler { + + @inject(ILogger) + protected readonly logger: ILogger; + + @inject(HostedPluginReader) + private readonly reader: HostedPluginReader; + + /** + * Managed plugin metadata backend entries. + */ + private readonly currentBackendPluginsMetadata: PluginMetadata[] = []; + + /** + * Managed plugin metadata frontend entries. + */ + private readonly currentFrontendPluginsMetadata: PluginMetadata[] = []; + + getDeployedFrontendMetadata(): PluginMetadata[] { + return this.currentFrontendPluginsMetadata; + } + + getDeployedBackendMetadata(): PluginMetadata[] { + return this.currentBackendPluginsMetadata; + } + + async deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise { + for (const plugin of frontendPlugins) { + const metadata = await this.reader.getPluginMetadata(plugin.path()); + if (metadata) { + this.currentFrontendPluginsMetadata.push(metadata); + this.logger.info(`Deploying frontend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.frontend || plugin.path()}"`); + } + } + } + + async deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise { + for (const plugin of backendPlugins) { + const metadata = await this.reader.getPluginMetadata(plugin.path()); + if (metadata) { + this.currentBackendPluginsMetadata.push(metadata); + this.logger.info(`Deploying backend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.backend || plugin.path()}"`); + } + } + } + +} diff --git a/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts b/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts index 8de9ffa707a9c..7ed9fcf9ac907 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-ext-hosted-backend-module.ts @@ -17,9 +17,9 @@ import { interfaces } from 'inversify'; import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; import { CliContribution } from '@theia/core/lib/node/cli'; +import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module'; import { HostedInstanceManager, NodeHostedPluginRunner } from './hosted-instance-manager'; import { HostedPluginUriPostProcessorSymbolName } from './hosted-plugin-uri-postprocessor'; -import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core/lib/common/messaging'; import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; import { MetadataScanner } from './metadata-scanner'; import { HostedPluginServerImpl } from './plugin-service'; @@ -32,44 +32,49 @@ import { GrammarsReader } from './scanners/grammars-reader'; import { HostedPluginProcess } from './hosted-plugin-process'; import { ExtPluginApiProvider } from '../../common/plugin-ext-api-contribution'; import { HostedPluginCliContribution } from './hosted-plugin-cli-contribution'; +import { HostedPluginDeployerHandler } from './hosted-plugin-deployer-handler'; -export function bindCommonHostedBackend(bind: interfaces.Bind): void { - bind(HostedPluginCliContribution).toSelf().inSingletonScope(); - bind(CliContribution).toService(HostedPluginCliContribution); +const commonHostedConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => { + bind(HostedPluginProcess).toSelf().inSingletonScope(); + bind(HostedPluginSupport).toSelf().inSingletonScope(); - bind(HostedPluginReader).toSelf().inSingletonScope(); + bind(HostedPluginsManagerImpl).toSelf().inSingletonScope(); + bind(HostedPluginsManager).toService(HostedPluginsManagerImpl); + bindContributionProvider(bind, Symbol.for(ExtPluginApiProvider)); bind(HostedPluginServerImpl).toSelf().inSingletonScope(); bind(HostedPluginServer).toService(HostedPluginServerImpl); - bind(PluginDeployerHandler).toService(HostedPluginServerImpl); - - bind(HostedPluginSupport).toSelf().inSingletonScope(); - bind(MetadataScanner).toSelf().inSingletonScope(); - bind(HostedPluginsManager).to(HostedPluginsManagerImpl).inSingletonScope(); + bindBackendService(hostedServicePath, HostedPluginServer, (server, client) => { + server.setClient(client); + client.onDidCloseConnection(() => server.dispose()); + return server; + }); +}); - bind(HostedPluginProcess).toSelf().inSingletonScope(); +export function bindCommonHostedBackend(bind: interfaces.Bind): void { + bind(HostedPluginCliContribution).toSelf().inSingletonScope(); + bind(CliContribution).toService(HostedPluginCliContribution); + bind(MetadataScanner).toSelf().inSingletonScope(); + bind(HostedPluginReader).toSelf().inSingletonScope(); bind(BackendApplicationContribution).toService(HostedPluginReader); - bind(ConnectionHandler).toDynamicValue(ctx => - new JsonRpcConnectionHandler(hostedServicePath, client => { - const server = ctx.container.get(HostedPluginServer); - server.setClient(client); - // FIXME: handle multiple remote connections - /* - client.onDidCloseConnection(() => server.dispose());*/ - return server; - }) - ).inSingletonScope(); + bind(HostedPluginDeployerHandler).toSelf().inSingletonScope(); + bind(PluginDeployerHandler).toService(HostedPluginDeployerHandler); bind(GrammarsReader).toSelf().inSingletonScope(); + + bind(ConnectionContainerModule).toConstantValue(commonHostedConnectionModule); } +const hostedBackendConnectionModule = ConnectionContainerModule.create(({ bind }) => { + bindContributionProvider(bind, Symbol.for(HostedPluginUriPostProcessorSymbolName)); + bind(HostedInstanceManager).to(NodeHostedPluginRunner).inSingletonScope(); +}); + export function bindHostedBackend(bind: interfaces.Bind): void { bindCommonHostedBackend(bind); + bind(ConnectionContainerModule).toConstantValue(hostedBackendConnectionModule); - bind(HostedInstanceManager).to(NodeHostedPluginRunner).inSingletonScope(); bind(PluginScanner).to(TheiaPluginScanner).inSingletonScope(); - bindContributionProvider(bind, Symbol.for(HostedPluginUriPostProcessorSymbolName)); - bindContributionProvider(bind, Symbol.for(ExtPluginApiProvider)); } diff --git a/packages/plugin-ext/src/hosted/node/plugin-service.ts b/packages/plugin-ext/src/hosted/node/plugin-service.ts index 14e6506267c48..a0651ab5d785e 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-service.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-service.ts @@ -14,7 +14,7 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { injectable, inject, named } from 'inversify'; -import { HostedPluginServer, HostedPluginClient, PluginMetadata, PluginDeployerEntry, DebugConfiguration, PluginDeployerHandler } from '../../common/plugin-protocol'; +import { HostedPluginServer, HostedPluginClient, PluginMetadata, DebugConfiguration } from '../../common/plugin-protocol'; import { HostedPluginReader } from './plugin-reader'; import { HostedInstanceManager } from './hosted-instance-manager'; import { HostedPluginSupport } from './hosted-plugin'; @@ -23,28 +23,22 @@ import URI from '@theia/core/lib/common/uri'; import { ILogger } from '@theia/core'; import { ContributionProvider } from '@theia/core'; import { ExtPluginApiProvider, ExtPluginApi } from '../../common/plugin-ext-api-contribution'; +import { HostedPluginDeployerHandler } from './hosted-plugin-deployer-handler'; @injectable() -export class HostedPluginServerImpl implements HostedPluginServer, PluginDeployerHandler { +export class HostedPluginServerImpl implements HostedPluginServer { @inject(ILogger) protected readonly logger: ILogger; @inject(HostedPluginsManager) protected readonly hostedPluginsManager: HostedPluginsManager; + @inject(HostedPluginDeployerHandler) + protected readonly deployerHandler: HostedPluginDeployerHandler; + @inject(ContributionProvider) @named(Symbol.for(ExtPluginApiProvider)) protected readonly extPluginAPIContributions: ContributionProvider; - /** - * Managed plugin metadata backend entries. - */ - private currentBackendPluginsMetadata: PluginMetadata[] = []; - - /** - * Managed plugin metadata frontend entries. - */ - private currentFrontendPluginsMetadata: PluginMetadata[] = []; - constructor( @inject(HostedPluginReader) private readonly reader: HostedPluginReader, @inject(HostedPluginSupport) private readonly hostedPlugin: HostedPluginSupport, @@ -66,13 +60,17 @@ export class HostedPluginServerImpl implements HostedPluginServer, PluginDeploye } getDeployedFrontendMetadata(): Promise { - return Promise.resolve(this.currentFrontendPluginsMetadata); + return Promise.resolve(this.deployerHandler.getDeployedFrontendMetadata()); } async getDeployedMetadata(): Promise { + const backendMetadata = this.deployerHandler.getDeployedBackendMetadata(); + if (backendMetadata.length > 0) { + this.hostedPlugin.runPluginServer(); + } const allMetadata: PluginMetadata[] = []; - allMetadata.push(...this.currentFrontendPluginsMetadata); - allMetadata.push(...this.currentBackendPluginsMetadata); + allMetadata.push(...this.deployerHandler.getDeployedFrontendMetadata()); + allMetadata.push(...backendMetadata); // ask remote as well const extraBackendPluginsMetadata = await this.hostedPlugin.getExtraPluginMetadata(); @@ -81,33 +79,8 @@ export class HostedPluginServerImpl implements HostedPluginServer, PluginDeploye return allMetadata; } - // need to run a new node instance with plugin-host for all plugins - async deployFrontendPlugins(frontendPlugins: PluginDeployerEntry[]): Promise { - for (const plugin of frontendPlugins) { - const metadata = await this.reader.getPluginMetadata(plugin.path()); - if (metadata) { - this.currentFrontendPluginsMetadata.push(metadata); - this.logger.info(`Deploying frontend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.frontend || plugin.path()}"`); - } - } - } - getDeployedBackendMetadata(): Promise { - return Promise.resolve(this.currentBackendPluginsMetadata); - } - - // need to run a new node instance with plugin-host for all plugins - async deployBackendPlugins(backendPlugins: PluginDeployerEntry[]): Promise { - if (backendPlugins.length > 0) { - this.hostedPlugin.runPluginServer(); - } - for (const plugin of backendPlugins) { - const metadata = await this.reader.getPluginMetadata(plugin.path()); - if (metadata) { - this.currentBackendPluginsMetadata.push(metadata); - this.logger.info(`Deploying backend plugin "${metadata.model.name}@${metadata.model.version}" from "${metadata.model.entryPoint.backend || plugin.path()}"`); - } - } + return Promise.resolve(this.deployerHandler.getDeployedBackendMetadata()); } onMessage(message: string): Promise {