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 ());
}