diff --git a/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs b/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs
index c5971598c8..a9c5c877e4 100644
--- a/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs
+++ b/Terminal.Gui/ViewBase/View.Drawing.Primitives.cs
@@ -106,16 +106,17 @@ public void DrawHotString (string text, Attribute hotColor, Attribute normalColo
Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
SetAttribute (normalColor);
- foreach (Rune rune in text.EnumerateRunes ())
+ foreach (string grapheme in GraphemeHelper.GetGraphemes (text))
{
- if (rune == new Rune (hotkeySpec.Value))
+ // The hotkey specifier is always a single simple character (e.g., '_')
+ if (grapheme.Length == 1 && new Rune (grapheme [0]) == new Rune (hotkeySpec.Value))
{
SetAttribute (hotColor);
continue;
}
- AddRune (rune);
+ AddStr (grapheme);
SetAttribute (normalColor);
}
}
diff --git a/Tests/UnitTestsParallelizable/ViewBase/Draw/DrawHotStringTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Draw/DrawHotStringTests.cs
new file mode 100644
index 0000000000..781f9371f1
--- /dev/null
+++ b/Tests/UnitTestsParallelizable/ViewBase/Draw/DrawHotStringTests.cs
@@ -0,0 +1,61 @@
+using UnitTests;
+
+namespace ViewBaseTests.Drawing;
+
+public class DrawHotStringTests (ITestOutputHelper output) : TestDriverBase
+{
+ ///
+ /// Verifies that iterates by grapheme cluster,
+ /// not by rune. When iterating by rune, combining marks (e.g., acute accent U+0301) are sent individually
+ /// via AddRune and fail to compose with their base character. Iterating by grapheme and using AddStr
+ /// ensures the combining mark stays attached to its base.
+ ///
+ [Theory]
+ [InlineData ("e\u0301", "é")] // e + combining acute → é
+ [InlineData ("n\u0303o", "ño")] // n + combining tilde + o → ño
+ [InlineData ("Les Mise\u0301rables", "Les Misérables")] // combining acute inside word
+ public void DrawHotString_CombiningMarks (string input, string expectedRendered)
+ {
+ // setup
+ IDriver driver = CreateTestDriver ();
+ driver.Clip = new Region (driver.Screen);
+ var view = new View
+ {
+ Driver = driver,
+ Width = 20, Height = 1
+ };
+
+ // execute
+ view.DrawHotString (input, Attribute.Default, Attribute.Default);
+
+ // verify
+ DriverAssert.AssertDriverContentsWithFrameAre (expectedRendered, output, driver);
+ }
+
+ ///
+ /// Verifies that correctly handles
+ /// the hotkey specifier when combined with grapheme clusters. The hotkey specifier ('_') should
+ /// switch to hot color, and subsequent grapheme clusters (including combining marks) should render
+ /// correctly.
+ ///
+ [Fact]
+ public void DrawHotString_HotkeyWithCombiningMarks ()
+ {
+ // setup — "_Re\u0301sume\u0301" → hotkey on 'R', combining accents compose correctly
+ IDriver driver = CreateTestDriver ();
+ driver.Clip = new Region (driver.Screen);
+ var view = new View
+ {
+ Driver = driver,
+ Width = 20, Height = 1
+ };
+
+ // execute
+ var hotColor = new Attribute (Color.Red, Color.Black);
+ var normalColor = new Attribute (Color.White, Color.Black);
+ view.DrawHotString ("_Re\u0301sume\u0301", hotColor, normalColor);
+
+ // verify — the rendered text should show "Résumé" (without the underscore)
+ DriverAssert.AssertDriverContentsWithFrameAre ("Résumé", output, driver);
+ }
+}