Skip to content

Commit a5830c3

Browse files
authored
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
2 parents 3f3d2b5 + 9735d8c commit a5830c3

File tree

11 files changed

+866
-226
lines changed

11 files changed

+866
-226
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
using Tui = Terminal.Gui;
4+
5+
namespace Terminal.Gui.Benchmarks.Text.StringExtensions;
6+
7+
/// <summary>
8+
/// Benchmarks for <see cref="Tui.StringExtensions.ToString(IEnumerable{Rune})"/> performance fine-tuning.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
public class ToStringEnumerable
12+
{
13+
14+
/// <summary>
15+
/// Benchmark for previous implementation.
16+
/// </summary>
17+
[Benchmark]
18+
[ArgumentsSource (nameof (DataSource))]
19+
public string Previous (IEnumerable<Rune> runes, int len)
20+
{
21+
return StringConcatInLoop (runes);
22+
}
23+
24+
/// <summary>
25+
/// Benchmark for current implementation with char buffer and
26+
/// fallback to rune chars appending to StringBuilder.
27+
/// </summary>
28+
/// <param name="runes"></param>
29+
/// <returns></returns>
30+
[Benchmark (Baseline = true)]
31+
[ArgumentsSource (nameof (DataSource))]
32+
public string Current (IEnumerable<Rune> runes, int len)
33+
{
34+
return Tui.StringExtensions.ToString (runes);
35+
}
36+
37+
/// <summary>
38+
/// Previous implementation with string concatenation in a loop.
39+
/// </summary>
40+
private static string StringConcatInLoop (IEnumerable<Rune> runes)
41+
{
42+
var str = string.Empty;
43+
44+
foreach (Rune rune in runes)
45+
{
46+
str += rune.ToString ();
47+
}
48+
49+
return str;
50+
}
51+
52+
public IEnumerable<object []> DataSource ()
53+
{
54+
// Extra length argument as workaround for the summary grouping
55+
// different length collections to same baseline making comparison difficult.
56+
foreach (string text in GetTextData ())
57+
{
58+
Rune [] runes = [..text.EnumerateRunes ()];
59+
yield return [runes, runes.Length];
60+
}
61+
}
62+
63+
private IEnumerable<string> GetTextData ()
64+
{
65+
string textSource =
66+
"""
67+
Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ.
68+
Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé.
69+
Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń.
70+
Óŕćí v́áŕíúś ńát́όq́úé ṕéńát́íb́úś ét́ ḿáǵńíś d́íś ṕáŕt́úŕíéńt́ ḿόńt́éś, ńáśćét́úŕ ŕíd́íćúĺúś ḿúś. F́úśćé át́ éx́ b́ĺáńd́ít́, ćόńv́áĺĺíś q́úáḿ ét́, v́úĺṕút́át́é ĺáćúś.
71+
Śúśṕéńd́íśśé śít́ áḿét́ áŕćú út́ áŕćú f́áúćíb́úś v́áŕíúś. V́ív́áḿúś śít́ áḿét́ ḿáx́íḿúś d́íáḿ. Ńáḿ éx́ ĺéό, ṕh́áŕét́ŕá éú ĺόb́όŕt́íś át́, t́ŕíśt́íq́úé út́ f́éĺíś.
72+
""";
73+
74+
int[] lengths = [1, 10, 100, textSource.Length / 2, textSource.Length];
75+
76+
foreach (int length in lengths)
77+
{
78+
yield return textSource [..length];
79+
}
80+
81+
string textLongerThanStackallocThreshold = string.Concat(Enumerable.Repeat(textSource, 10));
82+
yield return textLongerThanStackallocThreshold;
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
using Tui = Terminal.Gui;
4+
5+
namespace Terminal.Gui.Benchmarks.Text.TextFormatter;
6+
7+
/// <summary>
8+
/// Benchmarks for <see cref="Tui.TextFormatter.RemoveHotKeySpecifier"/> performance fine-tuning.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
[BenchmarkCategory (nameof(Tui.TextFormatter))]
12+
public class RemoveHotKeySpecifier
13+
{
14+
// Omit from summary table.
15+
private static readonly Rune HotkeySpecifier = (Rune)'_';
16+
17+
/// <summary>
18+
/// Benchmark for previous implementation.
19+
/// </summary>
20+
[Benchmark]
21+
[ArgumentsSource (nameof (DataSource))]
22+
public string Previous (string text, int hotPos)
23+
{
24+
return StringConcatLoop (text, hotPos, HotkeySpecifier);
25+
}
26+
27+
/// <summary>
28+
/// Benchmark for current implementation with stackalloc char buffer and fallback to rented array.
29+
/// </summary>
30+
[Benchmark (Baseline = true)]
31+
[ArgumentsSource (nameof (DataSource))]
32+
public string Current (string text, int hotPos)
33+
{
34+
return Tui.TextFormatter.RemoveHotKeySpecifier (text, hotPos, HotkeySpecifier);
35+
}
36+
37+
/// <summary>
38+
/// Previous implementation with string concatenation in a loop.
39+
/// </summary>
40+
public static string StringConcatLoop (string text, int hotPos, Rune hotKeySpecifier)
41+
{
42+
if (string.IsNullOrEmpty (text))
43+
{
44+
return text;
45+
}
46+
47+
// Scan
48+
var start = string.Empty;
49+
var i = 0;
50+
51+
foreach (Rune c in text.EnumerateRunes ())
52+
{
53+
if (c == hotKeySpecifier && i == hotPos)
54+
{
55+
i++;
56+
57+
continue;
58+
}
59+
60+
start += c;
61+
i++;
62+
}
63+
64+
return start;
65+
}
66+
67+
public IEnumerable<object []> DataSource ()
68+
{
69+
string[] texts = [
70+
"",
71+
// Typical scenario.
72+
"_Save file (Ctrl+S)",
73+
// Medium text, hotkey specifier somewhere in the middle.
74+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sed euismod metus. _Phasellus lectus metus, ultricies a commodo quis, facilisis vitae nulla.",
75+
// Long text, hotkey specifier almost at the beginning.
76+
"Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. _Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ. " +
77+
"Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé. " +
78+
"Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń.",
79+
// Long text, hotkey specifier almost at the end.
80+
"Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ. " +
81+
"Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé. " +
82+
"Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. _Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń.",
83+
];
84+
85+
foreach (string text in texts)
86+
{
87+
int hotPos = text.EnumerateRunes()
88+
.Select((r, i) => r == HotkeySpecifier ? i : -1)
89+
.FirstOrDefault(i => i > -1, -1);
90+
91+
yield return [text, hotPos];
92+
}
93+
94+
// Typical scenario but without hotkey and with misleading position.
95+
yield return ["Save file (Ctrl+S)", 3];
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
using Tui = Terminal.Gui;
4+
5+
namespace Terminal.Gui.Benchmarks.Text.TextFormatter;
6+
7+
/// <summary>
8+
/// Benchmarks for <see cref="Tui.TextFormatter.ReplaceCRLFWithSpace"/> performance fine-tuning.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
[BenchmarkCategory (nameof (Tui.TextFormatter))]
12+
public class ReplaceCRLFWithSpace
13+
{
14+
15+
/// <summary>
16+
/// Benchmark for previous implementation.
17+
/// </summary>
18+
[Benchmark]
19+
[ArgumentsSource (nameof (DataSource))]
20+
public string Previous (string str)
21+
{
22+
return ToRuneListReplaceImplementation (str);
23+
}
24+
25+
/// <summary>
26+
/// Benchmark for current implementation.
27+
/// </summary>
28+
[Benchmark (Baseline = true)]
29+
[ArgumentsSource (nameof (DataSource))]
30+
public string Current (string str)
31+
{
32+
return Tui.TextFormatter.ReplaceCRLFWithSpace (str);
33+
}
34+
35+
/// <summary>
36+
/// Previous implementation with intermediate rune list.
37+
/// </summary>
38+
/// <param name="str"></param>
39+
/// <returns></returns>
40+
private static string ToRuneListReplaceImplementation (string str)
41+
{
42+
var runes = str.ToRuneList ();
43+
for (int i = 0; i < runes.Count; i++)
44+
{
45+
switch (runes [i].Value)
46+
{
47+
case '\n':
48+
runes [i] = (Rune)' ';
49+
break;
50+
51+
case '\r':
52+
if ((i + 1) < runes.Count && runes [i + 1].Value == '\n')
53+
{
54+
runes [i] = (Rune)' ';
55+
runes.RemoveAt (i + 1);
56+
i++;
57+
}
58+
else
59+
{
60+
runes [i] = (Rune)' ';
61+
}
62+
break;
63+
}
64+
}
65+
return Tui.StringExtensions.ToString (runes);
66+
}
67+
68+
public IEnumerable<object> DataSource ()
69+
{
70+
// Extreme newline scenario
71+
yield return "E\r\nx\r\nt\r\nr\r\ne\r\nm\r\ne\r\nn\r\ne\r\nw\r\nl\r\ni\r\nn\r\ne\r\ns\r\nc\r\ne\r\nn\r\na\r\nr\r\ni\r\no\r\n";
72+
// Long text with few line endings
73+
yield return
74+
"""
75+
Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ.
76+
Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé.
77+
Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń.
78+
Óŕćí v́áŕíúś ńát́όq́úé ṕéńát́íb́úś ét́ ḿáǵńíś d́íś ṕáŕt́úŕíéńt́ ḿόńt́éś, ńáśćét́úŕ ŕíd́íćúĺúś ḿúś. F́úśćé át́ éx́ b́ĺáńd́ít́, ćόńv́áĺĺíś q́úáḿ ét́, v́úĺṕút́át́é ĺáćúś.
79+
Śúśṕéńd́íśśé śít́ áḿét́ áŕćú út́ áŕćú f́áúćíb́úś v́áŕíúś. V́ív́áḿúś śít́ áḿét́ ḿáx́íḿúś d́íáḿ. Ńáḿ éx́ ĺéό, ṕh́áŕét́ŕá éú ĺόb́όŕt́íś át́, t́ŕíśt́íq́úé út́ f́éĺíś.
80+
"""
81+
// Consistent line endings between systems for more consistent performance evaluation.
82+
.ReplaceLineEndings ("\r\n");
83+
// Long text without line endings
84+
yield return
85+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sed euismod metus. Phasellus lectus metus, ultricies a commodo quis, facilisis vitae nulla. " +
86+
"Curabitur mollis ex nisl, vitae mattis nisl consequat at. Aliquam dolor lectus, tincidunt ac nunc eu, elementum molestie lectus. Donec lacinia eget dolor a scelerisque. " +
87+
"Aenean elementum molestie rhoncus. Duis id ornare lorem. Nam eget porta sapien. Etiam rhoncus dignissim leo, ac suscipit magna finibus eu. Curabitur hendrerit elit erat, sit amet suscipit felis condimentum ut. " +
88+
"Nullam semper tempor mi, nec semper quam fringilla eu. Aenean sit amet pretium augue, in posuere ante. Aenean convallis porttitor purus, et posuere velit dictum eu.";
89+
}
90+
}
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System.Text;
2+
using BenchmarkDotNet.Attributes;
3+
using Tui = Terminal.Gui;
4+
5+
namespace Terminal.Gui.Benchmarks.Text.TextFormatter;
6+
7+
/// <summary>
8+
/// Benchmarks for <see cref="Tui.TextFormatter.StripCRLF"/> performance fine-tuning.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
[BenchmarkCategory (nameof (Tui.TextFormatter))]
12+
public class StripCRLF
13+
{
14+
/// <summary>
15+
/// Benchmark for previous implementation.
16+
/// </summary>
17+
/// <param name="str"></param>
18+
/// <param name="keepNewLine"></param>
19+
/// <returns></returns>
20+
[Benchmark]
21+
[ArgumentsSource (nameof (DataSource))]
22+
public string Previous (string str, bool keepNewLine)
23+
{
24+
return RuneListToString (str, keepNewLine);
25+
}
26+
27+
/// <summary>
28+
/// Benchmark for current implementation with StringBuilder and char span index of search.
29+
/// </summary>
30+
[Benchmark (Baseline = true)]
31+
[ArgumentsSource (nameof (DataSource))]
32+
public string Current (string str, bool keepNewLine)
33+
{
34+
return Tui.TextFormatter.StripCRLF (str, keepNewLine);
35+
}
36+
37+
/// <summary>
38+
/// Previous implementation with intermediate rune list.
39+
/// </summary>
40+
private static string RuneListToString (string str, bool keepNewLine = false)
41+
{
42+
List<Rune> runes = str.ToRuneList ();
43+
44+
for (var i = 0; i < runes.Count; i++)
45+
{
46+
switch ((char)runes [i].Value)
47+
{
48+
case '\n':
49+
if (!keepNewLine)
50+
{
51+
runes.RemoveAt (i);
52+
}
53+
54+
break;
55+
56+
case '\r':
57+
if (i + 1 < runes.Count && runes [i + 1].Value == '\n')
58+
{
59+
runes.RemoveAt (i);
60+
61+
if (!keepNewLine)
62+
{
63+
runes.RemoveAt (i);
64+
}
65+
66+
i++;
67+
}
68+
else
69+
{
70+
if (!keepNewLine)
71+
{
72+
runes.RemoveAt (i);
73+
}
74+
}
75+
76+
break;
77+
}
78+
}
79+
80+
return Tui.StringExtensions.ToString (runes);
81+
}
82+
83+
public IEnumerable<object []> DataSource ()
84+
{
85+
string[] textPermutations = [
86+
// Extreme newline scenario
87+
"E\r\nx\r\nt\r\nr\r\ne\r\nm\r\ne\r\nn\r\ne\r\nw\r\nl\r\ni\r\nn\r\ne\r\ns\r\nc\r\ne\r\nn\r\na\r\nr\r\ni\r\no\r\n",
88+
// Long text with few line endings
89+
"""
90+
Ĺόŕéḿ íṕśúḿ d́όĺόŕ śít́ áḿét́, ćόńśéćt́ét́úŕ ád́íṕíśćíńǵ éĺít́. Ṕŕáéśéńt́ q́úíś ĺúćt́úś éĺít́. Íńt́éǵéŕ út́ áŕćú éǵét́ d́όĺόŕ śćéĺéŕíśq́úé ḿát́t́íś áć ét́ d́íáḿ.
91+
Ṕéĺĺéńt́éśq́úé śéd́ d́áṕíb́úś ḿáśśá, v́éĺ t́ŕíśt́íq́úé d́úí. Śéd́ v́ít́áé ńéq́úé éú v́éĺít́ όŕńáŕé áĺíq́úét́. Út́ q́úíś όŕćí t́éḿṕόŕ, t́éḿṕόŕ t́úŕṕíś íd́, t́éḿṕúś ńéq́úé.
92+
Ṕŕáéśéńt́ śáṕíéń t́úŕṕíś, όŕńáŕé v́éĺ ḿáúŕíś át́, v́áŕíúś śúśćíṕít́ áńt́é. Út́ ṕúĺv́íńáŕ t́úŕṕíś ḿáśśá, q́úíś ćúŕśúś áŕćú f́áúćíb́úś íń.
93+
Óŕćí v́áŕíúś ńát́όq́úé ṕéńát́íb́úś ét́ ḿáǵńíś d́íś ṕáŕt́úŕíéńt́ ḿόńt́éś, ńáśćét́úŕ ŕíd́íćúĺúś ḿúś. F́úśćé át́ éx́ b́ĺáńd́ít́, ćόńv́áĺĺíś q́úáḿ ét́, v́úĺṕút́át́é ĺáćúś.
94+
Śúśṕéńd́íśśé śít́ áḿét́ áŕćú út́ áŕćú f́áúćíb́úś v́áŕíúś. V́ív́áḿúś śít́ áḿét́ ḿáx́íḿúś d́íáḿ. Ńáḿ éx́ ĺéό, ṕh́áŕét́ŕá éú ĺόb́όŕt́íś át́, t́ŕíśt́íq́úé út́ f́éĺíś.
95+
"""
96+
// Consistent line endings between systems for more consistent performance evaluation.
97+
.ReplaceLineEndings ("\r\n"),
98+
// Long text without line endings
99+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sed euismod metus. Phasellus lectus metus, ultricies a commodo quis, facilisis vitae nulla. " +
100+
"Curabitur mollis ex nisl, vitae mattis nisl consequat at. Aliquam dolor lectus, tincidunt ac nunc eu, elementum molestie lectus. Donec lacinia eget dolor a scelerisque. " +
101+
"Aenean elementum molestie rhoncus. Duis id ornare lorem. Nam eget porta sapien. Etiam rhoncus dignissim leo, ac suscipit magna finibus eu. Curabitur hendrerit elit erat, sit amet suscipit felis condimentum ut. " +
102+
"Nullam semper tempor mi, nec semper quam fringilla eu. Aenean sit amet pretium augue, in posuere ante. Aenean convallis porttitor purus, et posuere velit dictum eu."
103+
];
104+
105+
bool[] newLinePermutations = [true, false];
106+
107+
foreach (string text in textPermutations)
108+
{
109+
foreach (bool keepNewLine in newLinePermutations)
110+
{
111+
yield return [text, keepNewLine];
112+
}
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)