diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index b2e33dd00ae..c92d36d3ecf 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -52,7 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { mainWindow } from 'vs/base/browser/window'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; interface IExtensionStateProvider { (extension: Extension): T; @@ -1650,17 +1650,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation): Promise { - let installable: URI | { extension: IExtension; gallery: IGalleryExtension }; + let installable: URI | IGalleryExtension | undefined; + let extension: IExtension | undefined; if (arg instanceof URI) { installable = arg; } else { let installableInfo: IExtensionInfo | undefined; let gallery: IGalleryExtension | undefined; - let extension: IExtension | undefined; if (isString(arg)) { - installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions?.installPreReleaseVersion ?? this.preferPreReleases }; extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg })); + if (!extension?.isBuiltin) { + installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.preferPreReleases }; + } } else { extension = arg; gallery = arg.gallery; @@ -1672,47 +1674,44 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined; gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None)); } - if (!gallery) { - const id = isString(arg) ? arg : (arg).identifier.id; - if (installOptions.version) { - throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); - } else { - throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); - } - } - if (!extension) { + if (!extension && gallery) { extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery); + Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest()); } if (extension?.isMalicious) { throw new Error(nls.localize('malicious', "This extension is reported to be problematic.")); } - installable = { extension, gallery }; - if (installOptions.version) { - installOptions.installGivenVersion = true; + // Do not install if requested to enable and extension is already installed + if (!(installOptions.enable && extension?.local)) { + if (!gallery) { + const id = isString(arg) ? arg : (arg).identifier.id; + if (installOptions.version) { + throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version)); + } else { + throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id)); + } + } + installable = gallery; + if (installOptions.version) { + installOptions.installGivenVersion = true; + } } } - let extension: IExtension; - if (installable instanceof URI || !(installOptions.enable && installable.extension.local)) { + if (installable) { if (installOptions.justification) { const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions); + const buttons: IPromptButton[] = []; + buttons.push({ label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true }); + if (!extension) { + buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } }); + } const result = await this.dialogService.prompt({ title: nls.localize('installExtensionTitle', "Install Extension"), - message: installable instanceof URI ? nls.localize('installVSIXMessage', "Would you like to install the extension?") : nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", installable.extension.displayName, installable.extension.publisherDisplayName), + message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"), detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason, cancelButton: true, - buttons: [{ - label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), - run: () => true - }, { - label: nls.localize('open', "Open Extension"), - run: () => { - if (!(installable instanceof URI)) { - this.open(installable.extension); - } - return false; - } - }], + buttons, checkbox: syncCheck ? { label: nls.localize('sync extension', "Sync this extension"), checked: true, @@ -1725,11 +1724,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension installOptions.isMachineScoped = !result.checkboxChecked; } } - extension = await this.doInstall(installable instanceof URI ? installable : installable.extension, - () => installable instanceof URI ? this.installFromVSIX(installable, installOptions) : this.installFromGallery(installable.extension, installable.gallery, installOptions), - progressLocation); - } else { - extension = installable.extension; + if (installable instanceof URI) { + extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation); + } else if (extension) { + extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable, installOptions), progressLocation); + } + } + + if (!extension) { + throw new Error(nls.localize('unknown', "Unable to install extension")); } if (installOptions.version) { @@ -1902,21 +1905,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return extension; } - private doInstall(extension: IExtension | URI, installTask: () => Promise, progressLocation?: ProgressLocation): Promise { - const title = extension instanceof URI ? nls.localize('installing extension', 'Installing extension....') : nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName); + private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation): Promise { + const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); return this.withProgress({ location: progressLocation ?? ProgressLocation.Extensions, title }, async () => { try { - if (!(extension instanceof URI)) { + if (extension) { this.installing.push(extension); this._onChange.fire(extension); } const local = await installTask(); return await this.waitAndGetInstalledExtension(local.identifier); } finally { - if (!(extension instanceof URI)) { + if (extension) { this.installing = this.installing.filter(e => e !== extension); // Trigger the change without passing the extension because it is replaced by a new instance. this._onChange.fire(undefined); diff --git a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts index ab080153b5e..39d5869cd3c 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-sandbox/extensionsWorkbenchService.test.ts @@ -365,7 +365,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); const identifier = gallery.identifier; // Installing @@ -450,7 +449,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); installEvent.fire({ identifier: gallery.identifier, source: gallery }); const promise = Event.toPromise(testObject.onChange); @@ -470,7 +468,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { const extension = page.firstPage[0]; assert.strictEqual(ExtensionState.Uninstalled, extension.state); - testObject.install(extension); disposableStore.add(testObject.onChange(target)); // Installing