@@ -295,6 +295,8 @@ public void SetExit()
295
295
296
296
internal void ForceSetExit ( ) => _shouldExit = true ;
297
297
298
+ private void SetBusy ( bool busy ) => _languageServer ? . SendNotification ( "powerShell/executionBusyStatus" , busy ) ;
299
+
298
300
private bool CancelForegroundAndPrepend ( ISynchronousTask task , bool isIdle = false )
299
301
{
300
302
// NOTE: This causes foreground tasks to act like they have `ExecutionPriority.Next`.
@@ -313,9 +315,9 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal
313
315
314
316
_skipNextPrompt = true ;
315
317
316
- if ( task is SynchronousPowerShellTask < PSObject > psTask )
318
+ if ( task is ISynchronousPowerShellTask t )
317
319
{
318
- psTask . MaybeAddToHistory ( ) ;
320
+ t . MaybeAddToHistory ( ) ;
319
321
}
320
322
321
323
using ( _taskQueue . BlockConsumers ( ) )
@@ -334,6 +336,32 @@ private bool CancelForegroundAndPrepend(ISynchronousTask task, bool isIdle = fal
334
336
return true ;
335
337
}
336
338
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
+
337
365
public Task < T > InvokeTaskOnPipelineThreadAsync < T > ( SynchronousTask < T > task )
338
366
{
339
367
if ( CancelForegroundAndPrepend ( task ) )
@@ -769,8 +797,13 @@ private void RunExecutionLoop(bool isForDebug = false)
769
797
{
770
798
try
771
799
{
772
- task . ExecuteSynchronously ( cancellationScope . CancellationToken ) ;
800
+ ExecuteTaskSynchronously ( task , cancellationScope . CancellationToken ) ;
773
801
}
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.
774
807
catch ( OperationCanceledException e )
775
808
{
776
809
_logger . LogDebug ( e , "Task {Task} was canceled!" , task ) ;
@@ -935,19 +968,27 @@ private string InvokeReadLine(CancellationToken cancellationToken)
935
968
}
936
969
}
937
970
971
+ // TODO: Should we actually be directly invoking input versus queueing it as a task like everything else?
938
972
private void InvokeInput ( string input , CancellationToken cancellationToken )
939
973
{
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
+ }
951
992
}
952
993
953
994
private void AddRunspaceEventHandlers ( Runspace runspace )
@@ -1076,16 +1117,18 @@ private void OnPowerShellIdle(CancellationToken idleCancellationToken)
1076
1117
while ( ! cancellationScope . CancellationToken . IsCancellationRequested
1077
1118
&& _taskQueue . TryTake ( out ISynchronousTask task ) )
1078
1119
{
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.
1079
1123
if ( CancelForegroundAndPrepend ( task , isIdle : true ) )
1080
1124
{
1081
1125
return ;
1082
1126
}
1083
1127
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 ) ;
1089
1132
}
1090
1133
}
1091
1134
0 commit comments