diff --git a/Terminal.Gui/Text/StringExtensions.cs b/Terminal.Gui/Text/StringExtensions.cs
index a38bb5799..e379cf3da 100644
--- a/Terminal.Gui/Text/StringExtensions.cs
+++ b/Terminal.Gui/Text/StringExtensions.cs
@@ -1,5 +1,6 @@
๏ปฟ#nullable enable
using System.Buffers;
+using System.Globalization;
namespace Terminal.Gui.Text;
@@ -55,7 +56,29 @@ public static class StringExtensions
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to measure.
///
- public static int GetColumns (this string str) { return str is null ? 0 : str.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 0)); }
+ public static int GetColumns (this string str)
+ {
+ if (string.IsNullOrEmpty (str))
+ {
+ return 0;
+ }
+
+ var total = 0;
+ TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator (str);
+
+ while (enumerator.MoveNext ())
+ {
+ string element = enumerator.GetTextElement ();
+
+ // Get the maximum rune width within this grapheme cluster
+ int width = element
+ .EnumerateRunes ()
+ .Max (r => Math.Max (r.GetColumns (), 0));
+ total += width;
+ }
+
+ return total;
+ }
/// Gets the number of runes in the string.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
diff --git a/Tests/UnitTests/Views/HexViewTests.cs b/Tests/UnitTests/Views/HexViewTests.cs
index aafd21aeb..83d661f6f 100644
--- a/Tests/UnitTests/Views/HexViewTests.cs
+++ b/Tests/UnitTests/Views/HexViewTests.cs
@@ -72,7 +72,6 @@ public class HexViewTests
Application.Top.Dispose ();
Application.ResetState (true);
-
}
[Fact]
@@ -321,6 +320,7 @@ public class HexViewTests
[Fact]
public void PositionChanged_Event ()
{
+ Application.Navigation = new ApplicationNavigation ();
var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
Application.Top = new Toplevel ();
Application.Top.Add (hv);
@@ -346,6 +346,7 @@ public class HexViewTests
[Fact]
public void Source_Sets_Address_To_Zero_If_Greater_Than_Source_Length ()
{
+ Application.Navigation = new ApplicationNavigation ();
var hv = new HexView (LoadStream (null, out _)) { Width = 10, Height = 5 };
Application.Top = new Toplevel ();
Application.Top.Add (hv);
diff --git a/Tests/UnitTestsParallelizable/Text/RuneTests.cs b/Tests/UnitTestsParallelizable/Text/RuneTests.cs
index d37a94016..34214d6ef 100644
--- a/Tests/UnitTestsParallelizable/Text/RuneTests.cs
+++ b/Tests/UnitTestsParallelizable/Text/RuneTests.cs
@@ -7,7 +7,7 @@ namespace UnitTests_Parallelizable.TextTests;
public class RuneTests
{
[Fact]
- public void Cast_To_Char_Durrogate_Pair_Return_UTF16 ()
+ public void Cast_To_Char_Surrogate_Pair_Return_UTF16 ()
{
Assert.NotEqual ("๐น", $"{new Rune (unchecked ((char)0x1d539))}");
Assert.Equal ("ํน", $"{new Rune (unchecked ((char)0x1d539))}");
@@ -65,8 +65,11 @@ public class RuneTests
PrintTextElementCount ("\u0061\u0301", "aฬ", 1, 2, 2, 1);
PrintTextElementCount ("\u0061\u0301", "aฬ", 1, 2, 2, 1);
PrintTextElementCount ("\u0065\u0301", "eฬ", 1, 2, 2, 1);
- PrintTextElementCount ("\U0001f469\U0001f3fd\u200d\U0001f692", "๐ฉ๐ฝโ๐", 6, 4, 7, 1);
+ PrintTextElementCount ("\U0001f469\U0001f3fd\u200d\U0001f692", "๐ฉ๐ฝโ๐", 2, 4, 7, 1);
PrintTextElementCount ("\ud801\udccf", "๐", 1, 1, 2, 1);
+ PrintTextElementCount ("\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466", "๐จโ๐ฉโ๐งโ๐ฆ", 2, 7, 11, 1);
+ PrintTextElementCount ("\U0001f469\u200d\U0001f692", "๐ฉโ๐", 2, 3, 5, 1);
+ PrintTextElementCount ("\u0068\u0069", "hi", 2, 2, 2, 2);
}
[Theory]
@@ -84,8 +87,8 @@ public class RuneTests
2,
1
)] // the letters แแ
ฅแธ join to form the Korean word for "rice:" U+BC95 ๋ฒ (read from top left to bottom right)
- [InlineData ("\U0001F468\u200D\U0001F469\u200D\U0001F467", "๐จโ๐ฉโ๐ง", 8, 6, 8)] // Man, Woman and Girl emoji.
- [InlineData ("\u0915\u093f", "เคเคฟ", 2, 2, 2)] // Hindi เคเคฟ with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I
+ [InlineData ("\U0001F468\u200D\U0001F469\u200D\U0001F467", "๐จโ๐ฉโ๐ง", 8, 2, 8)] // Man, Woman and Girl emoji.
+ [InlineData ("\u0915\u093f", "เคเคฟ", 2, 1, 2)] // Hindi เคเคฟ with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I
[InlineData (
"\u0e4d\u0e32",
"เนเธฒ",
diff --git a/Tests/UnitTestsParallelizable/Text/StringTests.cs b/Tests/UnitTestsParallelizable/Text/StringTests.cs
index c2dc80224..80c52b96e 100644
--- a/Tests/UnitTestsParallelizable/Text/StringTests.cs
+++ b/Tests/UnitTestsParallelizable/Text/StringTests.cs
@@ -33,11 +33,11 @@ public class StringTests
[InlineData ("๐", 2)]
[InlineData ("a๐", 3)]
[InlineData ("๐a", 3)]
- [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ", 8)]
- [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐", 10)]
- [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐a", 11)]
- [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆa๐", 11)]
- [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐จโ๐ฉโ๐ฆโ๐ฆ", 16)]
+ [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ", 2)]
+ [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐", 4)]
+ [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐a", 5)]
+ [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆa๐", 5)]
+ [InlineData ("๐จโ๐ฉโ๐ฆโ๐ฆ๐จโ๐ฉโ๐ฆโ๐ฆ", 4)]
[InlineData ("ๅฑฑ", 2)] // The character for "mountain" in Chinese/Japanese/Korean (ๅฑฑ), Unicode U+5C71
[InlineData ("ๅฑฑ๐", 4)] // The character for "mountain" in Chinese/Japanese/Korean (ๅฑฑ), Unicode U+5C71
//[InlineData ("\ufe20\ufe21", 2)] // Combining Ligature Left Half ๏ธ - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
@@ -57,4 +57,26 @@ public class StringTests
var str = "a";
Assert.Equal (1, str.GetColumns ());
}
+
+ [Fact]
+ public void TestGetColumns_Zero_Width ()
+ {
+ var str = "\u200D";
+ Assert.Equal (0, str.GetColumns ());
+ }
+
+ [Theory]
+ [InlineData (null)]
+ [InlineData ("")]
+ public void TestGetColumns_Does_Not_Throws_With_Null_And_Empty_String (string? text)
+ {
+ if (text is null)
+ {
+ Assert.Equal (0, StringExtensions.GetColumns (text!));
+ }
+ else
+ {
+ Assert.Equal (0, text.GetColumns ());
+ }
+ }
}