diff --git a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts index 963e304601ebbf5f44dac5e80e4c2aa0a7387a7e..f66cde1d510d96c36e3fc7abf68f61b579d64020 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionGalleryService, InstallOperation } from '../common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { cloneAndChange } from 'vs/base/common/objects'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ILogService } from 'vs/platform/log/common/log'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { return URI.revive(transformer ? transformer.transformIncoming(uri) : uri); @@ -78,7 +80,12 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer _serviceBrand: any; - constructor(private channel: IChannel) { } + constructor( + private readonly channel: IChannel, + private readonly remote: boolean, + private readonly galleryService: IExtensionGalleryService, + private readonly logService: ILogService + ) { } get onInstallExtension(): Event { return this.channel.listen('onInstallExtension'); } get onDidInstallExtension(): Event { return Event.map(this.channel.listen('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); } @@ -97,8 +104,25 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('install', [vsix])); } - installFromGallery(extension: IGalleryExtension): Promise { - return Promise.resolve(this.channel.call('installFromGallery', [extension])); + async installFromGallery(extension: IGalleryExtension): Promise { + try { + await Promise.resolve(this.channel.call('installFromGallery', [extension])); + } catch (error) { + if (this.remote) { + try { + const compatible = await this.galleryService.getCompatibleExtension(extension); + if (compatible) { + const installed = await this.getInstalled(ExtensionType.User); + const location = await this.galleryService.download(compatible, installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install); + await this.install(URI.file(location)); + return; + } + } catch (e) { + this.logService.error(e); + } + } + throw error; + } } uninstall(extension: ILocalExtension, force = false): Promise { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ae1ffe0c8084ba52b2b3f51de9128183ac1fa788..e0e9a941d9cd1e9c3556fb478a6179efd0323022 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -130,7 +130,7 @@ export class ExtensionContainers extends Disposable { for (const container of this.containers) { if (extension && container.extension) { if (areSameExtensions(container.extension.identifier, extension.identifier)) { - if (!container.extension.server || container.extension.server === extension.server) { + if (!container.extension.server || !extension.server || container.extension.server === extension.server) { container.extension = extension; } else if (container.updateWhenCounterExtensionChanges) { container.update(); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 7a7282a0c42890363f508d081b1fcb76ad037af6..26cd86eec90cc9937a80ac550c0ba4a3ec9b596d 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -217,7 +217,7 @@ export class InstallAction extends ExtensionAction { alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Visual Studio Code to enable it.", this.extension.displayName)); - if (extension.local) { + if (extension && extension.local) { const runningExtension = await this.getRunningExtension(extension.local); if (runningExtension) { const colorThemes = await this.workbenchThemeService.getColorThemes(); @@ -237,7 +237,7 @@ export class InstallAction extends ExtensionAction { } - private install(extension: IExtension): Promise { + private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension) .then(null, err => { if (!extension.gallery) { diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index fd88186467d8a9bcfa9c5a3e5f1cde7b90bb4884..64eac307ad393014fab57db9b101d2e0b4e665c1 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -309,13 +309,13 @@ class Extensions extends Disposable { private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } - private readonly stateProvider: IExtensionStateProvider; private installing: Extension[] = []; private uninstalling: Extension[] = []; private installed: Extension[] = []; constructor( private readonly server: IExtensionManagementServer, + private readonly stateProvider: IExtensionStateProvider, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @ITelemetryService private readonly telemetryService: ITelemetryService, @ILogService private readonly logService: ILogService, @@ -323,7 +323,6 @@ class Extensions extends Disposable { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService ) { super(); - this.stateProvider = ext => this.getExtensionState(ext); this._register(server.extensionManagementService.onInstallExtension(e => this.onInstallExtension(e))); this._register(server.extensionManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); this._register(server.extensionManagementService.onUninstallExtension(e => this.onUninstallExtension(e))); @@ -484,6 +483,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension get onChange(): Event { return this._onChange.event; } private _extensionAllowedBadgeProviders: string[]; + private installing: IExtension[] = []; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -504,10 +504,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IModeService private readonly modeService: IModeService ) { super(); - this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer)); + this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.localExtensions.onChange(e => this._onChange.fire(e))); if (this.extensionManagementServerService.remoteExtensionManagementServer) { - this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer)); + this.remoteExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.remoteExtensionManagementServer, ext => this.getExtensionState(ext))); this._register(this.remoteExtensions.onChange(e => this._onChange.fire(e))); } else { this.remoteExtensions = null; @@ -672,6 +672,13 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } private getExtensionState(extension: Extension): ExtensionState { + const isInstalling = this.installing.some(i => areSameExtensions(i.identifier, extension.identifier)); + if (extension.server) { + const state = (extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.localExtensions : this.remoteExtensions!).getExtensionState(extension); + return state === ExtensionState.Uninstalled && isInstalling ? ExtensionState.Installing : state; + } else if (isInstalling) { + return ExtensionState.Installing; + } if (this.remoteExtensions) { const state = this.remoteExtensions.getExtensionState(extension); if (state !== ExtensionState.Uninstalled) { @@ -771,8 +778,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } return this.installWithProgress(async () => { - const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; - await extensionService.installFromGallery(gallery); + await this.installFromGallery(extension, gallery); this.checkAndEnableDisabledDependencies(gallery.identifier); return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0]; }, gallery.displayName); @@ -790,8 +796,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (!toUninstall) { return Promise.reject(new Error('Missing local')); } - - this.logService.info(`Requested uninstalling the extension ${extension.identifier.id} from window ${this.windowService.windowId}`); return this.progressService.withProgress({ location: ProgressLocation.Extensions, title: nls.localize('uninstallingExtension', 'Uninstalling extension....'), @@ -811,11 +815,10 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return this.galleryService.getCompatibleExtension(extension.gallery.identifier, version) .then(gallery => { if (!gallery) { - return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' with version '{1}' as it is not compatible with VS Code.", extension.gallery!.identifier.id, version))); + return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.gallery!.identifier.id, version))); } return this.installWithProgress(async () => { - const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; - await extensionService.installFromGallery(gallery); + await this.installFromGallery(extension, gallery); if (extension.latestVersion !== version) { this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(gallery.identifier, version)); } @@ -847,6 +850,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }, () => installTask()); } + private async installFromGallery(extension: IExtension, gallery: IGalleryExtension): Promise { + this.installing.push(extension); + this._onChange.fire(extension); + try { + const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; + await extensionService.installFromGallery(gallery); + const ids: string[] | undefined = extension.identifier.uuid ? [extension.identifier.uuid] : undefined; + const names: string[] | undefined = extension.identifier.uuid ? undefined : [extension.identifier.id]; + this.queryGallery({ names, ids, pageSize: 1 }, CancellationToken.None); + } finally { + this.installing = this.installing.filter(e => e !== extension); + this._onChange.fire(extension); + } + } + private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise { const extension = this.local.filter(e => (e.local || e.gallery) && areSameExtensions(extensionIdentifier, e.identifier))[0]; if (extension) { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 59758c4c6c3b580e0a2b7ae6563573628a19ee22..68f6b32c349c79531ba6fa7e606aaa5aa612220d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -78,7 +78,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 8bdce63bf94d4d308d8169e08e92434d6121541c..1425d24366c04b706034956d3f808c8dc6533a6c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -93,7 +93,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IExtensionManagementServerService, new class extends ExtensionManagementServerService { private _localExtensionManagementServer: IExtensionManagementServer = { extensionManagementService: instantiationService.get(IExtensionManagementService), label: 'local', authority: 'vscode-local' }; constructor() { - super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService)); + super(instantiationService.get(ISharedProcessService), instantiationService.get(IRemoteAgentService), instantiationService.get(IExtensionGalleryService), instantiationService.get(ILogService)); } get localExtensionManagementServer(): IExtensionManagementServer { return this._localExtensionManagementServer; } set localExtensionManagementServer(server: IExtensionManagementServer) { } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts index 64343563a11a59ce42da2d9791ed71e977fc8eff..b574fa9d9af14db891a435ccfb8bf0e9e84b00d7 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts @@ -6,13 +6,14 @@ import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; const localExtensionManagementServerAuthority: string = 'vscode-local'; @@ -25,14 +26,16 @@ export class ExtensionManagementServerService implements IExtensionManagementSer constructor( @ISharedProcessService sharedProcessService: ISharedProcessService, - @IRemoteAgentService remoteAgentService: IRemoteAgentService + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @ILogService logService: ILogService ) { - const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions')); + const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions'), false, galleryService, logService); this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") }; const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { - const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel('extensions')); + const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel('extensions'), true, galleryService, logService); this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: localize('remote', "Remote") }; } }