From 1fc845d413a0990846386bc4aef6535e48fc98e2 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Fri, 5 Jul 2019 10:40:18 +0000 Subject: [PATCH] [vscode] support workspaceContains activation events Signed-off-by: Anton Kosyakov --- CHANGELOG.md | 1 + .../src/hosted/browser/hosted-plugin.ts | 72 ++++++++++++++++++- .../plugin-ext/src/plugin/plugin-manager.ts | 7 +- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bb09dd22ef4..9b71c9dae5d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Breaking changes: - [plugin] removed member `processOptions` from `AbstractHostedInstanceManager` as it is not initialized or used - [plugin] added basic support of activation events [#5622](https://github.com/theia-ide/theia/pull/5622) - `HostedPluginSupport` is refactored to support multiple `PluginManagerExt` properly +- [plugin] added support of `workspaceContains` activation events [#5649](https://github.com/theia-ide/theia/pull/5649) ## v0.8.0 diff --git a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts index 5eb1126ad8ee0..5fa9060bb15a4 100644 --- a/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts +++ b/packages/plugin-ext/src/hosted/browser/hosted-plugin.ts @@ -13,6 +13,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// some code copied and modified from https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts // tslint:disable:no-any @@ -22,7 +27,7 @@ import { HostedPluginServer, PluginMetadata, getPluginId } from '../../common/pl import { HostedPluginWatcher } from './hosted-plugin-watcher'; import { setUpPluginApi } from '../../main/browser/main-context'; import { RPCProtocol, RPCProtocolImpl } from '../../api/rpc-protocol'; -import { ILogger, ContributionProvider, CommandRegistry, WillExecuteCommandEvent } from '@theia/core'; +import { ILogger, ContributionProvider, CommandRegistry, WillExecuteCommandEvent, CancellationTokenSource } from '@theia/core'; import { PreferenceServiceImpl, PreferenceProviderProvider } from '@theia/core/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { PluginContributionHandler } from '../../main/browser/plugin-contribution-handler'; @@ -40,6 +45,8 @@ import { Deferred } from '@theia/core/lib/common/promise-util'; import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { WaitUntilEvent } from '@theia/core/lib/common/event'; +import { FileSearchService } from '@theia/file-search/lib/common/file-search-service'; +import { isCancelled } from '@theia/core'; export type PluginHost = 'frontend' | string; export type DebugActivationEvent = 'onDebugResolve' | 'onDebugInitialConfigurations' | 'onDebugAdapterProtocolTracker'; @@ -94,6 +101,9 @@ export class HostedPluginSupport { @inject(DebugConfigurationManager) protected readonly debugConfigurationManager: DebugConfigurationManager; + @inject(FileSearchService) + protected readonly fileSearchService: FileSearchService; + private theiaReadyPromise: Promise; protected readonly managers: PluginManagerExt[] = []; @@ -178,11 +188,11 @@ export class HostedPluginSupport { return rpc; } - protected initPluginHostManager(rpc: RPCProtocol, data: PluginsInitializationData): void { + protected async initPluginHostManager(rpc: RPCProtocol, data: PluginsInitializationData): Promise { const manager = rpc.getProxy(MAIN_RPC_CONTEXT.HOSTED_PLUGIN_MANAGER_EXT); this.managers.push(manager); - manager.$init({ + await manager.$init({ plugins: data.plugins, preferences: getPreferences(this.preferenceProviderProvider, data.roots), globalState: data.globalStates, @@ -194,6 +204,10 @@ export class HostedPluginSupport { hostLogPath: data.logPath, hostStoragePath: data.storagePath || '' }); + + for (const plugin of data.plugins) { + this.activateByWorkspaceContains(manager, plugin); + } } private createServerRpc(pluginID: string, hostID: string): RPCProtocol { @@ -271,6 +285,58 @@ export class HostedPluginSupport { await Promise.all(promises); } + protected async activateByWorkspaceContains(manager: PluginManagerExt, plugin: PluginMetadata): Promise { + if (!plugin.source.activationEvents) { + return; + } + const paths: string[] = []; + const includePatterns: string[] = []; + // should be aligned with https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts#L460-L469 + for (const activationEvent of plugin.source.activationEvents) { + if (/^workspaceContains:/.test(activationEvent)) { + const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + includePatterns.push(fileNameOrGlob); + } else { + paths.push(fileNameOrGlob); + } + } + } + const activatePlugin = () => manager.$activateByEvent(`onPlugin:${plugin.model.id}`); + const promises: Promise[] = []; + if (paths.length) { + promises.push(this.workspaceService.containsSome(paths)); + } + if (includePatterns.length) { + const tokenSource = new CancellationTokenSource(); + const searchTimeout = setTimeout(() => { + tokenSource.cancel(); + // activate eagerly if took to long to search + activatePlugin(); + }, 7000); + promises.push((async () => { + try { + const result = await this.fileSearchService.find('', { + rootUris: this.workspaceService.tryGetRoots().map(r => r.uri), + includePatterns, + limit: 1 + }, tokenSource.token); + return result.length > 0; + } catch (e) { + if (!isCancelled(e)) { + console.error(e); + } + return false; + } finally { + clearTimeout(searchTimeout); + } + })()); + } + if (promises.length && await Promise.all(promises).then(exists => exists.some(v => v))) { + await activatePlugin(); + } + } + } interface PluginsInitializationData { diff --git a/packages/plugin-ext/src/plugin/plugin-manager.ts b/packages/plugin-ext/src/plugin/plugin-manager.ts index a46d75da15e83..d1eeaca837fb9 100644 --- a/packages/plugin-ext/src/plugin/plugin-manager.ts +++ b/packages/plugin-ext/src/plugin/plugin-manager.ts @@ -65,7 +65,8 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { '*', 'onLanguage', 'onCommand', - 'onDebug', 'onDebugInitialConfigurations', 'onDebugResolve', 'onDebugAdapterProtocolTracker' + 'onDebug', 'onDebugInitialConfigurations', 'onDebugResolve', 'onDebugAdapterProtocolTracker', + 'workspaceContains' ]); private readonly registry = new Map(); @@ -156,6 +157,8 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { this.registry.set(plugin.model.id, plugin); if (plugin.pluginPath && Array.isArray(plugin.rawModel.activationEvents)) { const activation = () => this.loadPlugin(plugin, configStorage); + // an internal activation event is a subject to change + this.setActivation(`onPlugin:${plugin.model.id}`, activation); const unsupportedActivationEvents = plugin.rawModel.activationEvents.filter(e => !PluginManagerExtImpl.SUPPORTED_ACTIVATION_EVENTS.has(e.split(':')[0])); if (unsupportedActivationEvents.length) { console.warn(`Unsupported activation events: ${unsupportedActivationEvents.join(', ')}, please open an issue: https://github.com/theia-ide/theia/issues/new`); @@ -164,7 +167,7 @@ export class PluginManagerExtImpl implements PluginManagerExt, PluginManager { } else { for (let activationEvent of plugin.rawModel.activationEvents) { if (activationEvent === 'onUri') { - activationEvent = `onUri:${plugin.model.id}`; + activationEvent = `onUri:theia://${plugin.model.publisher.toLowerCase()}.${plugin.model.name.toLowerCase()}`; } this.setActivation(activationEvent, activation); }