Skip to content

Commit 9fd0dfa

Browse files
authored
Merge pull request #1584 from rjmholt/pt-legacy-readline
Re-implement legacy readline support
2 parents a9bcc75 + ff77a68 commit 9fd0dfa

File tree

7 files changed

+748
-643
lines changed

7 files changed

+748
-643
lines changed

src/PowerShellEditorServices/Services/PowerShell/Console/ConsoleReadLine.cs

Lines changed: 0 additions & 631 deletions
This file was deleted.

src/PowerShellEditorServices/Services/PowerShell/Console/IReadLine.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,5 @@ internal interface IReadLine
1212
string ReadLine(CancellationToken cancellationToken);
1313

1414
SecureString ReadSecureLine(CancellationToken cancellationToken);
15-
16-
bool TryOverrideReadKey(Func<bool, ConsoleKeyInfo> readKeyOverride);
17-
18-
bool TryOverrideIdleHandler(Action<CancellationToken> idleHandler);
1915
}
2016
}

src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs

Lines changed: 539 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
5+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
6+
using System.Management.Automation;
7+
using System.Threading;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
10+
{
11+
using System;
12+
13+
internal class PsrlReadLine : TerminalReadLine
14+
{
15+
private readonly PSReadLineProxy _psrlProxy;
16+
17+
private readonly PsesInternalHost _psesHost;
18+
19+
private readonly EngineIntrinsics _engineIntrinsics;
20+
21+
#region Constructors
22+
23+
public PsrlReadLine(
24+
PSReadLineProxy psrlProxy,
25+
PsesInternalHost psesHost,
26+
EngineIntrinsics engineIntrinsics,
27+
Func<bool, ConsoleKeyInfo> readKeyFunc,
28+
Action<CancellationToken> onIdleAction)
29+
{
30+
_psrlProxy = psrlProxy;
31+
_psesHost = psesHost;
32+
_engineIntrinsics = engineIntrinsics;
33+
_psrlProxy.OverrideReadKey(readKeyFunc);
34+
_psrlProxy.OverrideIdleHandler(onIdleAction);
35+
}
36+
37+
#endregion
38+
39+
#region Public Methods
40+
41+
public override string ReadLine(CancellationToken cancellationToken)
42+
{
43+
return _psesHost.InvokeDelegate<string>(representation: "ReadLine", new ExecutionOptions { MustRunInForeground = true }, InvokePSReadLine, cancellationToken);
44+
}
45+
46+
protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken)
47+
{
48+
return ConsoleProxy.ReadKey(intercept: true, cancellationToken);
49+
}
50+
51+
#endregion
52+
53+
#region Private Methods
54+
55+
private string InvokePSReadLine(CancellationToken cancellationToken)
56+
{
57+
EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics;
58+
return _psrlProxy.ReadLine(_psesHost.Runspace, engineIntrinsics, cancellationToken, /* lastExecutionStatus */ null);
59+
}
60+
61+
#endregion
62+
}
63+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
5+
using System.Management.Automation;
6+
using System.Security;
7+
using System.Threading;
8+
9+
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
10+
{
11+
using System;
12+
13+
internal abstract class TerminalReadLine : IReadLine
14+
{
15+
public abstract string ReadLine(CancellationToken cancellationToken);
16+
17+
protected abstract ConsoleKeyInfo ReadKey(CancellationToken cancellationToken);
18+
19+
public SecureString ReadSecureLine(CancellationToken cancellationToken)
20+
{
21+
Console.TreatControlCAsInput = true;
22+
int previousInputLength = 0;
23+
SecureString secureString = new SecureString();
24+
try
25+
{
26+
bool enterPressed = false;
27+
while (!enterPressed && !cancellationToken.IsCancellationRequested)
28+
{
29+
ConsoleKeyInfo keyInfo = ReadKey(cancellationToken);
30+
31+
if (keyInfo.IsCtrlC())
32+
{
33+
throw new PipelineStoppedException();
34+
}
35+
36+
switch (keyInfo.Key)
37+
{
38+
case ConsoleKey.Enter:
39+
// Stop the while loop so we can realign the cursor
40+
// and then return the entered string
41+
enterPressed = true;
42+
continue;
43+
44+
case ConsoleKey.Tab:
45+
break;
46+
47+
case ConsoleKey.Backspace:
48+
if (secureString.Length > 0)
49+
{
50+
secureString.RemoveAt(secureString.Length - 1);
51+
}
52+
break;
53+
54+
default:
55+
if (keyInfo.KeyChar != 0 && !char.IsControl(keyInfo.KeyChar))
56+
{
57+
secureString.AppendChar(keyInfo.KeyChar);
58+
}
59+
break;
60+
}
61+
62+
// Re-render the secure string characters
63+
int currentInputLength = secureString.Length;
64+
int consoleWidth = Console.WindowWidth;
65+
66+
if (currentInputLength > previousInputLength)
67+
{
68+
Console.Write('*');
69+
}
70+
else if (previousInputLength > 0 && currentInputLength < previousInputLength)
71+
{
72+
int row = ConsoleProxy.GetCursorTop(cancellationToken);
73+
int col = ConsoleProxy.GetCursorLeft(cancellationToken);
74+
75+
// Back up the cursor before clearing the character
76+
col--;
77+
if (col < 0)
78+
{
79+
col = consoleWidth - 1;
80+
row--;
81+
}
82+
83+
Console.SetCursorPosition(col, row);
84+
Console.Write(' ');
85+
Console.SetCursorPosition(col, row);
86+
}
87+
88+
previousInputLength = currentInputLength;
89+
}
90+
}
91+
finally
92+
{
93+
Console.TreatControlCAsInput = false;
94+
}
95+
96+
return secureString;
97+
}
98+
}
99+
}

src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -625,12 +625,14 @@ private static PowerShell CreatePowerShellForRunspace(Runspace runspace)
625625

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

628-
if (hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine)
628+
if (hostStartupInfo.ConsoleReplEnabled)
629629
{
630-
var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh);
631-
var readLine = new ConsoleReadLine(psrlProxy, this, engineIntrinsics);
632-
readLine.TryOverrideReadKey(ReadKey);
633-
readLine.TryOverrideIdleHandler(OnPowerShellIdle);
630+
// If we've been configured to use it, or if we can't load PSReadLine, use the legacy readline
631+
if (hostStartupInfo.UsesLegacyReadLine || !TryLoadPSReadLine(pwsh, engineIntrinsics, out IReadLine readLine))
632+
{
633+
readLine = new LegacyReadLine(this, ReadKey, OnPowerShellIdle);
634+
}
635+
634636
readLineProvider.OverrideReadLine(readLine);
635637
System.Console.CancelKeyPress += OnCancelKeyPress;
636638
System.Console.InputEncoding = Encoding.UTF8;
@@ -747,9 +749,7 @@ private ConsoleKeyInfo ReadKey(bool intercept)
747749
private bool LastKeyWasCtrlC()
748750
{
749751
return _lastKey.HasValue
750-
&& _lastKey.Value.Key == ConsoleKey.C
751-
&& (_lastKey.Value.Modifiers & ConsoleModifiers.Control) != 0
752-
&& (_lastKey.Value.Modifiers & ConsoleModifiers.Alt) == 0;
752+
&& _lastKey.Value.IsCtrlC();
753753
}
754754

755755
private void OnDebuggerStopped(object sender, DebuggerStopEventArgs debuggerStopEventArgs)
@@ -825,6 +825,22 @@ private Task PopOrReinitializeRunspaceAsync()
825825
CancellationToken.None);
826826
}
827827

828+
private bool TryLoadPSReadLine(PowerShell pwsh, EngineIntrinsics engineIntrinsics, out IReadLine psrlReadLine)
829+
{
830+
psrlReadLine = null;
831+
try
832+
{
833+
var psrlProxy = PSReadLineProxy.LoadAndCreate(_loggerFactory, pwsh);
834+
psrlReadLine = new PsrlReadLine(psrlProxy, this, engineIntrinsics, ReadKey, OnPowerShellIdle);
835+
return true;
836+
}
837+
catch (Exception e)
838+
{
839+
_logger.LogError(e, "Unable to load PSReadLine. Will fall back to legacy readline implementation.");
840+
return false;
841+
}
842+
}
843+
828844
private record RunspaceFrame(
829845
Runspace Runspace,
830846
RunspaceInfo RunspaceInfo);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
6+
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility
7+
{
8+
internal static class ConsoleKeyInfoExtensions
9+
{
10+
public static bool IsCtrlC(this ConsoleKeyInfo keyInfo)
11+
{
12+
if ((int)keyInfo.Key == 3)
13+
{
14+
return true;
15+
}
16+
17+
return keyInfo.Key == ConsoleKey.C
18+
&& (keyInfo.Modifiers & ConsoleModifiers.Control) != 0
19+
&& (keyInfo.Modifiers & ConsoleModifiers.Shift) == 0
20+
&& (keyInfo.Modifiers & ConsoleModifiers.Alt) == 0;
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)