Fix Console.Write on iOS (#56713) (#58559)
authorMaxim Lipnin <v-maxlip@microsoft.com>
Fri, 3 Sep 2021 11:46:00 +0000 (14:46 +0300)
committerGitHub <noreply@github.com>
Fri, 3 Sep 2021 11:46:00 +0000 (13:46 +0200)
Backport of https://github.com/dotnet/runtime/pull/56713

src/libraries/System.Console/src/System/ConsolePal.iOS.cs
src/libraries/System.Console/tests/ReadAndWrite.cs

index 5279df2..a58d4ba 100644 (file)
@@ -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<byte> buffer) => throw Error.GetReadNotSupported();
 
-        public override unsafe void Write(ReadOnlySpan<byte> buffer)
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            int maxCharCount = _encoding.GetMaxCharCount(buffer.Length);
+            char[]? pooledBuffer = null;
+            Span<char> charSpan = maxCharCount <= 512 ? stackalloc char[512] : (pooledBuffer = ArrayPool<char>.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<char>.Shared.Return(pooledBuffer);
+                }
+            }
+        }
+
+        private static void WriteOrCache(StringBuilder cache, Span<char> charBuffer)
         {
-            fixed (byte* ptr = buffer)
+            int lastNewLine = charBuffer.LastIndexOf('\n');
+            if (lastNewLine != -1)
+            {
+                Span<char> 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<char> line)
             {
-                Interop.Sys.Log(ptr, buffer.Length);
+                fixed (char* ptr = line)
+                {
+                    Interop.Sys.Log((byte*)ptr, line.Length * 2);
+                }
             }
         }
     }
@@ -28,9 +88,9 @@ namespace System
 
         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();
 
index 6fd77d6..c671e4f 100644 (file)
@@ -194,6 +194,25 @@ public class ReadAndWrite
         }
     }
 
+    [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);