Skip to content

Commit ec85f59

Browse files
committed
Implement PowerShell engine OnIdle events
1 parent ccc2fc2 commit ec85f59

File tree

1 file changed

+44
-8
lines changed

1 file changed

+44
-8
lines changed

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
5555

5656
private readonly IdempotentLatch _isRunningLatch = new();
5757

58+
private EngineIntrinsics _mainRunspaceEngineIntrinsics;
59+
5860
private bool _shouldExit = false;
5961

6062
private string _localComputerName;
@@ -345,17 +347,18 @@ public Task SetInitialWorkingDirectoryAsync(string path, CancellationToken cance
345347

346348
private void Run()
347349
{
348-
(PowerShell pwsh, RunspaceInfo localRunspaceInfo) = CreateInitialPowerShellSession();
350+
(PowerShell pwsh, RunspaceInfo localRunspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession();
351+
_mainRunspaceEngineIntrinsics = engineIntrinsics;
349352
_localComputerName = localRunspaceInfo.SessionDetails.ComputerName;
350353
_runspaceStack.Push(new RunspaceFrame(pwsh.Runspace, localRunspaceInfo));
351354
PushPowerShellAndRunLoop(pwsh, PowerShellFrameType.Normal, localRunspaceInfo);
352355
}
353356

354-
private (PowerShell, RunspaceInfo) CreateInitialPowerShellSession()
357+
private (PowerShell, RunspaceInfo, EngineIntrinsics) CreateInitialPowerShellSession()
355358
{
356-
PowerShell pwsh = CreateInitialPowerShell(_hostInfo, _readLineProvider);
359+
(PowerShell pwsh, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShell(_hostInfo, _readLineProvider);
357360
RunspaceInfo localRunspaceInfo = RunspaceInfo.CreateFromLocalPowerShell(_logger, pwsh);
358-
return (pwsh, localRunspaceInfo);
361+
return (pwsh, localRunspaceInfo, engineIntrinsics);
359362
}
360363

361364
private void PushPowerShellAndRunLoop(SMA.PowerShell pwsh, PowerShellFrameType frameType, RunspaceInfo newRunspaceInfo = null)
@@ -611,7 +614,7 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace)
611614
return pwsh;
612615
}
613616

614-
public PowerShell CreateInitialPowerShell(
617+
public (PowerShell, EngineIntrinsics) CreateInitialPowerShell(
615618
HostStartupInfo hostStartupInfo,
616619
ReadLineProvider readLineProvider)
617620
{
@@ -647,7 +650,7 @@ public PowerShell CreateInitialPowerShell(
647650
}
648651
}
649652

650-
return pwsh;
653+
return (pwsh, engineIntrinsics);
651654
}
652655

653656
private Runspace CreateInitialRunspace(InitialSessionState initialSessionState)
@@ -666,7 +669,27 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState)
666669

667670
private void OnPowerShellIdle()
668671
{
669-
if (_taskQueue.Count == 0)
672+
IReadOnlyList<PSEventSubscriber> eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers;
673+
674+
// Go through pending event subscribers and:
675+
// - if we have any subscribers, ensure we process any events
676+
// - if we have any idle events, generate an idle event and process that
677+
bool runPipelineForEventProcessing = false;
678+
foreach (PSEventSubscriber subscriber in eventSubscribers)
679+
{
680+
runPipelineForEventProcessing = true;
681+
682+
if (string.Equals(subscriber.SourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase))
683+
{
684+
// PowerShell thinks we're in a call (the ReadLine call) rather than idle,
685+
// but we know we're sitting in the prompt.
686+
// So we need to generate the idle event ourselves
687+
_mainRunspaceEngineIntrinsics.Events.GenerateEvent(PSEngineEvent.OnIdle, sender: null, args: null, extraData: null);
688+
break;
689+
}
690+
}
691+
692+
if (!runPipelineForEventProcessing && _taskQueue.Count == 0)
670693
{
671694
return;
672695
}
@@ -686,9 +709,21 @@ private void OnPowerShellIdle()
686709
return;
687710
}
688711

712+
// If we're executing a task, we don't need to run an extra pipeline later for events
713+
// TODO: This may not be a PowerShell task, so ideally we can differentiate that here.
714+
// For now it's mostly true and an easy assumption to make.
715+
runPipelineForEventProcessing = false;
689716
task.ExecuteSynchronously(cancellationScope.CancellationToken);
690717
}
691718
}
719+
720+
// We didn't end up executinng anything in the background,
721+
// so we need to run a small artificial pipeline instead
722+
// to force event processing
723+
if (runPipelineForEventProcessing)
724+
{
725+
InvokePSCommand(new PSCommand().AddScript("0", useLocalScope: true), PowerShellExecutionOptions.Default, CancellationToken.None);
726+
}
692727
}
693728

694729
private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
@@ -769,7 +804,8 @@ private Task PopOrReinitializeRunspaceAsync()
769804
// If our main runspace was corrupted,
770805
// we must re-initialize our state.
771806
// TODO: Use runspace.ResetRunspaceState() here instead
772-
(PowerShell pwsh, RunspaceInfo runspaceInfo) = CreateInitialPowerShellSession();
807+
(PowerShell pwsh, RunspaceInfo runspaceInfo, EngineIntrinsics engineIntrinsics) = CreateInitialPowerShellSession();
808+
_mainRunspaceEngineIntrinsics = engineIntrinsics;
773809
PushPowerShell(new PowerShellContextFrame(pwsh, runspaceInfo, PowerShellFrameType.Normal));
774810

775811
_logger.LogError($"Top level runspace entered state '{oldRunspaceState.State}' for reason '{oldRunspaceState.Reason}' and was reinitialized."

0 commit comments

Comments
 (0)