@@ -20,13 +20,13 @@ import { Event, Emitter } from 'vs/base/common/event';
2020import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list' ;
2121import { ITreeRenderer , ITreeNode , IAsyncDataSource , ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree' ;
2222import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService' ;
23- import { Disposable , IDisposable , toDisposable , MutableDisposable , dispose } from 'vs/base/common/lifecycle' ;
23+ import { Disposable , IDisposable , toDisposable , MutableDisposable , dispose , DisposableStore } from 'vs/base/common/lifecycle' ;
2424import { ActionBar , ActionViewItem , IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar' ;
2525import { 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 , MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService' ;
29+ import { IRemoteExplorerService , TunnelModel , MakeAddress , TunnelType , ITunnelItem } 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' ;
@@ -55,13 +55,15 @@ export interface ITunnelViewModel {
5555 readonly forwarded : TunnelItem [ ] ;
5656 readonly detected : TunnelItem [ ] ;
5757 readonly candidates : Promise < TunnelItem [ ] > ;
58+ readonly input : ITunnelItem | ITunnelGroup | undefined ;
5859 groups ( ) : Promise < ITunnelGroup [ ] > ;
5960}
6061
6162export class TunnelViewModel extends Disposable implements ITunnelViewModel {
6263 private _onForwardedPortsChanged : Emitter < void > = new Emitter ( ) ;
6364 public onForwardedPortsChanged : Event < void > = this . _onForwardedPortsChanged . event ;
6465 private model : TunnelModel ;
66+ private _input : ITunnelItem | ITunnelGroup | undefined ;
6567
6668 constructor (
6769 @IRemoteExplorerService remoteExplorerService : IRemoteExplorerService ) {
@@ -70,6 +72,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
7072 this . _register ( this . model . onForwardPort ( ( ) => this . _onForwardedPortsChanged . fire ( ) ) ) ;
7173 this . _register ( this . model . onClosePort ( ( ) => this . _onForwardedPortsChanged . fire ( ) ) ) ;
7274 this . _register ( this . model . onPortName ( ( ) => this . _onForwardedPortsChanged . fire ( ) ) ) ;
75+ this . _register ( this . model . onCandidatesChanged ( ( ) => this . _onForwardedPortsChanged . fire ( ) ) ) ;
7376 }
7477
7578 async groups ( ) : Promise < ITunnelGroup [ ] > {
@@ -96,10 +99,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
9699 items : candidates
97100 } ) ;
98101 }
99- groups . push ( {
100- label : nls . localize ( 'remote.tunnelsView.add' , "Forward a Port..." ) ,
101- tunnelType : TunnelType . Add ,
102- } ) ;
102+ if ( ! this . _input ) {
103+ this . _input = {
104+ label : nls . localize ( 'remote.tunnelsView.add' , "Forward a Port..." ) ,
105+ tunnelType : TunnelType . Add ,
106+ } ;
107+ }
108+ groups . push ( this . _input ) ;
103109 return groups ;
104110 }
105111
@@ -128,6 +134,10 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
128134 } ) ;
129135 }
130136
137+ get input ( ) : ITunnelItem | ITunnelGroup | undefined {
138+ return this . _input ;
139+ }
140+
131141 dispose ( ) {
132142 super . dispose ( ) ;
133143 }
@@ -197,15 +207,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
197207 templateData . actionBar . clear ( ) ;
198208 let editableData : IEditableData | undefined ;
199209 if ( this . isTunnelItem ( node ) ) {
200- editableData = this . remoteExplorerService . getEditableData ( node . remoteHost , node . remotePort ) ;
210+ editableData = this . remoteExplorerService . getEditableData ( node ) ;
201211 if ( editableData ) {
202212 templateData . iconLabel . element . style . display = 'none' ;
203213 this . renderInputBox ( templateData . container , editableData ) ;
204214 } else {
205215 templateData . iconLabel . element . style . display = 'flex' ;
206216 this . renderTunnel ( node , templateData ) ;
207217 }
208- } else if ( ( node . tunnelType === TunnelType . Add ) && ( editableData = this . remoteExplorerService . getEditableData ( undefined , undefined ) ) ) {
218+ } else if ( ( node . tunnelType === TunnelType . Add ) && ( editableData = this . remoteExplorerService . getEditableData ( undefined ) ) ) {
209219 templateData . iconLabel . element . style . display = 'none' ;
210220 this . renderInputBox ( templateData . container , editableData ) ;
211221 } else {
@@ -217,14 +227,15 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
217227 private renderTunnel ( node : ITunnelItem , templateData : ITunnelTemplateData ) {
218228 templateData . iconLabel . setLabel ( node . label , node . description , { title : node . label + ' - ' + node . description , extraClasses : [ 'tunnel-view-label' ] } ) ;
219229 templateData . actionBar . context = node ;
220- const contextKeyService = this . contextKeyService . createScoped ( ) ;
230+ const contextKeyService = this . _register ( this . contextKeyService . createScoped ( ) ) ;
221231 contextKeyService . createKey ( 'view' , this . viewId ) ;
222232 contextKeyService . createKey ( 'tunnelType' , node . tunnelType ) ;
223233 contextKeyService . createKey ( 'tunnelCloseable' , node . closeable ) ;
224- const menu = this . menuService . createMenu ( MenuId . TunnelInline , contextKeyService ) ;
225- this . _register ( menu ) ;
234+ const disposableStore = new DisposableStore ( ) ;
235+ templateData . elementDisposable = disposableStore ;
236+ const menu = disposableStore . add ( this . menuService . createMenu ( MenuId . TunnelInline , contextKeyService ) ) ;
226237 const actions : IAction [ ] = [ ] ;
227- this . _register ( createAndFillInActionBarActions ( menu , { shouldForwardArgs : true } , actions ) ) ;
238+ disposableStore . add ( createAndFillInActionBarActions ( menu , { shouldForwardArgs : true } , actions ) ) ;
228239 if ( actions ) {
229240 templateData . actionBar . push ( actions , { icon : true , label : false } ) ;
230241 if ( this . _actionRunner ) {
@@ -324,30 +335,12 @@ class TunnelDataSource implements IAsyncDataSource<ITunnelViewModel, ITunnelItem
324335 }
325336}
326337
327- enum TunnelType {
328- Candidate = 'Candidate' ,
329- Detected = 'Detected' ,
330- Forwarded = 'Forwarded' ,
331- Add = 'Add'
332- }
333-
334338interface ITunnelGroup {
335339 tunnelType : TunnelType ;
336340 label : string ;
337341 items ?: ITunnelItem [ ] | Promise < ITunnelItem [ ] > ;
338342}
339343
340- interface ITunnelItem {
341- tunnelType : TunnelType ;
342- remoteHost : string ;
343- remotePort : number ;
344- localAddress ?: string ;
345- name ?: string ;
346- closeable ?: boolean ;
347- readonly description ?: string ;
348- readonly label : string ;
349- }
350-
351344class TunnelItem implements ITunnelItem {
352345 constructor (
353346 public tunnelType : TunnelType ,
@@ -472,12 +465,12 @@ export class TunnelPanel extends ViewPane {
472465
473466 this . _register ( Event . debounce ( navigator . onDidOpenResource , ( last , event ) => event , 75 , true ) ( e => {
474467 if ( e . element && ( e . element . tunnelType === TunnelType . Add ) ) {
475- this . commandService . executeCommand ( ForwardPortAction . ID , 'inline add' ) ;
468+ this . commandService . executeCommand ( ForwardPortAction . INLINE_ID ) ;
476469 }
477470 } ) ) ;
478471
479472 this . _register ( this . remoteExplorerService . onDidChangeEditable ( async e => {
480- const isEditing = ! ! this . remoteExplorerService . getEditableData ( e . host , e . port ) ;
473+ const isEditing = ! ! this . remoteExplorerService . getEditableData ( e ) ;
481474
482475 if ( ! isEditing ) {
483476 dom . removeClass ( treeContainer , 'highlight' ) ;
@@ -487,15 +480,15 @@ export class TunnelPanel extends ViewPane {
487480
488481 if ( isEditing ) {
489482 dom . addClass ( treeContainer , 'highlight' ) ;
483+ this . tree . reveal ( e ? e : this . viewModel . input ) ;
490484 } else {
491485 this . tree . domFocus ( ) ;
492486 }
493487 } ) ) ;
494488 }
495489
496490 private get contributedContextMenu ( ) : IMenu {
497- const contributedContextMenu = this . menuService . createMenu ( MenuId . TunnelContext , this . tree . contextKeyService ) ;
498- this . _register ( contributedContextMenu ) ;
491+ const contributedContextMenu = this . _register ( this . menuService . createMenu ( MenuId . TunnelContext , this . tree . contextKeyService ) ) ;
499492 return contributedContextMenu ;
500493 }
501494
@@ -578,12 +571,12 @@ namespace LabelTunnelAction {
578571 return async ( accessor , arg ) => {
579572 if ( arg instanceof TunnelItem ) {
580573 const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
581- remoteExplorerService . setEditable ( arg . remoteHost , arg . remotePort , {
574+ remoteExplorerService . setEditable ( arg , {
582575 onFinish : ( value , success ) => {
583576 if ( success ) {
584577 remoteExplorerService . tunnelModel . name ( arg . remoteHost , arg . remotePort , value ) ;
585578 }
586- remoteExplorerService . setEditable ( arg . remoteHost , arg . remotePort , null ) ;
579+ remoteExplorerService . setEditable ( arg , null ) ;
587580 } ,
588581 validationMessage : ( ) => null ,
589582 placeholder : nls . localize ( 'remote.tunnelsView.labelPlaceholder' , "Port label" ) ,
@@ -596,7 +589,8 @@ namespace LabelTunnelAction {
596589}
597590
598591namespace ForwardPortAction {
599- export const ID = 'remote.tunnel.forward' ;
592+ export const INLINE_ID = 'remote.tunnel.forwardInline' ;
593+ export const COMMANDPALETTE_ID = 'remote.tunnel.forwardCommandPalette' ;
600594 export const LABEL = nls . localize ( 'remote.tunnel.forward' , "Forward a Port" ) ;
601595 const forwardPrompt = nls . localize ( 'remote.tunnel.forwardPrompt' , "Port number or address (eg. 3000 or 10.10.10.10:2000)." ) ;
602596
@@ -615,35 +609,40 @@ namespace ForwardPortAction {
615609 return null ;
616610 }
617611
618- export function handler ( ) : ICommandHandler {
612+ export function inlineHandler ( ) : ICommandHandler {
619613 return async ( accessor , arg ) => {
620614 const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
621615 if ( arg instanceof TunnelItem ) {
622616 remoteExplorerService . forward ( { host : arg . remoteHost , port : arg . remotePort } ) ;
623- } else if ( arg ) {
624- remoteExplorerService . setEditable ( undefined , undefined , {
617+ } else {
618+ remoteExplorerService . setEditable ( undefined , {
625619 onFinish : ( value , success ) => {
626620 let parsed : { host : string , port : number } | undefined ;
627621 if ( success && ( parsed = parseInput ( value ) ) ) {
628622 remoteExplorerService . forward ( { host : parsed . host , port : parsed . port } ) ;
629623 }
630- remoteExplorerService . setEditable ( undefined , undefined , null ) ;
624+ remoteExplorerService . setEditable ( undefined , null ) ;
631625 } ,
632626 validationMessage : validateInput ,
633627 placeholder : forwardPrompt
634628 } ) ;
635- } else {
636- const viewsService = accessor . get ( IViewsService ) ;
637- const quickInputService = accessor . get ( IQuickInputService ) ;
638- await viewsService . openView ( TunnelPanel . ID , true ) ;
639- const value = await quickInputService . input ( {
640- prompt : forwardPrompt ,
641- validateInput : ( value ) => Promise . resolve ( validateInput ( value ) )
642- } ) ;
643- let parsed : { host : string , port : number } | undefined ;
644- if ( value && ( parsed = parseInput ( value ) ) ) {
645- remoteExplorerService . forward ( { host : parsed . host , port : parsed . port } ) ;
646- }
629+ }
630+ } ;
631+ }
632+
633+ export function commandPaletteHandler ( ) : ICommandHandler {
634+ return async ( accessor , arg ) => {
635+ const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
636+ const viewsService = accessor . get ( IViewsService ) ;
637+ const quickInputService = accessor . get ( IQuickInputService ) ;
638+ await viewsService . openView ( TunnelPanel . ID , true ) ;
639+ const value = await quickInputService . input ( {
640+ prompt : forwardPrompt ,
641+ validateInput : ( value ) => Promise . resolve ( validateInput ( value ) )
642+ } ) ;
643+ let parsed : { host : string , port : number } | undefined ;
644+ if ( value && ( parsed = parseInput ( value ) ) ) {
645+ remoteExplorerService . forward ( { host : parsed . host , port : parsed . port } ) ;
647646 }
648647 } ;
649648 }
@@ -702,30 +701,51 @@ namespace CopyAddressAction {
702701 }
703702}
704703
704+ namespace RefreshTunnelViewAction {
705+ export const ID = 'remote.tunnel.refresh' ;
706+ export const LABEL = nls . localize ( 'remote.tunnel.refreshView' , "Refresh" ) ;
707+
708+ export function handler ( ) : ICommandHandler {
709+ return ( accessor , arg ) => {
710+ const remoteExplorerService = accessor . get ( IRemoteExplorerService ) ;
711+ return remoteExplorerService . refresh ( ) ;
712+ } ;
713+ }
714+ }
715+
705716CommandsRegistry . registerCommand ( LabelTunnelAction . ID , LabelTunnelAction . handler ( ) ) ;
706- CommandsRegistry . registerCommand ( ForwardPortAction . ID , ForwardPortAction . handler ( ) ) ;
717+ CommandsRegistry . registerCommand ( ForwardPortAction . INLINE_ID , ForwardPortAction . inlineHandler ( ) ) ;
718+ CommandsRegistry . registerCommand ( ForwardPortAction . COMMANDPALETTE_ID , ForwardPortAction . commandPaletteHandler ( ) ) ;
707719CommandsRegistry . registerCommand ( ClosePortAction . ID , ClosePortAction . handler ( ) ) ;
708720CommandsRegistry . registerCommand ( OpenPortInBrowserAction . ID , OpenPortInBrowserAction . handler ( ) ) ;
709721CommandsRegistry . registerCommand ( CopyAddressAction . ID , CopyAddressAction . handler ( ) ) ;
722+ CommandsRegistry . registerCommand ( RefreshTunnelViewAction . ID , RefreshTunnelViewAction . handler ( ) ) ;
710723
711724MenuRegistry . appendMenuItem ( MenuId . CommandPalette , ( {
712725 command : {
713- id : ForwardPortAction . ID ,
726+ id : ForwardPortAction . COMMANDPALETTE_ID ,
714727 title : ForwardPortAction . LABEL
715728 } ,
716729 when : forwardedPortsViewEnabled
717730} ) ) ;
718-
719-
720731MenuRegistry . appendMenuItem ( MenuId . TunnelTitle , ( {
721732 group : 'navigation' ,
722733 order : 0 ,
723734 command : {
724- id : ForwardPortAction . ID ,
735+ id : ForwardPortAction . COMMANDPALETTE_ID ,
725736 title : ForwardPortAction . LABEL ,
726737 icon : { id : 'codicon/plus' }
727738 }
728739} ) ) ;
740+ MenuRegistry . appendMenuItem ( MenuId . TunnelTitle , ( {
741+ group : 'navigation' ,
742+ order : 1 ,
743+ command : {
744+ id : RefreshTunnelViewAction . ID ,
745+ title : RefreshTunnelViewAction . LABEL ,
746+ icon : { id : 'codicon/refresh' }
747+ }
748+ } ) ) ;
729749MenuRegistry . appendMenuItem ( MenuId . TunnelContext , ( {
730750 group : '0_manage' ,
731751 order : 0 ,
@@ -757,7 +777,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({
757777 group : '0_manage' ,
758778 order : 1 ,
759779 command : {
760- id : ForwardPortAction . ID ,
780+ id : ForwardPortAction . INLINE_ID ,
761781 title : ForwardPortAction . LABEL ,
762782 } ,
763783 when : TunnelTypeContextKey . isEqualTo ( TunnelType . Candidate )
@@ -784,7 +804,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({
784804MenuRegistry . appendMenuItem ( MenuId . TunnelInline , ( {
785805 order : 0 ,
786806 command : {
787- id : ForwardPortAction . ID ,
807+ id : ForwardPortAction . INLINE_ID ,
788808 title : ForwardPortAction . LABEL ,
789809 icon : { id : 'codicon/plus' }
790810 } ,
0 commit comments