From 6f63dca5910aa2832a993ea22f4334aa129ccdd2 Mon Sep 17 00:00:00 2001
From: Tonttu <15074459+TheTonttu@users.noreply.github.com>
Date: Fri, 14 Mar 2025 22:27:22 +0200
Subject: [PATCH] Rewrite TextFormatter.StripCRLF
Uses StringBuilder and char span indexof search to reduce intermediate allocations.
The new implementation behaves slightly different compared to old implementation. In synthetic LFCR scenario it is correctly removed while the old implementation left the CR, which seems like an off-by-one error.
---
Benchmarks/Text/TextFormatter/StripCRLF.cs | 102 ++++++++++++++++++
Terminal.Gui/Terminal.Gui.csproj | 3 +-
Terminal.Gui/Text/TextFormatter.cs | 86 +++++++++------
.../Text/TextFormatterTests.cs | 6 +-
4 files changed, 159 insertions(+), 38 deletions(-)
create mode 100644 Benchmarks/Text/TextFormatter/StripCRLF.cs
diff --git a/Benchmarks/Text/TextFormatter/StripCRLF.cs b/Benchmarks/Text/TextFormatter/StripCRLF.cs
new file mode 100644
index 000000000..069a23d92
--- /dev/null
+++ b/Benchmarks/Text/TextFormatter/StripCRLF.cs
@@ -0,0 +1,102 @@
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using Tui = Terminal.Gui;
+
+namespace Terminal.Gui.Benchmarks.Text.TextFormatter;
+
+[MemoryDiagnoser]
+public class StripCRLF
+{
+ ///
+ /// Benchmark for previous implementation.
+ ///
+ ///
+ ///
+ ///
+ [Benchmark]
+ [ArgumentsSource (nameof (DataSource))]
+ public string Previous (string str, bool keepNewLine)
+ {
+ return RuneListToString (str, keepNewLine);
+ }
+
+ ///
+ /// Benchmark for current implementation with StringBuilder and char span index of search.
+ ///
+ [Benchmark (Baseline = true)]
+ [ArgumentsSource (nameof (DataSource))]
+ public string Current (string str, bool keepNewLine)
+ {
+ return Tui.TextFormatter.StripCRLF (str, keepNewLine);
+ }
+
+ ///
+ /// Previous implementation with intermediate list allocation.
+ ///
+ private static string RuneListToString (string str, bool keepNewLine = false)
+ {
+ List runes = str.ToRuneList ();
+
+ for (var i = 0; i < runes.Count; i++)
+ {
+ switch ((char)runes [i].Value)
+ {
+ case '\n':
+ if (!keepNewLine)
+ {
+ runes.RemoveAt (i);
+ }
+
+ break;
+
+ case '\r':
+ if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
+ {
+ runes.RemoveAt (i);
+
+ if (!keepNewLine)
+ {
+ runes.RemoveAt (i);
+ }
+
+ i++;
+ }
+ else
+ {
+ if (!keepNewLine)
+ {
+ runes.RemoveAt (i);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return StringExtensions.ToString (runes);
+ }
+
+ public IEnumerable