Skip to content

Commit 4a0c7a9

Browse files
committed
recommend resolver extension
1 parent e346071 commit 4a0c7a9

3 files changed

Lines changed: 93 additions & 15 deletions

File tree

src/vs/platform/remote/common/remoteAuthorityResolver.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export enum RemoteAuthorityResolverErrorCode {
3535
Unknown = 'Unknown',
3636
NotAvailable = 'NotAvailable',
3737
TemporarilyNotAvailable = 'TemporarilyNotAvailable',
38+
NoResolverFound = 'NoResolverFound'
3839
}
3940

4041
export class RemoteAuthorityResolverError extends Error {
@@ -50,10 +51,11 @@ export class RemoteAuthorityResolverError extends Error {
5051
}
5152

5253
public static isTemporarilyNotAvailable(err: any): boolean {
53-
if (err instanceof RemoteAuthorityResolverError) {
54-
return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable;
55-
}
56-
return false;
54+
return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable;
55+
}
56+
57+
public static isNoResolverFound(err: any): boolean {
58+
return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound;
5759
}
5860

5961
public readonly _message: string | undefined;

src/vs/workbench/api/common/extHostExtensionService.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network';
2626
import { VSBuffer } from 'vs/base/common/buffer';
2727
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
2828
import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes';
29-
import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver';
29+
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
3030
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
3131
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
3232
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@@ -641,7 +641,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
641641

642642
const resolver = this._resolvers[authorityPrefix];
643643
if (!resolver) {
644-
throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`);
644+
return {
645+
type: 'error',
646+
error: {
647+
code: RemoteAuthorityResolverErrorCode.NoResolverFound,
648+
message: `No remote extension installed to resolve ${authorityPrefix}.`,
649+
detail: undefined
650+
}
651+
};
645652
}
646653

647654
try {

src/vs/workbench/services/extensions/electron-browser/extensionService.ts

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { AbstractExtensionService } from 'vs/workbench/services/extensions/commo
1111
import * as nls from 'vs/nls';
1212
import { runWhenIdle } from 'vs/base/common/async';
1313
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
14-
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
15-
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
14+
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
15+
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
1616
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1717
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient';
1818
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -41,6 +41,7 @@ import { Action } from 'vs/base/common/actions';
4141
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
4242
import { Registry } from 'vs/platform/registry/common/platform';
4343
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
44+
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
4445

4546
class DeltaExtensionsQueueItem {
4647
constructor(
@@ -73,7 +74,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
7374
@IElectronService private readonly _electronService: IElectronService,
7475
@IHostService private readonly _hostService: IHostService,
7576
@IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService,
76-
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService
77+
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
78+
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
7779
) {
7880
super(
7981
instantiationService,
@@ -455,13 +457,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten
455457
try {
456458
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
457459
} catch (err) {
458-
console.error(err);
459-
const plusIndex = remoteAuthority.indexOf('+');
460-
const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority;
461-
if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
462-
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) });
460+
const remoteName = getRemoteName(remoteAuthority);
461+
if (RemoteAuthorityResolverError.isNoResolverFound(err)) {
462+
this._handleNoResolverFound(remoteName);
463463
} else {
464-
console.log(`Not showing a notification for the error`);
464+
console.log(err);
465+
if (RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
466+
console.log(`Not showing a notification for the error`);
467+
} else {
468+
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", remoteName) });
469+
}
465470
}
466471

467472
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
@@ -578,6 +583,70 @@ export class ExtensionService extends AbstractExtensionService implements IExten
578583
this._electronService.closeWindow();
579584
}
580585
}
586+
587+
private async _handleNoResolverFound(remoteName: string): Promise<void> {
588+
const recommendation = this._productService.remoteExtensionTips?.[remoteName];
589+
if (!recommendation) {
590+
return;
591+
}
592+
const resolverExtensionId = recommendation.extensionId;
593+
const installedExtensions = await this._extensionManagementService.getInstalled();
594+
const extension = installedExtensions.filter(e => e.identifier.id === resolverExtensionId)[0];
595+
if (extension) {
596+
if (!await this._extensionEnablementService.isEnabled(extension)) {
597+
const message = nls.localize('enableResolver', "Extension '{0}' is required to open the remote window. Ok to enable the extension?", recommendation.friendlyName);
598+
this._notificationService.prompt(Severity.Info, message,
599+
[{
600+
label: nls.localize('enable', 'Enable'),
601+
run: async () => {
602+
this._extensionEnablementService.setEnablement([extension], EnablementState.EnabledGlobally);
603+
}
604+
}],
605+
{ sticky: true }
606+
);
607+
}
608+
} else {
609+
// Install the Extension and reload the window to handle.
610+
const message = nls.localize('installResolver', "Extension '{0}' is required to open the remote window. Ok to install the extension?", recommendation.friendlyName);
611+
this._notificationService.prompt(Severity.Info, message,
612+
[{
613+
label: nls.localize('install', 'Install and Reload'),
614+
run: async () => {
615+
/* __GDPR__
616+
"remoteExtensionRecommendations:popup" : {
617+
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
618+
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
619+
}
620+
*/
621+
this._telemetryService.publicLog('remoteExtensionRecommendations:popup', { userReaction: 'install', extensionId: resolverExtensionId });
622+
623+
const galleryExtension = await this._extensionGalleryService.getCompatibleExtension({ id: resolverExtensionId });
624+
if (galleryExtension) {
625+
await this._extensionManagementService.installFromGallery(galleryExtension);
626+
this._hostService.reload();
627+
} else {
628+
this._notificationService.error(nls.localize('resolverExtensionNotFound', "`{0}` not found on marketplace"));
629+
}
630+
631+
}
632+
}],
633+
{
634+
sticky: true,
635+
onCancel: () => {
636+
/* __GDPR__
637+
"remoteExtensionRecommendations:popup" : {
638+
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
639+
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
640+
}
641+
*/
642+
this._telemetryService.publicLog('remoteExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId: resolverExtensionId });
643+
}
644+
}
645+
);
646+
647+
}
648+
649+
}
581650
}
582651

583652
function remove(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[];

0 commit comments

Comments
 (0)