diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index a34615001de..0f05864a61d 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -35,6 +35,7 @@ export enum RemoteAuthorityResolverErrorCode { Unknown = 'Unknown', NotAvailable = 'NotAvailable', TemporarilyNotAvailable = 'TemporarilyNotAvailable', + NoResolverFound = 'NoResolverFound' } export class RemoteAuthorityResolverError extends Error { @@ -50,10 +51,11 @@ export class RemoteAuthorityResolverError extends Error { } public static isTemporarilyNotAvailable(err: any): boolean { - if (err instanceof RemoteAuthorityResolverError) { - return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; - } - return false; + return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; + } + + public static isNoResolverFound(err: any): boolean { + return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound; } public readonly _message: string | undefined; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 0adf5382b3b..197aa88c85c 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import { VSBuffer } from 'vs/base/common/buffer'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes'; -import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; @@ -641,7 +641,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const resolver = this._resolvers[authorityPrefix]; if (!resolver) { - throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); + return { + type: 'error', + error: { + code: RemoteAuthorityResolverErrorCode.NoResolverFound, + message: `No remote extension installed to resolve ${authorityPrefix}.`, + detail: undefined + } + }; } try { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index a3a45ed8ec4..da78c42b2b3 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -11,8 +11,8 @@ import { AbstractExtensionService } from 'vs/workbench/services/extensions/commo import * as nls from 'vs/nls'; import { runWhenIdle } from 'vs/base/common/async'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -41,6 +41,7 @@ import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; class DeltaExtensionsQueueItem { constructor( @@ -73,7 +74,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IElectronService private readonly _electronService: IElectronService, @IHostService private readonly _hostService: IHostService, @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService, - @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService + @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, + @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, ) { super( instantiationService, @@ -455,13 +457,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten try { resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority); } catch (err) { - console.error(err); - const plusIndex = remoteAuthority.indexOf('+'); - const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority; - if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) { - this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) }); + const remoteName = getRemoteName(remoteAuthority); + if (RemoteAuthorityResolverError.isNoResolverFound(err)) { + this._handleNoResolverFound(remoteName); } else { - console.log(`Not showing a notification for the error`); + console.log(err); + if (RemoteAuthorityResolverError.isHandledNotAvailable(err)) { + console.log(`Not showing a notification for the error`); + } else { + this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", remoteName) }); + } } this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err); @@ -578,6 +583,70 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._electronService.closeWindow(); } } + + private async _handleNoResolverFound(remoteName: string): Promise { + const recommendation = this._productService.remoteExtensionTips?.[remoteName]; + if (!recommendation) { + return; + } + const resolverExtensionId = recommendation.extensionId; + const installedExtensions = await this._extensionManagementService.getInstalled(); + const extension = installedExtensions.filter(e => e.identifier.id === resolverExtensionId)[0]; + if (extension) { + if (!await this._extensionEnablementService.isEnabled(extension)) { + const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window. Ok to enable the extension?", recommendation.friendlyName); + this._notificationService.prompt(Severity.Info, message, + [{ + label: nls.localize('enable', 'Enable'), + run: async () => { + this._extensionEnablementService.setEnablement([extension], EnablementState.EnabledGlobally); + } + }], + { sticky: true } + ); + } + } else { + // Install the Extension and reload the window to handle. + const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window. Ok to install the extension?", recommendation.friendlyName); + this._notificationService.prompt(Severity.Info, message, + [{ + label: nls.localize('install', 'Install and Reload'), + run: async () => { + /* __GDPR__ + "remoteExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this._telemetryService.publicLog('remoteExtensionRecommendations:popup', { userReaction: 'install', extensionId: resolverExtensionId }); + + const galleryExtension = await this._extensionGalleryService.getCompatibleExtension({ id: resolverExtensionId }); + if (galleryExtension) { + await this._extensionManagementService.installFromGallery(galleryExtension); + this._hostService.reload(); + } else { + this._notificationService.error(nls.localize('resolverExtensionNotFound', "`{0}` not found on marketplace")); + } + + } + }], + { + sticky: true, + onCancel: () => { + /* __GDPR__ + "remoteExtensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this._telemetryService.publicLog('remoteExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId: resolverExtensionId }); + } + } + ); + + } + + } } function remove(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[];