diff --git a/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsAppend.cs b/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsAppend.cs new file mode 100644 index 000000000..169ebd740 --- /dev/null +++ b/Benchmarks/ConsoleDrivers/EscSeqUtils/CSI_SetVsAppend.cs @@ -0,0 +1,48 @@ +using System.Text; +using BenchmarkDotNet.Attributes; +using Tui = Terminal.Gui; + +namespace Terminal.Gui.Benchmarks.ConsoleDrivers.EscSeqUtils; + +/// +/// Compares the Set and Append implementations in combination. +/// +/// +/// A bit misleading because *CursorPosition is called very seldom compared to the other operations +/// but they are very similar in performance because they do very similar things. +/// +[MemoryDiagnoser] +[BenchmarkCategory (nameof (Tui.EscSeqUtils))] +// Hide useless empty column from results. +[HideColumns ("stringBuilder")] +public class CSI_SetVsAppend +{ + [Benchmark (Baseline = true)] + [ArgumentsSource (nameof (StringBuilderSource))] + public StringBuilder Set (StringBuilder stringBuilder) + { + stringBuilder.Append (Tui.EscSeqUtils.CSI_SetBackgroundColorRGB (1, 2, 3)); + stringBuilder.Append (Tui.EscSeqUtils.CSI_SetForegroundColorRGB (3, 2, 1)); + stringBuilder.Append (Tui.EscSeqUtils.CSI_SetCursorPosition (4, 2)); + // Clear to prevent out of memory exception from consecutive iterations. + stringBuilder.Clear (); + return stringBuilder; + } + + [Benchmark] + [ArgumentsSource (nameof (StringBuilderSource))] + public StringBuilder Append (StringBuilder stringBuilder) + { + Tui.EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, 1, 2, 3); + Tui.EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, 3, 2, 1); + Tui.EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 4, 2); + // Clear to prevent out of memory exception from consecutive iterations. + stringBuilder.Clear (); + return stringBuilder; + } + + public static IEnumerable StringBuilderSource () + { + return [new StringBuilder ()]; + } +} diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs index 5ce2af939..5bfdf039e 100644 --- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs +++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs @@ -411,25 +411,25 @@ public static class EscSeqUtils { // These control characters are used in the vtXXX emulation. return c switch - { - 'D' => "IND", // Index - 'E' => "NEL", // Next Line - 'H' => "HTS", // Tab Set - 'M' => "RI", // Reverse Index - 'N' => "SS2", // Single Shift Select of G2 Character Set: affects next character only - 'O' => "SS3", // Single Shift Select of G3 Character Set: affects next character only - 'P' => "DCS", // Device Control String - 'V' => "SPA", // Start of Guarded Area - 'W' => "EPA", // End of Guarded Area - 'X' => "SOS", // Start of String - 'Z' => "DECID", // Return Terminal ID Obsolete form of CSI c (DA) - '[' => "CSI", // Control Sequence Introducer - '\\' => "ST", // String Terminator - ']' => "OSC", // Operating System Command - '^' => "PM", // Privacy Message - '_' => "APC", // Application Program Command - _ => string.Empty - }; + { + 'D' => "IND", // Index + 'E' => "NEL", // Next Line + 'H' => "HTS", // Tab Set + 'M' => "RI", // Reverse Index + 'N' => "SS2", // Single Shift Select of G2 Character Set: affects next character only + 'O' => "SS3", // Single Shift Select of G3 Character Set: affects next character only + 'P' => "DCS", // Device Control String + 'V' => "SPA", // Start of Guarded Area + 'W' => "EPA", // End of Guarded Area + 'X' => "SOS", // Start of String + 'Z' => "DECID", // Return Terminal ID Obsolete form of CSI c (DA) + '[' => "CSI", // Control Sequence Introducer + '\\' => "ST", // String Terminator + ']' => "OSC", // Operating System Command + '^' => "PM", // Privacy Message + '_' => "APC", // Application Program Command + _ => string.Empty + }; } @@ -462,46 +462,46 @@ public static class EscSeqUtils } return (terminator, value) switch - { - ('A', _) => ConsoleKey.UpArrow, - ('B', _) => ConsoleKey.DownArrow, - ('C', _) => ConsoleKey.RightArrow, - ('D', _) => ConsoleKey.LeftArrow, - ('E', _) => ConsoleKey.Clear, - ('F', _) => ConsoleKey.End, - ('H', _) => ConsoleKey.Home, - ('P', _) => ConsoleKey.F1, - ('Q', _) => ConsoleKey.F2, - ('R', _) => ConsoleKey.F3, - ('S', _) => ConsoleKey.F4, - ('Z', _) => ConsoleKey.Tab, - ('~', "2") => ConsoleKey.Insert, - ('~', "3") => ConsoleKey.Delete, - ('~', "5") => ConsoleKey.PageUp, - ('~', "6") => ConsoleKey.PageDown, - ('~', "15") => ConsoleKey.F5, - ('~', "17") => ConsoleKey.F6, - ('~', "18") => ConsoleKey.F7, - ('~', "19") => ConsoleKey.F8, - ('~', "20") => ConsoleKey.F9, - ('~', "21") => ConsoleKey.F10, - ('~', "23") => ConsoleKey.F11, - ('~', "24") => ConsoleKey.F12, - // These terminators are used by macOS on a numeric keypad without keys modifiers - ('l', null) => ConsoleKey.Add, - ('m', null) => ConsoleKey.Subtract, - ('p', null) => ConsoleKey.Insert, - ('q', null) => ConsoleKey.End, - ('r', null) => ConsoleKey.DownArrow, - ('s', null) => ConsoleKey.PageDown, - ('t', null) => ConsoleKey.LeftArrow, - ('u', null) => ConsoleKey.Clear, - ('v', null) => ConsoleKey.RightArrow, - ('w', null) => ConsoleKey.Home, - ('x', null) => ConsoleKey.UpArrow, - ('y', null) => ConsoleKey.PageUp, - (_, _) => 0 - }; + { + ('A', _) => ConsoleKey.UpArrow, + ('B', _) => ConsoleKey.DownArrow, + ('C', _) => ConsoleKey.RightArrow, + ('D', _) => ConsoleKey.LeftArrow, + ('E', _) => ConsoleKey.Clear, + ('F', _) => ConsoleKey.End, + ('H', _) => ConsoleKey.Home, + ('P', _) => ConsoleKey.F1, + ('Q', _) => ConsoleKey.F2, + ('R', _) => ConsoleKey.F3, + ('S', _) => ConsoleKey.F4, + ('Z', _) => ConsoleKey.Tab, + ('~', "2") => ConsoleKey.Insert, + ('~', "3") => ConsoleKey.Delete, + ('~', "5") => ConsoleKey.PageUp, + ('~', "6") => ConsoleKey.PageDown, + ('~', "15") => ConsoleKey.F5, + ('~', "17") => ConsoleKey.F6, + ('~', "18") => ConsoleKey.F7, + ('~', "19") => ConsoleKey.F8, + ('~', "20") => ConsoleKey.F9, + ('~', "21") => ConsoleKey.F10, + ('~', "23") => ConsoleKey.F11, + ('~', "24") => ConsoleKey.F12, + // These terminators are used by macOS on a numeric keypad without keys modifiers + ('l', null) => ConsoleKey.Add, + ('m', null) => ConsoleKey.Subtract, + ('p', null) => ConsoleKey.Insert, + ('q', null) => ConsoleKey.End, + ('r', null) => ConsoleKey.DownArrow, + ('s', null) => ConsoleKey.PageDown, + ('t', null) => ConsoleKey.LeftArrow, + ('u', null) => ConsoleKey.Clear, + ('v', null) => ConsoleKey.RightArrow, + ('w', null) => ConsoleKey.Home, + ('x', null) => ConsoleKey.UpArrow, + ('y', null) => ConsoleKey.PageUp, + (_, _) => 0 + }; } /// @@ -512,18 +512,18 @@ public static class EscSeqUtils public static ConsoleModifiers GetConsoleModifiers (string? value) { return value switch - { - "2" => ConsoleModifiers.Shift, - "3" => ConsoleModifiers.Alt, - "4" => ConsoleModifiers.Shift | ConsoleModifiers.Alt, - "5" => ConsoleModifiers.Control, - "6" => ConsoleModifiers.Shift | ConsoleModifiers.Control, - "7" => ConsoleModifiers.Alt | ConsoleModifiers.Control, - "8" => ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, - _ => 0 - }; + { + "2" => ConsoleModifiers.Shift, + "3" => ConsoleModifiers.Alt, + "4" => ConsoleModifiers.Shift | ConsoleModifiers.Alt, + "5" => ConsoleModifiers.Control, + "6" => ConsoleModifiers.Shift | ConsoleModifiers.Control, + "7" => ConsoleModifiers.Alt | ConsoleModifiers.Control, + "8" => ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control, + _ => 0 + }; } - #nullable restore +#nullable restore /// /// Gets all the needed information about an escape sequence. @@ -1675,6 +1675,19 @@ public static class EscSeqUtils /// public static string CSI_SetCursorPosition (int row, int col) { return $"{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 + /// + /// StringBuilder where to append the cursor position sequence. + /// Origin is (1,1). + /// Origin is (1,1). + public static void CSI_AppendCursorPosition (StringBuilder builder, int row, int col) + { + // InterpolatedStringHandler is composed in stack, skipping the string allocation. + builder.Append ($"{CSI}{row};{col}H"); + } + //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 @@ -1785,11 +1798,29 @@ public static class EscSeqUtils /// public static string CSI_SetForegroundColorRGB (int r, int g, int b) { return $"{CSI}38;2;{r};{g};{b}m"; } + /// + /// ESC[38;2;{r};{g};{b}m Append foreground color as RGB to StringBuilder. + /// + public static void CSI_AppendForegroundColorRGB (StringBuilder builder, int r, int g, int b) + { + // InterpolatedStringHandler is composed in stack, skipping the string allocation. + builder.Append ($"{CSI}38;2;{r};{g};{b}m"); + } + /// /// ESC[48;2;{r};{g};{b}m Set background color as RGB. /// public static string CSI_SetBackgroundColorRGB (int r, int g, int b) { return $"{CSI}48;2;{r};{g};{b}m"; } + /// + /// ESC[48;2;{r};{g};{b}m Append background color as RGB to StringBuilder. + /// + public static void CSI_AppendBackgroundColorRGB (StringBuilder builder, int r, int g, int b) + { + // InterpolatedStringHandler is composed in stack, skipping the string allocation. + builder.Append ($"{CSI}48;2;{r};{g};{b}m"); + } + #endregion #region Requests diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 826679051..4e93018cf 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -176,7 +176,7 @@ internal class WindowsConsole _stringBuilder.Clear (); _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0)); + EscSeqUtils.CSI_AppendCursorPosition (_stringBuilder, 0, 0); Attribute? prev = null; @@ -187,8 +187,8 @@ internal class WindowsConsole 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') @@ -710,14 +710,14 @@ internal class WindowsConsole public readonly override string ToString () { return (EventType switch - { - EventType.Focus => FocusEvent.ToString (), - EventType.Key => KeyEvent.ToString (), - EventType.Menu => MenuEvent.ToString (), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - })!; + { + EventType.Focus => FocusEvent.ToString (), + EventType.Key => KeyEvent.ToString (), + EventType.Menu => MenuEvent.ToString (), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + })!; } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 26ca3023b..1f74c0321 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -252,7 +252,7 @@ internal class WindowsDriver : ConsoleDriver else { var sb = new StringBuilder (); - sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); + EscSeqUtils.CSI_AppendCursorPosition (sb, position.Y + 1, position.X + 1); WinConsole?.WriteANSI (sb.ToString ()); }