Browse Source

Fixes #1925. Preserve trailing spaces on word wrap must be refactored… (#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 <[email protected]>
BDisp 3 years ago
parent
commit
a23c1bee19

+ 15 - 6
Terminal.Gui/Core/TextFormatter.cs

@@ -366,22 +366,28 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public bool NeedsFormat { get => needsFormat; set => needsFormat = value; }
 		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 ();
 			var runes = str.ToRuneList ();
 			for (int i = 0; i < runes.Count; i++) {
 			for (int i = 0; i < runes.Count; i++) {
 				switch (runes [i]) {
 				switch (runes [i]) {
 				case '\n':
 				case '\n':
-					runes.RemoveAt (i);
+					if (!keepNewLine) {
+						runes.RemoveAt (i);
+					}
 					break;
 					break;
 
 
 				case '\r':
 				case '\r':
 					if ((i + 1) < runes.Count && runes [i + 1] == '\n') {
 					if ((i + 1) < runes.Count && runes [i + 1] == '\n') {
 						runes.RemoveAt (i);
 						runes.RemoveAt (i);
-						runes.RemoveAt (i + 1);
+						if (!keepNewLine) {
+							runes.RemoveAt (i);
+						}
 						i++;
 						i++;
 					} else {
 					} else {
-						runes.RemoveAt (i);
+						if (!keepNewLine) {
+							runes.RemoveAt (i);
+						}
 					}
 					}
 					break;
 					break;
 				}
 				}
@@ -515,6 +521,7 @@ namespace Terminal.Gui {
 
 
 			int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
 			int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
 			{
 			{
+				var lastFrom = from;
 				var to = from;
 				var to = from;
 				var length = cLength;
 				var length = cLength;
 				incomplete = false;
 				incomplete = false;
@@ -552,8 +559,10 @@ namespace Terminal.Gui {
 					}
 					}
 					to++;
 					to++;
 				}
 				}
-				if (cLength > 0 && to < runes.Count && runes [to] != ' ') {
+				if (cLength > 0 && to < runes.Count && runes [to] != ' ' && runes [to] != '\t') {
 					return from;
 					return from;
+				} else if (cLength > 0 && to < runes.Count && (runes [to] == ' ' || runes [to] == '\t')) {
+					return lastFrom;
 				} else {
 				} else {
 					return to;
 					return to;
 				}
 				}
@@ -727,7 +736,7 @@ namespace Terminal.Gui {
 				return lineResult;
 				return lineResult;
 			}
 			}
 
 
-			var runes = text.ToRuneList ();
+			var runes = StripCRLF (text, true).ToRuneList ();
 			int runeCount = runes.Count;
 			int runeCount = runes.Count;
 			int lp = 0;
 			int lp = 0;
 			for (int i = 0; i < runeCount; i++) {
 			for (int i = 0; i < runeCount; i++) {

+ 2 - 26
Terminal.Gui/Views/TextView.cs

@@ -1519,7 +1519,7 @@ namespace Terminal.Gui {
 					out int nStartRow, out int nStartCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
 					currentRow, currentColumn,
 					selectionStartRow, selectionStartColumn,
 					selectionStartRow, selectionStartColumn,
-					tabWidth, preserveTrailingSpaces: !ReadOnly);
+					tabWidth, preserveTrailingSpaces: true);
 				currentRow = nRow;
 				currentRow = nRow;
 				currentColumn = nCol;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
 				selectionStartRow = nStartRow;
@@ -1610,10 +1610,7 @@ namespace Terminal.Gui {
 				}
 				}
 
 
 				SetWrapModel ();
 				SetWrapModel ();
-				var savedCurrentColumn = CurrentColumn;
-				currentColumn = GetCurrentColumnReadOnyWrapModel (true);
 				var sel = GetRegion ();
 				var sel = GetRegion ();
-				currentColumn = savedCurrentColumn;
 				UpdateWrapModel ();
 				UpdateWrapModel ();
 				Adjust ();
 				Adjust ();
 
 
@@ -1993,9 +1990,6 @@ namespace Terminal.Gui {
 				if (value != isReadOnly) {
 				if (value != isReadOnly) {
 					isReadOnly = value;
 					isReadOnly = value;
 
 
-					SetWrapModel ();
-					currentColumn = GetCurrentColumnReadOnyWrapModel ();
-					UpdateWrapModel ();
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 					Adjust ();
 					Adjust ();
 				}
 				}
@@ -2316,7 +2310,7 @@ namespace Terminal.Gui {
 				wrapManager.UpdateModel (model, out int nRow, out int nCol,
 				wrapManager.UpdateModel (model, out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
 					currentRow, currentColumn,
-					selectionStartRow, selectionStartColumn, preserveTrailingSpaces: !ReadOnly);
+					selectionStartRow, selectionStartColumn, preserveTrailingSpaces: true);
 				currentRow = nRow;
 				currentRow = nRow;
 				currentColumn = nCol;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
 				selectionStartRow = nStartRow;
@@ -2327,21 +2321,6 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
 				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/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
@@ -3803,8 +3782,6 @@ namespace Terminal.Gui {
 		public void Copy ()
 		public void Copy ()
 		{
 		{
 			SetWrapModel ();
 			SetWrapModel ();
-			var savedCurrentColumn = CurrentColumn;
-			currentColumn = GetCurrentColumnReadOnyWrapModel (true);
 			if (selecting) {
 			if (selecting) {
 				SetClipboard (GetRegion ());
 				SetClipboard (GetRegion ());
 				copyWithoutSelection = false;
 				copyWithoutSelection = false;
@@ -3813,7 +3790,6 @@ namespace Terminal.Gui {
 				SetClipboard (ustring.Make (currentLine));
 				SetClipboard (ustring.Make (currentLine));
 				copyWithoutSelection = true;
 				copyWithoutSelection = true;
 			}
 			}
-			currentColumn = savedCurrentColumn;
 			UpdateWrapModel ();
 			UpdateWrapModel ();
 			DoNeededAction ();
 			DoNeededAction ();
 		}
 		}

+ 10 - 10
UnitTests/ScrollBarViewTests.cs

@@ -745,7 +745,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (textView.WordWrap);
 			Assert.True (textView.WordWrap);
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.Equal (7, textView.Lines);
 			Assert.Equal (7, textView.Lines);
-			Assert.Equal (23, textView.Maxlength);
+			Assert.Equal (22, textView.Maxlength);
 			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
@@ -754,8 +754,8 @@ namespace Terminal.Gui.Views {
 │This is the help text   │
 │This is the help text   │
 │for the Second Step.    │
 │for the Second Step.    │
 │                        │
 │                        │
-│Press the button to see
-│ a message box.   
+│Press the button to    
+│see a message box.      │
 │                        │
 │                        │
 │Enter name too.         │
 │Enter name too.         │
 │                        │
 │                        │
@@ -780,21 +780,21 @@ namespace Terminal.Gui.Views {
 
 
 			Assert.True (textView.WordWrap);
 			Assert.True (textView.WordWrap);
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.True (scrollBar.AutoHideScrollBars);
-			Assert.Equal (19, textView.Lines);
+			Assert.Equal (20, textView.Lines);
 			Assert.Equal (7, textView.Maxlength);
 			Assert.Equal (7, textView.Maxlength);
 			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
 			expected = @"
 			expected = @"
 ┌ Test ──┐
 ┌ Test ──┐
-│This is▲│
-│ the   ┬│
+│This   ▲│
+│is the ┬│
 │help   ││
 │help   ││
 │text   ┴│
 │text   ┴│
-│for the░│
-│ Second░│
-│ Step. ░│
-│       ▼│
+│for    ░│
+│the    ░│
+│Second ░│
+│Step.  ▼│
 └────────┘
 └────────┘
 ";
 ";
 
 

+ 57 - 4
UnitTests/TextFormatterTests.cs

@@ -2006,10 +2006,33 @@ namespace Terminal.Gui.Core {
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
 			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
 			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
 			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.ConsoleWidth));
 			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);
 			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;
 			maxWidth = 3;
 			expectedClippedWidth = 3;
 			expectedClippedWidth = 3;
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
@@ -2312,6 +2335,18 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("words.", wrappedLines [2].ToString ());
 			Assert.Equal ("words.", wrappedLines [2].ToString ());
 			Assert.True (wrappedLines.Count == 3);
 			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;
 			maxWidth = 3;
 			expectedClippedWidth = 3;
 			expectedClippedWidth = 3;
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
 			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
@@ -2873,8 +2908,8 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (" A ", list2 [0].ToString ());
 			Assert.Equal (" A ", list2 [0].ToString ());
 			Assert.Equal ("sent", list2 [1].ToString ());
 			Assert.Equal ("sent", list2 [1].ToString ());
 			Assert.Equal ("ence", list2 [2].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 ("word", list2 [5].ToString ());
 			Assert.Equal ("s. ", list2 [6].ToString ());
 			Assert.Equal ("s. ", list2 [6].ToString ());
 			Assert.Equal (" ", list2 [7].ToString ());
 			Assert.Equal (" ", list2 [7].ToString ());
@@ -3959,5 +3994,23 @@ e
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			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]);
+		}
 	}
 	}
 }
 }

+ 22 - 21
UnitTests/TextViewTests.cs

@@ -1967,17 +1967,16 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			string expected = @"
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
 This is
 This is
-the first
- line.
+the    
+first  
+line.  
 This is
 This is
-the
-second
-line.
-";
-
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+the    
+second 
+line.  
+", output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -2050,26 +2049,28 @@ a
 			Application.Top.Add (tv);
 			Application.Top.Add (tv);
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
-			GraphViewTests.AssertDriverContentsAre (@"
-This is
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is  
 the first
 the first
-line.
-This is
-the second
- line.
+line.    
+This is  
+the      
+second   
+line.    
 ", output);
 ", output);
 
 
 			tv.ReadOnly = true;
 			tv.ReadOnly = true;
 			tv.CursorPosition = new Point (6, 2);
 			tv.CursorPosition = new Point (6, 2);
 			Assert.Equal (new Point (5, 2), tv.CursorPosition);
 			Assert.Equal (new Point (5, 2), tv.CursorPosition);
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
-			GraphViewTests.AssertDriverContentsAre (@"
-This is
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is  
 the first
 the first
-line.
-This is
-the second
-line.
+line.    
+This is  
+the      
+second   
+line.    
 ", output);
 ", output);
 
 
 			tv.SelectionStartRow = 0;
 			tv.SelectionStartRow = 0;