Skip to content

Commit e3278a8

Browse files
committed
Delete ConsoleProxy.cs and further simplify ReadKey logic
1 parent 9fa13a2 commit e3278a8

File tree

6 files changed

+32
-256
lines changed

6 files changed

+32
-256
lines changed

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

-47
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Security;
54
using System.Threading;
65

76
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
87
{
8+
// TODO: Do we really need a whole interface for this?
99
internal interface IReadLine
1010
{
1111
string ReadLine(CancellationToken cancellationToken);
12-
13-
SecureString ReadSecureLine(CancellationToken cancellationToken);
1412
}
1513
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public PsrlReadLine(
4040

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

43-
protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) => ConsoleProxy.SafeReadKey(intercept: true, cancellationToken);
43+
protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) => _psesHost.ReadKey(intercept: true, cancellationToken);
4444

4545
#endregion
4646

Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
5-
using System.Management.Automation;
6-
using System.Security;
74
using System.Threading;
85

96
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Console
@@ -15,85 +12,5 @@ internal abstract class TerminalReadLine : IReadLine
1512
public abstract string ReadLine(CancellationToken cancellationToken);
1613

1714
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();
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 = Console.CursorTop;
73-
int col = Console.CursorLeft;
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-
}
9815
}
9916
}

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

+1-114
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
// Licensed under the MIT License.
33

44
using System;
5-
using System.Management.Automation;
65
using System.Management.Automation.Host;
76
using Microsoft.Extensions.Logging;
8-
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console;
97

108
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host
119
{
@@ -15,7 +13,6 @@ internal class EditorServicesConsolePSHostRawUserInterface : PSHostRawUserInterf
1513

1614
private readonly PSHostRawUserInterface _internalRawUI;
1715
private readonly ILogger _logger;
18-
private KeyInfo? _lastKeyDown;
1916

2017
#endregion
2118

@@ -129,60 +126,7 @@ public override string WindowTitle
129126
/// </summary>
130127
/// <param name="options">Options for reading the current keypress.</param>
131128
/// <returns>A KeyInfo struct with details about the current keypress.</returns>
132-
public override KeyInfo ReadKey(ReadKeyOptions options)
133-
{
134-
bool includeUp = (options & ReadKeyOptions.IncludeKeyUp) != 0;
135-
136-
// Key Up was requested and we have a cached key down we can return.
137-
if (includeUp && _lastKeyDown != null)
138-
{
139-
KeyInfo info = _lastKeyDown.Value;
140-
_lastKeyDown = null;
141-
return new KeyInfo(
142-
info.VirtualKeyCode,
143-
info.Character,
144-
info.ControlKeyState,
145-
keyDown: false);
146-
}
147-
148-
bool intercept = (options & ReadKeyOptions.NoEcho) != 0;
149-
bool includeDown = (options & ReadKeyOptions.IncludeKeyDown) != 0;
150-
if (!(includeDown || includeUp))
151-
{
152-
throw new PSArgumentException(
153-
"Cannot read key options. To read options, set one or both of the following: IncludeKeyDown, IncludeKeyUp.",
154-
nameof(options));
155-
}
156-
157-
// Allow ControlC as input so we can emulate pipeline stop requests. We can't actually
158-
// determine if a stop is requested without using non-public API's.
159-
bool oldValue = System.Console.TreatControlCAsInput;
160-
try
161-
{
162-
System.Console.TreatControlCAsInput = true;
163-
ConsoleKeyInfo key = ConsoleProxy.SafeReadKey(intercept, default);
164-
165-
if (IsCtrlC(key))
166-
{
167-
// Caller wants CtrlC as input so return it.
168-
if ((options & ReadKeyOptions.AllowCtrlC) != 0)
169-
{
170-
return ProcessKey(key, includeDown);
171-
}
172-
173-
// Caller doesn't want CtrlC so throw a PipelineStoppedException to emulate
174-
// a real stop. This will not show an exception to a script based caller and it
175-
// will avoid having to return something like default(KeyInfo).
176-
throw new PipelineStoppedException();
177-
}
178-
179-
return ProcessKey(key, includeDown);
180-
}
181-
finally
182-
{
183-
System.Console.TreatControlCAsInput = oldValue;
184-
}
185-
}
129+
public override KeyInfo ReadKey(ReadKeyOptions options) => _internalRawUI.ReadKey(options);
186130

187131
/// <summary>
188132
/// Flushes the current input buffer.
@@ -279,62 +223,5 @@ public override void SetBufferContents(
279223
public override int LengthInBufferCells(string source, int offset) => _internalRawUI.LengthInBufferCells(source, offset);
280224

281225
#endregion
282-
283-
/// <summary>
284-
/// Determines if a key press represents the input Ctrl + C.
285-
/// </summary>
286-
/// <param name="keyInfo">The key to test.</param>
287-
/// <returns>
288-
/// <see langword="true" /> if the key represents the input Ctrl + C,
289-
/// otherwise <see langword="false" />.
290-
/// </returns>
291-
private static bool IsCtrlC(ConsoleKeyInfo keyInfo)
292-
{
293-
// In the VSCode terminal Ctrl C is processed as virtual key code "3", which
294-
// is not a named value in the ConsoleKey enum.
295-
if ((int)keyInfo.Key == 3)
296-
{
297-
return true;
298-
}
299-
300-
return keyInfo.Key == ConsoleKey.C && (keyInfo.Modifiers & ConsoleModifiers.Control) != 0;
301-
}
302-
303-
/// <summary>
304-
/// Converts <see cref="ConsoleKeyInfo" /> objects to <see cref="KeyInfo" /> objects and caches
305-
/// key down events for the next key up request.
306-
/// </summary>
307-
/// <param name="key">The key to convert.</param>
308-
/// <param name="isDown">
309-
/// A value indicating whether the result should be a key down event.
310-
/// </param>
311-
/// <returns>The converted value.</returns>
312-
private KeyInfo ProcessKey(ConsoleKeyInfo key, bool isDown)
313-
{
314-
// Translate ConsoleModifiers to ControlKeyStates
315-
ControlKeyStates states = default;
316-
if ((key.Modifiers & ConsoleModifiers.Alt) != 0)
317-
{
318-
states |= ControlKeyStates.LeftAltPressed;
319-
}
320-
321-
if ((key.Modifiers & ConsoleModifiers.Control) != 0)
322-
{
323-
states |= ControlKeyStates.LeftCtrlPressed;
324-
}
325-
326-
if ((key.Modifiers & ConsoleModifiers.Shift) != 0)
327-
{
328-
states |= ControlKeyStates.ShiftPressed;
329-
}
330-
331-
KeyInfo result = new((int)key.Key, key.KeyChar, states, isDown);
332-
if (isDown)
333-
{
334-
_lastKeyDown = result;
335-
}
336-
337-
return result;
338-
}
339226
}
340227
}

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

+29-8
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,13 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args)
875875
}
876876
}
877877

878+
private static readonly ConsoleKeyInfo s_nullKeyInfo = new(
879+
keyChar: ' ',
880+
ConsoleKey.DownArrow,
881+
shift: false,
882+
alt: false,
883+
control: false);
884+
878885
private ConsoleKeyInfo ReadKey(bool intercept)
879886
{
880887
// PSRL doesn't tell us when CtrlC was sent.
@@ -887,20 +894,34 @@ private ConsoleKeyInfo ReadKey(bool intercept)
887894
// PSReadLine's thread waiting on Console.ReadKey to return. Normally we'd just cancel
888895
// this call, but the .NET API ReadKey is not cancellable, and is stuck until we send
889896
// input. This leads to a myriad of problems, but we circumvent them by pretending to
890-
// press a key.
897+
// press a key, thus allowing ReadKey to return, and us to ignore it.
891898
using CancellationTokenRegistration registration = _readKeyCancellationToken.Register(
892-
() => _languageServer?.SendNotification("powerShell/sendKeyPress")
893-
);
894-
_lastKey = ConsoleProxy.SafeReadKey(intercept, _readKeyCancellationToken);
895-
return _lastKey.Value;
899+
() => _languageServer?.SendNotification("powerShell/sendKeyPress"));
900+
901+
// TODO: We may want to allow users of PSES to override this method call.
902+
_lastKey = System.Console.ReadKey(intercept);
903+
904+
// TODO: After fixing PSReadLine so that when canceled it doesn't read a key, we can
905+
// stop using s_nullKeyInfo (which is a down arrow so we don't change the buffer
906+
// content). Without this, the sent key press is translated to an @ symbol.
907+
return _readKeyCancellationToken.IsCancellationRequested ? s_nullKeyInfo : _lastKey.Value;
896908
}
897909

898-
private bool LastKeyWasCtrlC()
910+
internal ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken)
899911
{
900-
return _lastKey.HasValue
901-
&& _lastKey.Value.IsCtrlC();
912+
try
913+
{
914+
_readKeyCancellationToken = cancellationToken;
915+
return ReadKey(intercept);
916+
}
917+
finally
918+
{
919+
_readKeyCancellationToken = CancellationToken.None;
920+
}
902921
}
903922

923+
private bool LastKeyWasCtrlC() => _lastKey.HasValue && _lastKey.Value.IsCtrlC();
924+
904925
private void StopDebugContext()
905926
{
906927
// We are officially stopping the debugger.

0 commit comments

Comments
 (0)