26
26
namespace Microsoft . PowerShell
27
27
{
28
28
class ExitException : Exception { }
29
+ class LineAcceptedException : Exception { }
29
30
30
31
public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
31
32
{
@@ -44,7 +45,10 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
44
45
45
46
private bool _delayedOneTimeInitCompleted ;
46
47
// This is used by AIShell to check if PSReadLine is initialized and ready to render.
48
+ #pragma warning disable CS0414
47
49
private bool _readLineReady ;
50
+ #pragma warning restore CS0414
51
+ private bool _lineAcceptedExceptionThrown ;
48
52
49
53
private IPSConsoleReadLineMockableMethods _mockableMethods ;
50
54
private IConsole _console ;
@@ -175,9 +179,18 @@ internal static PSKeyInfo ReadKey()
175
179
// By waiting for a key on a different thread, our pipeline execution thread
176
180
// (the thread ReadLine is called from) avoid being blocked in code that can't
177
181
// 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
+ }
181
194
182
195
int handleId ;
183
196
System . Management . Automation . PowerShell ps = null ;
@@ -277,6 +290,16 @@ internal static PSKeyInfo ReadKey()
277
290
_singleton . Render ( ) ;
278
291
}
279
292
}
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
+ }
280
303
}
281
304
}
282
305
}
@@ -531,8 +554,16 @@ private string InputLoop()
531
554
// window resizing cannot and shouldn't happen within the processing of a given keybinding.
532
555
_handlePotentialResizing = true ;
533
556
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
+
536
567
if ( _inputAccepted )
537
568
{
538
569
_acceptedCommandLine = _buffer . ToString ( ) ;
0 commit comments