Browse Source

Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop

Tig Kindel 3 years ago
parent
commit
4791e7e8d2

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -12,7 +12,7 @@
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
     <PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
-    <PackageReference Include="ReactiveUI" Version="18.2.5" />
+    <PackageReference Include="ReactiveUI" Version="18.3.1" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 99 - 23
Terminal.Gui/Views/TextView.cs

@@ -770,7 +770,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
 		public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
-			int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0)
+			int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0, bool preserveTrailingSpaces = true)
 		{
 		{
 			frameWidth = width;
 			frameWidth = width;
 
 
@@ -791,8 +791,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Model.Count; i++) {
 			for (int i = 0; i < Model.Count; i++) {
 				var line = Model.GetLine (i);
 				var line = Model.GetLine (i);
 				var wrappedLines = ToListRune (
 				var wrappedLines = ToListRune (
-					TextFormatter.Format (ustring.Make (line), width,
-					TextAlignment.Left, true, true, tabWidth));
+					TextFormatter.Format (ustring.Make (line), width, TextAlignment.Left, true, preserveTrailingSpaces, tabWidth));
 				int sumColWidth = 0;
 				int sumColWidth = 0;
 				for (int j = 0; j < wrappedLines.Count; j++) {
 				for (int j = 0; j < wrappedLines.Count; j++) {
 					var wrapLine = wrappedLines [j];
 					var wrapLine = wrappedLines [j];
@@ -920,12 +919,13 @@ namespace Terminal.Gui {
 			var line = GetCurrentLine (modelRow);
 			var line = GetCurrentLine (modelRow);
 			var modelCol = GetModelColFromWrappedLines (row, col);
 			var modelCol = GetModelColFromWrappedLines (row, col);
 
 
-			if (modelCol >= line.Count) {
+			if (modelCol > line.Count) {
 				Model.RemoveLine (modelRow);
 				Model.RemoveLine (modelRow);
 				RemoveAt (row, 0);
 				RemoveAt (row, 0);
 				return false;
 				return false;
 			}
 			}
-			line.RemoveAt (modelCol);
+			if (modelCol < line.Count)
+				line.RemoveAt (modelCol);
 			if (line.Count > frameWidth || (row + 1 < wrappedModelLines.Count
 			if (line.Count > frameWidth || (row + 1 < wrappedModelLines.Count
 				&& wrappedModelLines [row + 1].ModelLine == modelRow)) {
 				&& wrappedModelLines [row + 1].ModelLine == modelRow)) {
 				return true;
 				return true;
@@ -997,13 +997,43 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
 		public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
-			int row, int col, int startRow, int startCol)
+			int row, int col, int startRow, int startCol, bool preserveTrailingSpaces)
 		{
 		{
 			isWrapModelRefreshing = true;
 			isWrapModelRefreshing = true;
 			Model = model;
 			Model = model;
-			WrapModel (frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol);
+			WrapModel (frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol, tabWidth: 0, preserveTrailingSpaces);
 			isWrapModelRefreshing = false;
 			isWrapModelRefreshing = false;
 		}
 		}
+
+		public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManager)
+		{
+			if (wrappedModelLines?.Count == 0)
+				return 0;
+
+			var wModelLines = wrapManager.wrappedModelLines;
+			var modelLine = GetModelLineFromWrappedLines (line);
+			var firstLine = wrappedModelLines.IndexOf (r => r.ModelLine == modelLine);
+			int modelCol = 0;
+			int colWidthOffset = 0;
+			int i = firstLine;
+
+			while (modelCol < col) {
+				var wLine = wrappedModelLines [i];
+				var wLineToCompare = wModelLines [i];
+
+				if (wLine.ModelLine != modelLine || wLineToCompare.ModelLine != modelLine)
+					break;
+
+				modelCol += Math.Max (wLine.ColWidth, wLineToCompare.ColWidth);
+				colWidthOffset += wLine.ColWidth - wLineToCompare.ColWidth;
+				if (modelCol > col) {
+					modelCol += col - modelCol;
+				}
+				i++;
+			}
+
+			return modelCol - colWidthOffset;
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -1462,7 +1492,7 @@ namespace Terminal.Gui {
 				model.LoadString (value);
 				model.LoadString (value);
 				if (wordWrap) {
 				if (wordWrap) {
 					wrapManager = new WordWrapManager (model);
 					wrapManager = new WordWrapManager (model);
-					model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0), out _, out _, out _, out _);
+					model = wrapManager.WrapModel (frameWidth, out _, out _, out _, out _);
 				}
 				}
 				TextChanged?.Invoke ();
 				TextChanged?.Invoke ();
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
@@ -1484,12 +1514,12 @@ namespace Terminal.Gui {
 		void WrapTextModel ()
 		void WrapTextModel ()
 		{
 		{
 			if (wordWrap && wrapManager != null) {
 			if (wordWrap && wrapManager != null) {
-				model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0),
+				model = wrapManager.WrapModel (frameWidth,
 					out int nRow, out int nCol,
 					out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
 					currentRow, currentColumn,
 					selectionStartRow, selectionStartColumn,
 					selectionStartRow, selectionStartColumn,
-					tabWidth);
+					tabWidth, preserveTrailingSpaces: !ReadOnly);
 				currentRow = nRow;
 				currentRow = nRow;
 				currentColumn = nCol;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
 				selectionStartRow = nStartRow;
@@ -1498,6 +1528,8 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		int frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0);
+
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the top row.
 		/// Gets or sets the top row.
 		/// </summary>
 		/// </summary>
@@ -1506,7 +1538,14 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Gets or sets the left column.
 		/// Gets or sets the left column.
 		/// </summary>
 		/// </summary>
-		public int LeftColumn { get => leftColumn; set => leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); }
+		public int LeftColumn {
+			get => leftColumn;
+			set {
+				if (value > 0 && wordWrap)
+					return;
+				leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0);
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the maximum visible length line.
 		/// Gets the maximum visible length line.
@@ -1571,7 +1610,10 @@ namespace Terminal.Gui {
 				}
 				}
 
 
 				SetWrapModel ();
 				SetWrapModel ();
+				var savedCurrentColumn = CurrentColumn;
+				currentColumn = GetCurrentColumnReadOnyWrapModel (true);
 				var sel = GetRegion ();
 				var sel = GetRegion ();
+				currentColumn = savedCurrentColumn;
 				UpdateWrapModel ();
 				UpdateWrapModel ();
 				Adjust ();
 				Adjust ();
 
 
@@ -1607,7 +1649,7 @@ namespace Terminal.Gui {
 				ResetPosition ();
 				ResetPosition ();
 				if (wordWrap) {
 				if (wordWrap) {
 					wrapManager = new WordWrapManager (model);
 					wrapManager = new WordWrapManager (model);
-					model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0), out _, out _, out _, out _);
+					model = wrapManager.WrapModel (frameWidth, out _, out _, out _, out _);
 				} else if (!wordWrap && wrapManager != null) {
 				} else if (!wordWrap && wrapManager != null) {
 					model = wrapManager.Model;
 					model = wrapManager.Model;
 				}
 				}
@@ -1637,7 +1679,7 @@ namespace Terminal.Gui {
 		public int RightOffset {
 		public int RightOffset {
 			get => rightOffset;
 			get => rightOffset;
 			set {
 			set {
-				if (currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) {
+				if (!wordWrap && currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) {
 					leftColumn = Math.Max (leftColumn - rightOffset, 0);
 					leftColumn = Math.Max (leftColumn - rightOffset, 0);
 				}
 				}
 				rightOffset = value;
 				rightOffset = value;
@@ -1794,9 +1836,18 @@ namespace Terminal.Gui {
 		/// <param name="path">Path to the file to load.</param>
 		/// <param name="path">Path to the file to load.</param>
 		public bool LoadFile (string path)
 		public bool LoadFile (string path)
 		{
 		{
-			var res = model.LoadFile (path);
-			ResetPosition ();
-			SetNeedsDisplay ();
+			bool res;
+			try {
+				SetWrapModel ();
+				res = model.LoadFile (path);
+				ResetPosition ();
+			} catch (Exception) {
+				throw;
+			} finally {
+				UpdateWrapModel ();
+				SetNeedsDisplay ();
+				Adjust ();
+			}
 			return res;
 			return res;
 		}
 		}
 
 
@@ -1939,8 +1990,15 @@ namespace Terminal.Gui {
 		public bool ReadOnly {
 		public bool ReadOnly {
 			get => isReadOnly;
 			get => isReadOnly;
 			set {
 			set {
-				isReadOnly = value;
-				SetNeedsDisplay ();
+				if (value != isReadOnly) {
+					isReadOnly = value;
+
+					SetWrapModel ();
+					currentColumn = GetCurrentColumnReadOnyWrapModel ();
+					UpdateWrapModel ();
+					SetNeedsDisplay ();
+					Adjust ();
+				}
 			}
 			}
 		}
 		}
 
 
@@ -2258,7 +2316,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);
+					selectionStartRow, selectionStartColumn, preserveTrailingSpaces: !ReadOnly);
 				currentRow = nRow;
 				currentRow = nRow;
 				currentColumn = nCol;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
 				selectionStartRow = nStartRow;
@@ -2269,6 +2327,21 @@ 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)
 		{
 		{
@@ -2542,8 +2615,8 @@ namespace Terminal.Gui {
 				leftColumn = TextModel.CalculateLeftColumn (line, leftColumn, currentColumn,
 				leftColumn = TextModel.CalculateLeftColumn (line, leftColumn, currentColumn,
 					Frame.Width + offB.width - RightOffset, TabWidth);
 					Frame.Width + offB.width - RightOffset, TabWidth);
 				need = true;
 				need = true;
-			} else if (dSize.size + RightOffset < Frame.Width + offB.width
-				&& tSize.size + RightOffset < Frame.Width + offB.width) {
+			} else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width
+				&& tSize.size + RightOffset < Frame.Width + offB.width)) {
 				leftColumn = 0;
 				leftColumn = 0;
 				need = true;
 				need = true;
 			}
 			}
@@ -2597,7 +2670,7 @@ namespace Terminal.Gui {
 				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
 				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
 			} else if (!wordWrap) {
 			} else if (!wordWrap) {
 				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height + RightOffset, TabWidth);
 				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height + RightOffset, TabWidth);
-				leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
+				leftColumn = Math.Max (!wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
 			}
 			}
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
@@ -3730,6 +3803,8 @@ 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;
@@ -3738,6 +3813,7 @@ namespace Terminal.Gui {
 				SetClipboard (ustring.Make (currentLine));
 				SetClipboard (ustring.Make (currentLine));
 				copyWithoutSelection = true;
 				copyWithoutSelection = true;
 			}
 			}
+			currentColumn = savedCurrentColumn;
 			UpdateWrapModel ();
 			UpdateWrapModel ();
 			DoNeededAction ();
 			DoNeededAction ();
 		}
 		}
@@ -4292,7 +4368,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public void ClearHistoryChanges ()
 		public void ClearHistoryChanges ()
 		{
 		{
-			historyText.Clear (Text);
+			historyText?.Clear (Text);
 		}
 		}
 	}
 	}
 
 

+ 3 - 1
Terminal.Gui/Windows/Wizard.cs

@@ -203,7 +203,9 @@ namespace Terminal.Gui {
 
 
 				base.Add (contentView);
 				base.Add (contentView);
 
 
-				helpTextView.ColorScheme = Colors.TopLevel;
+				helpTextView.ColorScheme = new ColorScheme () {  
+					Normal = new Attribute(Color.Gray, Color.DarkGray)
+				};
 				helpTextView.ReadOnly = true;
 				helpTextView.ReadOnly = true;
 				helpTextView.WordWrap = true;
 				helpTextView.WordWrap = true;
 				base.Add (helpTextView);
 				base.Add (helpTextView);

+ 0 - 2
UICatalog/Scenarios/Editor.cs

@@ -524,11 +524,9 @@ namespace UICatalog.Scenarios {
 			item.Action += () => {
 			item.Action += () => {
 				_textView.WordWrap = item.Checked = !item.Checked;
 				_textView.WordWrap = item.Checked = !item.Checked;
 				if (_textView.WordWrap) {
 				if (_textView.WordWrap) {
-					_scrollBar.AutoHideScrollBars = false;
 					_scrollBar.OtherScrollBarView.ShowScrollIndicator = false;
 					_scrollBar.OtherScrollBarView.ShowScrollIndicator = false;
 					_textView.BottomOffset = 0;
 					_textView.BottomOffset = 0;
 				} else {
 				} else {
-					_scrollBar.AutoHideScrollBars = true;
 					_textView.BottomOffset = 1;
 					_textView.BottomOffset = 1;
 				}
 				}
 			};
 			};

+ 31 - 3
UnitTests/ScrollBarViewTests.cs

@@ -709,6 +709,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.Equal (5, textView.Lines);
 			Assert.Equal (5, textView.Lines);
 			Assert.Equal (42, textView.Maxlength);
 			Assert.Equal (42, textView.Maxlength);
+			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);
 			var expected = @"
 			var expected = @"
@@ -744,7 +745,8 @@ 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 (22, textView.Maxlength);
+			Assert.Equal (23, textView.Maxlength);
+			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 = @"
@@ -752,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.         │
 │                        │
 │                        │
@@ -772,6 +774,32 @@ namespace Terminal.Gui.Views {
 
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 26, 20), pos);
 			Assert.Equal (new Rect (0, 0, 26, 20), pos);
+
+			((FakeDriver)Application.Driver).SetBufferSize (10, 10);
+			Application.Refresh ();
+
+			Assert.True (textView.WordWrap);
+			Assert.True (scrollBar.AutoHideScrollBars);
+			Assert.Equal (19, 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   ┬│
+│help   ││
+│text   ┴│
+│for the░│
+│ Second░│
+│ Step. ░│
+│       ▼│
+└────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 10, 10), pos);
 		}
 		}
 	}
 	}
 }
 }

+ 115 - 10
UnitTests/TextViewTests.cs

@@ -1850,22 +1850,28 @@ namespace Terminal.Gui.Views {
 		[Fact]
 		[Fact]
 		public void LoadFile_Throws_If_File_Is_Null ()
 		public void LoadFile_Throws_If_File_Is_Null ()
 		{
 		{
+			var result = false;
 			var tv = new TextView ();
 			var tv = new TextView ();
-			Assert.Throws<ArgumentNullException> (() => tv.LoadFile (null));
+			Assert.Throws<ArgumentNullException> (() => result = tv.LoadFile (null));
+			Assert.False (result);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
 		public void LoadFile_Throws_If_File_Is_Empty ()
 		public void LoadFile_Throws_If_File_Is_Empty ()
 		{
 		{
+			var result = false;
 			var tv = new TextView ();
 			var tv = new TextView ();
-			Assert.Throws<ArgumentException> (() => tv.LoadFile (""));
+			Assert.Throws<ArgumentException> (() => result = tv.LoadFile (""));
+			Assert.False (result);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
 		public void LoadFile_Throws_If_File_Not_Exist ()
 		public void LoadFile_Throws_If_File_Not_Exist ()
 		{
 		{
+			var result = false;
 			var tv = new TextView ();
 			var tv = new TextView ();
-			Assert.Throws<System.IO.FileNotFoundException> (() => tv.LoadFile ("blabla"));
+			Assert.Throws<System.IO.FileNotFoundException> (() => result = tv.LoadFile ("blabla"));
+			Assert.False (result);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -1962,19 +1968,118 @@ namespace Terminal.Gui.Views {
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
 			string expected = @"
 			string expected = @"
-This is 
-the 
-first 
-line.
-This is 
-the 
-second 
+This is
+the first
+ line.
+This is
+the
+second
 line.
 line.
 ";
 ";
 
 
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 		}
 		}
 
 
+		[Fact]
+		[AutoInitShutdown]
+		public void WordWrap_Deleting_Backwards ()
+		{
+			var tv = new TextView () {
+				Width = 5,
+				Height = 2,
+				WordWrap = true,
+				Text = "aaaa"
+			};
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aaaa
+", output);
+
+			tv.CursorPosition = new Point (5, 0);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aaa
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aa
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+a
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+
+", output);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void WordWrap_ReadOnly_CursorPosition_SelectedText_Copy ()
+		{
+			//          0123456789
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () { Width = 11, Height = 9 };
+			tv.Text = text;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+			tv.WordWrap = true;
+
+			Application.Top.Add (tv);
+
+			tv.Redraw (tv.Bounds);
+			GraphViewTests.AssertDriverContentsAre (@"
+This is
+the first
+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
+the first
+line.
+This is
+the second
+line.
+", output);
+
+			tv.SelectionStartRow = 0;
+			tv.SelectionStartColumn = 0;
+			Assert.Equal ("This is the first line.", tv.SelectedText);
+
+			tv.Copy ();
+			Assert.Equal ("This is the first line.", Clipboard.Contents);
+		}
+
 		[Fact]
 		[Fact]
 		public void Internal_Tests ()
 		public void Internal_Tests ()
 		{
 		{