diff --git a/src/vs/workbench/api/common/extHostContributions.ts b/src/vs/workbench/api/common/extHostContributions.ts new file mode 100644 index 00000000000..e496cd73cdc --- /dev/null +++ b/src/vs/workbench/api/common/extHostContributions.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrandedService, IInstantiationService, ServicesAccessor, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + +/** + * An ext host contribution that will be loaded when the extension host starts and disposed when the extension host shuts down. + */ +export interface IExtHostContribution extends IDisposable { + // Marker Interface +} + +export namespace Extensions { + export const ExtHost = 'exthost.contributions.kind'; +} + +type IExtHostContributionSignature = new (...services: Service) => IExtHostContribution; + +export interface IExtHostContributionsRegistry { + + /** + * Registers a ext host contribution to the platform that will be loaded when the extension host starts and disposed when the extension host shuts down. + */ + registerExtHostContribution(contribution: IExtHostContributionSignature): void; + + /** + * Starts the registry by providing the required services. + */ + start(accessor: ServicesAccessor): void; + + /** + * Stops the registry by disposing the instantiated contributions + */ + stop(): void; +} + +class ExtHostContributionsRegistry implements IExtHostContributionsRegistry { + + private instantiationService: IInstantiationService | undefined; + private readonly contributions: IConstructorSignature0[] = []; + private toBeInstantiated: IConstructorSignature0[] = []; + private instantiated: IExtHostContribution[] = []; + + registerExtHostContribution(ctor: { new(...services: Services): IExtHostContribution }): void { + this.contributions.push(ctor); + + // Instantiate directly if started + if (this.instantiationService) { + this.instantiate(ctor); + } + + // Otherwise keep contributions to be instantiated + else { + this.toBeInstantiated.push(ctor); + } + } + + start(accessor: ServicesAccessor): void { + this.instantiationService = accessor.get(IInstantiationService); + this.toBeInstantiated.forEach(ctor => this.instantiate(ctor), this); + this.toBeInstantiated = []; + } + + private instantiate(ctor: { new(...services: Services): IExtHostContribution }) { + this.instantiated.push(this.instantiationService!.createInstance(ctor)); + } + + stop(): void { + this.instantiationService = undefined; + this.instantiated.forEach(dispose); + this.instantiated = []; + this.toBeInstantiated = [...this.contributions]; + } + +} + +Registry.add(Extensions.ExtHost, new ExtHostContributionsRegistry()); + diff --git a/src/vs/workbench/api/common/extHostInitDataService.ts b/src/vs/workbench/api/common/extHostInitDataService.ts index 2f0acd57a3c..cefea262ce3 100644 --- a/src/vs/workbench/api/common/extHostInitDataService.ts +++ b/src/vs/workbench/api/common/extHostInitDataService.ts @@ -5,10 +5,12 @@ import { IInitData } from './extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; export const IExtHostInitDataService = createDecorator('IExtHostInitDataService'); export interface IExtHostInitDataService extends Readonly { _serviceBrand: undefined; + readonly logFile: URI; } diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts index f29d417620d..263758ec40a 100644 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -3,34 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { join } from 'vs/base/common/path'; import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { Schemas } from 'vs/base/common/network'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { dirname } from 'vs/base/common/resources'; +import { IExtHostContribution, IExtHostContributionsRegistry, Extensions } from 'vs/workbench/api/common/extHostContributions'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { localize } from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { constructor( @IExtHostInitDataService initData: IExtHostInitDataService, - @IExtHostOutputService extHostOutputService: IExtHostOutputService ) { - if (initData.logsLocation.scheme !== Schemas.file) { throw new Error('Only file-logging supported'); } - super(new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel)); - - // Register an output channel for exthost log - extHostOutputService.createOutputChannelFromLogFile( - initData.remote.isRemote ? localize('remote extension host Log', "Remote Extension Host") : localize('extension host Log', "Extension Host"), - URI.file(join(initData.logsLocation.fsPath, `${ExtensionHostLogFileName}.log`)) - ); + if (initData.logFile.scheme !== Schemas.file) { throw new Error('Only file-logging supported'); } + super(new SpdLogService(ExtensionHostLogFileName, dirname(initData.logFile).fsPath, initData.logLevel)); } $setLevel(level: LogLevel): void { this.setLevel(level); } } + +class ExtHostLogChannelContribution extends Disposable implements IExtHostContribution { + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService outputSerice: IExtHostOutputService, + ) { + super(); + outputSerice.createOutputChannelFromLogFile( + initData.remote.isRemote ? localize('remote extension host Log', "Remote Extension Host") : localize('extension host Log', "Extension Host"), + initData.logFile); + } + +} + +Registry.as(Extensions.ExtHost).registerExtHostContribution(ExtHostLogChannelContribution); diff --git a/src/vs/workbench/api/worker/extHostLogService.ts b/src/vs/workbench/api/worker/extHostLogService.ts index acf2d3be8da..7625e52cca4 100644 --- a/src/vs/workbench/api/worker/extHostLogService.ts +++ b/src/vs/workbench/api/worker/extHostLogService.ts @@ -8,10 +8,11 @@ import { ExtHostLogServiceShape, MainThreadLogShape, MainContext } from 'vs/work import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { joinPath } from 'vs/base/common/resources'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; import { UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { IExtHostContribution, IExtHostContributionsRegistry, Extensions } from 'vs/workbench/api/common/extHostContributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; export class ExtHostLogService extends AbstractLogService implements ILogService, ExtHostLogServiceShape { @@ -23,14 +24,11 @@ export class ExtHostLogService extends AbstractLogService implements ILogService constructor( @IExtHostRpcService rpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, - @IExtHostOutputService extHostOutputService: IExtHostOutputService ) { super(); - const logFile = joinPath(initData.logsLocation, `${ExtensionHostLogFileName}.log`); this._proxy = rpc.getProxy(MainContext.MainThreadLog); - this._logFile = logFile.toJSON(); + this._logFile = initData.logFile.toJSON(); this.setLevel(initData.logLevel); - extHostOutputService.createOutputChannelFromLogFile(localize('name', "Worker Extension Host"), logFile); } $setLevel(level: LogLevel): void { @@ -75,3 +73,18 @@ export class ExtHostLogService extends AbstractLogService implements ILogService flush(): void { } } + + +class ExtHostLogChannelContribution extends Disposable implements IExtHostContribution { + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService outputSerice: IExtHostOutputService, + ) { + super(); + outputSerice.createOutputChannelFromLogFile(localize('name', "Worker Extension Host"), initData.logFile); + } + +} + +Registry.as(Extensions.ExtHost).registerExtHostContribution(ExtHostLogChannelContribution); diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index da1dc8e9a9e..105756420fc 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -5,7 +5,7 @@ import { timeout } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; @@ -21,6 +21,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IExtHostContributionsRegistry, Extensions } from 'vs/workbench/api/common/extHostContributions'; +import { joinPath } from 'vs/base/common/resources'; +import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; export interface IExitFn { (code?: number): any; @@ -52,13 +56,18 @@ export class ExtensionHostMain { // bootstrap services const services = new ServiceCollection(...getSingletonServiceDescriptors()); - services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData }); + services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData, logFile: joinPath(initData.logsLocation, `${ExtensionHostLogFileName}.log`) }); services.set(IExtHostRpcService, new ExtHostRpcService(rpcProtocol)); services.set(IURITransformerService, new URITransformerService(uriTransformer)); services.set(IHostUtils, hostUtils); const instaService: IInstantiationService = new InstantiationService(services, true); + // start ext host contributions + const extHostContributionsRegistry = Registry.as(Extensions.ExtHost); + instaService.invokeFunction(accessor => extHostContributionsRegistry.start(accessor)); + this._disposables.add(toDisposable(() => extHostContributionsRegistry.stop())); + // todo@joh // ugly self - inject const logService = instaService.invokeFunction(accessor => accessor.get(ILogService));