@@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
2626import { ActionRunner , IAction } from 'vs/base/common/actions' ;
2727import { IMenuService , MenuId , IMenu , MenuRegistry , MenuItemAction } from 'vs/platform/actions/common/actions' ;
2828import { createAndFillInContextMenuActions , createAndFillInActionBarActions , ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem' ;
29- import { IRemoteExplorerService , TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService' ;
29+ import { IRemoteExplorerService , TunnelModel , MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService' ;
3030import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService' ;
3131import { INotificationService } from 'vs/platform/notification/common/notification' ;
3232import { InputBox , MessageType } from 'vs/base/browser/ui/inputbox/inputBox' ;
@@ -105,22 +105,23 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
105105
106106 get forwarded ( ) : TunnelItem [ ] {
107107 return Array . from ( this . model . forwarded . values ( ) ) . map ( tunnel => {
108- return new TunnelItem ( TunnelType . Forwarded , tunnel . remote , tunnel . localAddress , tunnel . closeable , tunnel . name , tunnel . description ) ;
108+ return new TunnelItem ( TunnelType . Forwarded , tunnel . remoteHost , tunnel . remotePort , tunnel . localAddress , tunnel . closeable , tunnel . name , tunnel . description ) ;
109109 } ) ;
110110 }
111111
112112 get detected ( ) : TunnelItem [ ] {
113113 return Array . from ( this . model . detected . values ( ) ) . map ( tunnel => {
114- return new TunnelItem ( TunnelType . Detected , tunnel . remote , tunnel . localAddress , false , tunnel . name , tunnel . description ) ;
114+ return new TunnelItem ( TunnelType . Detected , tunnel . remoteHost , tunnel . remotePort , tunnel . localAddress , false , tunnel . name , tunnel . description ) ;
115115 } ) ;
116116 }
117117
118118 get candidates ( ) : Promise < TunnelItem [ ] > {
119119 return this . model . candidates . then ( values => {
120120 const candidates : TunnelItem [ ] = [ ] ;
121121 values . forEach ( value => {
122- if ( ! this . model . forwarded . has ( value . port ) && ! this . model . detected . has ( value . port ) ) {
123- candidates . push ( new TunnelItem ( TunnelType . Candidate , value . port , undefined , false , undefined , value . detail ) ) ;
122+ const key = MakeAddress ( value . host , value . port ) ;
123+ if ( ! this . model . forwarded . has ( key ) && ! this . model . detected . has ( key ) ) {
124+ candidates . push ( new TunnelItem ( TunnelType . Candidate , value . host , value . port , undefined , false , undefined , value . detail ) ) ;
124125 }
125126 } ) ;
126127 return candidates ;
@@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
185186 }
186187
187188 private isTunnelItem ( item : ITunnelGroup | ITunnelItem ) : item is ITunnelItem {
188- return ! ! ( ( < ITunnelItem > item ) . remote ) ;
189+ return ! ! ( ( < ITunnelItem > item ) . remotePort ) ;
189190 }
190191
191192 renderElement ( element : ITreeNode < ITunnelGroup | ITunnelItem , ITunnelGroup | ITunnelItem > , index : number , templateData : ITunnelTemplateData ) : void {
@@ -196,15 +197,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
196197 templateData . actionBar . clear ( ) ;
197198 let editableData : IEditableData | undefined ;
198199 if ( this . isTunnelItem ( node ) ) {
199- editableData = this . remoteExplorerService . getEditableData ( node . remote ) ;
200+ editableData = this . remoteExplorerService . getEditableData ( node . remoteHost , node . remotePort ) ;
200201 if ( editableData ) {
201202 templateData . iconLabel . element . style . display = 'none' ;
202203 this . renderInputBox ( templateData . container , editableData ) ;
203204 } else {
204205 templateData . iconLabel . element . style . display = 'flex' ;
205206 this . renderTunnel ( node , templateData ) ;
206207 }
207- } else if ( ( node . tunnelType === TunnelType . Add ) && ( editableData = this . remoteExplorerService . getEditableData ( undefined ) ) ) {
208+ } else if ( ( node . tunnelType === TunnelType . Add ) && ( editableData = this . remoteExplorerService . getEditableData ( undefined , undefined ) ) ) {
208209 templateData . iconLabel . element . style . display = 'none' ;
209210 this . renderInputBox ( templateData . container , editableData ) ;
210211 } else {
@@ -338,7 +339,8 @@ interface ITunnelGroup {
338339
339340interface ITunnelItem {
340341 tunnelType : TunnelType ;
341- remote : number ;
342+ remoteHost : string ;
343+ remotePort : number ;
342344 localAddress ?: string ;
343345 name ?: string ;
344346 closeable ?: boolean ;
@@ -349,7 +351,8 @@ interface ITunnelItem {
349351class TunnelItem implements ITunnelItem {
350352 constructor (
351353 public tunnelType : TunnelType ,
352- public remote : number ,
354+ public remoteHost : string ,
355+ public remotePort : number ,
353356 public localAddress ?: string ,
354357 public closeable ?: boolean ,
355358 public name ?: string ,
@@ -359,17 +362,17 @@ class TunnelItem implements ITunnelItem {
359362 if ( this . name ) {
360363 return nls . localize ( 'remote.tunnelsView.forwardedPortLabel0' , "{0}" , this . name ) ;
361364 } else if ( this . localAddress ) {
362- return nls . localize ( 'remote.tunnelsView.forwardedPortLabel2' , "{0} to {1}" , this . remote , this . localAddress ) ;
365+ return nls . localize ( 'remote.tunnelsView.forwardedPortLabel2' , "{0} to {1}" , this . remotePort , this . localAddress ) ;
363366 } else {
364- return nls . localize ( 'remote.tunnelsView.forwardedPortLabel3' , "{0} not forwarded" , this . remote ) ;
367+ return nls . localize ( 'remote.tunnelsView.forwardedPortLabel3' , "{0} not forwarded" , this . remotePort ) ;
365368 }
366369 }
367370
368371 get description ( ) : string | undefined {
369372 if ( this . _description ) {
370373 return this . _description ;
371374 } else if ( this . name ) {
372- return nls . localize ( 'remote.tunnelsView.forwardedPortDescription0' , "{0} to {1}" , this . remote , this . localAddress ) ;
375+ return nls . localize ( 'remote.tunnelsView.forwardedPortDescription0' , "{0} to {1}" , this . remotePort , this . localAddress ) ;
373376 }
374377 return undefined ;
375378 }
@@ -474,7 +477,7 @@ export class TunnelPanel extends ViewPane {
474477 } ) ) ;
475478
476479 this . _register ( this . remoteExplorerService . onDidChangeEditable ( async e => {
477- const isEditing = ! ! this . remoteExplorerService . getEditableData ( e ) ;
480+ const isEditing = ! ! this . remoteExplorerService . getEditableData ( e . host , e . port ) ;
478481
479482 if ( ! isEditing ) {
480483 dom . removeClass ( treeContainer , 'highlight' ) ;
@@ -575,12 +578,12 @@ namespace LabelTunnelAction {
575578 return async ( accessor , arg ) => {
576579 if ( arg instanceof TunnelItem ) {
577580 const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
578- remoteExplorerService . setEditable ( arg . remote , {
581+ remoteExplorerService . setEditable ( arg . remoteHost , arg . remotePort , {
579582 onFinish : ( value , success ) => {
580583 if ( success ) {
581- remoteExplorerService . tunnelModel . name ( arg . remote , value ) ;
584+ remoteExplorerService . tunnelModel . name ( arg . remoteHost , arg . remotePort , value ) ;
582585 }
583- remoteExplorerService . setEditable ( arg . remote , null ) ;
586+ remoteExplorerService . setEditable ( arg . remoteHost , arg . remotePort , null ) ;
584587 } ,
585588 validationMessage : ( ) => null ,
586589 placeholder : nls . localize ( 'remote.tunnelsView.labelPlaceholder' , "Port label" ) ,
@@ -596,24 +599,32 @@ namespace ForwardPortAction {
596599 export const ID = 'remote.tunnel.forward' ;
597600 export const LABEL = nls . localize ( 'remote.tunnel.forward' , "Forward a Port" ) ;
598601
602+ function parseInput ( value : string ) : { host : string , port : number } | undefined {
603+ const matches = value . match ( / ^ ( [ 0 - 9 ] + \. [ 0 - 9 ] + \. [ 0 - 9 ] + \. [ 0 - 9 ] + \: | l o c a l h o s t : ) ? ( [ 0 - 9 ] + ) $ / ) ;
604+ if ( ! matches ) {
605+ return undefined ;
606+ }
607+ return { host : matches [ 1 ] ?. substring ( 0 , matches [ 1 ] . length - 1 ) || 'localhost' , port : Number ( matches [ 2 ] ) } ;
608+ }
609+
599610 export function handler ( ) : ICommandHandler {
600611 return async ( accessor , arg ) => {
601612 const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
602613 if ( arg instanceof TunnelItem ) {
603- remoteExplorerService . tunnelModel . forward ( arg . remote ) ;
614+ remoteExplorerService . forward ( { host : arg . remoteHost , port : arg . remotePort } ) ;
604615 } else {
605616 const viewsService = accessor . get ( IViewsService ) ;
606617 await viewsService . openView ( TunnelPanel . ID , true ) ;
607- remoteExplorerService . setEditable ( undefined , {
618+ remoteExplorerService . setEditable ( undefined , undefined , {
608619 onFinish : ( value , success ) => {
609- if ( success ) {
610- remoteExplorerService . tunnelModel . forward ( Number ( value ) ) ;
620+ let parsed : { host : string , port : number } | undefined ;
621+ if ( success && ( parsed = parseInput ( value ) ) ) {
622+ remoteExplorerService . forward ( { host : parsed . host , port : parsed . port } ) ;
611623 }
612- remoteExplorerService . setEditable ( undefined , null ) ;
624+ remoteExplorerService . setEditable ( undefined , undefined , null ) ;
613625 } ,
614626 validationMessage : ( value ) => {
615- const asNumber = Number ( value ) ;
616- if ( ( value === '' ) || isNaN ( asNumber ) || ( asNumber < 0 ) || ( asNumber > 65535 ) ) {
627+ if ( ! parseInput ( value ) ) {
617628 return nls . localize ( 'remote.tunnelsView.portNumberValid' , "Port number is invalid" ) ;
618629 }
619630 return null ;
@@ -633,7 +644,7 @@ namespace ClosePortAction {
633644 return async ( accessor , arg ) => {
634645 if ( arg instanceof TunnelItem ) {
635646 const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
636- await remoteExplorerService . tunnelModel . close ( arg . remote ) ;
647+ await remoteExplorerService . close ( { host : arg . remoteHost , port : arg . remotePort } ) ;
637648 }
638649 } ;
639650 }
@@ -648,9 +659,10 @@ namespace OpenPortInBrowserAction {
648659 if ( arg instanceof TunnelItem ) {
649660 const model = accessor . get ( IRemoteExplorerService ) . tunnelModel ;
650661 const openerService = accessor . get ( IOpenerService ) ;
651- const tunnel = model . forwarded . has ( arg . remote ) ? model . forwarded . get ( arg . remote ) : model . detected . get ( arg . remote ) ;
662+ const key = MakeAddress ( arg . remoteHost , arg . remotePort ) ;
663+ const tunnel = model . forwarded . get ( key ) || model . detected . get ( key ) ;
652664 let address : string | undefined ;
653- if ( tunnel && tunnel . localAddress && ( address = model . address ( tunnel . remote ) ) ) {
665+ if ( tunnel && tunnel . localAddress && ( address = model . address ( tunnel . remoteHost , tunnel . remotePort ) ) ) {
654666 return openerService . open ( URI . parse ( 'http://' + address ) ) ;
655667 }
656668 return Promise . resolve ( ) ;
@@ -668,7 +680,7 @@ namespace CopyAddressAction {
668680 if ( arg instanceof TunnelItem ) {
669681 const model = accessor . get ( IRemoteExplorerService ) . tunnelModel ;
670682 const clipboard = accessor . get ( IClipboardService ) ;
671- const address = model . address ( arg . remote ) ;
683+ const address = model . address ( arg . remoteHost , arg . remotePort ) ;
672684 if ( address ) {
673685 await clipboard . writeText ( address . toString ( ) ) ;
674686 }
0 commit comments