Merge branch 'v2_combining-tab-fix_3011' into v2_tabview-frame-fix_2882

This commit is contained in:
BDisp
2023-11-25 00:32:23 +00:00
2 changed files with 284 additions and 46 deletions

View File

@@ -169,6 +169,15 @@ namespace Terminal.Gui {
return StringExtensions.ToString (runes); return StringExtensions.ToString (runes);
} }
static string ReplaceTABWithSpaces (string str, int tabWidth)
{
if (tabWidth == 0) {
return str.Replace ("\t", "");
}
return str.Replace ("\t", new string (' ', tabWidth));
}
/// <summary> /// <summary>
/// Splits all newlines in the <paramref name="text"/> into a list /// Splits all newlines in the <paramref name="text"/> into a list
/// and supports both CRLF and LF, preserving the ending newline. /// and supports both CRLF and LF, preserving the ending newline.
@@ -368,7 +377,17 @@ namespace Terminal.Gui {
if (end == start) { if (end == start) {
end = start + width; end = start + width;
} }
lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start))); var zeroLength = 0;
for (int i = end; i < runes.Count - start; i++) {
var r = runes [i];
if (r.GetColumns () == 0) {
zeroLength++;
} else {
break;
}
}
lines.Add (StringExtensions.ToString (runes.GetRange (start, end - start + zeroLength)));
end += zeroLength;
start = end; start = end;
if (runes [end].Value == ' ') { if (runes [end].Value == ' ') {
start++; start++;
@@ -472,12 +491,13 @@ namespace Terminal.Gui {
if (IsHorizontalDirection (textDirection)) { if (IsHorizontalDirection (textDirection)) {
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width))); return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
} else { } else {
return StringExtensions.ToString (runes.GetRange (0, width)); var zeroLength = runes.Sum (r => r.GetColumns () == 0 ? 1 : 0);
return StringExtensions.ToString (runes.GetRange (0, width + zeroLength));
} }
} else { } else {
if (justify) { if (justify) {
return Justify (text, width, ' ', textDirection); return Justify (text, width, ' ', textDirection);
} else if (IsHorizontalDirection (textDirection) && text.GetColumns () > width) { } else if (IsHorizontalDirection (textDirection) && GetRuneWidth (text.GetColumns ()) > width) {
return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width))); return StringExtensions.ToString (runes.GetRange (0, GetLengthThatFits (text, width)));
} }
return text; return text;
@@ -602,6 +622,7 @@ namespace Terminal.Gui {
if (wordWrap == false) { if (wordWrap == false) {
text = ReplaceCRLFWithSpace (text); text = ReplaceCRLFWithSpace (text);
text = ReplaceTABWithSpaces (text, tabWidth);
lineResult.Add (ClipAndJustify (text, width, justify, textDirection)); lineResult.Add (ClipAndJustify (text, width, justify, textDirection));
return lineResult; return lineResult;
} }
@@ -623,7 +644,7 @@ namespace Terminal.Gui {
} }
} }
foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) { foreach (var line in WordWrapText (StringExtensions.ToString (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth, textDirection)) {
lineResult.Add (ClipAndJustify (line, width, justify, textDirection)); lineResult.Add (ClipAndJustify (ReplaceTABWithSpaces (line, tabWidth), width, justify, textDirection));
} }
return lineResult; return lineResult;
@@ -654,7 +675,7 @@ namespace Terminal.Gui {
var max = 0; var max = 0;
result.ForEach (s => { result.ForEach (s => {
var m = 0; var m = 0;
s.ToRuneList ().ForEach (r => m += Math.Max (r.GetColumns (), 1)); s.ToRuneList ().ForEach (r => m += GetRuneWidth (r.GetColumns ()));
if (m > max) { if (m > max) {
max = m; max = m;
} }
@@ -688,7 +709,7 @@ namespace Terminal.Gui {
for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) { for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? lines.Count : startIndex + length); i++) {
var runes = lines [i]; var runes = lines [i];
if (runes.Length > 0) if (runes.Length > 0)
max += runes.EnumerateRunes ().Max (r => Math.Max (r.GetColumns (), 1)); max += runes.EnumerateRunes ().Max (r => GetRuneWidth (r.GetColumns ()));
} }
return max; return max;
} }
@@ -706,7 +727,7 @@ namespace Terminal.Gui {
var max = 0; var max = 0;
var runes = text.ToRunes (); var runes = text.ToRunes ();
for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) { for (int i = (startIndex == -1 ? 0 : startIndex); i < (length == -1 ? runes.Length : startIndex + length); i++) {
max += Math.Max (runes [i].GetColumns (), 1); max += GetRuneWidth (runes [i].GetColumns ());
} }
return max; return max;
} }
@@ -734,7 +755,7 @@ namespace Terminal.Gui {
var runesLength = 0; var runesLength = 0;
var runeIdx = 0; var runeIdx = 0;
for (; runeIdx < runes.Count; runeIdx++) { for (; runeIdx < runes.Count; runeIdx++) {
var runeWidth = Math.Max (runes [runeIdx].GetColumns (), 1); var runeWidth = GetRuneWidth (runes [runeIdx].GetColumns ());
if (runesLength + runeWidth > columns) { if (runesLength + runeWidth > columns) {
break; break;
} }
@@ -743,6 +764,15 @@ namespace Terminal.Gui {
return runeIdx; return runeIdx;
} }
private static int GetRuneWidth (int runeWidth)
{
if (runeWidth < 0 || runeWidth > 0) {
return Math.Max (runeWidth, 1);
}
return runeWidth;
}
/// <summary> /// <summary>
/// Gets the index position from the list based on the <paramref name="width"/>. /// Gets the index position from the list based on the <paramref name="width"/>.
/// </summary> /// </summary>
@@ -756,7 +786,7 @@ namespace Terminal.Gui {
for (; lineIdx < lines.Count; lineIdx++) { for (; lineIdx < lines.Count; lineIdx++) {
var runes = lines [lineIdx].ToRuneList (); var runes = lines [lineIdx].ToRuneList ();
var maxRruneWidth = runes.Count > 0 var maxRruneWidth = runes.Count > 0
? runes.Max (r => Math.Max (r.GetColumns (), 1)) : 1; ? runes.Max (r => GetRuneWidth (r.GetColumns ())) : 1;
if (runesLength + maxRruneWidth > width) { if (runesLength + maxRruneWidth > width) {
break; break;
} }
@@ -772,8 +802,9 @@ namespace Terminal.Gui {
/// <param name="y">The y location of the rectangle</param> /// <param name="y">The y location of the rectangle</param>
/// <param name="text">The text to measure</param> /// <param name="text">The text to measure</param>
/// <param name="direction">The text direction.</param> /// <param name="direction">The text direction.</param>
/// <param name="tabWidth">The number of columns used for a tab.</param>
/// <returns></returns> /// <returns></returns>
public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom) public static Rect CalcRect (int x, int y, string text, TextDirection direction = TextDirection.LeftRight_TopBottom, int tabWidth = 0)
{ {
if (string.IsNullOrEmpty (text)) { if (string.IsNullOrEmpty (text)) {
return new Rect (new Point (x, y), Size.Empty); return new Rect (new Point (x, y), Size.Empty);
@@ -795,9 +826,16 @@ namespace Terminal.Gui {
cols = 0; cols = 0;
} else if (rune.Value != '\r') { } else if (rune.Value != '\r') {
cols++; cols++;
var rw = ((Rune)rune).GetColumns (); var rw = 0;
if (rw > 0) { if (rune.Value == '\t') {
rw--; rw += tabWidth - 1;
} else {
rw = ((Rune)rune).GetColumns ();
if (rw > 0) {
rw--;
} else if (rw == 0) {
cols--;
}
} }
cols += rw; cols += rw;
} }
@@ -822,10 +860,18 @@ namespace Terminal.Gui {
cw = 1; cw = 1;
} else if (rune.Value != '\r') { } else if (rune.Value != '\r') {
rows++; rows++;
var rw = ((Rune)rune).GetColumns (); var rw = 0;
if (cw < rw) { if (rune.Value == '\t') {
cw = rw; rw += tabWidth - 1;
vw++; rows += rw;
} else {
rw = ((Rune)rune).GetColumns ();
if (rw == 0) {
rows--;
} else if (cw < rw) {
cw = rw;
vw++;
}
} }
} }
} }
@@ -954,7 +1000,7 @@ namespace Terminal.Gui {
#endregion // Static Members #endregion // Static Members
List<string> _lines = new List<string> (); List<string> _lines = new List<string> ();
string _text; string _text = null;
TextAlignment _textAlignment; TextAlignment _textAlignment;
VerticalTextAlignment _textVerticalAlignment; VerticalTextAlignment _textVerticalAlignment;
TextDirection _textDirection; TextDirection _textDirection;
@@ -973,13 +1019,17 @@ namespace Terminal.Gui {
public virtual string Text { public virtual string Text {
get => _text; get => _text;
set { set {
if (AutoSize || (_text == null && value != null && Size.IsEmpty)) {
Size = CalcRect (0, 0, value, _textDirection, TabWidth).Size;
}
_text = value; _text = value;
if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) { //if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
// Provide a default size (width = length of longest line, height = 1) // // Provide a default size (width = length of longest line, height = 1)
// TODO: It might makes more sense for the default to be width = length of first line? // // TODO: It might makes more sense for the default to be width = length of first line?
Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1); // Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
} //}
NeedsFormat = true; NeedsFormat = true;
} }
@@ -1194,7 +1244,7 @@ namespace Terminal.Gui {
if (IsVerticalDirection (_textDirection)) { if (IsVerticalDirection (_textDirection)) {
var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1); var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1);
_lines = Format (shown_text, Size.Height, _textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth, _lines = Format (shown_text, Size.Height, _textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth,
PreserveTrailingSpaces, 0, _textDirection); PreserveTrailingSpaces, TabWidth, _textDirection);
if (!AutoSize) { if (!AutoSize) {
colsWidth = GetMaxColsForWidth (_lines, Size.Width); colsWidth = GetMaxColsForWidth (_lines, Size.Width);
if (_lines.Count > colsWidth) { if (_lines.Count > colsWidth) {
@@ -1203,7 +1253,7 @@ namespace Terminal.Gui {
} }
} else { } else {
_lines = Format (shown_text, Size.Width, _textAlignment == TextAlignment.Justified, Size.Height > 1, _lines = Format (shown_text, Size.Width, _textAlignment == TextAlignment.Justified, Size.Height > 1,
PreserveTrailingSpaces, 0, _textDirection); PreserveTrailingSpaces, TabWidth, _textDirection);
if (!AutoSize && _lines.Count > Size.Height) { if (!AutoSize && _lines.Count > Size.Height) {
_lines.RemoveRange (Size.Height, _lines.Count - Size.Height); _lines.RemoveRange (Size.Height, _lines.Count - Size.Height);
} }
@@ -1226,6 +1276,11 @@ namespace Terminal.Gui {
/// </remarks> /// </remarks>
public bool NeedsFormat { get; set; } public bool NeedsFormat { get; set; }
/// <summary>
/// Gets or sets the number of columns used for a tab.
/// </summary>
public int TabWidth { get; set; } = 4;
/// <summary> /// <summary>
/// Causes the <see cref="TextFormatter"/> to reformat the text. /// Causes the <see cref="TextFormatter"/> to reformat the text.
/// </summary> /// </summary>
@@ -1375,31 +1430,64 @@ namespace Terminal.Gui {
var start = isVertical ? bounds.Top : bounds.Left; var start = isVertical ? bounds.Top : bounds.Left;
var size = isVertical ? bounds.Height : bounds.Width; var size = isVertical ? bounds.Height : bounds.Width;
var current = start + colOffset; var current = start + colOffset;
List<Point?> lastZeroWidthPos = null;
Rune rune = default;
Rune lastRuneUsed;
var zeroLengthCount = isVertical ? runes.Sum (r => r.GetColumns () == 0 ? 1 : 0) : 0;
for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size; idx++) { for (var idx = (isVertical ? start - y : start - x) + colOffset; current < start + size + zeroLengthCount; idx++) {
if (idx < 0 || x + current + colOffset < 0) { lastRuneUsed = rune;
current++; if (lastZeroWidthPos == null) {
continue; if (idx < 0 || x + current + colOffset < 0) {
} else if (!fillRemaining && idx > runes.Length - 1) { current++;
break; continue;
} else if (!fillRemaining && idx > runes.Length - 1) {
break;
}
if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) {
break;
}
} }
if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset)
|| (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) // || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y))
break; // break;
var rune = (Rune)' '; rune = (Rune)' ';
if (isVertical) { if (isVertical) {
Application.Driver?.Move (x, current);
if (idx >= 0 && idx < runes.Length) { if (idx >= 0 && idx < runes.Length) {
rune = runes [idx]; rune = runes [idx];
} }
if (lastZeroWidthPos == null) {
Application.Driver?.Move (x, current);
} else {
var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
if (foundIdx > -1) {
if (rune.IsCombiningMark ()) {
lastZeroWidthPos [foundIdx] = (new Point (lastZeroWidthPos [foundIdx].Value.X + 1, current));
Application.Driver?.Move (lastZeroWidthPos [foundIdx].Value.X, current);
} else if (!rune.IsCombiningMark () && lastRuneUsed.IsCombiningMark ()) {
current++;
Application.Driver?.Move (x, current);
} else {
Application.Driver?.Move (x, current);
}
} else {
Application.Driver?.Move (x, current);
}
}
} else { } else {
Application.Driver?.Move (current, y); Application.Driver?.Move (current, y);
if (idx >= 0 && idx < runes.Length) { if (idx >= 0 && idx < runes.Length) {
rune = runes [idx]; rune = runes [idx];
} }
} }
var runeWidth = GetRuneWidth (rune.GetColumns ());
if (HotKeyPos > -1 && idx == HotKeyPos) { if (HotKeyPos > -1 && idx == HotKeyPos) {
if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) || if ((isVertical && _textVerticalAlignment == VerticalTextAlignment.Justified) ||
(!isVertical && _textAlignment == TextAlignment.Justified)) { (!isVertical && _textAlignment == TextAlignment.Justified)) {
@@ -1409,12 +1497,27 @@ namespace Terminal.Gui {
Application.Driver?.AddRune (rune); Application.Driver?.AddRune (rune);
Application.Driver?.SetAttribute (normalColor); Application.Driver?.SetAttribute (normalColor);
} else { } else {
if (isVertical) {
if (runeWidth == 0) {
if (lastZeroWidthPos == null) {
lastZeroWidthPos = new List<Point?> ();
}
var foundIdx = lastZeroWidthPos.IndexOf (p => p.Value.Y == current);
if (foundIdx == -1) {
current--;
lastZeroWidthPos.Add ((new Point (x + 1, current)));
}
Application.Driver?.Move (x + 1, current);
}
}
Application.Driver?.AddRune (rune); Application.Driver?.AddRune (rune);
} }
// BUGBUG: I think this is a bug. If rune is a combining mark current should not be incremented.
var runeWidth = rune.GetColumns (); //Math.Max (rune.GetColumns (), 1);
if (isVertical) { if (isVertical) {
current++; if (runeWidth > 0) {
current++;
}
} else { } else {
current += runeWidth; current += runeWidth;
} }

View File

@@ -65,13 +65,23 @@ namespace Terminal.Gui.TextTests {
Assert.NotEmpty (tf.Lines); Assert.NotEmpty (tf.Lines);
} }
[Fact] [Theory]
public void TestSize_TextChange () [InlineData (TextDirection.LeftRight_TopBottom, false)]
[InlineData (TextDirection.TopBottom_LeftRight, true)]
public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
{ {
var tf = new TextFormatter () { Text = "你" }; var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize };
Assert.Equal (2, tf.Size.Width); Assert.Equal (2, tf.Size.Width);
tf.Text = "你你"; tf.Text = "你你";
Assert.Equal (4, tf.Size.Width); if (autoSize) {
if (textDirection == TextDirection.LeftRight_TopBottom) {
Assert.Equal (4, tf.Size.Width);
} else {
Assert.Equal (2, tf.Size.Width);
}
} else {
Assert.Equal (2, tf.Size.Width);
}
} }
[Fact] [Fact]
@@ -620,21 +630,24 @@ namespace Terminal.Gui.TextTests {
[Theory] [Theory]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัา", "ำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะ", "ั", "า", "ำ" })] [InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })]
public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines) public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
{ {
List<string> wrappedLines; List<string> wrappedLines;
var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
Assert.Single (zeroWidth);
Assert.Equal ('ั', zeroWidth.ElementAt (0).Value);
Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset); Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth); var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
wrappedLines = TextFormatter.WordWrapText (text, maxWidth); wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
Assert.Equal (wrappedLines.Count, resultLines.Count ()); Assert.Equal (wrappedLines.Count, resultLines.Count ());
Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0)); Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0)); Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
Assert.Equal (resultLines, wrappedLines); Assert.Equal (resultLines, wrappedLines);
} }
@@ -1418,5 +1431,127 @@ namespace Terminal.Gui.TextTests {
Assert.Equal (expected, text [index].ToString ()); Assert.Equal (expected, text [index].ToString ());
} }
} }
[Fact]
public void GetLengthThatFits_With_Combining_Runes ()
{
var text = "Les Mise\u0328\u0301rables";
Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
}
[Fact]
public void GetMaxColsForWidth_With_Combining_Runes ()
{
var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
}
[Fact]
public void GetSumMaxCharWidth_With_Combining_Runes ()
{
var text = "Les Mise\u0328\u0301rables";
Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
}
[Fact]
public void GetSumMaxCharWidth_List_With_Combining_Runes ()
{
var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
}
[Theory]
[InlineData (14, 1, TextDirection.LeftRight_TopBottom)]
[InlineData (1, 14, TextDirection.TopBottom_LeftRight)]
public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
{
var text = "Les Mise\u0328\u0301rables";
Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
}
[Theory, AutoInitShutdown]
[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, TextDirection.TopBottom_LeftRight, @"
LMre
eias
ssb
ęl ")]
public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
{
var text = "Les Mise\u0328\u0301rables";
var tf = new TextFormatter ();
tf.Direction = textDirection;
tf.Text = text;
if (textDirection == TextDirection.LeftRight_TopBottom) {
Assert.Equal (new Size (width, height), tf.Size);
} else {
Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
tf.Size = new Size (width, height);
}
tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black));
TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
}
[Theory, AutoInitShutdown]
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
{
var text = "This is a \tTab";
var tf = new TextFormatter ();
tf.Direction = textDirection;
tf.TabWidth = tabWidth;
tf.Text = text;
Assert.False (tf.WordWrap);
Assert.False (tf.PreserveTrailingSpaces);
Assert.Equal (new Size (width, height), tf.Size);
tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black));
TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
}
[Theory, AutoInitShutdown]
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
{
var text = "This is a \tTab";
var tf = new TextFormatter ();
tf.Direction = textDirection;
tf.TabWidth = tabWidth;
tf.PreserveTrailingSpaces = true;
tf.Text = text;
Assert.False (tf.WordWrap);
Assert.Equal (new Size (width, height), tf.Size);
tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black));
TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
}
[Theory, AutoInitShutdown]
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
{
var text = "This is a \tTab";
var tf = new TextFormatter ();
tf.Direction = textDirection;
tf.TabWidth = tabWidth;
tf.WordWrap = true;
tf.Text = text;
Assert.Equal (new Size (width, height), tf.Size);
tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black));
TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
}
} }
} }