From ff538e31e77ecae1b04f9f32e397ba89b3606571 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 14 Nov 2025 20:53:08 +0000 Subject: [PATCH] Adjust CharMap scenario with graphemes --- Terminal.Gui/Views/CharMap/CharMap.cs | 81 +++++++++------------------ 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index 02308ac1e..c74d84029 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -148,10 +148,8 @@ public class CharMap : View, IDesignable break; } - var rune = new Rune (cp); - Span utf16 = new char [2]; - rune.EncodeToUtf16 (utf16); - UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]); + string grapheme = new Rune (cp).ToString (); + UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (cp); if (cat == ShowUnicodeCategory.Value) { anyVisible = true; @@ -685,7 +683,7 @@ public class CharMap : View, IDesignable // Don't render out-of-range scalars if (scalar > MAX_CODE_POINT) { - AddRune (' '); + AddStr (" "); if (visibleRow == selectedRowIndex && col == selectedCol) { SetAttributeForRole (VisualRole.Normal); @@ -693,22 +691,20 @@ public class CharMap : View, IDesignable continue; } - var rune = (Rune)'?'; + string grapheme = "?"; if (Rune.IsValid (scalar)) { - rune = new (scalar); + grapheme = new Rune (scalar).ToString (); } - int width = rune.GetColumns (); + int width = grapheme.GetColumns (); // Compute visibility based on ShowUnicodeCategory bool isVisible = Rune.IsValid (scalar); if (isVisible && ShowUnicodeCategory.HasValue) { - Span filterUtf16 = new char [2]; - rune.EncodeToUtf16 (filterUtf16); - UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (filterUtf16 [0]); + UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory (scalar); isVisible = cat == ShowUnicodeCategory.Value; } @@ -717,11 +713,11 @@ public class CharMap : View, IDesignable // Glyph row if (isVisible) { - RenderRune (rune, width); + RenderGrapheme (grapheme, width, scalar); } else { - AddRune (' '); + AddStr (" "); } } else @@ -736,7 +732,7 @@ public class CharMap : View, IDesignable } else { - AddRune (' '); + AddStr (" "); } } @@ -750,21 +746,18 @@ public class CharMap : View, IDesignable return true; - void RenderRune (Rune rune, int width) + void RenderGrapheme (string grapheme, int width, int scalar) { // Get the UnicodeCategory - Span utf16 = new char [2]; - int charCount = rune.EncodeToUtf16 (utf16); - // Get the bidi class for the first code unit // For most bidi characters, the first code unit is sufficient - UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (utf16 [0]); + UnicodeCategory category = CharUnicodeInfo.GetUnicodeCategory (scalar); switch (category) { case UnicodeCategory.OtherNotAssigned: SetAttributeForRole (VisualRole.Highlight); - AddRune (Rune.ReplacementChar); + AddStr (Rune.ReplacementChar.ToString ()); SetAttributeForRole (VisualRole.Normal); break; @@ -773,7 +766,7 @@ public class CharMap : View, IDesignable // These report width of 0 and don't render on their own. case UnicodeCategory.Format: SetAttributeForRole (VisualRole.Highlight); - AddRune ('F'); + AddStr ("F"); SetAttributeForRole (VisualRole.Normal); break; @@ -786,38 +779,8 @@ public class CharMap : View, IDesignable case UnicodeCategory.EnclosingMark: if (width > 0) { - AddRune (rune); + AddStr (grapheme); } - else - { - if (rune.IsCombiningMark ()) - { - // This is a hack to work around the fact that combining marks - // a) can't be rendered on their own - // b) that don't normalize are not properly supported in - // any known terminal (esp Windows/AtlasEngine). - // See Issue #2616 - var sb = new StringBuilder (); - sb.Append ('a'); - sb.Append (rune); - - // Try normalizing after combining with 'a'. If it normalizes, at least - // it'll show on the 'a'. If not, just show the replacement char. - string normal = sb.ToString ().Normalize (NormalizationForm.FormC); - - if (normal.Length == 1) - { - AddRune ((Rune)normal [0]); - } - else - { - SetAttributeForRole (VisualRole.Highlight); - AddRune ('M'); - SetAttributeForRole (VisualRole.Normal); - } - } - } - break; // These report width of 0, but render as 1 @@ -825,20 +788,28 @@ public class CharMap : View, IDesignable case UnicodeCategory.LineSeparator: case UnicodeCategory.ParagraphSeparator: case UnicodeCategory.Surrogate: - AddRune (rune); + AddStr (grapheme); break; + case UnicodeCategory.OtherLetter: + AddStr (grapheme); + if (width == 0) + { + AddStr (" "); + } + + break; default: // Draw the rune if (width > 0) { - AddRune (rune); + AddStr (grapheme); } else { - throw new InvalidOperationException ($"The Rune \"{rune}\" (U+{rune.Value:x6}) has zero width and no special-case UnicodeCategory logic applies."); + throw new InvalidOperationException ($"The Rune \"{grapheme}\" (U+{Rune.GetRuneAt (grapheme, 0).Value:x6}) has zero width and no special-case UnicodeCategory logic applies."); } break;