diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs
index 098601bfdbe92e..017c55e8c8a3ec 100644
--- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs
+++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs
@@ -43,7 +43,8 @@ internal static class ConsolePal
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()
@@ -68,7 +69,7 @@ public static Encoding OutputEncoding
private static SyncTextReader? s_stdInReader;
- private static SyncTextReader StdInReader
+ internal static SyncTextReader StdInReader
{
get
{
@@ -1410,15 +1411,19 @@ private sealed class UnixConsoleStream : ConsoleStream
/// The file descriptor for the opened file.
private readonly SafeFileHandle _handle;
+ private readonly bool _useReadLine;
+
/// Initialize the stream.
/// The file handle wrapped by this stream.
/// FileAccess.Read or FileAccess.Write.
- internal UnixConsoleStream(SafeFileHandle handle, FileAccess access)
+ /// Use ReadLine API for reading.
+ 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)
@@ -1434,7 +1439,14 @@ public override int Read(byte[] buffer, int offset, int count)
{
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)
diff --git a/src/libraries/System.Console/src/System/IO/StdInReader.cs b/src/libraries/System.Console/src/System/IO/StdInReader.cs
index 99ebbd34c61496..6396176fbc905a 100644
--- a/src/libraries/System.Console/src/System/IO/StdInReader.cs
+++ b/src/libraries/System.Console/src/System/IO/StdInReader.cs
@@ -22,6 +22,7 @@ internal sealed class StdInReader : TextReader
private readonly Stack _tmpKeys = new Stack(); // temporary working stack; should be empty outside of ReadLine
private readonly Stack _availableKeys = new Stack(); // 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.
@@ -79,13 +80,63 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize)
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 destination = buffer.AsSpan(offset, count);
+ foreach (ReadOnlyMemory 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
@@ -110,23 +161,15 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize)
// 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)
{
@@ -166,7 +209,10 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize)
}
else if (keyInfo.Key == ConsoleKey.Tab)
{
- _readLineSB.Append(keyInfo.KeyChar);
+ if (consumeKeys)
+ {
+ _readLineSB.Append(keyInfo.KeyChar);
+ }
if (!previouslyProcessed)
{
Console.Write(' ');
@@ -182,7 +228,10 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize)
}
else if (keyInfo.KeyChar != '\0')
{
- _readLineSB.Append(keyInfo.KeyChar);
+ if (consumeKeys)
+ {
+ _readLineSB.Append(keyInfo.KeyChar);
+ }
if (!previouslyProcessed)
{
Console.Write(keyInfo.KeyChar);
@@ -200,8 +249,6 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize)
_availableKeys.Push(_tmpKeys.Pop());
}
}
-
- return readLineStr;
}
public override int Read() => ReadOrPeek(peek: false);
@@ -213,7 +260,7 @@ private int ReadOrPeek(bool peek)
// 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.
diff --git a/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs b/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs
index f98499db4387a7..9e68d74c1b0023 100644
--- a/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs
+++ b/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs
@@ -39,5 +39,8 @@ public bool KeyAvailable
}
}
}
+
+ public int ReadLine(byte[] buffer, int offset, int count)
+ => Inner.ReadLine(buffer, offset, count);
}
}
diff --git a/src/libraries/System.Console/tests/ManualTests/ManualTests.cs b/src/libraries/System.Console/tests/ManualTests/ManualTests.cs
index 9bbec557dbb732..5928b7a34132b8 100644
--- a/src/libraries/System.Console/tests/ManualTests/ManualTests.cs
+++ b/src/libraries/System.Console/tests/ManualTests/ManualTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using System.IO;
using Xunit;
namespace System
@@ -23,6 +24,26 @@ public static void ReadLine(bool consoleIn)
AssertUserExpectedResults("the characters you typed properly echoed as you typed");
}
+ [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()
{
@@ -36,6 +57,7 @@ public static void ReadLine_BackSpaceCanMoveAccrossWrappedLines()
}
[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:");
@@ -91,19 +113,11 @@ static string RenderKeyChord(ConsoleKeyInfo key)
public static IEnumerable