diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts index 37b81a0be2a..9602dcd6bf5 100644 --- a/src/vs/platform/actions/test/common/menuService.test.ts +++ b/src/vs/platform/actions/test/common/menuService.test.ts @@ -11,6 +11,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { NullCommandService } from 'vs/platform/commands/common/commands'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService'; +import { IExtensionPoint } from "vs/platform/extensions/common/extensionsRegistry"; +import { TPromise } from "vs/base/common/winjs.base"; +import { ExtensionPointContribution, IExtensionDescription } from "vs/platform/extensions/common/extensions"; // --- service instances @@ -24,6 +27,12 @@ const extensionService = new class extends AbstractExtensionService { + throw new Error('Not implemented'); + } + public readExtensionPointContributions(extPoint: IExtensionPoint): TPromise[]> { + throw new Error('Not implemented'); + } }(true); const contextKeyService = new class extends MockContextKeyService { diff --git a/src/vs/platform/extensions/common/abstractExtensionService.ts b/src/vs/platform/extensions/common/abstractExtensionService.ts index ed0525f99c0..ac65223c76e 100644 --- a/src/vs/platform/extensions/common/abstractExtensionService.ts +++ b/src/vs/platform/extensions/common/abstractExtensionService.ts @@ -7,8 +7,7 @@ import * as nls from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IExtensionDescription, IExtensionService, IExtensionsStatus, ExtensionPointContribution } from 'vs/platform/extensions/common/extensions'; -import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry'; +import { IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions'; const hasOwnProperty = Object.hasOwnProperty; @@ -30,92 +29,52 @@ interface IActivatingExtensionMap { const NO_OP_VOID_PROMISE = TPromise.as(void 0); -export abstract class AbstractExtensionService implements IExtensionService { - public _serviceBrand: any; +export interface IExtensionsManagerHost { + showMessage(severity: Severity, message: string): void; - private _activatingExtensions: IActivatingExtensionMap; - protected _activatedExtensions: IActivatedExtensionMap; - private _onReady: TPromise; - private _onReadyC: (v: boolean) => void; - private _isReady: boolean; - protected _registry: ExtensionDescriptionRegistry; + createFailedExtension(): T; + actualActivateExtension(extensionDescription: IExtensionDescription): TPromise; +} + +export class ExtensionsManager { + + private readonly _registry: ExtensionDescriptionRegistry; + private readonly _host: IExtensionsManagerHost; + private readonly _activatingExtensions: IActivatingExtensionMap; + private readonly _activatedExtensions: IActivatedExtensionMap; /** * A map of already activated events to speed things up if the same activation event is triggered multiple times. */ - private _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; + private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; - constructor(isReadyByDefault: boolean) { - if (isReadyByDefault) { - this._isReady = true; - this._onReady = TPromise.as(true); - this._onReadyC = (v: boolean) => { /*no-op*/ }; - } else { - this._isReady = false; - this._onReady = new TPromise((c, e, p) => { - this._onReadyC = c; - }, () => { - console.warn('You should really not try to cancel this ready promise!'); - }); - } + constructor(registry: ExtensionDescriptionRegistry, host: IExtensionsManagerHost) { + this._registry = registry; + this._host = host; this._activatingExtensions = {}; this._activatedExtensions = {}; - this._registry = new ExtensionDescriptionRegistry(); this._alreadyActivatedEvents = Object.create(null); } - protected _triggerOnReady(): void { - this._isReady = true; - this._onReadyC(true); - } - - public onReady(): TPromise { - return this._onReady; - } - - public readExtensionPointContributions(extPoint: IExtensionPoint): TPromise[]> { - return this.onReady().then(() => { - let availableExtensions = this._registry.getAllExtensionDescriptions(); - - let result: ExtensionPointContribution[] = [], resultLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - - if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { - result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); - } - } - - return result; - }); - } - - public getExtensions(): TPromise { - return this.onReady().then(() => { - return this._registry.getAllExtensionDescriptions(); - }); - } - - public getExtensionsStatus(): { [id: string]: IExtensionsStatus } { - return null; - } - public isActivated(extensionId: string): boolean { return hasOwnProperty.call(this._activatedExtensions, extensionId); } + public getActivatedExtension(extensionId: string): T { + if (!hasOwnProperty.call(this._activatedExtensions, extensionId)) { + throw new Error('Extension `' + extensionId + '` is not known or not activated'); + } + return this._activatedExtensions[extensionId]; + } + + public setActivatedExtension(extensionId: string, value: T): void { + this._activatedExtensions[extensionId] = value; + } + public activateByEvent(activationEvent: string): TPromise { if (this._alreadyActivatedEvents[activationEvent]) { return NO_OP_VOID_PROMISE; } - if (this._isReady) { - return this._activateByEvent(activationEvent); - } else { - return this._onReady.then(() => this._activateByEvent(activationEvent)); - } - } - - private _activateByEvent(activationEvent: string): TPromise { let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); return this._activateExtensions(activateExtensions, 0).then(() => { this._alreadyActivatedEvents[activationEvent] = true; @@ -123,14 +82,12 @@ export abstract class AbstractExtensionService imp } public activateById(extensionId: string): TPromise { - return this._onReady.then(() => { - let desc = this._registry.getExtensionDescription(extensionId); - if (!desc) { - throw new Error('Extension `' + extensionId + '` is not known'); - } + let desc = this._registry.getExtensionDescription(extensionId); + if (!desc) { + throw new Error('Extension `' + extensionId + '` is not known'); + } - return this._activateExtensions([desc], 0); - }); + return this._activateExtensions([desc], 0); } /** @@ -147,8 +104,8 @@ export abstract class AbstractExtensionService imp if (!depDesc) { // Error condition 1: unknown dependency - this._showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentExtension.id)); - this._activatedExtensions[currentExtension.id] = this._createFailedExtension(); + this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Extension `{1}` failed to activate. Reason: unknown dependency `{0}`.", depId, currentExtension.id)); + this._activatedExtensions[currentExtension.id] = this._host.createFailedExtension(); return; } @@ -156,8 +113,8 @@ export abstract class AbstractExtensionService imp let dep = this._activatedExtensions[depId]; if (dep.activationFailed) { // Error condition 2: a dependency has already failed activation - this._showMessage(Severity.Error, nls.localize('failedDep1', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentExtension.id)); - this._activatedExtensions[currentExtension.id] = this._createFailedExtension(); + this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Extension `{1}` failed to activate. Reason: dependency `{0}` failed to activate.", depId, currentExtension.id)); + this._activatedExtensions[currentExtension.id] = this._host.createFailedExtension(); return; } } else { @@ -189,8 +146,8 @@ export abstract class AbstractExtensionService imp // More than 10 dependencies deep => most likely a dependency loop for (let i = 0, len = extensionDescriptions.length; i < len; i++) { // Error condition 3: dependency loop - this._showMessage(Severity.Error, nls.localize('failedDep2', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].id)); - this._activatedExtensions[extensionDescriptions[i].id] = this._createFailedExtension(); + this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension `{0}` failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].id)); + this._activatedExtensions[extensionDescriptions[i].id] = this._host.createFailedExtension(); } return TPromise.as(void 0); } @@ -224,7 +181,7 @@ export abstract class AbstractExtensionService imp }); } - protected _activateExtension(extensionDescription: IExtensionDescription): TPromise { + public _activateExtension(extensionDescription: IExtensionDescription): TPromise { if (hasOwnProperty.call(this._activatedExtensions, extensionDescription.id)) { return TPromise.as(void 0); } @@ -233,12 +190,12 @@ export abstract class AbstractExtensionService imp return this._activatingExtensions[extensionDescription.id]; } - this._activatingExtensions[extensionDescription.id] = this._actualActivateExtension(extensionDescription).then(null, (err) => { - this._showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message)); + this._activatingExtensions[extensionDescription.id] = this._host.actualActivateExtension(extensionDescription).then(null, (err) => { + this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension `{0}` failed: {1}.", extensionDescription.id, err.message)); console.error('Activating extension `' + extensionDescription.id + '` failed: ', err.message); console.log('Here is the error stack: ', err.stack); // Treat the extension as being empty - return this._createFailedExtension(); + return this._host.createFailedExtension(); }).then((x: T) => { this._activatedExtensions[extensionDescription.id] = x; delete this._activatingExtensions[extensionDescription.id]; @@ -246,6 +203,78 @@ export abstract class AbstractExtensionService imp return this._activatingExtensions[extensionDescription.id]; } +} + +export abstract class AbstractExtensionService { + public _serviceBrand: any; + + private _onReady: TPromise; + private _onReadyC: (v: boolean) => void; + private _isReady: boolean; + protected _registry: ExtensionDescriptionRegistry; + protected _manager: ExtensionsManager; + + constructor(isReadyByDefault: boolean) { + if (isReadyByDefault) { + this._isReady = true; + this._onReady = TPromise.as(true); + this._onReadyC = (v: boolean) => { /*no-op*/ }; + } else { + this._isReady = false; + this._onReady = new TPromise((c, e, p) => { + this._onReadyC = c; + }, () => { + console.warn('You should really not try to cancel this ready promise!'); + }); + } + this._registry = new ExtensionDescriptionRegistry(); + this._manager = new ExtensionsManager(this._registry, { + showMessage: (severity: Severity, message: string): void => { + this._showMessage(severity, message); + }, + + createFailedExtension: (): T => { + return this._createFailedExtension(); + }, + + actualActivateExtension: (extensionDescription: IExtensionDescription): TPromise => { + return this._actualActivateExtension(extensionDescription); + } + }); + } + + protected _triggerOnReady(): void { + this._isReady = true; + this._onReadyC(true); + } + + public onReady(): TPromise { + return this._onReady; + } + + public getExtensionsStatus(): { [id: string]: IExtensionsStatus } { + return null; + } + + public isActivated(extensionId: string): boolean { + return this._manager.isActivated(extensionId); + } + + public activateByEvent(activationEvent: string): TPromise { + if (this._isReady) { + return this._manager.activateByEvent(activationEvent); + } else { + return this._onReady.then(() => this._manager.activateByEvent(activationEvent)); + } + } + + public activateById(extensionId: string): TPromise { + if (this._isReady) { + return this._manager.activateById(extensionId); + } else { + return this._onReady.then(() => this._manager.activateById(extensionId)); + } + } protected abstract _showMessage(severity: Severity, message: string): void; diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index d75347f83dc..429b20540d6 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -17,8 +17,6 @@ import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/ import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; import { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol'; -const hasOwnProperty = Object.hasOwnProperty; - /** * Represents the source code (module) of an extension. */ @@ -201,16 +199,17 @@ export class ExtHostExtensionService extends AbstractExtensionService { let result: TPromise = TPromise.as(void 0); - let extension = this._activatedExtensions[extensionId]; + if (!this._manager.isActivated(extensionId)) { + return result; + } + + let extension = this._manager.getActivatedExtension(extensionId); if (!extension) { return result; } @@ -333,7 +332,7 @@ export class ExtHostExtensionService extends AbstractExtensionService { - return this._activateExtension(extensionDescription); + return this._manager._activateExtension(extensionDescription); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 1c3c7b476dc..a8118730acf 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -11,10 +11,10 @@ import { localize } from 'vs/nls'; import * as path from 'path'; import URI from 'vs/base/common/uri'; import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService'; -import { IMessage, IExtensionDescription, IExtensionsStatus } from 'vs/platform/extensions/common/extensions'; +import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService, ExtensionPointContribution } from 'vs/platform/extensions/common/extensions'; import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getGloballyDisabledExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; +import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry'; import { ExtensionScanner, MessagesCollector } from 'vs/workbench/node/extensionPoints'; import { IMessageService } from 'vs/platform/message/common/message'; import { IThreadService, ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; @@ -53,7 +53,7 @@ function messageWithSource(msg: IMessage): string { const hasOwnProperty = Object.hasOwnProperty; -export class MainProcessExtensionService extends AbstractExtensionService implements IThreadService { +export class MainProcessExtensionService extends AbstractExtensionService implements IThreadService, IExtensionService { private _proxy: ExtHostExtensionServiceShape; private _isDev: boolean; @@ -144,6 +144,29 @@ export class MainProcessExtensionService extends AbstractExtensionService { + return this.onReady().then(() => { + return this._registry.getAllExtensionDescriptions(); + }); + } + + public readExtensionPointContributions(extPoint: IExtensionPoint): TPromise[]> { + return this.onReady().then(() => { + let availableExtensions = this._registry.getAllExtensionDescriptions(); + + let result: ExtensionPointContribution[] = [], resultLen = 0; + for (let i = 0, len = availableExtensions.length; i < len; i++) { + let desc = availableExtensions[i]; + + if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { + result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); + } + } + + return result; + }); + } + public getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return this._extensionsStatus; } @@ -162,7 +185,7 @@ export class MainProcessExtensionService extends AbstractExtensionService { // the extension host calls $onExtensionActivated, where we write to `_activatedExtensions` - return this._activatedExtensions[extensionDescription.id]; + return this._manager.getActivatedExtension(extensionDescription.id); }); } @@ -201,11 +224,11 @@ export class MainProcessExtensionService extends AbstractExtensionService {