diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 97dd1483747..457818a9755 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -42,9 +42,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { DownloadService } from 'vs/platform/download/node/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { RemoteAuthorityResolverService } from 'vs/platform/remote/node/remoteAuthorityResolverService'; -import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { RemoteAuthorityResolverChannel } from 'vs/platform/remote/node/remoteAuthorityResolverChannel'; import { StaticRouter } from 'vs/base/parts/ipc/node/ipc'; import { DefaultURITransformer } from 'vs/base/common/uriIpc'; @@ -130,16 +127,11 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); - services.set(IRemoteAuthorityResolverService, new SyncDescriptor(RemoteAuthorityResolverService)); const instantiationService2 = instantiationService.createChild(services); instantiationService2.invokeFunction(accessor => { - const remoteAuthorityResolverService = accessor.get(IRemoteAuthorityResolverService); - const remoteAuthorityResolverChannel = new RemoteAuthorityResolverChannel(remoteAuthorityResolverService); - server.registerChannel('remoteAuthorityResolver', remoteAuthorityResolverChannel); - const extensionManagementService = accessor.get(IExtensionManagementService); const channel = new ExtensionManagementChannel(extensionManagementService, () => DefaultURITransformer); server.registerChannel('extensions', channel); @@ -153,7 +145,6 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I createSharedProcessContributions(instantiationService2); disposables.push(extensionManagementService as ExtensionManagementService); - disposables.push(remoteAuthorityResolverService as RemoteAuthorityResolverService); }); }); } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7e0aaa44e39..eccc31b79d1 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -209,7 +209,7 @@ export class CodeApplication extends Disposable { } else { const [host, strPort] = authority.split(':'); const port = parseInt(strPort, 10); - return { authority, host, port }; + return { authority, host, port, syncExtensions: false }; } }; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index c14a1e4bafd..ab490eb221d 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -11,7 +11,6 @@ import { ILocalization } from 'vs/platform/localizations/common/localizations'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IRemoteAuthorityResolver } from 'vs/platform/remote/common/remoteAuthorityResolver'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$'; export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); @@ -109,7 +108,6 @@ export interface IExtensionContributions { views?: { [location: string]: IView[] }; colors?: IColor[]; localizations?: ILocalization[]; - remoteAuthorityResolvers?: IRemoteAuthorityResolver[]; } export type ExtensionKind = 'ui' | 'workspace'; diff --git a/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts b/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts index 719b8887e38..1a626af33f4 100644 --- a/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts +++ b/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts @@ -14,7 +14,7 @@ import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IRemoteAuthorityResolverService, IRemoteAuthorityResolver } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; export class MulitExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -100,13 +100,13 @@ export class MulitExtensionManagementService extends Disposable implements IExte return this.extensionManagementServerService.getExtensionManagementServer(extension.location); } - private _remoteAuthorityResolverPromise: Thenable; + private _remoteAuthorityResolverPromise: Thenable; private hasToSyncExtensions(): Thenable { if (!this.extensionManagementServerService.remoteExtensionManagementServer) { return Promise.resolve(false); } if (!this._remoteAuthorityResolverPromise) { - this._remoteAuthorityResolverPromise = this.remoteAuthorityResolverService.getRemoteAuthorityResolver(this.extensionManagementServerService.remoteExtensionManagementServer.authority); + this._remoteAuthorityResolverPromise = this.remoteAuthorityResolverService.resolveAuthority(this.extensionManagementServerService.remoteExtensionManagementServer.authority); } return this._remoteAuthorityResolverPromise.then(({ syncExtensions }) => !!syncExtensions); } diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index ee8d7666782..ea9f3c1056a 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressStep } from 'vs/platform/progress/common/progress'; -import { Event } from 'vs/base/common/event'; export const IRemoteAuthorityResolverService = createDecorator('remoteAuthorityResolverService'); @@ -13,27 +11,14 @@ export interface ResolvedAuthority { readonly authority: string; readonly host: string; readonly port: number; + readonly syncExtensions: boolean; } -export type IResolvingProgressEvent = - { type: 'progress', authority: string, data: IProgressStep } - | { type: 'finished', authority: string } - | { type: 'output', authority: string, data: { channel: string, message: string; isErr?: boolean; } }; - export interface IRemoteAuthorityResolverService { _serviceBrand: any; - onResolvingProgress: Event; - resolveAuthority(authority: string): Thenable; - getRemoteAuthorityResolver(authority: string): Thenable; -} - -export interface IRemoteAuthorityResolver { - label: string; - path: string; - authorityPrefix: string; - syncExtensions?: boolean; + setResolvedAuthority(resolvedAuthority: ResolvedAuthority): void; } diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index 08d63019d7f..6c39fc6918b 100644 --- a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts @@ -3,48 +3,53 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel } from 'vs/base/parts/ipc/node/ipc'; -import { Event, buffer } from 'vs/base/common/event'; -import { ResolvedAuthority, IResolvingProgressEvent, IRemoteAuthorityResolverService, IRemoteAuthorityResolver } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ipcRenderer as ipc } from 'electron'; -export class RemoteAuthorityResolverChannelClient implements IRemoteAuthorityResolverService { +class PendingResolveAuthorityRequest { + constructor( + public readonly resolve: (value: ResolvedAuthority) => void, + public readonly reject: (err: any) => void, + public readonly promise: Promise, + ) { + } +} + +export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverService { _serviceBrand: any; - private _resolveAuthorityCache: { [authority: string]: Thenable; }; - get onResolvingProgress(): Event { return buffer(this.channel.listen('onResolvingProgress'), true); } + private _pendingResolveAuthorityRequests: { [authority: string]: PendingResolveAuthorityRequest; }; + private _resolvedAuthorities: { [authority: string]: ResolvedAuthority; }; - constructor(private channel: IChannel) { - this._resolveAuthorityCache = Object.create(null); + constructor() { + this._pendingResolveAuthorityRequests = Object.create(null); + this._resolvedAuthorities = Object.create(null); } resolveAuthority(authority: string): Thenable { - if (!this._resolveAuthorityCache[authority]) { - this._resolveAuthorityCache[authority] = this._resolveAuthority(authority); - this._resolveAuthorityCache[authority].then((r) => { - ipc.send('vscode:remoteAuthorityResolved', { - authority: authority, - host: r.host, - port: r.port - }); + if (this._resolvedAuthorities[authority]) { + return Promise.resolve(this._resolvedAuthorities[authority]); + } + if (!this._pendingResolveAuthorityRequests[authority]) { + let resolve: (value: ResolvedAuthority) => void; + let reject: (err: any) => void; + let promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; }); + this._pendingResolveAuthorityRequests[authority] = new PendingResolveAuthorityRequest(resolve, reject, promise); } - return this._resolveAuthorityCache[authority]; + return this._pendingResolveAuthorityRequests[authority].promise; } - getRemoteAuthorityResolver(authority: string): Thenable { - return this.channel.call('getRemoteAuthorityResolver', [authority]); - } - - private _resolveAuthority(authority: string): Thenable { - if (authority.indexOf('+') >= 0) { - return this.channel.call('resolveAuthority', [authority]); - } else { - const [host, strPort] = authority.split(':'); - const port = parseInt(strPort, 10); - return Promise.resolve({ authority, host, port }); + setResolvedAuthority(resolvedAuthority: ResolvedAuthority) { + this._resolvedAuthorities[resolvedAuthority.authority] = resolvedAuthority; + if (this._pendingResolveAuthorityRequests[resolvedAuthority.authority]) { + let request = this._pendingResolveAuthorityRequests[resolvedAuthority.authority]; + delete this._pendingResolveAuthorityRequests[resolvedAuthority.authority]; + ipc.send('vscode:remoteAuthorityResolved', resolvedAuthority); + request.resolve(resolvedAuthority); } } - } diff --git a/src/vs/platform/remote/node/remoteAuthorityResolverChannel.ts b/src/vs/platform/remote/node/remoteAuthorityResolverChannel.ts deleted file mode 100644 index debabbe1e21..00000000000 --- a/src/vs/platform/remote/node/remoteAuthorityResolverChannel.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { Event, buffer } from 'vs/base/common/event'; -import { IResolvingProgressEvent, IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; - -export class RemoteAuthorityResolverChannel implements IServerChannel { - - onResolvingProgress: Event; - - constructor(private service: IRemoteAuthorityResolverService) { - this.onResolvingProgress = buffer(service.onResolvingProgress, true); - } - - listen(_, event: string): Event { - switch (event) { - case 'onResolvingProgress': return this.onResolvingProgress; - } - - throw new Error('Invalid listen'); - } - - call(_, command: string, args?: any): Thenable { - switch (command) { - case 'resolveAuthority': return this.service.resolveAuthority(args[0]); - case 'getRemoteAuthorityResolver': return this.service.getRemoteAuthorityResolver(args[0]); - } - - throw new Error('Invalid call'); - } -} - diff --git a/src/vs/platform/remote/node/remoteAuthorityResolverService.ts b/src/vs/platform/remote/node/remoteAuthorityResolverService.ts deleted file mode 100644 index f7dc1b90dd2..00000000000 --- a/src/vs/platform/remote/node/remoteAuthorityResolverService.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IRemoteAuthorityResolverService, ResolvedAuthority, IResolvingProgressEvent, IRemoteAuthorityResolver } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; - -export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { - - _serviceBrand: any; - - private _onResolvingProgress: Emitter = this._register(new Emitter()); - readonly onResolvingProgress: Event = this._onResolvingProgress.event; - - constructor( - @IEnvironmentService environmentService: IEnvironmentService, - @IConfigurationService configurationService: IConfigurationService, - @ILogService logService: ILogService, - @IExtensionManagementService extensionManagementService: IExtensionManagementService - ) { - super(); - } - - async resolveAuthority(authority: string): Promise { - throw new Error(`Not implemented`); - } - - async getRemoteAuthorityResolver(authority: string): Promise { - throw new Error(`Not implemented`); - } -} diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 36a239a15e1..b58819bc96a 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -61,10 +61,11 @@ import { ExtHostWindow } from 'vs/workbench/api/node/extHostWindow'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { IExtensionDescription, throwProposedApiError, checkProposedApiEnabled, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import * as vscode from 'vscode'; export interface IExtensionApiFactory { - (extension: IExtensionDescription): typeof vscode; + (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry): typeof vscode; } function proposedApiFunction(extension: IExtensionDescription, fn: T): T { @@ -140,7 +141,7 @@ export function createApiFactory( // Register API-ish commands ExtHostApiCommands.register(extHostCommands); - return function (extension: IExtensionDescription): typeof vscode { + return function (extension: IExtensionDescription, extensionRegistry: ExtensionDescriptionRegistry): typeof vscode { // Check document selectors for being overly generic. Technically this isn't a problem but // in practice many extensions say they support `fooLang` but need fs-access to do so. Those @@ -259,14 +260,14 @@ export function createApiFactory( // namespace: extensions const extensions: typeof vscode.extensions = { getExtension(extensionId: string): Extension { - let desc = extensionService.getExtensionDescription(extensionId); + let desc = extensionRegistry.getExtensionDescription(extensionId); if (desc) { return new Extension(extensionService, desc); } return undefined; }, get all(): Extension[] { - return extensionService.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc)); + return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc)); } }; @@ -860,11 +861,11 @@ class Extension implements vscode.Extension { } } -export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory): Promise { - return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie)); +export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory, extensionRegistry: ExtensionDescriptionRegistry): Promise { + return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie, extensionRegistry)); } -function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree): void { +function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree, extensionRegistry: ExtensionDescriptionRegistry): void { // each extension is meant to get its own api implementation const extApiImpl = new Map(); @@ -882,7 +883,7 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT if (ext) { let apiImpl = extApiImpl.get(ext.id); if (!apiImpl) { - apiImpl = factory(ext); + apiImpl = factory(ext, extensionRegistry); extApiImpl.set(ext.id, apiImpl); } return apiImpl; @@ -893,7 +894,7 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT let extensionPathsPretty = ''; extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.id}\n`); console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); - defaultApiImpl = factory(nullExtensionDescription); + defaultApiImpl = factory(nullExtensionDescription, extensionRegistry); } return defaultApiImpl; }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 68c1a155551..c58857be4ff 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -43,6 +43,7 @@ import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/pro import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -50,7 +51,7 @@ export interface IEnvironment { appSettingsHome: URI; extensionDevelopmentLocationURI: URI; extensionTestsPath: string; - globalStorageHome: string; + globalStorageHome: URI; } export interface IWorkspaceData { @@ -70,6 +71,7 @@ export interface IInitData { telemetryInfo: ITelemetryInfo; logLevel: LogLevel; logsLocation: URI; + autoStart: boolean; remoteAuthority?: string | null; } @@ -732,6 +734,8 @@ export interface ExtHostSearchShape { } export interface ExtHostExtensionServiceShape { + $resolveAuthority(remoteAuthority: string): Thenable; + $startExtensionHost(enabledExtensionIds: string[]): Thenable; $activateByEvent(activationEvent: string): Thenable; } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index b84110540c5..bafe4d4f68d 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -31,6 +31,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; export class ExtHostDebugService implements ExtHostDebugServiceShape { @@ -119,28 +120,29 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._breakpoints = new Map(); this._breakpointEventsActive = false; - - // register all debug extensions - const debugTypes: string[] = []; - for (const ed of this._extensionService.getAllExtensionDescriptions()) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - // only debugger contributions with a "label" are considered a "defining" debugger contribution - if (dbg.type && dbg.label) { - debugTypes.push(dbg.type); - if (dbg.adapterExecutableCommand) { - this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); + this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { + // register all debug extensions + const debugTypes: string[] = []; + for (const ed of extensionRegistry.getAllExtensionDescriptions()) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + // only debugger contributions with a "label" are considered a "defining" debugger contribution + if (dbg.type && dbg.label) { + debugTypes.push(dbg.type); + if (dbg.adapterExecutableCommand) { + this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); + } } } } } } - } - if (debugTypes.length > 0) { - this._debugServiceProxy.$registerDebugTypes(debugTypes); - } + if (debugTypes.length > 0) { + this._debugServiceProxy.$registerDebugTypes(debugTypes); + } + }); } // extension debug API @@ -722,7 +724,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - private getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory, session: ExtHostDebugSession): Thenable { + private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory, session: ExtHostDebugSession): Promise { // a "debugServer" attribute in the launch config takes precedence const serverPort = session.configuration.debugServer; @@ -739,7 +741,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } if (adapterProvider) { - return asThenable(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session))); + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return asThenable(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))); } // try deprecated command based extension API "adapterExecutableCommand" to determine the executable @@ -754,11 +757,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } // fallback: use executable information from package.json - return Promise.resolve(this.daExecutableFromPackage(session)); + const extensionRegistry = await this._extensionService.getExtensionRegistry(); + return Promise.resolve(this.daExecutableFromPackage(session, extensionRegistry)); } - private daExecutableFromPackage(session: ExtHostDebugSession): DebugAdapterExecutable | undefined { - const dae = ExecutableDebugAdapter.platformAdapterExecutable(this._extensionService.getAllExtensionDescriptions(), session.type); + private daExecutableFromPackage(session: ExtHostDebugSession, extensionRegistry: ExtensionDescriptionRegistry): DebugAdapterExecutable | undefined { + const dae = ExecutableDebugAdapter.platformAdapterExecutable(extensionRegistry.getAllExtensionDescriptions(), session.type); if (dae) { return new DebugAdapterExecutable(dae.command, dae.args, dae.options); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index ebaee9ffdef..7841dcdaa20 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -3,16 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'path'; +import * as nls from 'vs/nls'; +import * as path from 'path'; import { Barrier } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; -import { dirExists, mkdirp, realpath, writeFile } from 'vs/base/node/pfs'; +import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl'; -import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, IWorkspaceData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol'; +import { createApiFactory, initializeExtensionApi, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; +import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, IWorkspaceData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule } from 'vs/workbench/api/node/extHostExtensionActivator'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; @@ -21,6 +22,9 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { IExtensionDescription, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { connectProxyResolver } from 'vs/workbench/node/proxyResolver'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import * as errors from 'vs/base/common/errors'; +import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; class ExtensionMemento implements IExtensionMemento { @@ -93,13 +97,13 @@ class ExtensionStoragePath { workspaceValue(extension: IExtensionDescription): string { if (this._value) { - return join(this._value, extension.id); + return path.join(this._value, extension.id); } return undefined; } globalValue(extension: IExtensionDescription): string { - return join(this._environment.globalStorageHome, extension.id); + return path.join(this._environment.globalStorageHome.fsPath, extension.id); } private async _getOrCreateWorkspaceStoragePath(): Promise { @@ -108,18 +112,18 @@ class ExtensionStoragePath { } const storageName = this._workspace.id; - const storagePath = join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName); + const storagePath = path.join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName); - const exists = await dirExists(storagePath); + const exists = await pfs.dirExists(storagePath); if (exists) { return storagePath; } try { - await mkdirp(storagePath); - await writeFile( - join(storagePath, 'meta.json'), + await pfs.mkdirp(storagePath); + await pfs.writeFile( + path.join(storagePath, 'meta.json'), JSON.stringify({ id: this._workspace.id, configuration: this._workspace.configuration && URI.revive(this._workspace.configuration).toString(), @@ -134,71 +138,112 @@ class ExtensionStoragePath { } } } + +interface ITestRunner { + run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; +} + export class ExtHostExtensionService implements ExtHostExtensionServiceShape { + private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; + + private readonly _nativeExit: (code?: number) => void; + private readonly _initData: IInitData; + private readonly _extHostContext: IMainContext; + private readonly _extHostWorkspace: ExtHostWorkspace; + private readonly _extHostConfiguration: ExtHostConfiguration; + private readonly _extHostLogService: ExtHostLogService; + + private readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; + private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; + private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; + private readonly _barrier: Barrier; private readonly _registry: ExtensionDescriptionRegistry; - private readonly _mainThreadTelemetry: MainThreadTelemetryShape; private readonly _storage: ExtHostStorage; private readonly _storagePath: ExtensionStoragePath; - private readonly _proxy: MainThreadExtensionServiceShape; - private readonly _extHostLogService: ExtHostLogService; - private _activator: ExtensionsActivator; + private readonly _activator: ExtensionsActivator; private _extensionPathIndex: Promise>; - /** - * This class is constructed manually because it is a service, so it doesn't use any ctor injection - */ - constructor(initData: IInitData, + private readonly _extensionApiFactory: IExtensionApiFactory; + + private _started: boolean; + + constructor( + nativeExit: (code?: number) => void, + initData: IInitData, extHostContext: IMainContext, extHostWorkspace: ExtHostWorkspace, extHostConfiguration: ExtHostConfiguration, - extHostLogService: ExtHostLogService, - mainThreadTelemetry: MainThreadTelemetryShape + extHostLogService: ExtHostLogService ) { + this._nativeExit = nativeExit; + this._initData = initData; + this._extHostContext = extHostContext; + this._extHostWorkspace = extHostWorkspace; + this._extHostConfiguration = extHostConfiguration; + this._extHostLogService = extHostLogService; + + this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); + this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); + this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); + this._barrier = new Barrier(); this._registry = new ExtensionDescriptionRegistry(initData.extensions); - this._extHostLogService = extHostLogService; - this._mainThreadTelemetry = mainThreadTelemetry; - this._storage = new ExtHostStorage(extHostContext); + this._storage = new ExtHostStorage(this._extHostContext); this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); - this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService); - this._activator = null; + this._activator = new ExtensionsActivator(this._registry, { + showMessage: (severity: Severity, message: string): void => { + this._mainThreadExtensionsProxy.$localShowMessage(severity, message); + + switch (severity) { + case Severity.Error: + console.error(message); + break; + case Severity.Warning: + console.warn(message); + break; + default: + console.log(message); + } + }, + + actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise => { + return this._activateExtension(extensionDescription, reason); + } + }); + this._extensionPathIndex = null; // initialize API first (i.e. do not release barrier until the API is initialized) - const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, this._extHostLogService, this._storage); + this._extensionApiFactory = createApiFactory(this._initData, this._extHostContext, this._extHostWorkspace, this._extHostConfiguration, this, this._extHostLogService, this._storage); - initializeExtensionApi(this, apiFactory).then(() => { + this._started = false; + + initializeExtensionApi(this, this._extensionApiFactory, this._registry).then(() => { // Do this when extension service exists, but extensions are not being activated yet. - return connectProxyResolver(extHostWorkspace, extHostConfiguration, this, this._extHostLogService, this._mainThreadTelemetry); + return connectProxyResolver(this._extHostWorkspace, this._extHostConfiguration, this, this._extHostLogService, this._mainThreadTelemetryProxy); }).then(() => { - - this._activator = new ExtensionsActivator(this._registry, { - showMessage: (severity: Severity, message: string): void => { - this._proxy.$localShowMessage(severity, message); - - switch (severity) { - case Severity.Error: - console.error(message); - break; - case Severity.Warning: - console.warn(message); - break; - default: - console.log(message); - } - }, - - actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise => { - return this._activateExtension(extensionDescription, reason); - } - }); - this._barrier.open(); }); + + if (this._initData.autoStart) { + this._startExtensionHost(); + } } - public onExtensionAPIReady(): Thenable { - return this._barrier.wait(); + public async deactivateAll(): Promise { + let allPromises: Thenable[] = []; + try { + const allExtensions = this._registry.getAllExtensionDescriptions(); + const allExtensionsIds = allExtensions.map(ext => ext.id); + const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); + + allPromises = activatedExtensions.map((extensionId) => { + return this._deactivate(extensionId); + }); + } catch (err) { + // TODO: write to log once we have one + } + await allPromises; } public isActivated(extensionId: string): boolean { @@ -208,25 +253,17 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { return false; } - public activateByEvent(activationEvent: string, startup: boolean): Thenable { + private _activateByEvent(activationEvent: string, startup: boolean): Thenable { const reason = new ExtensionActivatedByEvent(startup, activationEvent); - if (this._barrier.isOpen()) { - return this._activator.activateByEvent(activationEvent, reason); - } else { - return this._barrier.wait().then(() => this._activator.activateByEvent(activationEvent, reason)); - } + return this._activator.activateByEvent(activationEvent, reason); } - public activateById(extensionId: string, reason: ExtensionActivationReason): Thenable { - if (this._barrier.isOpen()) { - return this._activator.activateById(extensionId, reason); - } else { - return this._barrier.wait().then(() => this._activator.activateById(extensionId, reason)); - } + private _activateById(extensionId: string, reason: ExtensionActivationReason): Thenable { + return this._activator.activateById(extensionId, reason); } public activateByIdWithErrors(extensionId: string, reason: ExtensionActivationReason): Thenable { - return this.activateById(extensionId, reason).then(() => { + return this._activateById(extensionId, reason).then(() => { const extension = this._activator.getActivatedExtension(extensionId); if (extension.activationFailed) { // activation failed => bubble up the error as the promise result @@ -236,12 +273,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { }); } - public getAllExtensionDescriptions(): IExtensionDescription[] { - return this._registry.getAllExtensionDescriptions(); - } - - public getExtensionDescription(extensionId: string): IExtensionDescription { - return this._registry.getExtensionDescription(extensionId); + public getExtensionRegistry(): Promise { + return this._barrier.wait().then(_ => this._registry); } public getExtensionExports(extensionId: string): IExtensionAPI { @@ -256,19 +289,18 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { public getExtensionPathIndex(): Promise> { if (!this._extensionPathIndex) { const tree = TernarySearchTree.forPaths(); - const extensions = this.getAllExtensionDescriptions().map(ext => { + const extensions = this._registry.getAllExtensionDescriptions().map(ext => { if (!ext.main) { return undefined; } - return realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); + return pfs.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); }); this._extensionPathIndex = Promise.all(extensions).then(() => tree); } return this._extensionPathIndex; } - - public deactivate(extensionId: string): Thenable { + private _deactivate(extensionId: string): Thenable { let result = Promise.resolve(void 0); if (!this._barrier.isOpen()) { @@ -307,7 +339,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } public addMessage(extensionId: string, severity: Severity, message: string): void { - this._proxy.$addMessage(extensionId, severity, message); + this._mainThreadExtensionsProxy.$addMessage(extensionId, severity, message); } // --- impl @@ -316,11 +348,11 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { const activationTimes = activatedExtension.activationTimes; let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - this._proxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); + this._mainThreadExtensionsProxy.$onExtensionActivated(extensionDescription.id, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); return activatedExtension; }, (err) => { - this._proxy.$onExtensionActivationFailed(extensionDescription.id); + this._mainThreadExtensionsProxy.$onExtensionActivationFailed(extensionDescription.id); this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); throw err; }); @@ -337,7 +369,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this._mainThreadTelemetry.$publicLog('extensionActivationTimes', { + this._mainThreadTelemetryProxy.$publicLog('extensionActivationTimes', { ...event, ...(activationTimes || {}), outcome, @@ -353,7 +385,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { ] } */ - this._mainThreadTelemetry.$publicLog('activatePlugin', event); + this._mainThreadTelemetryProxy.$publicLog('activatePlugin', event); if (!extensionDescription.main) { // Treat the extension as being empty => NOT AN ERROR CASE return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); @@ -389,7 +421,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, storagePath: this._storagePath.workspaceValue(extensionDescription), get globalStoragePath(): string { checkProposedApiEnabled(extensionDescription); return that._storagePath.globalValue(extensionDescription); }, - asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); }, + asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, logPath: that._extHostLogService.getLogDirectory(extensionDescription.id) }); }); @@ -429,10 +461,188 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } } + // -- eager activation + + // Handle "eager" activation extensions + private _handleEagerExtensions(): Promise { + this._activateByEvent('*', true).then(null, (err) => { + console.error(err); + }); + + return this._handleWorkspaceContainsEagerExtensions(this._initData.workspace); + } + + private _handleWorkspaceContainsEagerExtensions(workspace: IWorkspaceData): Promise { + if (!workspace || workspace.folders.length === 0) { + return Promise.resolve(null); + } + + return Promise.all( + this._registry.getAllExtensionDescriptions().map((desc) => { + return this._handleWorkspaceContainsEagerExtension(workspace, desc); + }) + ).then(() => { }); + } + + private _handleWorkspaceContainsEagerExtension(workspace: IWorkspaceData, desc: IExtensionDescription): Promise { + const activationEvents = desc.activationEvents; + if (!activationEvents) { + return Promise.resolve(void 0); + } + + const fileNames: string[] = []; + const globPatterns: string[] = []; + + for (let i = 0; i < activationEvents.length; i++) { + if (/^workspaceContains:/.test(activationEvents[i])) { + const fileNameOrGlob = activationEvents[i].substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + globPatterns.push(fileNameOrGlob); + } else { + fileNames.push(fileNameOrGlob); + } + } + } + + if (fileNames.length === 0 && globPatterns.length === 0) { + return Promise.resolve(void 0); + } + + const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(workspace, desc.id, fileName))).then(() => { }); + const globPatternPromise = this._activateIfGlobPatterns(desc.id, globPatterns); + + return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); + } + + private async _activateIfFileName(workspace: IWorkspaceData, extensionId: string, fileName: string): Promise { + + // find exact path + for (const { uri } of workspace.folders) { + if (await pfs.exists(path.join(URI.revive(uri).fsPath, fileName))) { + // the file was found + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) + .then(null, err => console.error(err)) + ); + } + } + + return undefined; + } + + private async _activateIfGlobPatterns(extensionId: string, globPatterns: string[]): Promise { + this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId}, entryPoint: workspaceContains`); + + if (globPatterns.length === 0) { + return Promise.resolve(void 0); + } + + const tokenSource = new CancellationTokenSource(); + const searchP = this._mainThreadWorkspaceProxy.$checkExists(globPatterns, tokenSource.token); + + const timer = setTimeout(async () => { + tokenSource.cancel(); + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) + .then(null, err => console.error(err)); + }, ExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); + + let exists: boolean; + try { + exists = await searchP; + } catch (err) { + if (!errors.isPromiseCanceledError(err)) { + console.error(err); + } + } + + tokenSource.dispose(); + clearTimeout(timer); + + if (exists) { + // a file was found matching one of the glob patterns + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) + .then(null, err => console.error(err)) + ); + } + + return Promise.resolve(void 0); + } + + private _handleExtensionTests(): Promise { + if (!this._initData.environment.extensionTestsPath || !this._initData.environment.extensionDevelopmentLocationURI) { + return Promise.resolve(null); + } + + // Require the test runner via node require from the provided path + let testRunner: ITestRunner; + let requireError: Error; + try { + testRunner = require.__$__nodeRequire(this._initData.environment.extensionTestsPath); + } catch (error) { + requireError = error; + } + + // Execute the runner if it follows our spec + if (testRunner && typeof testRunner.run === 'function') { + return new Promise((c, e) => { + testRunner.run(this._initData.environment.extensionTestsPath, (error, failures) => { + if (error) { + e(error.toString()); + } else { + c(null); + } + + // after tests have run, we shutdown the host + this._gracefulExit(failures && failures > 0 ? 1 /* ERROR */ : 0 /* OK */); + }); + }); + } + + // Otherwise make sure to shutdown anyway even in case of an error + else { + this._gracefulExit(1 /* ERROR */); + } + + return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", this._initData.environment.extensionTestsPath))); + } + + private _gracefulExit(code: number): void { + // to give the PH process a chance to flush any outstanding console + // messages to the main process, we delay the exit() by some time + setTimeout(() => this._nativeExit(code), 500); + } + + private _startExtensionHost(): Thenable { + if (this._started) { + throw new Error(`Extension host is already started!`); + } + this._started = true; + + return this._barrier.wait() + .then(() => this._handleEagerExtensions()) + .then(() => this._handleExtensionTests()) + .then(() => { + this._extHostLogService.info(`eager extensions activated`); + }); + } + // -- called by main thread + public async $resolveAuthority(remoteAuthority: string): Promise { + throw new Error(`Not implemented`); + } + + public $startExtensionHost(enabledExtensionIds: string[]): Thenable { + this._registry.keepOnly(enabledExtensionIds); + return this._startExtensionHost(); + } + public $activateByEvent(activationEvent: string): Thenable { - return this.activateByEvent(activationEvent, false); + return ( + this._barrier.wait() + .then(_ => this._activateByEvent(activationEvent, false)) + ); } } diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 78796b5c45f..e6f6d7eeba4 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -103,7 +103,7 @@ import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; import { TextResourcePropertiesService } from 'vs/workbench/services/textfile/electron-browser/textResourcePropertiesService'; import { MulitExtensionManagementService } from 'vs/platform/extensionManagement/node/multiExtensionManagement'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { RemoteAuthorityResolverChannelClient } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; +import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; /** * Services that we require for the Shell @@ -437,8 +437,7 @@ export class WorkbenchShell extends Disposable { serviceCollection.set(IDownloadService, new SyncDescriptor(DownloadService)); serviceCollection.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - const remoteAuthorityResolverChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('remoteAuthorityResolver'))); - const remoteAuthorityResolverService = new RemoteAuthorityResolverChannelClient(remoteAuthorityResolverChannel); + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); const remoteAgentService = new RemoteAgentService(this.configuration, this.notificationService, this.environmentService, remoteAuthorityResolverService); diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 914e5b81574..54999cd59c5 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -3,20 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import { join } from 'path'; import { timeout } from 'vs/base/common/async'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Counter } from 'vs/base/common/numbers'; import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; -import * as pfs from 'vs/base/node/pfs'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; -import { IEnvironment, IInitData, IWorkspaceData, MainContext, MainThreadWorkspaceShape } from 'vs/workbench/api/node/extHost.protocol'; +import { IEnvironment, IInitData, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; -import { ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; @@ -48,33 +43,26 @@ export function exit(code?: number) { nativeExit(code); } -interface ITestRunner { - run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; -} - export class ExtensionHostMain { - private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; - private _isTerminating: boolean = false; - private _workspace: IWorkspaceData; - private _environment: IEnvironment; - private _extensionService: ExtHostExtensionService; - private _extHostConfiguration: ExtHostConfiguration; - private _extHostLogService: ExtHostLogService; + private _isTerminating: boolean; + private readonly _environment: IEnvironment; + private readonly _extensionService: ExtHostExtensionService; + private readonly _extHostConfiguration: ExtHostConfiguration; + private readonly _extHostLogService: ExtHostLogService; private disposables: IDisposable[] = []; private _searchRequestIdProvider: Counter; - private _mainThreadWorkspace: MainThreadWorkspaceShape; constructor(protocol: IMessagePassingProtocol, initData: IInitData) { + this._isTerminating = false; const uriTransformer: IURITransformer = null; const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); // ensure URIs are transformed and revived initData = this.transform(initData, rpcProtocol); this._environment = initData.environment; - this._workspace = initData.workspace; const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) patchProcess(allowExit); @@ -90,8 +78,7 @@ export class ExtensionHostMain { this._extHostLogService.trace('initData', initData); this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration); - const mainThreadTelemetry = rpcProtocol.getProxy(MainContext.MainThreadTelemetry); - this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService, mainThreadTelemetry); + this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService); // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) @@ -125,17 +112,6 @@ export class ExtensionHostMain { mainThreadErrors.$onUnexpectedError(data); } }); - - this._mainThreadWorkspace = rpcProtocol.getProxy(MainContext.MainThreadWorkspace); - } - - start(): Thenable { - return this._extensionService.onExtensionAPIReady() - .then(() => this.handleEagerExtensions()) - .then(() => this.handleExtensionTests()) - .then(() => { - this._extHostLogService.info(`eager extensions activated`); - }); } terminate(): void { @@ -151,184 +127,22 @@ export class ExtensionHostMain { // TODO: write to log once we have one }); - let allPromises: Thenable[] = []; - try { - const allExtensions = this._extensionService.getAllExtensionDescriptions(); - const allExtensionsIds = allExtensions.map(ext => ext.id); - const activatedExtensions = allExtensionsIds.filter(id => this._extensionService.isActivated(id)); + const extensionsDeactivated = this._extensionService.deactivateAll(); - allPromises = activatedExtensions.map((extensionId) => { - return this._extensionService.deactivate(extensionId); - }); - } catch (err) { - // TODO: write to log once we have one - } - - const extensionsDeactivated = Promise.all(allPromises).then(() => void 0); - - // Give extensions 1 second to wrap up any async dispose, then exit + // Give extensions 1 second to wrap up any async dispose, then exit in at most 4 seconds setTimeout(() => { Promise.race([timeout(4000), extensionsDeactivated]).then(() => exit(), () => exit()); }, 1000); } - // Handle "eager" activation extensions - private handleEagerExtensions(): Promise { - this._extensionService.activateByEvent('*', true).then(null, (err) => { - console.error(err); - }); - - return this.handleWorkspaceContainsEagerExtensions(); - } - - private handleWorkspaceContainsEagerExtensions(): Promise { - if (!this._workspace || this._workspace.folders.length === 0) { - return Promise.resolve(null); - } - - return Promise.all( - this._extensionService.getAllExtensionDescriptions().map((desc) => { - return this.handleWorkspaceContainsEagerExtension(desc); - }) - ).then(() => { }); - } - - private handleWorkspaceContainsEagerExtension(desc: IExtensionDescription): Promise { - const activationEvents = desc.activationEvents; - if (!activationEvents) { - return Promise.resolve(void 0); - } - - const fileNames: string[] = []; - const globPatterns: string[] = []; - - for (let i = 0; i < activationEvents.length; i++) { - if (/^workspaceContains:/.test(activationEvents[i])) { - const fileNameOrGlob = activationEvents[i].substr('workspaceContains:'.length); - if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { - globPatterns.push(fileNameOrGlob); - } else { - fileNames.push(fileNameOrGlob); - } - } - } - - if (fileNames.length === 0 && globPatterns.length === 0) { - return Promise.resolve(void 0); - } - - const fileNamePromise = Promise.all(fileNames.map((fileName) => this.activateIfFileName(desc.id, fileName))).then(() => { }); - const globPatternPromise = this.activateIfGlobPatterns(desc.id, globPatterns); - - return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); - } - - private async activateIfFileName(extensionId: string, fileName: string): Promise { - - // find exact path - for (const { uri } of this._workspace.folders) { - if (await pfs.exists(join(URI.revive(uri).fsPath, fileName))) { - // the file was found - return ( - this._extensionService.activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) - .then(null, err => console.error(err)) - ); - } - } - - return undefined; - } - - private async activateIfGlobPatterns(extensionId: string, globPatterns: string[]): Promise { - this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId}, entryPoint: workspaceContains`); - - if (globPatterns.length === 0) { - return Promise.resolve(void 0); - } - - const tokenSource = new CancellationTokenSource(); - const searchP = this._mainThreadWorkspace.$checkExists(globPatterns, tokenSource.token); - - const timer = setTimeout(async () => { - tokenSource.cancel(); - this._extensionService.activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) - .then(null, err => console.error(err)); - }, ExtensionHostMain.WORKSPACE_CONTAINS_TIMEOUT); - - let exists: boolean; - try { - exists = await searchP; - } catch (err) { - if (!errors.isPromiseCanceledError(err)) { - console.error(err); - } - } - - tokenSource.dispose(); - clearTimeout(timer); - - if (exists) { - // a file was found matching one of the glob patterns - return ( - this._extensionService.activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) - .then(null, err => console.error(err)) - ); - } - - return Promise.resolve(void 0); - } - - private handleExtensionTests(): Promise { - if (!this._environment.extensionTestsPath || !this._environment.extensionDevelopmentLocationURI) { - return Promise.resolve(null); - } - - // Require the test runner via node require from the provided path - let testRunner: ITestRunner; - let requireError: Error; - try { - testRunner = require.__$__nodeRequire(this._environment.extensionTestsPath); - } catch (error) { - requireError = error; - } - - // Execute the runner if it follows our spec - if (testRunner && typeof testRunner.run === 'function') { - return new Promise((c, e) => { - testRunner.run(this._environment.extensionTestsPath, (error, failures) => { - if (error) { - e(error.toString()); - } else { - c(null); - } - - // after tests have run, we shutdown the host - this.gracefulExit(failures && failures > 0 ? 1 /* ERROR */ : 0 /* OK */); - }); - }); - } - - // Otherwise make sure to shutdown anyway even in case of an error - else { - this.gracefulExit(1 /* ERROR */); - } - - return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", this._environment.extensionTestsPath))); - } - private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData { initData.extensions.forEach((ext) => (ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation))); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome)); initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI)); + initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome)); initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation)); initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace); return initData; } - - private gracefulExit(code: number): void { - // to give the PH process a chance to flush any outstanding console - // messages to the main process, we delay the exit() by some time - setTimeout(() => exit(code), 500); - } } diff --git a/src/vs/workbench/node/extensionHostProcess.ts b/src/vs/workbench/node/extensionHostProcess.ts index b2de25204b0..8d182d18d9e 100644 --- a/src/vs/workbench/node/extensionHostProcess.ts +++ b/src/vs/workbench/node/extensionHostProcess.ts @@ -167,7 +167,6 @@ createExtHostProtocol().then(protocol => { // setup things const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData); onTerminate = () => extensionHostMain.terminate(); - return extensionHostMain.start(); }).catch(err => console.error(err)); function patchExecArgv() { diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index 8a5eaf9bd75..b22725edd21 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -38,7 +38,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ILabelService } from 'vs/platform/label/common/label'; import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { join } from 'path'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -117,7 +117,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, @IStorageService storageService: IStorageService, - @IRemoteAuthorityResolverService private remoteAuthorityResolverService: IRemoteAuthorityResolverService + @ILabelService private readonly _labelService: ILabelService ) { super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -375,11 +375,11 @@ export class RuntimeExtensionsEditor extends BaseEditor { const el = $('span'); el.innerHTML = renderOcticons(`$(rss) ${element.description.extensionLocation.authority}`); data.msgContainer.appendChild(el); - this.remoteAuthorityResolverService.getRemoteAuthorityResolver(element.description.extensionLocation.authority).then(resolver => { - if (resolver && resolver.label.length) { - el.innerHTML = renderOcticons(`$(rss) ${resolver.label}`); - } - }); + + const hostLabel = this._labelService.getHostLabel(); + if (hostLabel) { + el.innerHTML = renderOcticons(`$(rss) ${hostLabel}`); + } } if (this._profileInfo) { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 1b955e1bd16..d01e90b0c72 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -42,7 +42,7 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e export interface IExtensionHostStarter { readonly onCrashed: Event<[number, string]>; - start(): Promise; + start(): Thenable; getInspectPort(): number; dispose(): void; } @@ -93,6 +93,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private _messageProtocol: Promise; constructor( + private readonly _autoStart: boolean, private readonly _extensions: Promise, private readonly _extensionHostLogsLocation: URI, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -425,7 +426,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : void 0, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsPath: this._environmentService.extensionTestsPath, - globalStorageHome: this._environmentService.globalStorageHome + globalStorageHome: URI.file(this._environmentService.globalStorageHome) }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, @@ -438,7 +439,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { configuration: !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment ? { ...configurationData, configurationScopes: getScopes() } : configurationData, telemetryInfo, logLevel: this._logService.getLevel(), - logsLocation: this._extensionHostLogsLocation + logsLocation: this._extensionHostLogsLocation, + autoStart: this._autoStart }; return r; }); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts index f1787d08395..28da54a0eb7 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts @@ -17,6 +17,7 @@ import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol'; +import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; @@ -171,6 +172,14 @@ export class ExtensionHostProcessManager extends Disposable { } return 0; } + + public resolveAuthority(remoteAuthority: string): Thenable { + return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority)); + } + + public start(enabledExtensionIds: string[]): Thenable { + return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds)); + } } const colorTables = [ diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 026c242e2f0..92dc0437492 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -112,8 +112,8 @@ export class ExtensionService extends Disposable implements IExtensionService { // reschedule to ensure this runs after restoring viewlets, panels, and editors runWhenIdle(() => { perf.mark('willLoadExtensions'); + this._startExtensionHostProcess(true, []); this._scanAndHandleExtensions(); - this._startExtensionHostProcess([]); this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions')); }, 50 /*max delay*/); }); @@ -127,11 +127,11 @@ export class ExtensionService extends Disposable implements IExtensionService { public restartExtensionHost(): void { this._stopExtensionHostProcess(); - this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents)); + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); } public startExtensionHost(): void { - this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents)); + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); } public stopExtensionHost(): void { @@ -153,10 +153,10 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private _startExtensionHostProcess(initialActivationEvents: string[]): void { + private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void { this._stopExtensionHostProcess(); - const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, this.getExtensions(), this._extensionHostLogsLocation); + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, !isInitialStart, this.getExtensions(), this._extensionHostLogsLocation); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents); extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal)); extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ target: extHostProcessManager, isResponsive: responsiveState === ResponsiveState.Responsive }); }); @@ -196,7 +196,7 @@ export class ExtensionService extends Disposable implements IExtensionService { }, { label: nls.localize('restart', "Restart Extension Host"), - run: () => this._startExtensionHostProcess(Object.keys(this._allRequestedActivateEvents)) + run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)) }] ); } @@ -318,7 +318,7 @@ export class ExtensionService extends Disposable implements IExtensionService { // --- impl - private _scanAndHandleExtensions(): void { + private async _scanAndHandleExtensions(): Promise { this._extensionScanner.startScanningExtensions(new Logger((severity, source, message) => { if (this._isDev && source) { this._logOrShowMessage(severity, `[${source}]: ${message}`); @@ -327,25 +327,29 @@ export class ExtensionService extends Disposable implements IExtensionService { } })); - this._extensionScanner.scannedExtensions - .then(allExtensions => this._getRuntimeExtensions(allExtensions)) - .then(allExtensions => { - this._registry = new ExtensionDescriptionRegistry(allExtensions); + const extensionHost = this._extensionHostProcessManagers[0]; + const extensions = await this._extensionScanner.scannedExtensions; + const enabledExtensions = await this._getRuntimeExtensions(extensions); + extensionHost.start(enabledExtensions.map(extension => extension.id)); + this._onHasExtensions(enabledExtensions); + } - let availableExtensions = this._registry.getAllExtensionDescriptions(); - let extensionPoints = ExtensionsRegistry.getExtensionPoints(); + private _onHasExtensions(allExtensions: IExtensionDescription[]): void { + this._registry = new ExtensionDescriptionRegistry(allExtensions); - let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); + let availableExtensions = this._registry.getAllExtensionDescriptions(); + let extensionPoints = ExtensionsRegistry.getExtensionPoints(); - for (let i = 0, len = extensionPoints.length; i < len; i++) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } + let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - perf.mark('extensionHostReady'); - this._installedExtensionsReady.open(); - this._onDidRegisterExtensions.fire(void 0); - this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id)); - }); + for (let i = 0, len = extensionPoints.length; i < len; i++) { + ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); + } + + perf.mark('extensionHostReady'); + this._installedExtensionsReady.open(); + this._onDidRegisterExtensions.fire(void 0); + this._onDidChangeExtensionsStatus.fire(availableExtensions.map(e => e.id)); } private _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { diff --git a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts index b474728f10e..e97ae641200 100644 --- a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts @@ -8,17 +8,23 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e const hasOwnProperty = Object.hasOwnProperty; export class ExtensionDescriptionRegistry { + private _extensionDescriptions: IExtensionDescription[]; private _extensionsMap: { [extensionId: string]: IExtensionDescription; }; private _extensionsArr: IExtensionDescription[]; private _activationMap: { [activationEvent: string]: IExtensionDescription[]; }; constructor(extensionDescriptions: IExtensionDescription[]) { + this._extensionDescriptions = extensionDescriptions; + this._initialize(); + } + + private _initialize(): void { this._extensionsMap = {}; this._extensionsArr = []; this._activationMap = {}; - for (let i = 0, len = extensionDescriptions.length; i < len; i++) { - let extensionDescription = extensionDescriptions[i]; + for (let i = 0, len = this._extensionDescriptions.length; i < len; i++) { + let extensionDescription = this._extensionDescriptions[i]; if (hasOwnProperty.call(this._extensionsMap, extensionDescription.id)) { // No overwriting allowed! @@ -45,6 +51,13 @@ export class ExtensionDescriptionRegistry { } } + public keepOnly(extensionIds: string[]): void { + let toKeep = new Set(); + extensionIds.forEach(extensionId => toKeep.add(extensionId)); + this._extensionDescriptions = this._extensionDescriptions.filter(extension => toKeep.has(extension.id)); + this._initialize(); + } + public containsActivationEvent(activationEvent: string): boolean { return hasOwnProperty.call(this._activationMap, activationEvent); } diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index 6d21a8fa62a..9fcf190f9f9 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -41,7 +41,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection readonly remoteAuthority: string; private _connection: Thenable> | null; - private _environment: Thenable | null; + private _environment: Thenable | null; constructor( remoteAuthority: string, @@ -55,7 +55,7 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection this._environment = null; } - getEnvironment(): Thenable { + getEnvironment(): Thenable { if (!this._environment) { const client = new RemoteExtensionEnvironmentChannelClient(this.getChannel('remoteextensionsenvironment')); diff --git a/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts index 1db9073cae3..321d169c711 100644 --- a/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts @@ -16,6 +16,7 @@ export interface IRemoteAgentEnvironmentDTO { logsPath: UriComponents; extensionsPath: UriComponents; extensionHostLogsPath: UriComponents; + globalStorageHome: UriComponents; extensions: IExtensionDescription[]; os: OperatingSystem; } @@ -34,6 +35,7 @@ export class RemoteExtensionEnvironmentChannelClient { logsPath: URI.revive(data.logsPath), extensionsPath: URI.revive(data.extensionsPath), extensionHostLogsPath: URI.revive(data.extensionHostLogsPath), + globalStorageHome: URI.revive(data.globalStorageHome), extensions: data.extensions.map(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }), os: data.os }; diff --git a/src/vs/workbench/services/remote/node/remoteAgentService.ts b/src/vs/workbench/services/remote/node/remoteAgentService.ts index 2cf1bf6562c..638ea9e8697 100644 --- a/src/vs/workbench/services/remote/node/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/node/remoteAgentService.ts @@ -21,6 +21,7 @@ export interface IRemoteAgentEnvironment { logsPath: URI; extensionsPath: URI; extensionHostLogsPath: URI; + globalStorageHome: URI; extensions: IExtensionDescription[]; os: OperatingSystem; } @@ -34,7 +35,7 @@ export interface IRemoteAgentService { export interface IRemoteAgentConnection { readonly remoteAuthority: string; - getEnvironment(): Thenable; + getEnvironment(): Thenable; getChannel(channelName: string): T; registerChannel>(channelName: string, channel: T);