diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
index 4022880b77..e78baa96a4 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
@@ -1401,7 +1401,10 @@ public static void Write (char value)
///
public static void Write (char [] buffer)
{
- throw new NotImplementedException ();
+ _buffer [CursorLeft, CursorTop] = (char)0;
+ foreach (var ch in buffer) {
+ _buffer [CursorLeft, CursorTop] += ch;
+ }
}
//
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index a59a863a01..ff85286fd4 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -104,12 +104,12 @@ public override void AddRune (Rune rune)
needMove = false;
}
if (runeWidth < 2 && ccol > 0
- && Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
+ && Rune.ColumnWidth ((Rune)contents [crow, ccol - 1, 0]) > 1) {
contents [crow, ccol - 1, 0] = (int)(uint)' ';
} else if (runeWidth < 2 && ccol <= Clip.Right - 1
- && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
+ && Rune.ColumnWidth ((Rune)contents [crow, ccol, 0]) > 1) {
contents [crow, ccol + 1, 0] = (int)(uint)' ';
contents [crow, ccol + 1, 2] = 1;
@@ -234,7 +234,12 @@ public override void UpdateScreen ()
if (color != redrawColor)
SetColor (color);
- FakeConsole.Write ((char)contents [row, col, 0]);
+ Rune rune = contents [row, col, 0];
+ if (Rune.DecodeSurrogatePair (rune, out char [] spair)) {
+ FakeConsole.Write (spair);
+ } else {
+ FakeConsole.Write ((char)rune);
+ }
contents [row, col, 2] = 0;
}
}
diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs
index 8586a288d3..edd0a8d74d 100644
--- a/Terminal.Gui/Core/ConsoleDriver.cs
+++ b/Terminal.Gui/Core/ConsoleDriver.cs
@@ -681,7 +681,7 @@ public abstract class ConsoleDriver {
/// Column to move the cursor to.
/// Row to move the cursor to.
public abstract void Move (int col, int row);
-
+
///
/// Adds the specified rune to the display at the current cursor position.
///
@@ -696,11 +696,10 @@ public abstract class ConsoleDriver {
///
public static Rune MakePrintable (Rune c)
{
- var controlChars = c & 0xFFFF;
- if (controlChars <= 0x1F || controlChars >= 0X7F && controlChars <= 0x9F) {
+ if (c <= 0x1F || (c >= 0X7F && c <= 0x9F)) {
// ASCII (C0) control characters.
// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
- return new Rune (controlChars + 0x2400);
+ return new Rune (c + 0x2400);
}
return c;
diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs
index 8dbe1d23fc..56458b3e01 100644
--- a/Terminal.Gui/Core/TextFormatter.cs
+++ b/Terminal.Gui/Core/TextFormatter.cs
@@ -293,12 +293,6 @@ internal set {
}
}
- ///
- /// Specifies the mask to apply to the hotkey to tag it as the hotkey. The default value of 0x100000 causes
- /// the underlying Rune to be identified as a "private use" Unicode character.
- /// HotKeyTagMask
- public uint HotKeyTagMask { get; set; } = 0x100000;
-
///
/// Gets the cursor position from . If the is defined, the cursor will be positioned over it.
///
@@ -317,8 +311,9 @@ public List Lines {
get {
// With this check, we protect against subclasses with overrides of Text
if (ustring.IsNullOrEmpty (Text) || Size.IsEmpty) {
- lines = new List ();
- lines.Add (ustring.Empty);
+ lines = new List {
+ ustring.Empty
+ };
NeedsFormat = false;
return lines;
}
@@ -716,7 +711,7 @@ public static ustring Justify (ustring text, int width, char spaceChar = ' ', Te
}
static char [] whitespace = new char [] { ' ', '\t' };
- private int hotKeyPos;
+ private int hotKeyPos = -1;
///
/// Reformats text into lines, applying text alignment and optionally wrapping text to new lines on word boundaries.
@@ -1113,14 +1108,13 @@ public static bool FindHotKey (ustring text, Rune hotKeySpecifier, bool firstUpp
/// The text with the hotkey tagged.
///
/// The returned string will not render correctly without first un-doing the tag. To undo the tag, search for
- /// Runes with a bitmask of otKeyTagMask and remove that bitmask.
///
public ustring ReplaceHotKeyWithTag (ustring text, int hotPos)
{
// Set the high bit
var runes = text.ToRuneList ();
if (Rune.IsLetterOrNumber (runes [hotPos])) {
- runes [hotPos] = new Rune ((uint)runes [hotPos] | HotKeyTagMask);
+ runes [hotPos] = new Rune ((uint)runes [hotPos]);
}
return ustring.Make (runes);
}
@@ -1297,13 +1291,13 @@ public void Draw (Rect bounds, Attribute normalColor, Attribute hotColor, Rect c
rune = runes [idx];
}
}
- if ((rune & HotKeyTagMask) == HotKeyTagMask) {
+ if (idx == HotKeyPos) {
if ((isVertical && textVerticalAlignment == VerticalTextAlignment.Justified) ||
- (!isVertical && textAlignment == TextAlignment.Justified)) {
+ (!isVertical && textAlignment == TextAlignment.Justified)) {
CursorPosition = idx - start;
}
Application.Driver?.SetAttribute (hotColor);
- Application.Driver?.AddRune ((Rune)((uint)rune & ~HotKeyTagMask));
+ Application.Driver?.AddRune (rune);
Application.Driver?.SetAttribute (normalColor);
} else {
Application.Driver?.AddRune (rune);
diff --git a/UnitTests/ConsoleDriverTests.cs b/UnitTests/ConsoleDriverTests.cs
index 7b61d4cf84..11aa2bb4ff 100644
--- a/UnitTests/ConsoleDriverTests.cs
+++ b/UnitTests/ConsoleDriverTests.cs
@@ -614,7 +614,7 @@ public void Write_Do_Not_Change_On_ProcessKey ()
[InlineData (0x0000001F, 0x241F)]
[InlineData (0x0000007F, 0x247F)]
[InlineData (0x0000009F, 0x249F)]
- [InlineData (0x0001001A, 0x241A)]
+ [InlineData (0x0001001A, 0x1001A)]
public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected)
{
var actual = ConsoleDriver.MakePrintable (code);
diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs
index 95ece35f68..bdf99dc6d7 100644
--- a/UnitTests/TestHelpers.cs
+++ b/UnitTests/TestHelpers.cs
@@ -53,7 +53,15 @@ public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelp
for (int r = 0; r < driver.Rows; r++) {
for (int c = 0; c < driver.Cols; c++) {
- sb.Append ((char)contents [r, c, 0]);
+ Rune rune = contents [r, c, 0];
+ if (Rune.DecodeSurrogatePair (rune, out char [] spair)) {
+ sb.Append (spair);
+ } else {
+ sb.Append ((char)rune);
+ }
+ if (Rune.ColumnWidth (rune) > 1) {
+ c++;
+ }
}
sb.AppendLine ();
}
@@ -82,7 +90,7 @@ public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelp
public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
{
- var lines = new List> ();
+ var lines = new List> ();
var sb = new StringBuilder ();
var driver = ((FakeDriver)Application.Driver);
var x = -1;
@@ -93,15 +101,15 @@ public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestO
var contents = driver.Contents;
for (int r = 0; r < driver.Rows; r++) {
- var runes = new List ();
+ var runes = new List ();
for (int c = 0; c < driver.Cols; c++) {
- var rune = (char)contents [r, c, 0];
+ var rune = (Rune)contents [r, c, 0];
if (rune != ' ') {
if (x == -1) {
x = c;
y = r;
for (int i = 0; i < c; i++) {
- runes.InsertRange (i, new List () { ' ' });
+ runes.InsertRange (i, new List () { ' ' });
}
}
if (Rune.ColumnWidth (rune) > 1) {
@@ -130,7 +138,7 @@ public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestO
// Remove trailing whitespace on each line
for (int r = 0; r < lines.Count; r++) {
- List row = lines [r];
+ List row = lines [r];
for (int c = row.Count - 1; c >= 0; c--) {
var rune = row [c];
if (rune != ' ' || (row.Sum (x => Rune.ColumnWidth (x)) == w)) {
@@ -140,9 +148,9 @@ public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestO
}
}
- // Convert char list to string
+ // Convert Rune list to string
for (int r = 0; r < lines.Count; r++) {
- var line = new string (lines [r].ToArray ());
+ var line = NStack.ustring.Make (lines [r]).ToString ();
if (r == lines.Count - 1) {
sb.Append (line);
} else {
diff --git a/UnitTests/TextFormatterTests.cs b/UnitTests/TextFormatterTests.cs
index 5fb6bc98b2..8fef1dff21 100644
--- a/UnitTests/TextFormatterTests.cs
+++ b/UnitTests/TextFormatterTests.cs
@@ -2424,38 +2424,38 @@ public void ReplaceHotKeyWithTag ()
var tf = new TextFormatter ();
ustring text = "test";
int hotPos = 0;
- uint tag = tf.HotKeyTagMask | 't';
+ uint tag = 't';
Assert.Equal (ustring.Make (new Rune [] { tag, 'e', 's', 't' }), tf.ReplaceHotKeyWithTag (text, hotPos));
- tag = tf.HotKeyTagMask | 'e';
+ tag = 'e';
hotPos = 1;
Assert.Equal (ustring.Make (new Rune [] { 't', tag, 's', 't' }), tf.ReplaceHotKeyWithTag (text, hotPos));
var result = tf.ReplaceHotKeyWithTag (text, hotPos);
- Assert.Equal ('e', (uint)(result.ToRunes () [1] & ~tf.HotKeyTagMask));
+ Assert.Equal ('e', (uint)(result.ToRunes () [1]));
text = "Ok";
- tag = 0x100000 | 'O';
+ tag = 'O';
hotPos = 0;
Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
- Assert.Equal ('O', (uint)(result.ToRunes () [0] & ~tf.HotKeyTagMask));
+ Assert.Equal ('O', (uint)(result.ToRunes () [0]));
text = "[◦ Ok ◦]";
text = ustring.Make (new Rune [] { '[', '◦', ' ', 'O', 'k', ' ', '◦', ']' });
var runes = text.ToRuneList ();
Assert.Equal (text.RuneCount, runes.Count);
Assert.Equal (text, ustring.Make (runes));
- tag = tf.HotKeyTagMask | 'O';
+ tag = 'O';
hotPos = 3;
Assert.Equal (ustring.Make (new Rune [] { '[', '◦', ' ', tag, 'k', ' ', '◦', ']' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
- Assert.Equal ('O', (uint)(result.ToRunes () [3] & ~tf.HotKeyTagMask));
+ Assert.Equal ('O', (uint)(result.ToRunes () [3]));
text = "^k";
tag = '^';
hotPos = 0;
Assert.Equal (ustring.Make (new Rune [] { tag, 'k' }), result = tf.ReplaceHotKeyWithTag (text, hotPos));
- Assert.Equal ('^', (uint)(result.ToRunes () [0] & ~tf.HotKeyTagMask));
+ Assert.Equal ('^', (uint)(result.ToRunes () [0]));
}
[Fact]
@@ -4163,5 +4163,101 @@ public void Ustring_Array_Is_Not_Equal_ToRunes_Array_And_String_Array ()
Assert.Equal ("你", ((Rune)usToRunes [9]).ToString ());
Assert.Equal ("你", s [9].ToString ());
}
+
+ [Fact, AutoInitShutdown]
+ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
+ {
+ ustring us = "\U0001d539";
+ Rune r = 0x1d539;
+
+ Assert.Equal ("𝔹", us);
+ Assert.Equal ("𝔹", r.ToString ());
+ Assert.Equal (us, r.ToString ());
+
+ Assert.Equal (2, us.ConsoleWidth);
+ Assert.Equal (2, Rune.ColumnWidth (r));
+
+ var win = new Window (us);
+ var label = new Label (ustring.Make (r));
+ var tf = new TextField (us) { Y = 1, Width = 3 };
+ win.Add (label, tf);
+ var top = Application.Top;
+ top.Add (win);
+
+ Application.Begin (top);
+ ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+ var expected = @"
+┌ 𝔹 ────┐
+│𝔹 │
+│𝔹 │
+└────────┘";
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+ TestHelpers.AssertDriverContentsAre (expected, output);
+
+ var expectedColors = new Attribute [] {
+ // 0
+ Colors.Base.Normal,
+ // 1
+ Colors.Base.Focus,
+ // 2
+ Colors.Base.HotNormal
+ };
+
+ TestHelpers.AssertDriverColorsAre (@"
+0222200000
+0000000000
+0111000000
+0000000000", expectedColors);
+ }
+
+ [Fact, AutoInitShutdown]
+ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
+ {
+ ustring us = "\U0000f900";
+ Rune r = 0xf900;
+
+ Assert.Equal ("豈", us);
+ Assert.Equal ("豈", r.ToString ());
+ Assert.Equal (us, r.ToString ());
+
+ Assert.Equal (2, us.ConsoleWidth);
+ Assert.Equal (2, Rune.ColumnWidth (r));
+
+ var win = new Window (us);
+ var label = new Label (ustring.Make (r));
+ var tf = new TextField (us) { Y = 1, Width = 3 };
+ win.Add (label, tf);
+ var top = Application.Top;
+ top.Add (win);
+
+ Application.Begin (top);
+ ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+ var expected = @"
+┌ 豈 ────┐
+│豈 │
+│豈 │
+└────────┘";
+ TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+ TestHelpers.AssertDriverContentsAre (expected, output);
+
+ var expectedColors = new Attribute [] {
+ // 0
+ Colors.Base.Normal,
+ // 1
+ Colors.Base.Focus,
+ // 2
+ Colors.Base.HotNormal
+ };
+
+ TestHelpers.AssertDriverColorsAre (@"
+0222200000
+0000000000
+0111000000
+0000000000", expectedColors);
+ }
}
}
\ No newline at end of file