Skip to content

Re-implement legacy readline support #1584

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

Merged
merged 12 commits into from
Oct 15, 2021

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,5 @@ internal interface IReadLine
string ReadLine(CancellationToken cancellationToken);

SecureString ReadSecureLine(CancellationToken cancellationToken);

bool TryOverrideReadKey(Func<bool, ConsoleKeyInfo> readKeyOverride);

bool TryOverrideIdleHandler(Action<CancellationToken> idleHandler);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
using System.Management.Automation;
using System.Threading;

namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
{
using System;

internal class PsrlReadLine : TerminalReadLine
{
private readonly PSReadLineProxy _psrlProxy;

private readonly PsesInternalHost _psesHost;

private readonly EngineIntrinsics _engineIntrinsics;

#region Constructors

public PsrlReadLine(
PSReadLineProxy psrlProxy,
PsesInternalHost psesHost,
EngineIntrinsics engineIntrinsics,
Func<bool, ConsoleKeyInfo> readKeyFunc,
Action<CancellationToken> onIdleAction)
{
_psrlProxy = psrlProxy;
_psesHost = psesHost;
_engineIntrinsics = engineIntrinsics;
_psrlProxy.OverrideReadKey(readKeyFunc);
_psrlProxy.OverrideIdleHandler(onIdleAction);
}

#endregion

#region Public Methods

public override string ReadLine(CancellationToken cancellationToken)
{
return _psesHost.InvokeDelegate<string>(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken);
}

protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken)
{
return ConsoleProxy.ReadKey(intercept: true, cancellationToken);
}

#endregion

#region Private Methods

private string InvokePSReadLine(CancellationToken cancellationToken)
{
EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics;
return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken, /* lastExecutionStatus */ null);
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
using System.Management.Automation;
using System.Security;
using System.Threading;

namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
{
using System;

internal abstract class TerminalReadLine : IReadLine
{
public abstract string ReadLine(CancellationToken cancellationToken);

protected abstract ConsoleKeyInfo ReadKey(CancellationToken cancellationToken);

public SecureString ReadSecureLine(CancellationToken cancellationToken)
{
Console.TreatControlCAsInput = true;
int previousInputLength = 0;
SecureString secureString = new SecureString();
try
{
bool enterPressed = false;
while (!enterPressed && !cancellationToken.IsCancellationRequested)
{
ConsoleKeyInfo keyInfo = ReadKey(cancellationToken);

if (keyInfo.IsCtrlC())
{
throw new PipelineStoppedException();
}

switch (keyInfo.Key)
{
case ConsoleKey.Enter:
// Stop the while loop so we can realign the cursor
// and then return the entered string
enterPressed = true;
continue;

case ConsoleKey.Tab:
break;

case ConsoleKey.Backspace:
if (secureString.Length > 0)
{
secureString.RemoveAt(secureString.Length - 1);
}
break;

default:
if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar))
{
secureString.AppendChar(keyInfo.KeyChar);
}
break;
}

// Re-render the secure string characters
int currentInputLength = secureString.Length;
int consoleWidth = Console.WindowWidth;

if (currentInputLength > previousInputLength)
{
Console.Write('*');
}
else if (previousInputLength > 0 && currentInputLength < previousInputLength)
{
int row = ConsoleProxy.GetCursorTop(cancellationToken);
int col = ConsoleProxy.GetCursorLeft(cancellationToken);

// Back up the cursor before clearing the character
col--;
if (col < 0)
{
col = consoleWidth - 1;
row--;
}

Console.SetCursorPosition(col, row);
Console.Write(' ');
Console.SetCursorPosition(col, row);
}

previousInputLength = currentInputLength;
}
}
finally
{
Console.TreatControlCAsInput = false;
}

return secureString;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -625,12 +625,14 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace)

var engineIntrinsics = (EngineIntrinsics)runspace.SessionStateProxy.GetVariable("ExecutionContext");

if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine)
if (hostStartupInfo.ConsoleReplEnabled)
{
var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh);
var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics);
readLine.TryOverrideReadKey(ReadKey);
readLine.TryOverrideIdleHandler(OnPowerShellIdle);
// If we've been configured to use it, or if we can't load PSReadLine, use the legacy readline
if (hostStartupInfo.UsesLegacyReadLine || !TryLoadPSReadLine(pwsh, engineIntrinsics, out IReadLine readLine))
{
readLine = new LegacyReadLine(this, ReadKey, OnPowerShellIdle);
}

readLineProvider.OverrideReadLine(readLine);
System.Console.CancelKeyPress += OnCancelKeyPress;
System.Console.InputEncoding = Encoding.UTF8;
Expand Down Expand Up @@ -747,9 +749,7 @@ private ConsoleKeyInfo ReadKey(bool intercept)
private bool LastKeyWasCtrlC()
{
return _lastKey.HasValue
&& _lastKey.Value.Key == ConsoleKey.C
&& (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0
&& (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0;
&& _lastKey.Value.IsCtrlC();
}

private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs)
Expand Down Expand Up @@ -825,6 +825,22 @@ private Task PopOrReinitializeRunspaceAsync()
CancellationToken.None);
}

private bool TryLoadPSReadLine(PowerShell pwsh, EngineIntrinsics engineIntrinsics, out IReadLine psrlReadLine)
{
psrlReadLine = null;
try
{
var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh);
psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics, ReadKey, OnPowerShellIdle);
return true;
}
catch (Exception e)
{
_logger.LogError(e, "Unable to load PSReadLine. Will fall back to legacy readline implementation.");
return false;
}
}

private record RunspaceFrame(
Runspace Runspace,
RunspaceInfo RunspaceInfo);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility
{
internal static class ConsoleKeyInfoExtensions
{
public static bool IsCtrlC(this ConsoleKeyInfo keyInfo)
{
if ((int)keyInfo.Key == 3)
{
return true;
}

return keyInfo.Key == ConsoleKey.C
&& (keyInfo.Modifiers & ConsoleModifiers.Control) != 0
&& (keyInfo.Modifiers & ConsoleModifiers.Shift) == 0
&& (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0;
}
}
}