diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 4e770b08026..a34615001de 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -23,7 +23,6 @@ export interface TunnelDescription { } export interface TunnelInformation { environmentTunnels?: TunnelDescription[]; - hideCandidatePorts?: boolean; } export interface ResolverResult { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 7b6da75c329..575f399d2d8 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -108,7 +108,6 @@ declare module 'vscode' { */ environmentTunnels?: TunnelDescription[]; - hideCandidatePorts?: boolean; } export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; @@ -128,6 +127,11 @@ declare module 'vscode' { * When implemented, the core will use this to forward ports. */ tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable | undefined; + + /** + * Provides filtering for candidate ports. + */ + showCandidatePort?: (host: string, port: number, detail: string) => Thenable; } export namespace workspace { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 398c7eecb05..4c35635dd26 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -59,6 +59,22 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { this.tunnelService.setTunnelProvider(tunnelProvider); } + async $setCandidateFilter(): Promise { + 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) { + return candidates; + } + for (let i = 0; i < candidates.length; i++) { + if (filters[i]) { + filteredCandidates.push(candidates[i]); + } + } + return filteredCandidates; + }); + } + dispose(): void { // } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 30b60794ee1..fce42f2164d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -787,6 +787,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $closeTunnel(remote: { host: string, port: number }): Promise; $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; + $setCandidateFilter(): Promise; } // -- extension host @@ -1421,6 +1422,7 @@ export interface MainThreadThemingShape extends IDisposable { export interface ExtHostTunnelServiceShape { $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>; + $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise; $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; $closeTunnel(remote: { host: string, port: number }): Promise; } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 4fe9d010a9f..978bf32fcda 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -645,7 +645,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio try { const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); - this._disposables.add(await this._extHostTunnelService.setForwardPortProvider(resolver)); + this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver)); // Split merged API result into separate authority/options const authority: ResolvedAuthority = { @@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio value: { authority, options, - tunnelInformation: { environmentTunnels: result.environmentTunnels, hideCandidatePorts: result.hideCandidatePorts } + tunnelInformation: { environmentTunnels: result.environmentTunnels } } }; } catch (err) { diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index fe666a32b52..06ce1da7cdf 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -31,7 +31,7 @@ export interface Tunnel extends vscode.Disposable { export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { readonly _serviceBrand: undefined; openTunnel(forward: TunnelOptions): Promise; - setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise; + setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); @@ -44,7 +44,10 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> { return []; } - async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } + async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { + return candidates.map(() => true); + } + async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined { return undefined; } async $closeTunnel(remote: { host: string, port: number }): Promise { } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 58e07babca1..7d3e3abdb8f 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -37,6 +37,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe readonly _serviceBrand: undefined; private readonly _proxy: MainThreadTunnelServiceShape; private _forwardPortProvider: ((tunnelOptions: TunnelOptions) => Thenable | undefined) | undefined; + private _showCandidatePort: (host: string, port: number, detail: string) => Thenable = () => { return Promise.resolve(true); }; private _extensionTunnels: Map> = new Map(); constructor( @@ -65,10 +66,22 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return this._proxy.$registerCandidateFinder(); } - async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - if (provider && provider.tunnelFactory) { - this._forwardPortProvider = provider.tunnelFactory; - await this._proxy.$setTunnelProvider(); + $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise { + return Promise.all(candidates.map(candidate => { + return this._showCandidatePort(candidate.host, candidate.port, candidate.detail); + })); + } + + async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise { + if (provider) { + if (provider.showCandidatePort) { + this._showCandidatePort = provider.showCandidatePort; + await this._proxy.$setCandidateFilter(); + } + if (provider.tunnelFactory) { + this._forwardPortProvider = provider.tunnelFactory; + await this._proxy.$setTunnelProvider(); + } } else { this._forwardPortProvider = undefined; } @@ -128,9 +141,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const childStat = fs.statSync(childUri.fsPath); if (childStat.isDirectory() && !isNaN(pid)) { const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath); - const rawCmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); - const nullIndex = rawCmd.indexOf('\0'); - const cmd = rawCmd.substr(0, nullIndex > 0 ? nullIndex : rawCmd.length).trim(); + const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); processes.push({ pid, cwd, cmd }); } } catch (e) { diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 279769e064a..6b4c0e2046c 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -59,7 +59,6 @@ export function MakeAddress(host: string, port: number): string { export class TunnelModel extends Disposable { readonly forwarded: Map; readonly detected: Map; - private _candidatesEnabled: boolean = true; private _onForwardPort: Emitter = new Emitter(); public onForwardPort: Event = this._onForwardPort.event; private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter(); @@ -70,6 +69,7 @@ export class TunnelModel extends Disposable { private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; private _onCandidatesChanged: Emitter = new Emitter(); public onCandidatesChanged: Event = this._onCandidatesChanged.event; + private _candidateFilter: ((candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>) | undefined; constructor( @ITunnelService private readonly tunnelService: ITunnelService, @@ -187,29 +187,31 @@ export class TunnelModel extends Disposable { }); } - set candidateEnabled(enabled: boolean) { - this._candidatesEnabled = enabled; - } - registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void { this._candidateFinder = finder; } + setCandidateFilter(filter: (candidates: { host: string, port: number, detail: string }[]) => Promise<{ host: string, port: number, detail: string }[]>): void { + this._candidateFilter = filter; + } + get candidates(): Promise<{ host: string, port: number, detail: string }[]> { return this.updateCandidates().then(() => this._candidates); } private async updateCandidates(): Promise { - if (!this._candidatesEnabled) { - this._candidates = []; - return; - } if (this._candidateFinder) { - this._candidates = (await this._candidateFinder()).map(value => { + let candidates = await this._candidateFinder(); + if (this._candidateFilter && (candidates.length > 0)) { + candidates = await this._candidateFilter(candidates); + } + this._candidates = candidates.map(value => { + const nullIndex = value.detail.indexOf('\0'); + const detail = value.detail.substr(0, nullIndex > 0 ? nullIndex : value.detail.length).trim(); return { host: ToLocalHost(value.host), port: value.port, - detail: value.detail + detail }; }); } @@ -233,6 +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; refresh(): Promise; } @@ -285,8 +288,6 @@ class RemoteExplorerService implements IRemoteExplorerService { if (tunnelInformation && tunnelInformation.environmentTunnels) { this.tunnelModel.addEnvironmentTunnels(tunnelInformation.environmentTunnels); } - - this.tunnelModel.candidateEnabled = tunnelInformation ? (tunnelInformation.hideCandidatePorts !== true) : true; } setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void { @@ -309,6 +310,10 @@ 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 { + this.tunnelModel.setCandidateFilter(filter); + } + refresh(): Promise { return this.tunnelModel.refresh(); }