Skip to content

Allow accepting the current input automatically from within 'OnIdle' event handler #4830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions PSReadLine/ReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
namespace Microsoft.PowerShell
{
class ExitException : Exception { }
class LineAcceptedException : Exception { }

public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
{
Expand All @@ -44,7 +45,10 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods

private bool _delayedOneTimeInitCompleted;
// This is used by AIShell to check if PSReadLine is initialized and ready to render.
#pragma warning disable CS0414
private bool _readLineReady;
#pragma warning restore CS0414
private bool _lineAcceptedExceptionThrown;

private IPSConsoleReadLineMockableMethods _mockableMethods;
private IConsole _console;
Expand Down Expand Up @@ -175,9 +179,18 @@ internal static PSKeyInfo ReadKey()
// By waiting for a key on a different thread, our pipeline execution thread
// (the thread ReadLine is called from) avoid being blocked in code that can't
// be unblocked and instead blocks on events we control.

// First, set an event so the thread to read a key actually attempts to read a key.
_singleton._readKeyWaitHandle.Set();
if (_singleton._lineAcceptedExceptionThrown)
{
// If we threw a 'LineAcceptedException', it means that "AcceptLine" was called within an 'OnIdle' handler the last time
// this method was called, and thus we didn't wait for '_keyReadWaitHandle' to be signalled by the 'readkey thread'.
// In this case, we don't want to signal '_readKeyWaitHandle' again as the 'readkey thread' already got a chance to run.
_singleton._lineAcceptedExceptionThrown = false;
}
else
{
// Set an event so the 'readkey thread' actually attempts to read a key.
_singleton._readKeyWaitHandle.Set();
}

int handleId;
System.Management.Automation.PowerShell ps = null;
Expand Down Expand Up @@ -277,6 +290,16 @@ internal static PSKeyInfo ReadKey()
_singleton.Render();
}
}

if (_singleton._inputAccepted && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// 'AcceptLine' was called by an 'OnIdle' handler.
// In this case, we only want to break out of the loop and accept the current input on Windows, because
// accepting input without a keystroke would leave the 'readkey thread' blocked on the 'ReadKey()' call,
// and that will make all subsequent writes to console blocked on Linux and macOS until a key is pressed.
_singleton._lineAcceptedExceptionThrown = true;
throw new LineAcceptedException();
}
}
}
}
Expand Down Expand Up @@ -531,8 +554,16 @@ private string InputLoop()
// window resizing cannot and shouldn't happen within the processing of a given keybinding.
_handlePotentialResizing = true;

var key = ReadKey();
ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null);
try
{
var key = ReadKey();
ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null);
}
catch (LineAcceptedException)
{
Debug.Assert(_inputAccepted, "LineAcceptedException should only be thrown when input was accepted within an 'OnIdle' handler.");
}

if (_inputAccepted)
{
_acceptedCommandLine = _buffer.ToString();
Expand Down