@@ -38,6 +38,15 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
3838import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService' ;
3939import { IWorkbenchActionRegistry , Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions' ;
4040import { SyncActionDescriptor } from 'vs/platform/actions/common/actions' ;
41+ import { IProgress , IProgressStep , IProgressService , ProgressLocation } from 'vs/platform/progress/common/progress' ;
42+ import { IWorkbenchContribution , IWorkbenchContributionsRegistry , Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions' ;
43+ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService' ;
44+ import { IDialogService } from 'vs/platform/dialogs/common/dialogs' ;
45+ import { ReconnectionWaitEvent , PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection' ;
46+ import Severity from 'vs/base/common/severity' ;
47+ import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions' ;
48+ import { IDisposable } from 'vs/base/common/lifecycle' ;
49+ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle' ;
4150
4251interface HelpInformation {
4352 extensionDescription : IExtensionDescription ;
@@ -445,3 +454,189 @@ Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions
445454 'View: Show Remote Explorer' ,
446455 nls . localize ( 'view' , "View" )
447456) ;
457+
458+
459+ class ProgressReporter {
460+ private _currentProgress : IProgress < IProgressStep > | null = null ;
461+ private lastReport : string | null = null ;
462+
463+ constructor ( currentProgress : IProgress < IProgressStep > | null ) {
464+ this . _currentProgress = currentProgress ;
465+ }
466+
467+ set currentProgress ( progress : IProgress < IProgressStep > ) {
468+ this . _currentProgress = progress ;
469+ }
470+
471+ report ( message ?: string ) {
472+ if ( message ) {
473+ this . lastReport = message ;
474+ }
475+
476+ if ( this . lastReport && this . _currentProgress ) {
477+ this . _currentProgress . report ( { message : this . lastReport } ) ;
478+ }
479+ }
480+ }
481+
482+ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
483+ constructor (
484+ @IRemoteAgentService remoteAgentService : IRemoteAgentService ,
485+ @IProgressService progressService : IProgressService ,
486+ @IDialogService dialogService : IDialogService ,
487+ @ICommandService commandService : ICommandService ,
488+ @IContextKeyService contextKeyService : IContextKeyService
489+ ) {
490+ const connection = remoteAgentService . getConnection ( ) ;
491+ if ( connection ) {
492+ let currentProgressPromiseResolve : ( ( ) => void ) | null = null ;
493+ let progressReporter : ProgressReporter | null = null ;
494+ let lastLocation : ProgressLocation | null = null ;
495+ let currentTimer : ReconnectionTimer | null = null ;
496+ let reconnectWaitEvent : ReconnectionWaitEvent | null = null ;
497+ let disposableListener : IDisposable | null = null ;
498+
499+ function showProgress ( location : ProgressLocation , buttons ?: string [ ] ) {
500+ if ( currentProgressPromiseResolve ) {
501+ currentProgressPromiseResolve ( ) ;
502+ }
503+
504+ const promise = new Promise < void > ( ( resolve ) => currentProgressPromiseResolve = resolve ) ;
505+ lastLocation = location ;
506+
507+ if ( location === ProgressLocation . Dialog ) {
508+ // Show dialog
509+ progressService ! . withProgress (
510+ { location : ProgressLocation . Dialog , buttons } ,
511+ ( progress ) => { if ( progressReporter ) { progressReporter . currentProgress = progress ; } return promise ; } ,
512+ ( choice ?) => {
513+ // Handle choice from dialog
514+ if ( choice === 0 && buttons && reconnectWaitEvent ) {
515+ reconnectWaitEvent . skipWait ( ) ;
516+ } else {
517+ showProgress ( ProgressLocation . Notification , buttons ) ;
518+ }
519+
520+ progressReporter ! . report ( ) ;
521+ } ) ;
522+ } else {
523+ // Show notification
524+ progressService ! . withProgress (
525+ { location : ProgressLocation . Notification , buttons } ,
526+ ( progress ) => { if ( progressReporter ) { progressReporter . currentProgress = progress ; } return promise ; } ,
527+ ( choice ?) => {
528+ // Handle choice from notification
529+ if ( choice === 0 && buttons && reconnectWaitEvent ) {
530+ reconnectWaitEvent . skipWait ( ) ;
531+ } else {
532+ hideProgress ( ) ;
533+ }
534+ } ) ;
535+ }
536+ }
537+
538+ function hideProgress ( ) {
539+ if ( currentProgressPromiseResolve ) {
540+ currentProgressPromiseResolve ( ) ;
541+ }
542+
543+ currentProgressPromiseResolve = null ;
544+ }
545+
546+ connection . onDidStateChange ( ( e ) => {
547+ if ( currentTimer ) {
548+ currentTimer . dispose ( ) ;
549+ currentTimer = null ;
550+ }
551+
552+ if ( disposableListener ) {
553+ disposableListener . dispose ( ) ;
554+ disposableListener = null ;
555+ }
556+ switch ( e . type ) {
557+ case PersistentConnectionEventType . ConnectionLost :
558+ if ( ! currentProgressPromiseResolve ) {
559+ progressReporter = new ProgressReporter ( null ) ;
560+ showProgress ( ProgressLocation . Dialog , [ nls . localize ( 'reconnectNow' , "Reconnect Now" ) ] ) ;
561+ }
562+
563+ progressReporter ! . report ( nls . localize ( 'connectionLost' , "Connection Lost" ) ) ;
564+ break ;
565+ case PersistentConnectionEventType . ReconnectionWait :
566+ hideProgress ( ) ;
567+ reconnectWaitEvent = e ;
568+ showProgress ( lastLocation || ProgressLocation . Notification , [ nls . localize ( 'reconnectNow' , "Reconnect Now" ) ] ) ;
569+ currentTimer = new ReconnectionTimer ( progressReporter ! , Date . now ( ) + 1000 * e . durationSeconds ) ;
570+ break ;
571+ case PersistentConnectionEventType . ReconnectionRunning :
572+ hideProgress ( ) ;
573+ showProgress ( lastLocation || ProgressLocation . Notification ) ;
574+ progressReporter ! . report ( nls . localize ( 'reconnectionRunning' , "Attempting to reconnect..." ) ) ;
575+
576+ // Register to listen for quick input is opened
577+ disposableListener = contextKeyService . onDidChangeContext ( ( contextKeyChangeEvent ) => {
578+ const reconnectInteraction = new Set < string > ( [ 'inQuickOpen' ] ) ;
579+ if ( contextKeyChangeEvent . affectsSome ( reconnectInteraction ) ) {
580+ // Need to move from dialog if being shown and user needs to type in a prompt
581+ if ( lastLocation === ProgressLocation . Dialog && progressReporter !== null ) {
582+ hideProgress ( ) ;
583+ showProgress ( ProgressLocation . Notification ) ;
584+ progressReporter . report ( ) ;
585+ }
586+ }
587+ } ) ;
588+
589+ break ;
590+ case PersistentConnectionEventType . ReconnectionPermanentFailure :
591+ hideProgress ( ) ;
592+ progressReporter = null ;
593+
594+ dialogService . show ( Severity . Error , nls . localize ( 'reconnectionPermanentFailure' , "Cannot reconnect. Please reload the window." ) , [ nls . localize ( 'reloadWindow' , "Reload Window" ) , nls . localize ( 'cancel' , "Cancel" ) ] , { cancelId : 1 } ) . then ( result => {
595+ // Reload the window
596+ if ( result . choice === 0 ) {
597+ commandService . executeCommand ( ReloadWindowAction . ID ) ;
598+ }
599+ } ) ;
600+ break ;
601+ case PersistentConnectionEventType . ConnectionGain :
602+ hideProgress ( ) ;
603+ progressReporter = null ;
604+ break ;
605+ }
606+ } ) ;
607+ }
608+ }
609+ }
610+
611+ class ReconnectionTimer implements IDisposable {
612+ private readonly _progressReporter : ProgressReporter ;
613+ private readonly _completionTime : number ;
614+ private readonly _token : any ;
615+
616+ constructor ( progressReporter : ProgressReporter , completionTime : number ) {
617+ this . _progressReporter = progressReporter ;
618+ this . _completionTime = completionTime ;
619+ this . _token = setInterval ( ( ) => this . _render ( ) , 1000 ) ;
620+ this . _render ( ) ;
621+ }
622+
623+ public dispose ( ) : void {
624+ clearInterval ( this . _token ) ;
625+ }
626+
627+ private _render ( ) {
628+ const remainingTimeMs = this . _completionTime - Date . now ( ) ;
629+ if ( remainingTimeMs < 0 ) {
630+ return ;
631+ }
632+ const remainingTime = Math . ceil ( remainingTimeMs / 1000 ) ;
633+ if ( remainingTime === 1 ) {
634+ this . _progressReporter . report ( nls . localize ( 'reconnectionWaitOne' , "Attempting to reconnect in {0} second..." , remainingTime ) ) ;
635+ } else {
636+ this . _progressReporter . report ( nls . localize ( 'reconnectionWaitMany' , "Attempting to reconnect in {0} seconds..." , remainingTime ) ) ;
637+ }
638+ }
639+ }
640+
641+ const workbenchContributionsRegistry = Registry . as < IWorkbenchContributionsRegistry > ( WorkbenchExtensions . Workbench ) ;
642+ workbenchContributionsRegistry . registerWorkbenchContribution ( RemoteAgentConnectionStatusListener , LifecyclePhase . Eventually ) ;
0 commit comments