diff --git a/src/libraries/System.Console/src/System/ConsolePal.iOS.cs b/src/libraries/System.Console/src/System/ConsolePal.iOS.cs index 5279df26401d0..19e2bbd7408de 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.iOS.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.iOS.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; +using System.Diagnostics; using System.IO; using System.Text; @@ -8,15 +10,73 @@ namespace System { internal sealed class NSLogStream : ConsoleStream { - public NSLogStream() : base(FileAccess.Write) {} + private readonly StringBuilder _buffer = new StringBuilder(); + private readonly Encoding _encoding; + private readonly Decoder _decoder; + + public NSLogStream(Encoding encoding) : base(FileAccess.Write) + { + _encoding = encoding; + _decoder = _encoding.GetDecoder(); + } public override int Read(Span buffer) => throw Error.GetReadNotSupported(); public override unsafe void Write(ReadOnlySpan buffer) { - fixed (byte* ptr = buffer) + int maxCharCount = _encoding.GetMaxCharCount(buffer.Length); + char[]? pooledBuffer = null; + Span charSpan = maxCharCount <= 512 ? stackalloc char[512] : (pooledBuffer = ArrayPool.Shared.Rent(maxCharCount)); + try + { + int count = _decoder.GetChars(buffer, charSpan, false); + if (count > 0) + { + WriteOrCache(_buffer, charSpan.Slice(0, count)); + } + } + finally + { + if (pooledBuffer != null) + { + ArrayPool.Shared.Return(pooledBuffer); + } + } + } + + private static void WriteOrCache(StringBuilder cache, Span charBuffer) + { + int lastNewLine = charBuffer.LastIndexOf('\n'); + if (lastNewLine != -1) + { + Span lineSpan = charBuffer.Slice(0, lastNewLine); + if (cache.Length > 0) + { + Print(cache.Append(lineSpan).ToString()); + cache.Clear(); + } + else + { + Print(lineSpan); + } + + if (lastNewLine + 1 < charBuffer.Length) + { + cache.Append(charBuffer.Slice(lastNewLine + 1)); + } + + return; + } + + // no newlines found, add the entire buffer to the cache + cache.Append(charBuffer); + + static unsafe void Print(ReadOnlySpan line) { - Interop.Sys.Log(ptr, buffer.Length); + fixed (char* ptr = line) + { + Interop.Sys.Log((byte*)ptr, line.Length * 2); + } } } } @@ -28,9 +88,9 @@ internal static void EnsureConsoleInitialized() public static Stream OpenStandardInput() => throw new PlatformNotSupportedException(); - public static Stream OpenStandardOutput() => new NSLogStream(); + public static Stream OpenStandardOutput() => new NSLogStream(OutputEncoding); - public static Stream OpenStandardError() => new NSLogStream(); + public static Stream OpenStandardError() => new NSLogStream(OutputEncoding); public static Encoding InputEncoding => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Console/tests/ReadAndWrite.cs b/src/libraries/System.Console/tests/ReadAndWrite.cs index 6fd77d6afa991..c671e4f90313f 100644 --- a/src/libraries/System.Console/tests/ReadAndWrite.cs +++ b/src/libraries/System.Console/tests/ReadAndWrite.cs @@ -194,6 +194,25 @@ public static async Task OutWriteAndWriteLineOverloads() } } + [Fact] + [PlatformSpecific(TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS)] + public void TestConsoleWrite() + { + Stream s = new MemoryStream(); + TextWriter w = new StreamWriter(s); + ((StreamWriter)w).AutoFlush = true; + TextReader r = new StreamReader(s); + Console.SetOut(w); + + Console.Write("A"); + Console.Write("B"); + Console.Write("C"); + + s.Position = 0; + string line = r.ReadToEnd(); + Assert.Equal("ABC", line); + } + private static unsafe void ValidateConsoleEncoding(Encoding encoding) { Assert.NotNull(encoding);