Skip to content

Commit

Permalink
Console related classes changes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
SeeminglyScience committed Jun 2, 2018
1 parent 7ca8b9b commit 59bfa3b
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 38 deletions.
83 changes: 83 additions & 0 deletions src/PowerShellEditorServices/Console/ConsoleProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.PowerShell.EditorServices.Console
{
internal static class ConsoleProxy
{
private static IConsoleOperations s_consoleProxy;

static ConsoleProxy()
{
// Maybe we should just include the RuntimeInformation package for FullCLR?
#if CoreCLR
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
s_consoleProxy = new WindowsConsoleOperations();
return;
}

s_consoleProxy = new UnixConsoleOperations();
#else
s_consoleProxy = new WindowsConsoleOperations();
#endif
}

public static Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) =>
s_consoleProxy.ReadKeyAsync(cancellationToken);

public static int GetCursorLeft() =>
s_consoleProxy.GetCursorLeft();

public static int GetCursorLeft(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorLeft(cancellationToken);

public static Task<int> GetCursorLeftAsync() =>
s_consoleProxy.GetCursorLeftAsync();

public static Task<int> GetCursorLeftAsync(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorLeftAsync(cancellationToken);

public static int GetCursorTop() =>
s_consoleProxy.GetCursorTop();

public static int GetCursorTop(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorTop(cancellationToken);

public static Task<int> GetCursorTopAsync() =>
s_consoleProxy.GetCursorTopAsync();

public static Task<int> GetCursorTopAsync(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorTopAsync(cancellationToken);

/// <summary>
/// On Unix platforms this method is sent to PSReadLine as a work around for issues
/// with the System.Console implementation for that platform. Functionally it is the
/// same as System.Console.ReadKey, with the exception that it will not lock the
/// standard input stream.
/// </summary>
/// <param name="intercept">
/// Determines whether to display the pressed key in the console window.
/// true to not display the pressed key; otherwise, false.
/// </param>
/// <returns>
/// An object that describes the ConsoleKey constant and Unicode character, if any,
/// that correspond to the pressed console key. The ConsoleKeyInfo object also describes,
/// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt,
/// or Ctrl modifier keys was pressed simultaneously with the console key.
/// </returns>
internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken)
{
try
{
return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken);
}
catch (OperationCanceledException)
{
return default(ConsoleKeyInfo);
}
}
}
}
52 changes: 29 additions & 23 deletions src/PowerShellEditorServices/Console/ConsoleReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -20,27 +19,13 @@ namespace Microsoft.PowerShell.EditorServices.Console
internal class ConsoleReadLine
{
#region Private Field
private static IConsoleOperations s_consoleProxy;

private PowerShellContext powerShellContext;

#endregion

#region Constructors
static ConsoleReadLine()
{
// Maybe we should just include the RuntimeInformation package for FullCLR?
#if CoreCLR
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
s_consoleProxy = new WindowsConsoleOperations();
return;
}

s_consoleProxy = new UnixConsoleOperations();
#else
s_consoleProxy = new WindowsConsoleOperations();
#endif
}

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

int initialPromptRow = Console.CursorTop;
int initialPromptCol = Console.CursorLeft;
int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
int previousInputLength = 0;

Console.TreatControlCAsInput = true;
Expand Down Expand Up @@ -114,7 +99,8 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
}
else if (previousInputLength > 0 && currentInputLength < previousInputLength)
{
int row = Console.CursorTop, col = Console.CursorLeft;
int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);

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

private static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
{
return await s_consoleProxy.ReadKeyAsync(cancellationToken);
return await ConsoleProxy.ReadKeyAsync(cancellationToken);
}

private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancellationToken)
{
return await this.powerShellContext.InvokeReadLine(isCommandLine, cancellationToken);
}

/// <summary>
/// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine.
/// This method should be used when PSReadLine is disabled, either by user settings or
/// unsupported PowerShell versions.
/// </summary>
/// <param name="isCommandLine">
/// Indicates whether ReadLine should act like a command line.
/// </param>
/// <param name="cancellationToken">
/// The cancellation token that will be checked prior to completing the returned task.
/// </param>
/// <returns>
/// A task object representing the asynchronus operation. The Result property on
/// the task object returns the user input string.
/// </returns>
internal async Task<string> InvokeLegacyReadLine(bool isCommandLine, CancellationToken cancellationToken)
{
string inputBeforeCompletion = null;
string inputAfterCompletion = null;
Expand All @@ -160,8 +166,8 @@ private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancel

StringBuilder inputLine = new StringBuilder();

int initialCursorCol = Console.CursorLeft;
int initialCursorRow = Console.CursorTop;
int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);

int initialWindowLeft = Console.WindowLeft;
int initialWindowTop = Console.WindowTop;
Expand Down Expand Up @@ -492,8 +498,8 @@ private int CalculateIndexFromCursor(
int consoleWidth)
{
return
((Console.CursorTop - promptStartRow) * consoleWidth) +
Console.CursorLeft - promptStartCol;
((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) +
ConsoleProxy.GetCursorLeft() - promptStartCol;
}

private void CalculateCursorFromIndex(
Expand Down
92 changes: 92 additions & 0 deletions src/PowerShellEditorServices/Console/IConsoleOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,97 @@ public interface IConsoleOperations
/// A task that will complete with a result of the key pressed by the user.
/// </returns>
Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken);

/// <summary>
/// Obtains the horizontal position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <returns>The horizontal position of the console cursor.</returns>
int GetCursorLeft();

/// <summary>
/// Obtains the horizontal position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
/// <returns>The horizontal position of the console cursor.</returns>
int GetCursorLeft(CancellationToken cancellationToken);

/// <summary>
/// Obtains the horizontal position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <returns>
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
/// <see cref="Task{int}.Result" /> property will return the horizontal position
/// of the console cursor.
/// </returns>
Task<int> GetCursorLeftAsync();

/// <summary>
/// Obtains the horizontal position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
/// <returns>
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
/// <see cref="Task{int}.Result" /> property will return the horizontal position
/// of the console cursor.
/// </returns>
Task<int> GetCursorLeftAsync(CancellationToken cancellationToken);

/// <summary>
/// Obtains the vertical position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <returns>The vertical position of the console cursor.</returns>
int GetCursorTop();

/// <summary>
/// Obtains the vertical position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
/// <returns>The vertical position of the console cursor.</returns>
int GetCursorTop(CancellationToken cancellationToken);

/// <summary>
/// Obtains the vertical position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <returns>
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
/// <see cref="Task{int}.Result" /> property will return the vertical position
/// of the console cursor.
/// </returns>
Task<int> GetCursorTopAsync();

/// <summary>
/// Obtains the vertical position of the console cursor. Use this method
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(CancellationToken)" />
/// on Unix platforms.
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
/// <returns>
/// A <see cref="Task{int}" /> representing the asynchronous operation. The
/// <see cref="Task{int}.Result" /> property will return the vertical position
/// of the console cursor.
/// </returns>
Task<int> GetCursorTopAsync(CancellationToken cancellationToken);
}
}
Loading

0 comments on commit 59bfa3b

Please sign in to comment.