diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 4c35635dd26..f70bad49228 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -8,9 +8,10 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; +import { Disposable } from 'vs/base/common/lifecycle'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) -export class MainThreadTunnelService implements MainThreadTunnelServiceShape { +export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape { private readonly _proxy: ExtHostTunnelServiceShape; constructor( @@ -18,6 +19,7 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @ITunnelService private readonly tunnelService: ITunnelService ) { + super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); } @@ -60,7 +62,7 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { } async $setCandidateFilter(): Promise { - this.remoteExplorerService.setCandidateFilter(async (candidates: { host: string, port: number, detail: string }[]): Promise<{ host: string, port: number, detail: string }[]> => { + this._register(this.remoteExplorerService.setCandidateFilter(async (candidates: { host: string, port: number, detail: string }[]): Promise<{ host: string, port: number, detail: string }[]> => { const filters: boolean[] = await this._proxy.$filterCandidates(candidates); const filteredCandidates: { host: string, port: number, detail: string }[] = []; if (filters.length !== candidates.length) { @@ -72,10 +74,10 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { } } return filteredCandidates; - }); + })); } dispose(): void { - // + } } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index e5bdd22c73b..435eef33f04 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -17,6 +17,7 @@ import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/common/tunnelFactory'; +import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/common/showCandidate'; export const VIEWLET_ID = 'workbench.view.remote'; @@ -85,3 +86,4 @@ workbenchContributionsRegistry.registerWorkbenchContribution(LabelContribution, workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChannels, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready); +workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready); diff --git a/src/vs/workbench/contrib/remote/common/showCandidate.ts b/src/vs/workbench/contrib/remote/common/showCandidate.ts new file mode 100644 index 00000000000..7ab26b73803 --- /dev/null +++ b/src/vs/workbench/contrib/remote/common/showCandidate.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; + +export class ShowCandidateContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + if (workbenchEnvironmentService.options && workbenchEnvironmentService.options.showCandidate) { + this._register(remoteExplorerService.setCandidateFilter(async (candidates: { host: string, port: number, detail: string }[]): Promise<{ host: string, port: number, detail: string }[]> => { + const filters: boolean[] = await Promise.all(candidates.map(candidate => workbenchEnvironmentService.options!.showCandidate!(candidate.host, candidate.port, candidate.detail))); + const filteredCandidates: { host: string, port: number, detail: string }[] = []; + if (filters.length !== candidates.length) { + return candidates; + } + for (let i = 0; i < candidates.length; i++) { + if (filters[i]) { + filteredCandidates.push(candidates[i]); + } + } + return filteredCandidates; + })); + } + } +} diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 6b4c0e2046c..00b178504bf 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TunnelInformation, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -191,7 +191,7 @@ export class TunnelModel extends Disposable { this._candidateFinder = finder; } - setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): void { + setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): void { this._candidateFilter = filter; } @@ -235,7 +235,7 @@ export interface IRemoteExplorerService { close(remote: { host: string, port: number }): Promise; setTunnelInformation(tunnelInformation: TunnelInformation | undefined): void; registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; - setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): void; + setCandidateFilter(filter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined): IDisposable; refresh(): Promise; } @@ -310,8 +310,18 @@ class RemoteExplorerService implements IRemoteExplorerService { this.tunnelModel.registerCandidateFinder(finder); } - setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): void { + setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): IDisposable { + if (!filter) { + return { + dispose: () => { } + }; + } this.tunnelModel.setCandidateFilter(filter); + return { + dispose: () => { + this.tunnelModel.setCandidateFilter(undefined); + } + }; } refresh(): Promise { diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index d33ec02d21b..977a34632ac 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -54,6 +54,10 @@ interface ITunnelFactory { (tunnelOptions: TunnelOptions): Thenable | undefined; } +interface IShowCandidate { + (host: string, port: number, detail: string): Thenable; +} + interface IWorkbenchConstructionOptions { /** @@ -129,6 +133,11 @@ interface IWorkbenchConstructionOptions { */ readonly tunnelFactory?: ITunnelFactory; + /** + * Support for filtering candidate ports + */ + readonly showCandidate?: IShowCandidate; + /** * Current logging level. Default is `LogLevel.Info`. */