Skip to content

Commit a7d988f

Browse files
authored
Allow accepting the current input automatically from within an OnIdle event handler (#4830)
1 parent 5863e8d commit a7d988f

File tree

1 file changed

+36
-5
lines changed

1 file changed

+36
-5
lines changed

PSReadLine/ReadLine.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
namespace 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

Comments
 (0)