@@ -295,6 +295,8 @@ public void SetExit()
295295
296296 internal void ForceSetExit ( ) => _shouldExit = true ;
297297
298+ private void SetBusy ( bool busy ) => _languageServer ? . SendNotification ( "powerShell/executionBusyStatus" , busy ) ;
299+
298300 private bool CancelForegroundAndPrepend ( ISynchronousTask task , bool isIdle = false )
299301 {
300302 // NOTE: This causes foreground tasks to act like they have `ExecutionPriority.Next`.
@@ -313,9 +315,9 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal
313315
314316 _skipNextPrompt = true ;
315317
316- if ( task is SynchronousPowerShellTask < PSObject > psTask )
318+ if ( task is ISynchronousPowerShellTask t )
317319 {
318- psTask . MaybeAddToHistory ( ) ;
320+ t . MaybeAddToHistory ( ) ;
319321 }
320322
321323 using ( _taskQueue . BlockConsumers ( ) )
@@ -334,6 +336,32 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal
334336 return true ;
335337 }
336338
339+ // This handles executing the task while also notifying the client that the pipeline is
340+ // currently busy with a PowerShell task. The extension indicates this with a spinner.
341+ private void ExecuteTaskSynchronously ( ISynchronousTask task , CancellationToken cancellationToken )
342+ {
343+ // TODO: Simplify this logic.
344+ bool busy = false ;
345+ if ( task is ISynchronousPowerShellTask t
346+ && ( t . PowerShellExecutionOptions . AddToHistory
347+ || t . PowerShellExecutionOptions . FromRepl ) )
348+ {
349+ busy = true ;
350+ SetBusy ( true ) ;
351+ }
352+ try
353+ {
354+ task . ExecuteSynchronously ( cancellationToken ) ;
355+ }
356+ finally
357+ {
358+ if ( busy )
359+ {
360+ SetBusy ( false ) ;
361+ }
362+ }
363+ }
364+
337365 public Task < T > InvokeTaskOnPipelineThreadAsync < T > ( SynchronousTask < T > task )
338366 {
339367 if ( CancelForegroundAndPrepend ( task ) )
@@ -769,8 +797,13 @@ private void RunExecutionLoop(bool isForDebug = false)
769797 {
770798 try
771799 {
772- task . ExecuteSynchronously ( cancellationScope . CancellationToken ) ;
800+ ExecuteTaskSynchronously ( task , cancellationScope . CancellationToken ) ;
773801 }
802+ // Our flaky extension command test seems to be such because sometimes another
803+ // task gets queued, and since it runs in the foreground it cancels that task.
804+ // Interactively, this happens in the first loop (with DoOneRepl) which catches
805+ // the cancellation exception, but when under test that is a no-op, so it
806+ // happens in this second loop. Hence we need to catch it here too.
774807 catch ( OperationCanceledException e )
775808 {
776809 _logger . LogDebug ( e , "Task {Task} was canceled!" , task ) ;
@@ -935,19 +968,27 @@ private string InvokeReadLine(CancellationToken cancellationToken)
935968 }
936969 }
937970
971+ // TODO: Should we actually be directly invoking input versus queueing it as a task like everything else?
938972 private void InvokeInput ( string input , CancellationToken cancellationToken )
939973 {
940- PSCommand command = new PSCommand ( ) . AddScript ( input , useLocalScope : false ) ;
941- InvokePSCommand (
942- command ,
943- new PowerShellExecutionOptions
944- {
945- AddToHistory = true ,
946- ThrowOnError = false ,
947- WriteOutputToHost = true ,
948- FromRepl = true ,
949- } ,
950- cancellationToken ) ;
974+ SetBusy ( true ) ;
975+ try
976+ {
977+ InvokePSCommand (
978+ new PSCommand ( ) . AddScript ( input , useLocalScope : false ) ,
979+ new PowerShellExecutionOptions
980+ {
981+ AddToHistory = true ,
982+ ThrowOnError = false ,
983+ WriteOutputToHost = true ,
984+ FromRepl = true ,
985+ } ,
986+ cancellationToken ) ;
987+ }
988+ finally
989+ {
990+ SetBusy ( false ) ;
991+ }
951992 }
952993
953994 private void AddRunspaceEventHandlers ( Runspace runspace )
@@ -1076,16 +1117,18 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken)
10761117 while ( ! cancellationScope . CancellationToken . IsCancellationRequested
10771118 && _taskQueue . TryTake ( out ISynchronousTask task ) )
10781119 {
1120+ // Tasks which require the foreground cannot run under this idle handler, so the
1121+ // current foreground tasks gets canceled, the new task gets prepended, and this
1122+ // handler returns.
10791123 if ( CancelForegroundAndPrepend ( task , isIdle : true ) )
10801124 {
10811125 return ;
10821126 }
10831127
1084- // If we're executing a task, we don't need to run an extra pipeline later for events
1085- // TODO: This may not be a PowerShell task, so ideally we can differentiate that here.
1086- // For now it's mostly true and an easy assumption to make.
1087- runPipelineForEventProcessing = false ;
1088- task . ExecuteSynchronously ( cancellationScope . CancellationToken ) ;
1128+ // If we're executing a PowerShell task, we don't need to run an extra pipeline
1129+ // later for events.
1130+ runPipelineForEventProcessing = task is not ISynchronousPowerShellTask ;
1131+ ExecuteTaskSynchronously ( task , cancellationScope . CancellationToken ) ;
10891132 }
10901133 }
10911134
0 commit comments