From 3241b8acbaecd0ded7bb7864bd235777baba478a Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 10:39:38 +0200 Subject: [PATCH 01/10] Change IConsoleOutput.Write(string) overload parameter to ReadOnlySpan Allows the caller more flexibility about choosing a buffer per use case. --- Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs | 2 +- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 5 ++++- Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs | 11 ++++++----- .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 9 +++++---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs index 6004bf5e1b..cbd703ee89 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs @@ -11,7 +11,7 @@ public interface IConsoleOutput : IDisposable /// overload. /// /// - void Write (string text); + void Write (ReadOnlySpan text); /// /// Write the contents of the to the console diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index b7a9ea2806..02f7a51236 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -34,7 +34,10 @@ public NetOutput () } /// - public void Write (string text) { Console.Write (text); } + public void Write (ReadOnlySpan text) + { + Console.Out.Write (text); + } /// public void Write (IOutputBuffer buffer) diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs index 382b01aa87..5e1e78bf37 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs @@ -6,12 +6,13 @@ namespace Terminal.Gui; -internal class WindowsOutput : IConsoleOutput +internal partial class WindowsOutput : IConsoleOutput { - [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool WriteConsole ( + [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool WriteConsole ( nint hConsoleOutput, - string lpbufer, + ReadOnlySpan lpbufer, uint numberOfCharsToWriten, out uint lpNumberOfCharsWritten, nint lpReserved @@ -84,7 +85,7 @@ public WindowsOutput () } } - public void Write (string str) + public void Write (ReadOnlySpan str) { if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero)) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 4e93018cf5..f2fa41e25b 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -6,7 +6,7 @@ namespace Terminal.Gui; -internal class WindowsConsole +internal partial class WindowsConsole { private CancellationTokenSource? _inputReadyCancellationTokenSource; private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); @@ -926,10 +926,11 @@ public static extern bool WriteConsoleOutput ( ref SmallRect lpWriteRegion ); - [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool WriteConsole ( + [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool WriteConsole ( nint hConsoleOutput, - string lpbufer, + ReadOnlySpan lpbufer, uint NumberOfCharsToWriten, out uint lpNumberOfCharsWritten, nint lpReserved From bd396779b3526bbeca76a622ebfb0c8ecf4b1dd8 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 10:40:59 +0200 Subject: [PATCH 02/10] NetOutput: Write StringBuilder directly to the std out text stream --- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index 02f7a51236..a0282972b5 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -188,7 +188,7 @@ public void Write (IOutputBuffer buffer) private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { SetCursorPositionImpl (lastCol, row); - Console.Write (output); + Console.Out.Write (output); output.Clear (); lastCol += outputWidth; outputWidth = 0; From a4c42b448361fb066f9d1507f3bb9d73e63e1425 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 12:40:34 +0200 Subject: [PATCH 03/10] Add EscSeqUtils.CSI_WriteCursorPosition Writes cursor position sequence to text writer without string allocation. --- .../EscSeqUtils/CSI_SetVsWrite.cs | 31 +++++++++++++++++++ .../ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 28 ++++++++++++++++- Tests/UnitTests/Input/EscSeqUtilsTests.cs | 21 +++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsWrite.cs diff --git a/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsWrite.cs b/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsWrite.cs new file mode 100644 index 0000000000..9ed6b328ca --- /dev/null +++ b/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsWrite.cs @@ -0,0 +1,31 @@ +using BenchmarkDotNet.Attributes; +using Tui = Terminal.Gui; + +namespace Terminal.Gui.Benchmarks.ConsoleDrivers.EscSeqUtils; + +[MemoryDiagnoser] +// Hide useless column from results. +[HideColumns ("writer")] +public class CSI_SetVsWrite +{ + [Benchmark (Baseline = true)] + [ArgumentsSource (nameof (TextWriterSource))] + public TextWriter Set (TextWriter writer) + { + writer.Write (Tui.EscSeqUtils.CSI_SetCursorPosition (1, 1)); + return writer; + } + + [Benchmark] + [ArgumentsSource (nameof (TextWriterSource))] + public TextWriter Write (TextWriter writer) + { + Tui.EscSeqUtils.CSI_WriteCursorPosition (writer, 1, 1); + return writer; + } + + public static IEnumerable TextWriterSource () + { + return [StringWriter.Null]; + } +} diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 5bfdf039ee..22c720df40 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -1,5 +1,5 @@ #nullable enable -using Terminal.Gui.ConsoleDrivers; +using System.Globalization; using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui; @@ -1688,6 +1688,32 @@ public static void CSI_AppendCursorPosition (StringBuilder builder, int row, int builder.Append ($"{CSI}{row};{col}H"); } + /// + /// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column + /// of the y line + /// + /// TextWriter where to write the cursor position sequence. + /// Origin is (1,1). + /// Origin is (1,1). + public static void CSI_WriteCursorPosition (TextWriter writer, int row, int col) + { + const int maxInputBufferSize = + // CSI (2) + ';' + 'H' + 4 + + // row + col (2x int sign + int max value) + 2 + 20; + Span buffer = stackalloc char[maxInputBufferSize]; + if (!buffer.TryWrite (CultureInfo.InvariantCulture, $"{CSI}{row};{col}H", out int charsWritten)) + { + string tooLongCursorPositionSequence = $"{CSI}{row};{col}H"; + throw new InvalidOperationException ( + $"{nameof(CSI_WriteCursorPosition)} buffer (len: {buffer.Length}) is too short for cursor position sequence '{tooLongCursorPositionSequence}' (len: {tooLongCursorPositionSequence.Length})."); + } + + ReadOnlySpan cursorPositionSequence = buffer[..charsWritten]; + writer.Write (cursorPositionSequence); + } + //ESC [ ; f - HVP Horizontal Vertical Position* Cursor moves to; coordinate within the viewport, where is the column of the line //ESC [ s - ANSISYSSC Save Cursor – Ansi.sys emulation **With no parameters, performs a save cursor operation like DECSC //ESC [ u - ANSISYSRC Restore Cursor – Ansi.sys emulation **With no parameters, performs a restore cursor operation like DECRC diff --git a/Tests/UnitTests/Input/EscSeqUtilsTests.cs b/Tests/UnitTests/Input/EscSeqUtilsTests.cs index 10dd823237..73ec0998ea 100644 --- a/Tests/UnitTests/Input/EscSeqUtilsTests.cs +++ b/Tests/UnitTests/Input/EscSeqUtilsTests.cs @@ -1,4 +1,4 @@ -using JetBrains.Annotations; +using System.Text; using UnitTests; // ReSharper disable HeuristicUnreachableCode @@ -685,7 +685,7 @@ public void DecodeEscSeq_Multiple_Tests () top.Add (view); Application.Begin (top); - Application.RaiseMouseEvent (new() { Position = new (0, 0), Flags = 0 }); + Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 }); ClearAll (); @@ -741,7 +741,7 @@ public void DecodeEscSeq_Multiple_Tests () // set Application.WantContinuousButtonPressedView to null view.WantContinuousButtonPressed = false; - Application.RaiseMouseEvent (new() { Position = new (0, 0), Flags = 0 }); + Application.RaiseMouseEvent (new () { Position = new (0, 0), Flags = 0 }); Application.RequestStop (); } @@ -1548,6 +1548,21 @@ public void InsertArray_Tests (string toInsert, string current, int? index, stri Assert.Equal (result, cki); } + [Theory] + [InlineData (0, 0, $"{EscSeqUtils.CSI}0;0H")] + [InlineData (int.MaxValue, int.MaxValue, $"{EscSeqUtils.CSI}2147483647;2147483647H")] + [InlineData (int.MinValue, int.MinValue, $"{EscSeqUtils.CSI}-2147483648;-2147483648H")] + public void CSI_WriteCursorPosition_ReturnsCorrectEscSeq (int row, int col, string expected) + { + StringBuilder builder = new(); + using StringWriter writer = new(builder); + + EscSeqUtils.CSI_WriteCursorPosition (writer, row, col); + + string actual = builder.ToString(); + Assert.Equal (expected, actual); + } + private void ClearAll () { EscSeqRequests.Clear (); From 946aa6ddddfbf80a01ec85af16ad656e87182a93 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 12:41:02 +0200 Subject: [PATCH 04/10] NetOutput: Skip cursor position escape sequence string allocation --- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index a0282972b5..9a950cdd27 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -225,7 +225,7 @@ private bool SetCursorPositionImpl (int col, int row) // + 1 is needed because non-Windows is based on 1 instead of 0 and // Console.CursorTop/CursorLeft isn't reliable. - Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1)); + EscSeqUtils.CSI_WriteCursorPosition (Console.Out, row + 1, col + 1); return true; } From cbbb69e1ab3eed5a12e37f99349226696aa20c86 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 12:51:23 +0200 Subject: [PATCH 05/10] Replace CSI_(Enable|Disable)MouseEvents static properties with readonly fields Changed for the sake of consistency with rest of the EscSegutils fields rather than performance. Also prevents bugs from accidentally setting the properties. --- Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 22c720df40..adc463cc2c 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -154,13 +154,13 @@ public enum ClearScreenOptions /// /// Control sequence for disabling mouse events. /// - public static string CSI_DisableMouseEvents { get; set; } = + public static readonly string CSI_DisableMouseEvents = CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse; /// /// Control sequence for enabling mouse events. /// - public static string CSI_EnableMouseEvents { get; set; } = + public static readonly string CSI_EnableMouseEvents = CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse; /// From 92d6c8d09dda260c8f1aa35a9c34c4ddb0022473 Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 13:29:04 +0200 Subject: [PATCH 06/10] Use EscSeqUtils.CSI_Append(Foreground|Background)ColorRGB in v2 drivers --- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 28 +++++++++---------- .../ConsoleDrivers/V2/WindowsOutput.cs | 6 ++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index 9a950cdd27..2b041fd7da 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -118,21 +118,19 @@ public void Write (IOutputBuffer buffer) { redrawAttr = attr; - output.Append ( - EscSeqUtils.CSI_SetForegroundColorRGB ( - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ) - ); - - output.Append ( - EscSeqUtils.CSI_SetBackgroundColorRGB ( - attr.Background.R, - attr.Background.G, - attr.Background.B - ) - ); + EscSeqUtils.CSI_AppendForegroundColorRGB ( + output, + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ); + + EscSeqUtils.CSI_AppendBackgroundColorRGB ( + output, + attr.Background.R, + attr.Background.G, + attr.Background.B + ); } outputWidth++; diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs index 5e1e78bf37..f9eaa69501 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs @@ -217,7 +217,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord stringBuilder.Clear (); stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); + EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0); Attribute? prev = null; @@ -228,8 +228,8 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord if (attr != prev) { prev = attr; - stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B)); - stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B)); + EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B); + EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B); } if (info.Char != '\x1b') From 16a6aba8b867bbd50f2e81d2d446a00c1106f26d Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:47:27 +0200 Subject: [PATCH 07/10] WindowsOutput SetCursorVisibility: Remove intermediate string builder --- Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs index f9eaa69501..957ae7ef21 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs @@ -298,9 +298,10 @@ public Size GetWindowSize () /// public void SetCursorVisibility (CursorVisibility visibility) { - var sb = new StringBuilder (); - sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); - Write (sb.ToString ()); + string cursorVisibilitySequence = visibility != CursorVisibility.Invisible + ? EscSeqUtils.CSI_ShowCursor + : EscSeqUtils.CSI_HideCursor; + Write (cursorVisibilitySequence); } private Point _lastCursorPosition; From 8b604a87bab9bed3e7bf98c2ca16ad2e97f4bd6a Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 15:46:21 +0200 Subject: [PATCH 08/10] WindowsOutput.WriteToConsole: Use rented array as intermediate write buffer The large intermediate string builder remains a challenge. :) --- .../ConsoleDrivers/V2/WindowsOutput.cs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs index 957ae7ef21..ba111c130f 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Buffers; using System.ComponentModel; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; @@ -184,7 +185,6 @@ public void Write (IOutputBuffer buffer) public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) { - var stringBuilder = new StringBuilder (); //Debug.WriteLine ("WriteToConsole"); @@ -214,7 +214,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord } else { - stringBuilder.Clear (); + StringBuilder stringBuilder = new(); stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0); @@ -248,14 +248,20 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); stringBuilder.Append (EscSeqUtils.CSI_HideCursor); - var s = stringBuilder.ToString (); + // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed. + char [] rentedWriteArray = ArrayPool.Shared.Rent (minimumLength: stringBuilder.Length); + try + { + Span writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length); + stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length); - // TODO: requires extensive testing if we go down this route - // If console output has changed - //if (s != _lastWrite) - //{ - // supply console with the new content - result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero); + // Supply console with the new content. + result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); + } + finally + { + ArrayPool.Shared.Return (rentedWriteArray); + } foreach (SixelToRender sixel in Application.Sixel) { From be6e1f8e21826bae1307e5fa05b071a5cf9e85ba Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 19:23:29 +0200 Subject: [PATCH 09/10] NetOutput: Console.Out for the sake of consistency Also might have missed one of the Console.Out.Write(StringBuilder) calls... --- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index 2b041fd7da..54cc568bd1 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -163,7 +163,7 @@ public void Write (IOutputBuffer buffer) if (output.Length > 0) { SetCursorPositionImpl (lastCol, row); - Console.Write (output); + Console.Out.Write (output); } } @@ -172,7 +172,7 @@ public void Write (IOutputBuffer buffer) if (!string.IsNullOrWhiteSpace (s.SixelData)) { SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y); - Console.Write (s.SixelData); + Console.Out.Write (s.SixelData); } } From 6727ac5ef8e58ed188e387794a21449ea1e745ef Mon Sep 17 00:00:00 2001 From: Tonttu <15074459+TheTonttu@users.noreply.github.com> Date: Sun, 9 Mar 2025 21:08:09 +0200 Subject: [PATCH 10/10] Avoid Rune.ToString() in NetOutput.Write(IOutputBuffer) --- Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs index 54cc568bd1..69defb82e5 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs @@ -60,6 +60,9 @@ public void Write (IOutputBuffer buffer) CursorVisibility? savedVisibility = _cachedCursorVisibility; SetCursorVisibility (CursorVisibility.Invisible); + const int maxCharsPerRune = 2; + Span runeBuffer = stackalloc char[maxCharsPerRune]; + for (int row = top; row < rows; row++) { if (Console.WindowHeight < 1) @@ -134,8 +137,12 @@ public void Write (IOutputBuffer buffer) } outputWidth++; + + // Avoid Rune.ToString() by appending the rune chars. Rune rune = buffer.Contents [row, col].Rune; - output.Append (rune); + int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer); + ReadOnlySpan runeChars = runeBuffer[..runeCharsWritten]; + output.Append (runeChars); if (buffer.Contents [row, col].CombiningMarks.Count > 0) {