diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 52105cb7f24..8de660c78fe 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -401,6 +401,11 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), default: false + }, + 'remote.restoreForwardedPorts': { + type: 'boolean', + markdownDescription: nls.localize('remote.restoreForwardedPorts', "Restores the ports you forwarded in a workspace."), + default: false } } }); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index f83efc87dc3..6dc3baf25dc 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -13,9 +13,11 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; +const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; export interface Tunnel { remoteHost: string; @@ -46,7 +48,9 @@ export class TunnelModel extends Disposable { private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; constructor( - @ITunnelService private readonly tunnelService: ITunnelService + @ITunnelService private readonly tunnelService: ITunnelService, + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.forwarded = new Map(); @@ -74,6 +78,7 @@ export class TunnelModel extends Disposable { localPort: tunnel.tunnelLocalPort, closeable: true }); + this.storeForwarded(); } this._onForwardPort.fire(this.forwarded.get(key)!); })); @@ -81,9 +86,29 @@ export class TunnelModel extends Disposable { const key = MakeAddress(address.host, address.port); if (this.forwarded.has(key)) { this.forwarded.delete(key); + this.storeForwarded(); this._onClosePort.fire(address); } })); + + this.restoreForwarded(); + } + + private async restoreForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); + if (tunnelsString) { + (JSON.parse(tunnelsString))?.forEach(tunnel => { + this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name); + }); + } + } + } + + private storeForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + this.storageService.store(TUNNELS_TO_RESTORE, JSON.stringify(Array.from(this.forwarded.values())), StorageScope.WORKSPACE); + } } async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { @@ -110,6 +135,7 @@ export class TunnelModel extends Disposable { const key = MakeAddress(host, port); if (this.forwarded.has(key)) { this.forwarded.get(key)!.name = name; + this.storeForwarded(); this._onPortName.fire({ host, port }); } else if (this.detected.has(key)) { this.detected.get(key)!.name = name; @@ -212,8 +238,10 @@ class RemoteExplorerService implements IRemoteExplorerService { constructor( @IStorageService private readonly storageService: IStorageService, - @ITunnelService tunnelService: ITunnelService) { - this._tunnelModel = new TunnelModel(tunnelService); + @ITunnelService tunnelService: ITunnelService, + @IConfigurationService configurationService: IConfigurationService + ) { + this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; for (let extension of extensions) {