Skip to content

Commit

Permalink
Fixes #1925. Preserve trailing spaces on word wrap must be refactored… (
Browse files Browse the repository at this point in the history
#1929)

* Fixes #1925. Preserve trailing spaces on word wrap must be refactored on TextFormatter.

* Fixes a bug on Format when the preserveTrailingSpaces is enabled.

Co-authored-by: Tig Kindel <tig@users.noreply.github.com>
  • Loading branch information
BDisp and tig committed Aug 4, 2022
1 parent 666c2e1 commit a23c1be
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 67 deletions.
21 changes: 15 additions & 6 deletions Terminal.Gui/Core/TextFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,22 +366,28 @@ public static bool IsTopToBottom (TextDirection textDirection)
/// </remarks>
public bool NeedsFormat { get => needsFormat; set => needsFormat = value; }

static ustring StripCRLF (ustring str)
static ustring StripCRLF (ustring str, bool keepNewLine = false)
{
var runes = str.ToRuneList ();
for (int i = 0; i < runes.Count; i++) {
switch (runes [i]) {
case '\n':
runes.RemoveAt (i);
if (!keepNewLine) {
runes.RemoveAt (i);
}
break;

case '\r':
if ((i + 1) < runes.Count && runes [i + 1] == '\n') {
runes.RemoveAt (i);
runes.RemoveAt (i + 1);
if (!keepNewLine) {
runes.RemoveAt (i);
}
i++;
} else {
runes.RemoveAt (i);
if (!keepNewLine) {
runes.RemoveAt (i);
}
}
break;
}
Expand Down Expand Up @@ -515,6 +521,7 @@ public static string ClipOrPad (string text, int width)

int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
{
var lastFrom = from;
var to = from;
var length = cLength;
incomplete = false;
Expand Down Expand Up @@ -552,8 +559,10 @@ int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength =
}
to++;
}
if (cLength > 0 && to < runes.Count && runes [to] != ' ') {
if (cLength > 0 && to < runes.Count && runes [to] != ' ' && runes [to] != '\t') {
return from;
} else if (cLength > 0 && to < runes.Count && (runes [to] == ' ' || runes [to] == '\t')) {
return lastFrom;
} else {
return to;
}
Expand Down Expand Up @@ -727,7 +736,7 @@ public static List<ustring> Format (ustring text, int width, TextAlignment talig
return lineResult;
}

var runes = text.ToRuneList ();
var runes = StripCRLF (text, true).ToRuneList ();
int runeCount = runes.Count;
int lp = 0;
for (int i = 0; i < runeCount; i++) {
Expand Down
28 changes: 2 additions & 26 deletions Terminal.Gui/Views/TextView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,7 @@ void WrapTextModel ()
out int nStartRow, out int nStartCol,
currentRow, currentColumn,
selectionStartRow, selectionStartColumn,
tabWidth, preserveTrailingSpaces: !ReadOnly);
tabWidth, preserveTrailingSpaces: true);
currentRow = nRow;
currentColumn = nCol;
selectionStartRow = nStartRow;
Expand Down Expand Up @@ -1610,10 +1610,7 @@ void WrapTextModel ()
}

SetWrapModel ();
var savedCurrentColumn = CurrentColumn;
currentColumn = GetCurrentColumnReadOnyWrapModel (true);
var sel = GetRegion ();
currentColumn = savedCurrentColumn;
UpdateWrapModel ();
Adjust ();

Expand Down Expand Up @@ -1993,9 +1990,6 @@ protected virtual void ColorUsed (List<Rune> line, int idx)
if (value != isReadOnly) {
isReadOnly = value;

SetWrapModel ();
currentColumn = GetCurrentColumnReadOnyWrapModel ();
UpdateWrapModel ();
SetNeedsDisplay ();
Adjust ();
}
Expand Down Expand Up @@ -2316,7 +2310,7 @@ void UpdateWrapModel ([CallerMemberName] string caller = null)
wrapManager.UpdateModel (model, out int nRow, out int nCol,
out int nStartRow, out int nStartCol,
currentRow, currentColumn,
selectionStartRow, selectionStartColumn, preserveTrailingSpaces: !ReadOnly);
selectionStartRow, selectionStartColumn, preserveTrailingSpaces: true);
currentRow = nRow;
currentColumn = nCol;
selectionStartRow = nStartRow;
Expand All @@ -2327,21 +2321,6 @@ void UpdateWrapModel ([CallerMemberName] string caller = null)
throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
}

int GetCurrentColumnReadOnyWrapModel (bool forcePreserveTrailingSpaces = false)
{
if (wordWrap) {
var wManager = new WordWrapManager (wrapManager.Model);
if (ReadOnly && !forcePreserveTrailingSpaces) {
wManager.WrapModel (frameWidth, out _, out _, out _, out _, preserveTrailingSpaces: false);
} else {
wManager.WrapModel (frameWidth, out _, out _, out _, out _, preserveTrailingSpaces: true);
}
var currentLine = wrapManager.GetWrappedLineColWidth (CurrentRow, CurrentColumn, wManager);
return currentLine;
}
return currentColumn;
}

///<inheritdoc/>
public override void Redraw (Rect bounds)
{
Expand Down Expand Up @@ -3803,8 +3782,6 @@ bool DeleteTextBackwards ()
public void Copy ()
{
SetWrapModel ();
var savedCurrentColumn = CurrentColumn;
currentColumn = GetCurrentColumnReadOnyWrapModel (true);
if (selecting) {
SetClipboard (GetRegion ());
copyWithoutSelection = false;
Expand All @@ -3813,7 +3790,6 @@ public void Copy ()
SetClipboard (ustring.Make (currentLine));
copyWithoutSelection = true;
}
currentColumn = savedCurrentColumn;
UpdateWrapModel ();
DoNeededAction ();
}
Expand Down
20 changes: 10 additions & 10 deletions UnitTests/ScrollBarViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ public void Hosting_ShowBothScrollIndicator_Invisible ()
Assert.True (textView.WordWrap);
Assert.True (scrollBar.AutoHideScrollBars);
Assert.Equal (7, textView.Lines);
Assert.Equal (23, textView.Maxlength);
Assert.Equal (22, textView.Maxlength);
Assert.Equal (0, textView.LeftColumn);
Assert.Equal (0, scrollBar.Position);
Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
Expand All @@ -754,8 +754,8 @@ public void Hosting_ShowBothScrollIndicator_Invisible ()
│This is the help text │
│for the Second Step. │
│ │
│Press the button to see
│ a message box.
│Press the button to
see a message box. │
│ │
│Enter name too. │
│ │
Expand All @@ -780,21 +780,21 @@ public void Hosting_ShowBothScrollIndicator_Invisible ()

Assert.True (textView.WordWrap);
Assert.True (scrollBar.AutoHideScrollBars);
Assert.Equal (19, textView.Lines);
Assert.Equal (20, textView.Lines);
Assert.Equal (7, textView.Maxlength);
Assert.Equal (0, textView.LeftColumn);
Assert.Equal (0, scrollBar.Position);
Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
expected = @"
┌ Test ──┐
│This is▲│
│ the ┬│
│This ▲│
is the ┬│
│help ││
│text ┴│
│for the░│
Second░│
Step. ░│
▼│
│for ░│
the ░│
Second ░│
Step. ▼│
└────────┘
";

Expand Down
61 changes: 57 additions & 4 deletions UnitTests/TextFormatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2006,10 +2006,33 @@ public void WordWrap_preserveTrailingSpaces ()
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.ConsoleWidth));
Assert.Equal ("A sentence has", wrappedLines [0].ToString ());
Assert.Equal (" words.", wrappedLines [1].ToString ());
Assert.Equal ("A sentence ", wrappedLines [0].ToString ());
Assert.Equal ("has words.", wrappedLines [1].ToString ());
Assert.True (wrappedLines.Count == 2);

maxWidth = 8;
expectedClippedWidth = 8;
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.ConsoleWidth));
Assert.Equal ("A ", wrappedLines [0].ToString ());
Assert.Equal ("sentence", wrappedLines [1].ToString ());
Assert.Equal (" has ", wrappedLines [2].ToString ());
Assert.Equal ("words.", wrappedLines [^1].ToString ());
Assert.True (wrappedLines.Count == 4);

maxWidth = 6;
expectedClippedWidth = 6;
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.ConsoleWidth));
Assert.Equal ("A ", wrappedLines [0].ToString ());
Assert.Equal ("senten", wrappedLines [1].ToString ());
Assert.Equal ("ce ", wrappedLines [2].ToString ());
Assert.Equal ("has ", wrappedLines [3].ToString ());
Assert.Equal ("words.", wrappedLines [^1].ToString ());
Assert.True (wrappedLines.Count == 5);

maxWidth = 3;
expectedClippedWidth = 3;
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
Expand Down Expand Up @@ -2312,6 +2335,18 @@ public void WordWrap_preserveTrailingSpaces_With_Tab ()
Assert.Equal ("words.", wrappedLines [2].ToString ());
Assert.True (wrappedLines.Count == 3);

maxWidth = 8;
expectedClippedWidth = 8;
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
Assert.Equal ("A ", wrappedLines [0].ToString ());
Assert.Equal ("sentence", wrappedLines [1].ToString ());
Assert.Equal ("\t\t", wrappedLines [2].ToString ());
Assert.Equal ("\t ", wrappedLines [3].ToString ());
Assert.Equal ("has ", wrappedLines [4].ToString ());
Assert.Equal ("words.", wrappedLines [^1].ToString ());
Assert.True (wrappedLines.Count == 6);

maxWidth = 3;
expectedClippedWidth = 3;
wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
Expand Down Expand Up @@ -2873,8 +2908,8 @@ public void Format_WordWrap_preserveTrailingSpaces ()
Assert.Equal (" A ", list2 [0].ToString ());
Assert.Equal ("sent", list2 [1].ToString ());
Assert.Equal ("ence", list2 [2].ToString ());
Assert.Equal (" has", list2 [3].ToString ());
Assert.Equal (" ", list2 [4].ToString ());
Assert.Equal (" ", list2 [3].ToString ());
Assert.Equal ("has ", list2 [4].ToString ());
Assert.Equal ("word", list2 [5].ToString ());
Assert.Equal ("s. ", list2 [6].ToString ());
Assert.Equal (" ", list2 [7].ToString ());
Expand Down Expand Up @@ -3959,5 +3994,23 @@ public void AutoSize_False_View_Width_Zero_Returns_Minimum_Width_With_Wide_Rune
pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
Assert.Equal (new Rect (0, 0, 4, 10), pos);
}

[Fact]
public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces ()
{
var text = $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}";
var width = 60;
var preserveTrailingSpaces = false;
var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
Assert.Equal ("Line1", formated [0]);
Assert.Equal ("Line2", formated [1]);
Assert.Equal ("Line3", formated [^1]);

preserveTrailingSpaces = true;
formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
Assert.Equal ("Line1", formated [0]);
Assert.Equal ("Line2", formated [1]);
Assert.Equal ("Line3", formated [^1]);
}
}
}
43 changes: 22 additions & 21 deletions UnitTests/TextViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1967,17 +1967,16 @@ public void WordWrap_WrapModel_Output ()

tv.Redraw (tv.Bounds);

string expected = @"
GraphViewTests.AssertDriverContentsWithFrameAre (@"
This is
the first
line.
the
first
line.
This is
the
second
line.
";

GraphViewTests.AssertDriverContentsAre (expected, output);
the
second
line.
", output);
}

[Fact]
Expand Down Expand Up @@ -2050,26 +2049,28 @@ public void WordWrap_ReadOnly_CursorPosition_SelectedText_Copy ()
Application.Top.Add (tv);

tv.Redraw (tv.Bounds);
GraphViewTests.AssertDriverContentsAre (@"
This is
GraphViewTests.AssertDriverContentsWithFrameAre (@"
This is
the first
line.
This is
the second
line.
line.
This is
the
second
line.
", output);

tv.ReadOnly = true;
tv.CursorPosition = new Point (6, 2);
Assert.Equal (new Point (5, 2), tv.CursorPosition);
tv.Redraw (tv.Bounds);
GraphViewTests.AssertDriverContentsAre (@"
This is
GraphViewTests.AssertDriverContentsWithFrameAre (@"
This is
the first
line.
This is
the second
line.
line.
This is
the
second
line.
", output);

tv.SelectionStartRow = 0;
Expand Down

0 comments on commit a23c1be

Please sign in to comment.