public static Stream OpenStandardInput()
{
- return new UnixConsoleStream(SafeFileHandleHelper.Open(() => Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDIN_FILENO)), FileAccess.Read);
+ return new UnixConsoleStream(SafeFileHandleHelper.Open(() => Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDIN_FILENO)), FileAccess.Read,
+ useReadLine: !Console.IsInputRedirected);
}
public static Stream OpenStandardOutput()
private static SyncTextReader? s_stdInReader;
- private static SyncTextReader StdInReader
+ internal static SyncTextReader StdInReader
{
get
{
/// <summary>The file descriptor for the opened file.</summary>
private readonly SafeFileHandle _handle;
+ private readonly bool _useReadLine;
+
/// <summary>Initialize the stream.</summary>
/// <param name="handle">The file handle wrapped by this stream.</param>
/// <param name="access">FileAccess.Read or FileAccess.Write.</param>
- internal UnixConsoleStream(SafeFileHandle handle, FileAccess access)
+ /// <param name="useReadLine">Use ReadLine API for reading.</param>
+ internal UnixConsoleStream(SafeFileHandle handle, FileAccess access, bool useReadLine = false)
: base(access)
{
Debug.Assert(handle != null, "Expected non-null console handle");
Debug.Assert(!handle.IsInvalid, "Expected valid console handle");
_handle = handle;
+ _useReadLine = useReadLine;
}
protected override void Dispose(bool disposing)
{
ValidateRead(buffer, offset, count);
- return ConsolePal.Read(_handle, buffer, offset, count);
+ if (_useReadLine)
+ {
+ return ConsolePal.StdInReader.ReadLine(buffer, offset, count);
+ }
+ else
+ {
+ return ConsolePal.Read(_handle, buffer, offset, count);
+ }
}
public override void Write(byte[] buffer, int offset, int count)
private readonly Stack<ConsoleKeyInfo> _tmpKeys = new Stack<ConsoleKeyInfo>(); // temporary working stack; should be empty outside of ReadLine
private readonly Stack<ConsoleKeyInfo> _availableKeys = new Stack<ConsoleKeyInfo>(); // a queue of already processed key infos available for reading
private readonly Encoding _encoding;
+ private Encoder? _bufferReadEncoder;
private char[] _unprocessedBufferToBeRead; // Buffer that might have already been read from stdin but not yet processed.
private const int BytesToBeRead = 1024; // No. of bytes to be read from the stream at a time.
public override string? ReadLine()
{
- return ReadLine(consumeKeys: true);
+ bool isEnter = ReadLineCore(consumeKeys: true);
+ string? line = null;
+ if (isEnter || _readLineSB.Length > 0)
+ {
+ line = _readLineSB.ToString();
+ _readLineSB.Clear();
+ }
+ return line;
}
- private string? ReadLine(bool consumeKeys)
+ public int ReadLine(byte[] buffer, int offset, int count)
+ {
+ if (count == 0)
+ {
+ return 0;
+ }
+
+ // Don't read a new line if there are remaining characters in the StringBuilder.
+ if (_readLineSB.Length == 0)
+ {
+ bool isEnter = ReadLineCore(consumeKeys: true);
+ if (isEnter)
+ {
+ _readLineSB.Append('\n');
+ }
+ }
+
+ // Encode line into buffer.
+ Encoder encoder = _bufferReadEncoder ??= _encoding.GetEncoder();
+ int bytesUsedTotal = 0;
+ int charsUsedTotal = 0;
+ Span<byte> destination = buffer.AsSpan(offset, count);
+ foreach (ReadOnlyMemory<char> chunk in _readLineSB.GetChunks())
+ {
+ encoder.Convert(chunk.Span, destination, flush: false, out int charsUsed, out int bytesUsed, out bool completed);
+ destination = destination.Slice(bytesUsed);
+ bytesUsedTotal += bytesUsed;
+ charsUsedTotal += charsUsed;
+
+ if (charsUsed == 0)
+ {
+ break;
+ }
+ }
+ _readLineSB.Remove(0, charsUsedTotal);
+ return bytesUsedTotal;
+ }
+
+ // Reads a line in _readLineSB when consumeKeys is true,
+ // or _availableKeys when consumeKeys is false.
+ // Returns whether the line was terminated using the Enter key.
+ private bool ReadLineCore(bool consumeKeys)
{
Debug.Assert(_tmpKeys.Count == 0);
- string? readLineStr = null;
+
+ // Don't carry over chars from previous ReadLine call.
+ _readLineSB.Clear();
Interop.Sys.InitializeConsoleBeforeRead();
try
// try to keep this very simple, at least for now.
if (keyInfo.Key == ConsoleKey.Enter)
{
- readLineStr = _readLineSB.ToString();
- _readLineSB.Clear();
if (!previouslyProcessed)
{
Console.WriteLine();
}
- break;
+ return true;
}
else if (IsEol(keyInfo.KeyChar))
{
- string line = _readLineSB.ToString();
- _readLineSB.Clear();
- if (line.Length > 0)
- {
- readLineStr = line;
- }
- break;
+ return false;
}
else if (keyInfo.Key == ConsoleKey.Backspace)
{
}
else if (keyInfo.Key == ConsoleKey.Tab)
{
- _readLineSB.Append(keyInfo.KeyChar);
+ if (consumeKeys)
+ {
+ _readLineSB.Append(keyInfo.KeyChar);
+ }
if (!previouslyProcessed)
{
Console.Write(' ');
}
else if (keyInfo.KeyChar != '\0')
{
- _readLineSB.Append(keyInfo.KeyChar);
+ if (consumeKeys)
+ {
+ _readLineSB.Append(keyInfo.KeyChar);
+ }
if (!previouslyProcessed)
{
Console.Write(keyInfo.KeyChar);
_availableKeys.Push(_tmpKeys.Pop());
}
}
-
- return readLineStr;
}
public override int Read() => ReadOrPeek(peek: false);
// If there aren't any keys in our processed keys stack, read a line to populate it.
if (_availableKeys.Count == 0)
{
- ReadLine(consumeKeys: false);
+ ReadLineCore(consumeKeys: false);
}
// Now if there are keys, use the first.
}
}
}
+
+ public int ReadLine(byte[] buffer, int offset, int count)
+ => Inner.ReadLine(buffer, offset, count);
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
+using System.IO;
using Xunit;
namespace System
}
[ConditionalFact(nameof(ManualTestsEnabled))]
+ public static void ReadLineFromOpenStandardInput()
+ {
+ string expectedLine = "aab";
+
+ // Use Console.ReadLine
+ Console.WriteLine($"Please type 'a' 3 times, press 'Backspace' to erase 1, then type a single 'b' and press 'Enter'.");
+ string result = Console.ReadLine();
+ Assert.Equal(expectedLine, result);
+ AssertUserExpectedResults("the characters you typed properly echoed as you typed");
+
+ // ReadLine from Console.OpenStandardInput
+ Console.WriteLine($"Please type 'a' 3 times, press 'Backspace' to erase 1, then type a single 'b' and press 'Enter'.");
+ using Stream inputStream = Console.OpenStandardInput();
+ using StreamReader reader = new StreamReader(inputStream);
+ result = reader.ReadLine();
+ Assert.Equal(expectedLine, result);
+ AssertUserExpectedResults("the characters you typed properly echoed as you typed");
+ }
+
+ [ConditionalFact(nameof(ManualTestsEnabled))]
public static void ReadLine_BackSpaceCanMoveAccrossWrappedLines()
{
Console.WriteLine("Please press 'a' until it wraps to the next terminal line, then press 'Backspace' until the input is erased, and then type a single 'a' and press 'Enter'.");
}
[ConditionalFact(nameof(ManualTestsEnabled))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/40735", TestPlatforms.Windows)]
public static void InPeek()
{
Console.WriteLine("Please type \"peek\" (without the quotes). You should see it as you type:");
public static IEnumerable<object[]> GetKeyChords()
{
- yield return MkConsoleKeyInfo('\x01', ConsoleKey.A, ConsoleModifiers.Control);
- yield return MkConsoleKeyInfo('\x01', ConsoleKey.A, ConsoleModifiers.Control | ConsoleModifiers.Alt);
+ yield return MkConsoleKeyInfo('\x02', ConsoleKey.B, ConsoleModifiers.Control);
+ yield return MkConsoleKeyInfo(OperatingSystem.IsWindows() ? '\x00' : '\x02', ConsoleKey.B, ConsoleModifiers.Control | ConsoleModifiers.Alt);
yield return MkConsoleKeyInfo('\r', ConsoleKey.Enter, (ConsoleModifiers)0);
-
- if (OperatingSystem.IsWindows())
- {
- // windows will report '\n' as 'Ctrl+Enter', which is typically not picked up by Unix terminals
- yield return MkConsoleKeyInfo('\n', ConsoleKey.Enter, ConsoleModifiers.Control);
- }
- else
- {
- yield return MkConsoleKeyInfo('\n', ConsoleKey.J, ConsoleModifiers.Control);
- }
+ // windows will report '\n' as 'Ctrl+Enter', which is typically not picked up by Unix terminals
+ yield return MkConsoleKeyInfo('\n', OperatingSystem.IsWindows() ? ConsoleKey.Enter : ConsoleKey.J, ConsoleModifiers.Control);
static object[] MkConsoleKeyInfo (char keyChar, ConsoleKey consoleKey, ConsoleModifiers modifiers)
{
}
[ConditionalFact(nameof(ManualTestsEnabled))]
- public static void OpenStandardInput()
- {
- Console.WriteLine("Please type \"console\" (without the quotes). You shouldn't see it as you type:");
- var stream = Console.OpenStandardInput();
- var textReader = new System.IO.StreamReader(stream);
- var result = textReader.ReadLine();
-
- Assert.Equal("console", result);
- AssertUserExpectedResults("\"console\" correctly not echoed as you typed it");
- }
-
- [ConditionalFact(nameof(ManualTestsEnabled))]
public static void ConsoleOutWriteLine()
{
Console.Out.WriteLine("abcdefghijklmnopqrstuvwxyz");
}
}
- AssertUserExpectedResults("the arrow keys move around the screen as expected with no other bad artificts");
+ AssertUserExpectedResults("the arrow keys move around the screen as expected with no other bad artifacts");
}
[ConditionalFact(nameof(ManualTestsEnabled))]