diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f91906a7d350ce275d1de88c19d579920f886abf..39b348072b9f9363a2e60fc7c243f3c3cde01204 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -44,6 +44,7 @@ import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/brow import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -372,3 +373,4 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(RemoteExtensionsInstaller, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index ea26aa79a2c5ef9c74a0efb658f92e75785ad33e..4ac602fc5fb179cc0b782777c54c2753698d4c86 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -61,6 +61,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IProductService } from 'vs/platform/product/common/product'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export function toExtensionDescription(local: ILocalExtension): IExtensionDescription { return { @@ -324,7 +325,6 @@ export class InstallInOtherServerAction extends ExtensionAction { && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) // Not installed in other server and can install in other server && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server) - && this.extensionsWorkbenchService.canInstall(this.extension) ) { this.enabled = true; this.updateLabel(); @@ -338,8 +338,14 @@ export class InstallInOtherServerAction extends ExtensionAction { this.update(); this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); - if (this.extension.gallery) { - await this.server.extensionManagementService.installFromGallery(this.extension.gallery); + try { + if (this.extension.gallery) { + await this.server.extensionManagementService.installFromGallery(this.extension.gallery); + } else { + const vsix = await this.extension.server!.extensionManagementService.zip(this.extension.local!); + await this.server.extensionManagementService.install(vsix); + } + } finally { this.installing = false; this.update(); } @@ -3017,6 +3023,111 @@ export class InstallSpecificVersionOfExtensionAction extends Action { } } +interface IExtensionPickItem extends IQuickPickItem { + extension?: IExtension; +} + +export class InstallLocalExtensionsOnRemoteAction extends Action { + + constructor( + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, + @IWindowService private readonly windowService: IWindowService, + @IProgressService private readonly progressService: IProgressService + ) { + super('workbench.extensions.actions.installLocalExtensionsOnRemote'); + this.enabled = !!this.extensionManagementServerService.localExtensionManagementServer && !!this.extensionManagementServerService.remoteExtensionManagementServer; + } + + async run(): Promise { + if (this.enabled) { + await this.selectAndInstallLocalExtensions(); + } + } + + private async getLocalExtensionsToInstall(): Promise { + const localExtensions = await this.extensionsWorkbenchService.queryLocal(this.extensionManagementServerService.localExtensionManagementServer!); + const remoteExtensions = await this.extensionsWorkbenchService.queryLocal(this.extensionManagementServerService.remoteExtensionManagementServer!); + const remoteExtensionsIds: Set = new Set(); + const remoteExtensionsUUIDs: Set = new Set(); + for (const extension of remoteExtensions) { + remoteExtensionsIds.add(extension.identifier.id.toLowerCase()); + if (extension.identifier.uuid) { + remoteExtensionsUUIDs.add(extension.identifier.uuid); + } + } + const localExtensionsToInstall: IExtension[] = []; + for (const localExtension of localExtensions) { + if (localExtension.type === ExtensionType.User + && (isLanguagePackExtension(localExtension.local!.manifest) || localExtension.enablementState === EnablementState.DisabledByExtensionKind)) { + if (!remoteExtensionsIds.has(localExtension.identifier.id.toLowerCase()) || (localExtension.identifier.uuid && !remoteExtensionsUUIDs.has(localExtension.identifier.uuid))) { + localExtensionsToInstall.push(localExtension); + } + } + } + return localExtensionsToInstall; + } + + private async selectAndInstallLocalExtensions(): Promise { + const result = await this.quickInputService.pick( + this.getLocalExtensionsToInstall() + .then(extensions => { + if (extensions.length) { + extensions = extensions.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)); + return extensions.map(extension => ({ extension, label: extension.displayName, description: extension.version })); + } + return [{ label: localize('no local extensions', "There are no extensions to install.") }]; + }), + { + canPickMany: true, + placeHolder: localize('select extensions to install', "Select extensions to install") + } + ); + if (result) { + const localExtensionsToInstall = result.filter(r => !!r.extension).map(r => r.extension!); + if (localExtensionsToInstall.length) { + this.progressService.withProgress( + { + location: ProgressLocation.Notification, + title: localize('installing extensions', "Installing Extensions...") + }, + () => this.installLocalExtensions(localExtensionsToInstall)); + } + } + } + + private async installLocalExtensions(localExtensionsToInstall: IExtension[]): Promise { + const galleryExtensions: IGalleryExtension[] = []; + const vsixs: URI[] = []; + await Promise.all(localExtensionsToInstall.map(async extension => { + if (this.extensionGalleryService.isEnabled()) { + const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, extension.version); + if (gallery) { + galleryExtensions.push(gallery); + return; + } + } + const vsix = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.zip(extension.local!); + vsixs.push(vsix); + })); + + await Promise.all(galleryExtensions.map(gallery => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.installFromGallery(gallery))); + await Promise.all(vsixs.map(vsix => this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.install(vsix))); + + this.notificationService.notify({ + severity: Severity.Info, + message: localize('finished installing', "Completed installing the extensions. Please reload the window now."), + actions: { + primary: [new Action('realod', localize('reload', "Realod Window"), '', true, + () => this.windowService.reloadWindow())] + } + }); + } +} + CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) { const viewletService = accessor.get(IViewletService); diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts new file mode 100644 index 0000000000000000000000000000000000000000..afb0204f7d70a4b9aa014df372c70f291c6c88c6 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { InstallLocalExtensionsOnRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; + +export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution { + + constructor( + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @ILabelService labelService: ILabelService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => instantiationService.createInstance(InstallLocalExtensionsOnRemoteAction).run()); + let disposable = Disposable.None; + const appendMenuItem = () => { + disposable.dispose(); + disposable = MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'workbench.extensions.installLocalExtensions', + category: localize('extensions', "Extensions"), + title: localize('istall local extensions', "Install Local Extensions on {0}", this.extensionManagementServerService.remoteExtensionManagementServer!.label) + } + }); + }; + appendMenuItem(); + this._register(labelService.onDidChangeFormatters(e => appendMenuItem())); + this._register(toDisposable(() => disposable.dispose())); + } + } + +} \ No newline at end of file diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index ff5534b49011cce97352578c4964aea4bc55f522..337f52b278a1ca6c05fc3d5e5b0a836cd1a42ca2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -130,7 +130,11 @@ export class ExtensionManagementService extends Disposable implements IExtension } zip(extension: ILocalExtension): Promise { - throw new Error('Not Supported'); + const server = this.getServer(extension); + if (server) { + return server.extensionManagementService.zip(extension); + } + return Promise.reject(`Invalid location ${extension.location.toString()}`); } unzip(zipLocation: URI, type: ExtensionType): Promise {