Skip to content

Commit 59bfa3b

Browse files
Console related classes changes
Change ReadLine method to call out to PowerShellContext. This lets the PowerShellContext determine which ReadLine implementation to use based on available modules. Also includes some changes to the System.Console proxy classes to account for PSReadLine.
1 parent 7ca8b9b commit 59bfa3b

File tree

5 files changed

+407
-38
lines changed

5 files changed

+407
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace Microsoft.PowerShell.EditorServices.Console
7+
{
8+
internal static class ConsoleProxy
9+
{
10+
private static IConsoleOperations s_consoleProxy;
11+
12+
static ConsoleProxy()
13+
{
14+
// Maybe we should just include the RuntimeInformation package for FullCLR?
15+
#if CoreCLR
16+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
17+
{
18+
s_consoleProxy = new WindowsConsoleOperations();
19+
return;
20+
}
21+
22+
s_consoleProxy = new UnixConsoleOperations();
23+
#else
24+
s_consoleProxy = new WindowsConsoleOperations();
25+
#endif
26+
}
27+
28+
public static Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) =>
29+
s_consoleProxy.ReadKeyAsync(cancellationToken);
30+
31+
public static int GetCursorLeft() =>
32+
s_consoleProxy.GetCursorLeft();
33+
34+
public static int GetCursorLeft(CancellationToken cancellationToken) =>
35+
s_consoleProxy.GetCursorLeft(cancellationToken);
36+
37+
public static Task<int> GetCursorLeftAsync() =>
38+
s_consoleProxy.GetCursorLeftAsync();
39+
40+
public static Task<int> GetCursorLeftAsync(CancellationToken cancellationToken) =>
41+
s_consoleProxy.GetCursorLeftAsync(cancellationToken);
42+
43+
public static int GetCursorTop() =>
44+
s_consoleProxy.GetCursorTop();
45+
46+
public static int GetCursorTop(CancellationToken cancellationToken) =>
47+
s_consoleProxy.GetCursorTop(cancellationToken);
48+
49+
public static Task<int> GetCursorTopAsync() =>
50+
s_consoleProxy.GetCursorTopAsync();
51+
52+
public static Task<int> GetCursorTopAsync(CancellationToken cancellationToken) =>
53+
s_consoleProxy.GetCursorTopAsync(cancellationToken);
54+
55+
/// <summary>
56+
/// On Unix platforms this method is sent to PSReadLine as a work around for issues
57+
/// with the System.Console implementation for that platform. Functionally it is the
58+
/// same as System.Console.ReadKey, with the exception that it will not lock the
59+
/// standard input stream.
60+
/// </summary>
61+
/// <param name="intercept">
62+
/// Determines whether to display the pressed key in the console window.
63+
/// true to not display the pressed key; otherwise, false.
64+
/// </param>
65+
/// <returns>
66+
/// An object that describes the ConsoleKey constant and Unicode character, if any,
67+
/// that correspond to the pressed console key. The ConsoleKeyInfo object also describes,
68+
/// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt,
69+
/// or Ctrl modifier keys was pressed simultaneously with the console key.
70+
/// </returns>
71+
internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken)
72+
{
73+
try
74+
{
75+
return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken);
76+
}
77+
catch (OperationCanceledException)
78+
{
79+
return default(ConsoleKeyInfo);
80+
}
81+
}
82+
}
83+
}

src/PowerShellEditorServices/Console/ConsoleReadLine.cs

+29-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Collections.ObjectModel;
77
using System.Linq;
88
using System.Text;
9-
using System.Runtime.InteropServices;
109
using System.Threading;
1110
using System.Threading.Tasks;
1211

@@ -20,27 +19,13 @@ namespace Microsoft.PowerShell.EditorServices.Console
2019
internal class ConsoleReadLine
2120
{
2221
#region Private Field
23-
private static IConsoleOperations s_consoleProxy;
24-
2522
private PowerShellContext powerShellContext;
2623

2724
#endregion
2825

2926
#region Constructors
3027
static ConsoleReadLine()
3128
{
32-
// Maybe we should just include the RuntimeInformation package for FullCLR?
33-
#if CoreCLR
34-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
35-
{
36-
s_consoleProxy = new WindowsConsoleOperations();
37-
return;
38-
}
39-
40-
s_consoleProxy = new UnixConsoleOperations();
41-
#else
42-
s_consoleProxy = new WindowsConsoleOperations();
43-
#endif
4429
}
4530

4631
public ConsoleReadLine(PowerShellContext powerShellContext)
@@ -66,8 +51,8 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
6651
{
6752
SecureString secureString = new SecureString();
6853

69-
int initialPromptRow = Console.CursorTop;
70-
int initialPromptCol = Console.CursorLeft;
54+
int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
55+
int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
7156
int previousInputLength = 0;
7257

7358
Console.TreatControlCAsInput = true;
@@ -114,7 +99,8 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
11499
}
115100
else if (previousInputLength > 0 && currentInputLength < previousInputLength)
116101
{
117-
int row = Console.CursorTop, col = Console.CursorLeft;
102+
int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
103+
int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
118104

119105
// Back up the cursor before clearing the character
120106
col--;
@@ -146,10 +132,30 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
146132

147133
private static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
148134
{
149-
return await s_consoleProxy.ReadKeyAsync(cancellationToken);
135+
return await ConsoleProxy.ReadKeyAsync(cancellationToken);
150136
}
151137

152138
private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancellationToken)
139+
{
140+
return await this.powerShellContext.InvokeReadLine(isCommandLine, cancellationToken);
141+
}
142+
143+
/// <summary>
144+
/// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine.
145+
/// This method should be used when PSReadLine is disabled, either by user settings or
146+
/// unsupported PowerShell versions.
147+
/// </summary>
148+
/// <param name="isCommandLine">
149+
/// Indicates whether ReadLine should act like a command line.
150+
/// </param>
151+
/// <param name="cancellationToken">
152+
/// The cancellation token that will be checked prior to completing the returned task.
153+
/// </param>
154+
/// <returns>
155+
/// A task object representing the asynchronus operation. The Result property on
156+
/// the task object returns the user input string.
157+
/// </returns>
158+
internal async Task<string> InvokeLegacyReadLine(bool isCommandLine, CancellationToken cancellationToken)
153159
{
154160
string inputBeforeCompletion = null;
155161
string inputAfterCompletion = null;
@@ -160,8 +166,8 @@ private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancel
160166

161167
StringBuilder inputLine = new StringBuilder();
162168

163-
int initialCursorCol = Console.CursorLeft;
164-
int initialCursorRow = Console.CursorTop;
169+
int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
170+
int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
165171

166172
int initialWindowLeft = Console.WindowLeft;
167173
int initialWindowTop = Console.WindowTop;
@@ -492,8 +498,8 @@ private int CalculateIndexFromCursor(
492498
int consoleWidth)
493499
{
494500
return
495-
((Console.CursorTop - promptStartRow) * consoleWidth) +
496-
Console.CursorLeft - promptStartCol;
501+
((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) +
502+
ConsoleProxy.GetCursorLeft() - promptStartCol;
497503
}
498504

499505
private void CalculateCursorFromIndex(

src/PowerShellEditorServices/Console/IConsoleOperations.cs

+92
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,97 @@ public interface IConsoleOperations
1818
/// A task that will complete with a result of the key pressed by the user.
1919
/// </returns>
2020
Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken);
21+
22+
/// <summary>
23+
/// Obtains the horizontal position of the console cursor. Use this method
24+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
25+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
26+
/// on Unix platforms.
27+
/// </summary>
28+
/// <returns>The horizontal position of the console cursor.</returns>
29+
int GetCursorLeft();
30+
31+
/// <summary>
32+
/// Obtains the horizontal position of the console cursor. Use this method
33+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
34+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
35+
/// on Unix platforms.
36+
/// </summary>
37+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
38+
/// <returns>The horizontal position of the console cursor.</returns>
39+
int GetCursorLeft(CancellationToken cancellationToken);
40+
41+
/// <summary>
42+
/// Obtains the horizontal position of the console cursor. Use this method
43+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
44+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
45+
/// on Unix platforms.
46+
/// </summary>
47+
/// <returns>
48+
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
49+
/// <see cref="Task{int}.Result" /> property will return the horizontal position
50+
/// of the console cursor.
51+
/// </returns>
52+
Task<int> GetCursorLeftAsync();
53+
54+
/// <summary>
55+
/// Obtains the horizontal position of the console cursor. Use this method
56+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
57+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
58+
/// on Unix platforms.
59+
/// </summary>
60+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
61+
/// <returns>
62+
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
63+
/// <see cref="Task{int}.Result" /> property will return the horizontal position
64+
/// of the console cursor.
65+
/// </returns>
66+
Task<int> GetCursorLeftAsync(CancellationToken cancellationToken);
67+
68+
/// <summary>
69+
/// Obtains the vertical position of the console cursor. Use this method
70+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
71+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
72+
/// on Unix platforms.
73+
/// </summary>
74+
/// <returns>The vertical position of the console cursor.</returns>
75+
int GetCursorTop();
76+
77+
/// <summary>
78+
/// Obtains the vertical position of the console cursor. Use this method
79+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
80+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
81+
/// on Unix platforms.
82+
/// </summary>
83+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
84+
/// <returns>The vertical position of the console cursor.</returns>
85+
int GetCursorTop(CancellationToken cancellationToken);
86+
87+
/// <summary>
88+
/// Obtains the vertical position of the console cursor. Use this method
89+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
90+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
91+
/// on Unix platforms.
92+
/// </summary>
93+
/// <returns>
94+
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
95+
/// <see cref="Task{int}.Result" /> property will return the vertical position
96+
/// of the console cursor.
97+
/// </returns>
98+
Task<int> GetCursorTopAsync();
99+
100+
/// <summary>
101+
/// Obtains the vertical position of the console cursor. Use this method
102+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
103+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
104+
/// on Unix platforms.
105+
/// </summary>
106+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
107+
/// <returns>
108+
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
109+
/// <see cref="Task{int}.Result" /> property will return the vertical position
110+
/// of the console cursor.
111+
/// </returns>
112+
Task<int> GetCursorTopAsync(CancellationToken cancellationToken);
21113
}
22114
}

0 commit comments

Comments
 (0)