diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index 79198349f1..315b2bbc06 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -67,6 +67,28 @@ public static string ToString (IConsoleDriver? driver)
{
sb.Append (sp);
}
+ else if (contents [r, c].CombiningMarks is { Count: > 0 })
+ {
+ // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+ // compatible with the driver architecture. Any CMs (except in the first col)
+ // are correctly combined with the base char, but are ALSO treated as 1 column
+ // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[ ]`.
+ //
+ // For now, we just ignore the list of CMs.
+ string combine = rune.ToString ();
+ string? normalized = null;
+
+ foreach (Rune combMark in contents [r, c].CombiningMarks)
+ {
+ combine += combMark;
+ normalized = combine.Normalize (NormalizationForm.FormC);
+ }
+
+ foreach (Rune enumerateRune in normalized!.EnumerateRunes ())
+ {
+ sb.Append (enumerateRune);
+ }
+ }
else
{
sb.Append ((char)rune.Value);
@@ -76,11 +98,6 @@ public static string ToString (IConsoleDriver? driver)
{
c++;
}
-
- // See Issue #2616
- //foreach (var combMark in contents [r, c].CombiningMarks) {
- // sb.Append ((char)combMark.Value);
- //}
}
sb.AppendLine ();
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index 2ba8ba6018..462ff09c16 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -1,6 +1,7 @@
#nullable enable
using System.Diagnostics;
+using System.Globalization;
namespace Terminal.Gui;
@@ -54,6 +55,10 @@ public abstract class ConsoleDriver : IConsoleDriver
// This is in addition to the dirty flag on each cell.
internal bool []? _dirtyLines;
+ // Represent the necessary space character that must be added at
+ // the end of the line due the use of Format (Cf) unicode category
+ internal int []? _lineColsOffset;
+
// QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
/// Gets the location and size of the terminal screen.
public Rectangle Screen => new (0, 0, Cols, Rows);
@@ -169,6 +174,13 @@ public virtual int Rows
/// The topmost row in the terminal.
public virtual int Top { get; set; } = 0;
+ ///
+ /// Gets or sets whenever is ignored or not.
+ ///
+ public static bool IgnoreIsCombiningMark { get; set; }
+
+ private Point? _lastValidAddRuneCell = null;
+
/// Adds the specified rune to the display at the current cursor position.
///
///
@@ -185,15 +197,15 @@ public virtual int Rows
/// Rune to add.
public void AddRune (Rune rune)
{
- int runeWidth = -1;
- bool validLocation = IsValidLocation (rune, Col, Row);
-
if (Contents is null)
{
return;
}
+ int runeWidth = -1;
+ bool validLocation = IsValidLocation (rune, Col, Row);
Rectangle clipRect = Clip!.GetBounds ();
+ bool wasAddedToCombiningMarks = false;
if (validLocation)
{
@@ -202,7 +214,7 @@ public void AddRune (Rune rune)
lock (Contents)
{
- if (runeWidth == 0 && rune.IsCombiningMark ())
+ if (Rune.GetUnicodeCategory (rune) == UnicodeCategory.Format || (runeWidth == 0 && rune.IsCombiningMark ()))
{
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
// compatible with the driver architecture. Any CMs (except in the first col)
@@ -212,42 +224,27 @@ public void AddRune (Rune rune)
// Until this is addressed (see Issue #), we do our best by
// a) Attempting to normalize any CM with the base char to it's left
// b) Ignoring any CMs that don't normalize
- if (Col > 0)
+ if (Col > 0
+ && _lastValidAddRuneCell is { }
+ && _lastValidAddRuneCell.Value.Y == Row
+ && Contents [Row, _lastValidAddRuneCell.Value.X].IsDirty)
{
- if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
+ if (Contents [Row, _lastValidAddRuneCell.Value.X].CombiningMarks is null)
{
- // Just add this mark to the list
- Contents [Row, Col - 1].CombiningMarks.Add (rune);
-
- // Ignore. Don't move to next column (let the driver figure out what to do).
+ Contents [Row, _lastValidAddRuneCell.Value.X].CombiningMarks = [];
}
- else
- {
- // Attempt to normalize the cell to our left combined with this mark
- string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
- // Normalize to Form C (Canonical Composition)
- string normalized = combined.Normalize (NormalizationForm.FormC);
-
- if (normalized.Length == 1)
- {
- // It normalized! We can just set the Cell to the left with the
- // normalized codepoint
- Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+ // Just add this mark to the list
+ Contents [Row, _lastValidAddRuneCell.Value.X].CombiningMarks.Add (rune);
+ wasAddedToCombiningMarks = true;
- // Ignore. Don't move to next column because we're already there
- }
- else
- {
- // It didn't normalize. Add it to the Cell to left's CM list
- Contents [Row, Col - 1].CombiningMarks.Add (rune);
-
- // Ignore. Don't move to next column (let the driver figure out what to do).
- }
+ if (runeWidth == 0 && Rune.GetUnicodeCategory (rune) == UnicodeCategory.Format)
+ {
+ _lineColsOffset! [Row] += Contents [Row, _lastValidAddRuneCell.Value.X].Rune.GetColumns ();
}
- Contents [Row, Col - 1].Attribute = CurrentAttribute;
- Contents [Row, Col - 1].IsDirty = true;
+ Contents [Row, _lastValidAddRuneCell.Value.X].Attribute = CurrentAttribute;
+ Contents [Row, _lastValidAddRuneCell.Value.X].IsDirty = true;
}
else
{
@@ -255,7 +252,11 @@ public void AddRune (Rune rune)
Contents [Row, Col].Rune = rune;
Contents [Row, Col].Attribute = CurrentAttribute;
Contents [Row, Col].IsDirty = true;
- Col++;
+
+ if (runeWidth == 0)
+ {
+ Col++;
+ }
}
}
else
@@ -325,7 +326,12 @@ public void AddRune (Rune rune)
}
}
- if (runeWidth is < 0 or > 0)
+ if (!IgnoreIsCombiningMark && !wasAddedToCombiningMarks)
+ {
+ _lastValidAddRuneCell = new (Col, Row);
+ }
+
+ if (runeWidth is < 0 or > 0 && !wasAddedToCombiningMarks)
{
Col++;
}
@@ -339,13 +345,10 @@ public void AddRune (Rune rune)
lock (Contents!)
{
// This is a double-width character, and we are not at the end of the line.
- // Col now points to the second column of the character. Ensure it doesn't
- // Get rendered.
- Contents [Row, Col].IsDirty = false;
+ // Col now points to the second column of the character. Ensure it does get
+ // rendered to allow the driver to handle it in its own way.
+ Contents [Row, Col].IsDirty = true;
Contents [Row, Col].Attribute = CurrentAttribute;
-
- // TODO: Determine if we should wipe this out (for now now)
- //Contents [Row, Col].Rune = (Rune)' ';
}
}
@@ -415,12 +418,14 @@ public void FillRect (Rectangle rect, Rune rune = default)
public void ClearContents ()
{
Contents = new Cell [Rows, Cols];
+ _lastValidAddRuneCell = null;
//CONCURRENCY: Unsynchronized access to Clip isn't safe.
// TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
Clip = new (Screen);
_dirtyLines = new bool [Rows];
+ _lineColsOffset = new int [Rows];
lock (Contents)
{
@@ -499,11 +504,8 @@ public bool IsValidLocation (Rune rune, int col, int row)
{
return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
}
- else
- {
- return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
- }
+ return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
}
/// Called when the terminal size changes. Fires the event.
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 48bcb713f5..51b257e7b6 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -318,9 +318,23 @@ public override bool UpdateScreen ()
outputWidth++;
Rune rune = Contents [row, col].Rune;
+
+ if (rune == Rune.ReplacementChar)
+ {
+ continue;
+ }
+
output.Append (rune);
- if (Contents [row, col].CombiningMarks.Count > 0)
+ if (rune.IsCombiningMark ())
+ {
+ if (rune.GetColumns () == 0)
+ {
+ output.Append (' ');
+ }
+ }
+
+ if (Contents [row, col].CombiningMarks is { Count: > 0 })
{
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
// compatible with the driver architecture. Any CMs (except in the first col)
@@ -328,21 +342,32 @@ public override bool UpdateScreen ()
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
//
// For now, we just ignore the list of CMs.
- //foreach (var combMark in Contents [row, col].CombiningMarks) {
- // output.Append (combMark);
- //}
- // WriteToConsole (output, ref lastCol, row, ref outputWidth);
- }
- else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
- {
- WriteToConsole (output, ref lastCol, row, ref outputWidth);
- SetCursorPosition (col - 1, row);
+ foreach (var combMark in Contents [row, col].CombiningMarks)
+ {
+ output.Append (combMark);
+ }
+ //WriteToConsole (output, ref lastCol, row, ref outputWidth);
}
+ //else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+ //{
+ // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ // SetCursorPosition (col - 1, row);
+ //}
Contents [row, col].IsDirty = false;
}
}
+ if (_lineColsOffset! [row] > 0)
+ {
+ for (var i = 0; i < _lineColsOffset [row]; i++)
+ {
+ output.Append (' ');
+ }
+
+ _lineColsOffset! [row] = 0;
+ }
+
if (output.Length > 0)
{
SetCursorPosition (lastCol, row);
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
index 2ede00a000..140e748555 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
@@ -166,9 +166,23 @@ public override bool UpdateScreen ()
outputWidth++;
Rune rune = Contents [row, col].Rune;
+
+ if (rune == Rune.ReplacementChar)
+ {
+ continue;
+ }
+
output.Append (rune);
- if (Contents [row, col].CombiningMarks.Count > 0)
+ if (rune.IsCombiningMark ())
+ {
+ if (rune.GetColumns () == 0)
+ {
+ output.Append (' ');
+ }
+ }
+
+ if (Contents [row, col].CombiningMarks is { Count: > 0 })
{
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
// compatible with the driver architecture. Any CMs (except in the first col)
@@ -176,21 +190,32 @@ public override bool UpdateScreen ()
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
//
// For now, we just ignore the list of CMs.
- //foreach (var combMark in Contents [row, col].CombiningMarks) {
- // output.Append (combMark);
- //}
- // WriteToConsole (output, ref lastCol, row, ref outputWidth);
- }
- else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
- {
- WriteToConsole (output, ref lastCol, row, ref outputWidth);
- SetCursorPosition (col - 1, row);
+ foreach (var combMark in Contents [row, col].CombiningMarks)
+ {
+ output.Append (combMark);
+ }
+ //WriteToConsole (output, ref lastCol, row, ref outputWidth);
}
+ //else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+ //{
+ // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ // SetCursorPosition (col - 1, row);
+ //}
Contents [row, col].IsDirty = false;
}
}
+ if (_lineColsOffset! [row] > 0)
+ {
+ for (var i = 0; i < _lineColsOffset [row]; i++)
+ {
+ output.Append (' ');
+ }
+
+ _lineColsOffset! [row] = 0;
+ }
+
if (output.Length > 0)
{
SetCursorPosition (lastCol, row);
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
index 37be0766f6..dab711ca67 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
@@ -139,6 +139,11 @@ private void NetInputHandler ()
_waitForProbe.Reset ();
}
+ if (_inputHandlerTokenSource.IsCancellationRequested)
+ {
+ return;
+ }
+
ProcessInputQueue ();
}
catch (OperationCanceledException)
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 2c6689737f..abe5a373af 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -10,7 +10,6 @@ internal class WindowsConsole
{
private CancellationTokenSource? _inputReadyCancellationTokenSource;
private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ());
- internal WindowsMainLoop? _mainLoop;
public const int STD_OUTPUT_HANDLE = -11;
public const int STD_INPUT_HANDLE = -10;
@@ -197,6 +196,22 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
{
_stringBuilder.Append (info.Char);
}
+
+ if (Rune.IsValid (info.Char) && ((Rune)info.Char).IsCombiningMark ())
+ {
+ if (((Rune)info.Char).GetColumns () == 0)
+ {
+ _stringBuilder.Append (' ');
+ }
+ }
+
+ if (info.CombiningMarks is { })
+ {
+ foreach (var combMark in info.CombiningMarks)
+ {
+ _stringBuilder.Append (combMark);
+ }
+ }
}
else
{
@@ -785,6 +800,7 @@ public struct ExtendedCharInfo
public char Char { get; set; }
public Attribute Attribute { get; set; }
public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
+ internal List? CombiningMarks;
public ExtendedCharInfo (char character, Attribute attribute)
{
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index 02d8a03ff7..cf534d09fa 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -335,6 +335,20 @@ public override bool UpdateScreen ()
_outputBuffer [position].Empty = false;
+ if (Contents [row, col].CombiningMarks is { Count: > 0 })
+ {
+ _outputBuffer [position].CombiningMarks = [];
+
+ foreach (var combMark in Contents [row, col].CombiningMarks)
+ {
+ _outputBuffer [position].CombiningMarks!.Add ((char)combMark.Value);
+ }
+ }
+ else
+ {
+ _outputBuffer [position].CombiningMarks = null;
+ }
+
if (Contents [row, col].Rune.IsBmp)
{
_outputBuffer [position].Char = (char)Contents [row, col].Rune.Value;
@@ -342,7 +356,21 @@ public override bool UpdateScreen ()
else
{
//_outputBuffer [position].Empty = true;
- _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+ //_outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+ var rune = Contents [row, col].Rune;
+ char [] surrogatePair = rune.ToString ().ToCharArray ();
+ Debug.Assert (surrogatePair.Length == 2);
+ _outputBuffer [position].Char = surrogatePair [0];
+
+ if (_outputBuffer [position].CombiningMarks == null)
+ {
+ _outputBuffer [position].CombiningMarks = [];
+ _outputBuffer [position].CombiningMarks!.Add (surrogatePair [1]);
+ }
+ else
+ {
+ _outputBuffer [position].CombiningMarks!.Insert (0, surrogatePair [1]);
+ }
if (Contents [row, col].Rune.GetColumns () > 1 && col + 1 < Cols)
{
@@ -350,7 +378,7 @@ public override bool UpdateScreen ()
col++;
position = row * Cols + col;
_outputBuffer [position].Empty = false;
- _outputBuffer [position].Char = ' ';
+ _outputBuffer [position].Char = '\0';
}
}
}
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
index fedcf6f732..f4856fa882 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
@@ -36,7 +36,6 @@ public WindowsMainLoop (IConsoleDriver consoleDriver)
if (!ConsoleDriver.RunningUnitTests)
{
_winConsole = ((WindowsDriver)consoleDriver).WinConsole;
- _winConsole!._mainLoop = this;
}
}
diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs
index 5ce4e21df5..d769265609 100644
--- a/Terminal.Gui/Drawing/Cell.cs
+++ b/Terminal.Gui/Drawing/Cell.cs
@@ -4,7 +4,7 @@
/// Represents a single row/column in a Terminal.Gui rendering surface (e.g. and
/// ).
///
-public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
+public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default, List CombiningMarks = null)
{
/// The attributes to use when drawing the Glyph.
public Attribute? Attribute { get; set; } = Attribute;
@@ -23,13 +23,11 @@ public Rune Rune
get => _rune;
set
{
- CombiningMarks.Clear ();
+ CombiningMarks = null;
_rune = value;
}
}
- private List _combiningMarks;
-
///
/// The combining marks for that when combined makes this Cell a combining sequence. If
/// empty, then is ignored.
@@ -38,11 +36,7 @@ public Rune Rune
/// Only valid in the rare case where is a combining sequence that could not be normalized to a
/// single Rune.
///
- internal List CombiningMarks
- {
- get => _combiningMarks ?? [];
- private set => _combiningMarks = value ?? [];
- }
+ internal List CombiningMarks { get; set; } = CombiningMarks;
///
public override string ToString () { return $"[{Rune}, {Attribute}]"; }
diff --git a/Terminal.Gui/Text/RuneExtensions.cs b/Terminal.Gui/Text/RuneExtensions.cs
index 835dba6791..e6912f4c22 100644
--- a/Terminal.Gui/Text/RuneExtensions.cs
+++ b/Terminal.Gui/Text/RuneExtensions.cs
@@ -129,7 +129,8 @@ public static bool IsCombiningMark (this Rune rune)
return Rune.GetUnicodeCategory (rune) == UnicodeCategory.NonSpacingMark
|| category == UnicodeCategory.SpacingCombiningMark
- || category == UnicodeCategory.EnclosingMark;
+ || category == UnicodeCategory.EnclosingMark
+ || category == UnicodeCategory.Format;
}
/// Reports whether a rune is a surrogate code point.
diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs
index b49c8c4e6f..581f2f6efd 100644
--- a/Terminal.Gui/Views/CharMap/CharMap.cs
+++ b/Terminal.Gui/Views/CharMap/CharMap.cs
@@ -247,6 +247,8 @@ public bool ShowGlyphWidths
///
protected override bool OnDrawingContent ()
{
+ ConsoleDriver.IgnoreIsCombiningMark = true;
+
if (Viewport.Height == 0 || Viewport.Width == 0)
{
return true;
@@ -324,37 +326,37 @@ protected override bool OnDrawingContent ()
if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0)
{
// Draw the rune
- if (width > 0)
- {
+ //if (width > 0)
+ //{
AddRune (rune);
- }
- 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
- {
- AddRune (Rune.ReplacementChar);
- }
- }
- }
+ //}
+ //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
+ // {
+ // AddRune (Rune.ReplacementChar);
+ // }
+ // }
+ //}
}
else
{
@@ -385,6 +387,8 @@ protected override bool OnDrawingContent ()
}
}
+ ConsoleDriver.IgnoreIsCombiningMark = false;
+
return true;
}
diff --git a/Terminal.Gui/Views/CharMap/UnicodeRange.cs b/Terminal.Gui/Views/CharMap/UnicodeRange.cs
index 1880b2671e..baa1c1fac7 100644
--- a/Terminal.Gui/Views/CharMap/UnicodeRange.cs
+++ b/Terminal.Gui/Views/CharMap/UnicodeRange.cs
@@ -5,7 +5,7 @@
namespace Terminal.Gui;
///
-/// Represents all of the Uniicode ranges.from System.Text.Unicode.UnicodeRange plus
+/// Represents all the Unicode ranges from System.Text.Unicode.UnicodeRange plus
/// the non-BMP ranges not included.
///
public class UnicodeRange (int start, int end, string category)
diff --git a/UICatalog/Scenarios/CombiningMarks.cs b/UICatalog/Scenarios/CombiningMarks.cs
index d351982513..89f178133d 100644
--- a/UICatalog/Scenarios/CombiningMarks.cs
+++ b/UICatalog/Scenarios/CombiningMarks.cs
@@ -12,22 +12,79 @@ public override void Main ()
var top = new Toplevel ();
top.DrawComplete += (s, e) =>
- {
- top.Move (0, 0);
- top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
- top.Move (0, 2);
- top.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
- top.Move (0, 3);
- top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
- top.Move (0, 4);
- top.AddRune ('[');
- top.AddRune ('a');
- top.AddRune ('\u0301');
- top.AddRune ('\u0301');
- top.AddRune ('\u0328');
- top.AddRune (']');
- top.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
- };
+ {
+ // Forces reset _lineColsOffset because we're dealing with direct draw
+ Application.ClearScreenNextIteration = true;
+
+ var i = -1;
+ top.Move (0, ++i);
+ top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
+ top.Move (0, ++i);
+ top.AddStr ("\u0301<- \"\\u0301\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\u0301]<- \"[\\u0301]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[ \u0301]<- \"[ \\u0301]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\u0301 ]<- \"[\\u0301 ]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("\u0301\u0301\u0328<- \"\\u0301\\u0301\\u0328\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\u0301\u0301\u0328]<- \"[\\u0301\\u0301\\u0328]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddRune ('[');
+ top.AddRune ('a');
+ top.AddRune ('\u0301');
+ top.AddRune ('\u0301');
+ top.AddRune ('\u0328');
+ top.AddRune (']');
+ top.AddStr ("<- \"[a\\u0301\\u0301\\u0328]\" using AddRune for each.");
+ top.Move (0, ++i);
+ top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("\u00ad<- \"\\u00ad\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\u00ad]<- \"[\\u00ad]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddRune ('[');
+ top.AddRune ('\u00ad');
+ top.AddRune (']');
+ top.AddStr ("<- \"[\\u00ad]\" using AddRune for each.");
+ i++;
+ top.Move (0, ++i);
+ top.AddStr ("From now on we are using TextFormatter");
+ TextFormatter tf = new () { Text = "[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using TextFormatter." };
+ tf.Draw (new (0, ++i, tf.Text.Length, 1), top.ColorScheme.Normal, top.ColorScheme.Normal);
+ tf.Text = "[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using TextFormatter.";
+ tf.Draw (new (0, ++i, tf.Text.Length, 1), top.ColorScheme.Normal, top.ColorScheme.Normal);
+ i++;
+ top.Move (0, ++i);
+ top.AddStr ("From now on we are using Surrogate pairs with combining diacritics");
+ top.Move (0, ++i);
+ top.AddStr ("[\ud835\udc4b\u0302]<- \"[\\ud835\\udc4b\\u0302]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\ud83d\udc68\ud83e\uddd2]<- \"[\\ud83d\\udc68\\ud83e\\uddd2]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("\u200d<- \"\\u200d\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\u200d]<- \"[\\u200d]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\ud83d\udc68\u200d\ud83e\uddd2]<- \"[\\ud83d\\udc68\\u200d\\ud83e\\uddd2]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\U0001F469\U0001F9D2]<- \"[\\U0001F469\\U0001F9D2]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\U0001F468\U0001F469\U0001F9D2]<- \"[\\U0001F468\\U0001F469\\U0001F9D2]\" using AddStr.");
+ top.Move (0, ++i);
+ top.AddStr ("[\U0001F468\u200D\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F468\\u200D\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
+ };
Application.Run (top);
top.Dispose ();
diff --git a/UnitTests/Application/ApplicationScreenTests.cs b/UnitTests/Application/ApplicationScreenTests.cs
index f53bf8531b..ea965abce8 100644
--- a/UnitTests/Application/ApplicationScreenTests.cs
+++ b/UnitTests/Application/ApplicationScreenTests.cs
@@ -8,6 +8,7 @@ public class ApplicationScreenTests (ITestOutputHelper output)
public void ClearScreenNextIteration_Resets_To_False_After_LayoutAndDraw ()
{
// Arrange
+ ConsoleDriver.RunningUnitTests = true;
Application.Init ();
// Act
diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs
index 5c753386ee..ed624b495c 100644
--- a/UnitTests/ConsoleDrivers/AddRuneTests.cs
+++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs
@@ -55,7 +55,9 @@ public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars ()
text = "\u0103\u0301";
driver.AddStr (text);
- Assert.Equal (expected, driver.Contents [0, 0].Rune);
+ Assert.Single (driver.Contents [0, 0].CombiningMarks);
+ string combined = driver.Contents [0, 0].Rune + driver.Contents [0, 0].CombiningMarks [0].ToString ();
+ Assert.Equal (expected, (Rune)combined.Normalize (NormalizationForm.FormC) [0]);
Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
driver.ClearContents ();
@@ -63,7 +65,9 @@ public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars ()
text = "\u0061\u0306\u0301";
driver.AddStr (text);
- Assert.Equal (expected, driver.Contents [0, 0].Rune);
+ Assert.Equal (2, driver.Contents [0, 0].CombiningMarks.Count);
+ combined = driver.Contents [0, 0].Rune + driver.Contents [0, 0].CombiningMarks [0].ToString () + driver.Contents [0, 0].CombiningMarks [1];
+ Assert.Equal (expected, (Rune)combined.Normalize (NormalizationForm.FormC) [0]);
Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
// var s = "a\u0301\u0300\u0306";
diff --git a/UnitTests/ConsoleDrivers/ContentsTests.cs b/UnitTests/ConsoleDrivers/ContentsTests.cs
index d0f2d83ff7..6299c3a7f3 100644
--- a/UnitTests/ConsoleDrivers/ContentsTests.cs
+++ b/UnitTests/ConsoleDrivers/ContentsTests.cs
@@ -56,32 +56,36 @@ public void AddStr_With_Combining_Characters (Type driverType)
// a + ogonek + acute = ( ą́ )
var ogonek = new Rune (0x0328); // Combining ogonek (a small hook or comma shape)
combined = "a" + ogonek + acuteaccent;
- expected = ("a" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ expected = ("á" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ driver.ClearContents ();
driver.Move (0, 0);
driver.AddStr (combined);
TestHelpers.AssertDriverContentsAre (expected, output, driver);
// e + ogonek + acute = ( ę́́ )
combined = "e" + ogonek + acuteaccent;
- expected = ("e" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ expected = ("é" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ driver.ClearContents ();
driver.Move (0, 0);
driver.AddStr (combined);
TestHelpers.AssertDriverContentsAre (expected, output, driver);
// i + ogonek + acute = ( į́́́ )
combined = "i" + ogonek + acuteaccent;
- expected = ("i" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ expected = ("í" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ driver.ClearContents ();
driver.Move (0, 0);
driver.AddStr (combined);
TestHelpers.AssertDriverContentsAre (expected, output, driver);
// u + ogonek + acute = ( ų́́́́ )
combined = "u" + ogonek + acuteaccent;
- expected = ("u" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ expected = ("ú" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
+ driver.ClearContents ();
driver.Move (0, 0);
driver.AddStr (combined);
TestHelpers.AssertDriverContentsAre (expected, output, driver);
diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs
index ed687e61b0..fceb821584 100644
--- a/UnitTests/TestHelpers.cs
+++ b/UnitTests/TestHelpers.cs
@@ -409,7 +409,7 @@ public static Rectangle AssertDriverContentsWithFrameAre (
colIndex++;
}
- if (colIndex + 1 > w)
+ if (colIndex + 1 > w && !runeAtCurrentLocation.IsCombiningMark ())
{
w = colIndex + 1;
}
@@ -417,15 +417,28 @@ public static Rectangle AssertDriverContentsWithFrameAre (
h = rowIndex - y + 1;
}
- if (x > -1)
+ if (x > -1 && contents [rowIndex, colIndex].CombiningMarks is null)
{
runes.Add (runeAtCurrentLocation);
}
- // See Issue #2616
- //foreach (var combMark in contents [r, c].CombiningMarks) {
- // runes.Add (combMark);
- //}
+ if (contents [rowIndex, colIndex].CombiningMarks is { Count: > 0 })
+ {
+ string combine = runeAtCurrentLocation.ToString ();
+ string? normalized = null;
+
+ // See Issue #2616
+ foreach (var combMark in contents [rowIndex, colIndex].CombiningMarks)
+ {
+ combine += combMark;
+ normalized = combine.Normalize (NormalizationForm.FormC);
+ }
+
+ foreach (Rune enumerateRune in normalized!.EnumerateRunes ())
+ {
+ runes.Add (enumerateRune);
+ }
+ }
}
if (runes.Count > 0)
diff --git a/UnitTests/Text/TextFormatterTests.cs b/UnitTests/Text/TextFormatterTests.cs
index 5435d02044..806ecc0502 100644
--- a/UnitTests/Text/TextFormatterTests.cs
+++ b/UnitTests/Text/TextFormatterTests.cs
@@ -4114,8 +4114,8 @@ public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, stri
}
[Theory]
- [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
- [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
+ [InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misę́rables")]
+ [InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę́\nr\na\nb\nl\ne\ns")]
[InlineData (
4,
4,
@@ -4124,7 +4124,7 @@ public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, stri
LMre
eias
ssb
- ęl "
+ ę́l "
)]
public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
{
diff --git a/UnitTests/View/Draw/AllViewsDrawTests.cs b/UnitTests/View/Draw/AllViewsDrawTests.cs
index fa865068bf..f9e6c806ac 100644
--- a/UnitTests/View/Draw/AllViewsDrawTests.cs
+++ b/UnitTests/View/Draw/AllViewsDrawTests.cs
@@ -51,5 +51,7 @@ public void AllViews_Draw_Does_Not_Layout (Type viewType)
Assert.Equal (1, layoutStartedCount);
Assert.Equal (1, layoutCompleteCount);
}
+
+ Application.ResetState (true);
}
}