2626namespace Microsoft . PowerShell
2727{
2828 class ExitException : Exception { }
29+ class LineAcceptedException : Exception { }
2930
3031 public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
3132 {
@@ -44,7 +45,10 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
4445
4546 private bool _delayedOneTimeInitCompleted ;
4647 // This is used by AIShell to check if PSReadLine is initialized and ready to render.
48+ #pragma warning disable CS0414
4749 private bool _readLineReady ;
50+ #pragma warning restore CS0414
51+ private bool _lineAcceptedExceptionThrown ;
4852
4953 private IPSConsoleReadLineMockableMethods _mockableMethods ;
5054 private IConsole _console ;
@@ -175,9 +179,18 @@ internal static PSKeyInfo ReadKey()
175179 // By waiting for a key on a different thread, our pipeline execution thread
176180 // (the thread ReadLine is called from) avoid being blocked in code that can't
177181 // be unblocked and instead blocks on events we control.
178-
179- // First, set an event so the thread to read a key actually attempts to read a key.
180- _singleton . _readKeyWaitHandle . Set ( ) ;
182+ if ( _singleton . _lineAcceptedExceptionThrown )
183+ {
184+ // If we threw a 'LineAcceptedException', it means that "AcceptLine" was called within an 'OnIdle' handler the last time
185+ // this method was called, and thus we didn't wait for '_keyReadWaitHandle' to be signalled by the 'readkey thread'.
186+ // In this case, we don't want to signal '_readKeyWaitHandle' again as the 'readkey thread' already got a chance to run.
187+ _singleton . _lineAcceptedExceptionThrown = false ;
188+ }
189+ else
190+ {
191+ // Set an event so the 'readkey thread' actually attempts to read a key.
192+ _singleton . _readKeyWaitHandle . Set ( ) ;
193+ }
181194
182195 int handleId ;
183196 System . Management . Automation . PowerShell ps = null ;
@@ -277,6 +290,16 @@ internal static PSKeyInfo ReadKey()
277290 _singleton . Render ( ) ;
278291 }
279292 }
293+
294+ if ( _singleton . _inputAccepted && RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
295+ {
296+ // 'AcceptLine' was called by an 'OnIdle' handler.
297+ // In this case, we only want to break out of the loop and accept the current input on Windows, because
298+ // accepting input without a keystroke would leave the 'readkey thread' blocked on the 'ReadKey()' call,
299+ // and that will make all subsequent writes to console blocked on Linux and macOS until a key is pressed.
300+ _singleton . _lineAcceptedExceptionThrown = true ;
301+ throw new LineAcceptedException ( ) ;
302+ }
280303 }
281304 }
282305 }
@@ -531,8 +554,16 @@ private string InputLoop()
531554 // window resizing cannot and shouldn't happen within the processing of a given keybinding.
532555 _handlePotentialResizing = true ;
533556
534- var key = ReadKey ( ) ;
535- ProcessOneKey ( key , _dispatchTable , ignoreIfNoAction : false , arg : null ) ;
557+ try
558+ {
559+ var key = ReadKey ( ) ;
560+ ProcessOneKey ( key , _dispatchTable , ignoreIfNoAction : false , arg : null ) ;
561+ }
562+ catch ( LineAcceptedException )
563+ {
564+ Debug . Assert ( _inputAccepted , "LineAcceptedException should only be thrown when input was accepted within an 'OnIdle' handler." ) ;
565+ }
566+
536567 if ( _inputAccepted )
537568 {
538569 _acceptedCommandLine = _buffer . ToString ( ) ;
0 commit comments