Skip to content

poc: Custom ReadLine #311

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions CommandDotNet.TestTools/TestConsole.TestConsoleReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using CommandDotNet.Prompts;
using CommandDotNet.Rendering;

namespace CommandDotNet.TestTools
{
public partial class TestConsole
{
private class TestConsoleReader : IConsoleReader
{
private readonly IConsoleWriter _standardOut;
private readonly Func<string?>? _onReadLine;
private readonly Func<ConsoleKeyInfo>? _onReadKey;

public TestConsoleReader(IConsoleWriter standardOut, Func<string?>? onReadLine, Func<ConsoleKeyInfo>? onReadKey)
{
_standardOut = standardOut ?? throw new ArgumentNullException(nameof(standardOut));
_onReadLine = onReadLine;
_onReadKey = onReadKey;
}

public bool KeyAvailable { get; }
public bool NumberLock { get; }
public bool CapsLock { get; }
public bool TreatControlCAsInput { get; set; }

/// <summary>
/// Read a key from the input
/// </summary>
/// <param name="intercept">When true, the key is not displayed in the output</param>
/// <returns></returns>
public ConsoleKeyInfo ReadKey(bool intercept)
{
ConsoleKeyInfo consoleKeyInfo;

do
{
consoleKeyInfo = _onReadKey?.Invoke()
?? new ConsoleKeyInfo('\u0000', ConsoleKey.Enter, false, false, false);

// mimic System.Console which does not interrupt during ReadKey
// and does not return Ctrl+C unless TreatControlCAsInput == true.
} while (!TreatControlCAsInput && consoleKeyInfo.IsCtrlC());

if (!intercept)
{
if (consoleKeyInfo.Key == ConsoleKey.Enter)
{
_standardOut.WriteLine("");
}
else
{
_standardOut.Write(consoleKeyInfo.KeyChar.ToString());
}
}
return consoleKeyInfo;
}

public string? ReadLine()
{
return _onReadLine?.Invoke();
}
}
}
}
38 changes: 38 additions & 0 deletions CommandDotNet.TestTools/TestConsole.TestConsoleWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.IO;
using System.Text;
using CommandDotNet.Rendering;

namespace CommandDotNet.TestTools
{
public partial class TestConsole
{
private class TestConsoleWriter : TextWriter, IConsoleWriter
{
private readonly TestConsoleWriter? _inner;
private readonly StringBuilder _stringBuilder = new StringBuilder();

public TestConsoleWriter(
TestConsoleWriter? inner = null)
{
_inner = inner;
}

public override void Write(char value)
{
_inner?.Write(value);
if (value == '\b' && _stringBuilder.Length > 0)
{
_stringBuilder.Length = _stringBuilder.Length - 1;
}
else
{
_stringBuilder.Append(value);
}
}

public override Encoding Encoding { get; } = Encoding.Unicode;

public override string ToString() => _stringBuilder.ToString();
}
}
}
190 changes: 101 additions & 89 deletions CommandDotNet.TestTools/TestConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using CommandDotNet.Extensions;
using CommandDotNet.Logging;
using CommandDotNet.Prompts;
using CommandDotNet.Rendering;

namespace CommandDotNet.TestTools
Expand All @@ -21,18 +17,32 @@ namespace CommandDotNet.TestTools
/// - provide piped input <br/>
/// - handle ReadLine and ReadToEnd requests
/// </summary>
public class TestConsole : IConsole
public partial class TestConsole : IConsole
{
private static ILog Log = LogProvider.GetCurrentClassLogger();
private readonly Action<TestConsole> _onClear;
private static readonly ILog Log = LogProvider.GetCurrentClassLogger();

private readonly Func<TestConsole, ConsoleKeyInfo>? _onReadKey;
public static class Default
{
public static ConsoleColor BackgroundColor { get; set; } = ConsoleColor.Black;
public static ConsoleColor ForegroundColor { get; set; } = ConsoleColor.White;

public static int WindowLeft { get; set; } = 0;
public static int WindowTop { get; set; } = 0;
public static int WindowWidth { get; set; } = 120;
public static int WindowHeight { get; set; } = 30;

public static int BufferWidth { get; set; } = WindowWidth;
public static int BufferHeight { get; set; } = WindowHeight;
}

public TestConsole(
Func<TestConsole, string?>? onReadLine = null,
IEnumerable<string>? pipedInput = null,
Func<TestConsole, ConsoleKeyInfo>? onReadKey = null)
Func<TestConsole, ConsoleKeyInfo>? onReadKey = null,
Action<TestConsole> onClear = null)
{
_onReadKey = onReadKey;
_onClear = onClear;
IsInputRedirected = pipedInput != null;

if (pipedInput != null)
Expand All @@ -54,29 +64,53 @@ public TestConsole(
}
}

var all = new StandardStreamWriter();
var all = new TestConsoleWriter();
All = all;
Out = new StandardStreamWriter(all);
Error = new StandardStreamWriter(all);
In = new StandardStreamReader(
Out = new TestConsoleWriter(all);
Error = new TestConsoleWriter(all);
In = new TestConsoleReader(Out,
() =>
{
var input = onReadLine?.Invoke(this);
Log.Info($"IConsole.ReadLine > {input}");
return input;
},
onReadKey switch
{
{ } _ => () => onReadKey!.Invoke(this),
_ => null
});
}
public string Title { get; set; }

/// <summary>
/// This is the combined output for <see cref="Error"/> and <see cref="Out"/> in the order the lines were output.
/// </summary>
public IStandardStreamWriter All { get; }
#region IStandardError

public IStandardStreamWriter Out { get; }
public IConsoleWriter Error { get; }

public IStandardStreamWriter Error { get; }
public bool IsErrorRedirected { get; } = false;

public string OutLastLine => Out.ToString().SplitIntoLines().Last();
#endregion

#region IStandardOut

public IConsoleWriter Out { get; }

public bool IsOutputRedirected { get; } = false;

#endregion

#region IStandardIn

public IConsoleReader In { get; }

public bool IsInputRedirected { get; }

#endregion

/// <summary>
/// This is the combined output for <see cref="Error"/> and <see cref="Out"/> in the order the lines were output.
/// </summary>
public IConsoleWriter All { get; }

/// <summary>
/// The combination of <see cref="Console.Error"/> and <see cref="Console.Out"/>
Expand All @@ -95,95 +129,73 @@ public TestConsole(
/// </summary>
public string ErrorText() => Error.ToString();

public bool IsOutputRedirected { get; } = false;

public bool IsErrorRedirected { get; } = false;
public string OutLastLine => Out.ToString().SplitIntoLines().Last();

public IStandardStreamReader In { get; }
#region IConsoleColor

public bool IsInputRedirected { get; }
public ConsoleColor BackgroundColor { get; set; } = Default.BackgroundColor;
public ConsoleColor ForegroundColor { get; set; } = Default.ForegroundColor;

/// <summary>
/// Read a key from the input
/// </summary>
/// <param name="intercept">When true, the key is not displayed in the output</param>
/// <returns></returns>
public ConsoleKeyInfo ReadKey(bool intercept)
public void ResetColor()
{
ConsoleKeyInfo consoleKeyInfo;
BackgroundColor = Default.BackgroundColor;
ForegroundColor = Default.ForegroundColor;
}

do
{
consoleKeyInfo = _onReadKey?.Invoke(this)
?? new ConsoleKeyInfo('\u0000', ConsoleKey.Enter, false, false, false);
#endregion

// mimic System.Console which does not interrupt during ReadKey
// and does not return Ctrl+C unless TreatControlCAsInput == true.
} while (!TreatControlCAsInput && consoleKeyInfo.IsCtrlC());
#region IConsoleBuffer

if (!intercept)
{
if (consoleKeyInfo.Key == ConsoleKey.Enter)
{
Out.WriteLine("");
}
else
{
Out.Write(consoleKeyInfo.KeyChar.ToString());
}
}
return consoleKeyInfo;
}
public int BufferWidth { get; set; } = Default.BufferWidth;
public int BufferHeight { get; set; } = Default.BufferHeight;

public bool TreatControlCAsInput { get; set; }
public void SetBufferSize(int width, int height)
{
BufferWidth = width;
BufferHeight = height;
}

private class StandardStreamReader : IStandardStreamReader
public void Clear()
{
private readonly Func<string?>? _onReadLine;
_onClear?.Invoke(this);
}

public StandardStreamReader(Func<string?>? onReadLine)
{
_onReadLine = onReadLine;
}
#endregion

public string? ReadLine()
{
return _onReadLine?.Invoke();
}
#region IConsoleWindow

public string? ReadToEnd()
{
return _onReadLine?.EnumeratePipedInput().ToCsv(Environment.NewLine);
}
public int WindowLeft { get; set; } = Default.WindowLeft;
public int WindowTop { get; set; } = Default.WindowTop;
public int WindowWidth { get; set; } = Default.WindowWidth;
public int WindowHeight { get; set; } = Default.WindowHeight;

public void SetWindowPosition(int left, int top)
{
WindowLeft = left;
WindowTop = top;
}

private class StandardStreamWriter : TextWriter, IStandardStreamWriter
public void SetWindowSize(int width, int height)
{
private readonly StandardStreamWriter? _inner;
private readonly StringBuilder _stringBuilder = new StringBuilder();
WindowWidth = width;
WindowHeight = height;
}

public StandardStreamWriter(
StandardStreamWriter? inner = null)
{
_inner = inner;
}
#endregion

public override void Write(char value)
{
_inner?.Write(value);
if (value == '\b' && _stringBuilder.Length > 0)
{
_stringBuilder.Length = _stringBuilder.Length - 1;
}
else
{
_stringBuilder.Append(value);
}
}
#region IConsoleCursor

public override Encoding Encoding { get; } = Encoding.Unicode;
public int CursorSize { get; set; }
public bool CursorVisible { get; set; }
public int CursorLeft { get; set; }
public int CursorTop { get; set; }

public override string ToString() => _stringBuilder.ToString();
public void SetCursorPosition(int left, int top)
{
CursorLeft = left;
CursorTop = top;
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace CommandDotNet
{
public static class ConsoleExtensions
public static class ConsoleReadLineExtensions
{
public static void Write(this IConsole console, object? value = null)
{
Expand Down
Loading