浏览代码

Fixes #1211. Added support to TextView for word based operations Ctrl+Del and Ctrl+Backspace. (#1212)

* Fixes #1211. Added support to TextView for word based operations Ctrl+Del and Ctrl+Backspace,

* Updating nuget packages.

* Revert "Updating nuget packages."

This reverts commit e7afc56a679bcfac755e08f587b64e03ab631a32.

* Removed commented code.

* Replacing '\r\n' with System.Environment.NewLine due the unit tests errors on the server.

* Ensures UpdateWrapModel is always processed after a SetWrapModel in the cases where WordWrap is true.

* Added new feature where pressing Ctrl+C copies the  entire current line into the clipboard if there are no selection.

* Fixes copy and paste without selection.

* Fixing Copy/Paste and unit tests.

* Added RemoveRange method to the WordWrapManager and more unit tests.
BDisp 4 年之前
父节点
当前提交
0fe512d198
共有 2 个文件被更改,包括 777 次插入74 次删除
  1. 214 73
      Terminal.Gui/Views/TextView.cs
  2. 563 1
      UnitTests/TextViewTests.cs

+ 214 - 73
Terminal.Gui/Views/TextView.cs

@@ -689,6 +689,21 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
+		public bool RemoveRange (int row, int index, int count)
+		{
+			var modelRow = GetModelLineFromWrappedLines (row);
+			var line = GetCurrentLine (modelRow);
+			var modelCol = GetModelColFromWrappedLines (row, index);
+
+			try {
+				line.RemoveRange (modelCol, count);
+			} catch (Exception) {
+				return false;
+			}
+
+			return true;
+		}
+
 		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)
 		{
 		{
@@ -1568,7 +1583,7 @@ namespace Terminal.Gui {
 
 
 		void SetClipboard (ustring text)
 		void SetClipboard (ustring text)
 		{
 		{
-			if (!text.IsEmpty) {
+			if (text != null) {
 				Clipboard.Contents = text;
 				Clipboard.Contents = text;
 			}
 			}
 		}
 		}
@@ -1775,7 +1790,6 @@ namespace Terminal.Gui {
 		{
 		{
 			int restCount;
 			int restCount;
 			List<Rune> rest;
 			List<Rune> rest;
-			bool lineRemoved = false;
 
 
 			// Handle some state here - whether the last command was a kill
 			// Handle some state here - whether the last command was a kill
 			// operation and the column tracking (up/down)
 			// operation and the column tracking (up/down)
@@ -1946,38 +1960,8 @@ namespace Terminal.Gui {
 					ClearSelectedRegion ();
 					ClearSelectedRegion ();
 					return true;
 					return true;
 				}
 				}
-				if (currentColumn > 0) {
-					// Delete backwards 
-					currentLine = GetCurrentLine ();
-					currentLine.RemoveAt (currentColumn - 1);
-					if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) {
-						wrapNeeded = true;
-					}
-					currentColumn--;
-					if (currentColumn < leftColumn) {
-						leftColumn--;
-						SetNeedsDisplay ();
-					} else
-						SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width));
-				} else {
-					// Merges the current line with the previous one.
-					if (currentRow == 0)
-						return true;
-					var prowIdx = currentRow - 1;
-					var prevRow = model.GetLine (prowIdx);
-					var prevCount = prevRow.Count;
-					model.GetLine (prowIdx).AddRange (GetCurrentLine ());
-					model.RemoveLine (currentRow);
-					if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out lineRemoved, false)) {
-						wrapNeeded = true;
-					}
-					currentRow--;
-					if (wrapNeeded && !lineRemoved) {
-						currentColumn = Math.Max (prevCount - 1, 0);
-					} else {
-						currentColumn = prevCount;
-					}
-					Adjust ();
+				if (DeleteTextBackwards ()) {
+					return true;
 				}
 				}
 				break;
 				break;
 
 
@@ -2001,25 +1985,8 @@ namespace Terminal.Gui {
 					ClearSelectedRegion ();
 					ClearSelectedRegion ();
 					return true;
 					return true;
 				}
 				}
-				currentLine = GetCurrentLine ();
-				if (currentColumn == currentLine.Count) {
-					if (currentRow + 1 == model.Count)
-						break;
-					var nextLine = model.GetLine (currentRow + 1);
-					currentLine.AddRange (nextLine);
-					model.RemoveLine (currentRow + 1);
-					if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) {
-						wrapNeeded = true;
-					}
-					var sr = currentRow - topRow;
-					SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
-				} else {
-					currentLine.RemoveAt (currentColumn);
-					if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) {
-						wrapNeeded = true;
-					}
-					var r = currentRow - topRow;
-					SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
+				if (DeleteTextForwards ()) {
+					return true;
 				}
 				}
 				break;
 				break;
 
 
@@ -2038,14 +2005,19 @@ namespace Terminal.Gui {
 				break;
 				break;
 
 
 			case Key.K | Key.CtrlMask: // kill-to-end
 			case Key.K | Key.CtrlMask: // kill-to-end
+			case Key.DeleteChar | Key.CtrlMask | Key.ShiftMask:
 				if (isReadOnly)
 				if (isReadOnly)
 					break;
 					break;
-				var cRow = currentRow;
-				SetWrapModel ();
 				currentLine = GetCurrentLine ();
 				currentLine = GetCurrentLine ();
 				var setLastWasKill = true;
 				var setLastWasKill = true;
+				if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
+					DeleteTextForwards ();
+					return true;
+				}
 				if (currentLine.Count == 0) {
 				if (currentLine.Count == 0) {
-					model.RemoveLine (currentRow);
+					if (currentRow < model.Count - 1) {
+						model.RemoveLine (currentRow);
+					}
 					if (model.Count > 0 || lastWasKill) {
 					if (model.Count > 0 || lastWasKill) {
 						var val = ustring.Make ((Rune)'\n');
 						var val = ustring.Make ((Rune)'\n');
 						if (lastWasKill) {
 						if (lastWasKill) {
@@ -2058,9 +2030,6 @@ namespace Terminal.Gui {
 						// Prevents from adding line feeds if there is no more lines.
 						// Prevents from adding line feeds if there is no more lines.
 						setLastWasKill = false;
 						setLastWasKill = false;
 					}
 					}
-					if (currentRow > 0) {
-						currentRow--;
-					}
 				} else {
 				} else {
 					restCount = currentLine.Count - currentColumn;
 					restCount = currentLine.Count - currentColumn;
 					rest = currentLine.GetRange (currentColumn, restCount);
 					rest = currentLine.GetRange (currentColumn, restCount);
@@ -2074,20 +2043,44 @@ namespace Terminal.Gui {
 					} else {
 					} else {
 						SetClipboard (val);
 						SetClipboard (val);
 					}
 					}
-					if (currentColumn == 0) {
+					currentLine.RemoveRange (currentColumn, restCount);
+				}
+				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+				lastWasKill = setLastWasKill;
+				break;
+
+			case Key.Backspace | Key.CtrlMask | Key.ShiftMask: // kill-to-start
+				if (isReadOnly)
+					break;
+				currentLine = GetCurrentLine ();
+				setLastWasKill = true;
+				if (currentLine.Count > 0 && currentColumn == 0) {
+					DeleteTextBackwards ();
+					return true;
+				}
+				if (currentLine.Count == 0) {
+					if (currentRow > 0) {
 						model.RemoveLine (currentRow);
 						model.RemoveLine (currentRow);
-						if (currentRow > 0) {
-							currentRow--;
-						}
-					} else {
-						currentLine.RemoveRange (currentColumn, restCount);
+						currentRow--;
+						currentLine = model.GetLine (currentRow);
+						currentColumn = currentLine.Count;
 					}
 					}
-					if (model.Count == 0) {
-						// Prevents from adding line feeds if there is no more lines.
-						setLastWasKill = false;
+				} else {
+					restCount = currentColumn;
+					rest = currentLine.GetRange (0, restCount);
+					var val = ustring.Empty;
+					if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
+						val = ustring.Make ((Rune)'\n');
 					}
 					}
+					val += StringFromRunes (rest);
+					if (lastWasKill) {
+						AppendClipboard (val);
+					} else {
+						SetClipboard (val);
+					}
+					currentLine.RemoveRange (0, restCount);
+					currentColumn = 0;
 				}
 				}
-				UpdateWrapModel ();
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 				lastWasKill = setLastWasKill;
 				lastWasKill = setLastWasKill;
 				break;
 				break;
@@ -2148,6 +2141,65 @@ namespace Terminal.Gui {
 				Adjust ();
 				Adjust ();
 				break;
 				break;
 
 
+			case Key.DeleteChar | Key.CtrlMask: // kill-word-forwards
+				if (isReadOnly)
+					break;
+				currentLine = GetCurrentLine ();
+				if (currentLine.Count == 0 || currentColumn == currentLine.Count) {
+					DeleteTextForwards ();
+					return true;
+				}
+				newPos = WordForward (currentColumn, currentRow);
+				restCount = 0;
+				if (newPos.HasValue && currentRow == newPos.Value.row) {
+					restCount = newPos.Value.col - currentColumn;
+					currentLine.RemoveRange (currentColumn, restCount);
+				} else if (newPos.HasValue) {
+					restCount = currentLine.Count - currentColumn;
+					currentLine.RemoveRange (currentColumn, restCount);
+				}
+				if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
+					wrapNeeded = true;
+				}
+				if (wrapNeeded) {
+					SetNeedsDisplay ();
+				} else {
+					SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+				}
+				break;
+
+			case Key.Backspace | Key.CtrlMask: // kill-word-backwards
+				if (isReadOnly)
+					break;
+				currentLine = GetCurrentLine ();
+				if (currentColumn == 0) {
+					DeleteTextBackwards ();
+					return true;
+				}
+				newPos = WordBackward (currentColumn, currentRow);
+				if (newPos.HasValue && currentRow == newPos.Value.row) {
+					restCount = currentColumn - newPos.Value.col;
+					currentLine.RemoveRange (newPos.Value.col, restCount);
+					if (wordWrap && wrapManager.RemoveRange (currentRow, newPos.Value.col, restCount)) {
+						wrapNeeded = true;
+					}
+					currentColumn = newPos.Value.col;
+				} else if (newPos.HasValue) {
+					restCount = currentLine.Count - currentColumn;
+					currentLine.RemoveRange (currentColumn, restCount);
+					if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
+						wrapNeeded = true;
+					}
+					currentColumn = newPos.Value.col;
+					currentRow = newPos.Value.row;
+				}
+				if (wrapNeeded) {
+					SetNeedsDisplay ();
+				} else {
+					SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+				}
+				break;
+
 			case Key.Enter:
 			case Key.Enter:
 				if (isReadOnly)
 				if (isReadOnly)
 					break;
 					break;
@@ -2255,13 +2307,88 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		bool DeleteTextForwards ()
+		{
+			var currentLine = GetCurrentLine ();
+			if (currentColumn == currentLine.Count) {
+				if (currentRow + 1 == model.Count)
+					return true;
+				var nextLine = model.GetLine (currentRow + 1);
+				currentLine.AddRange (nextLine);
+				model.RemoveLine (currentRow + 1);
+				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) {
+					wrapNeeded = true;
+				}
+				var sr = currentRow - topRow;
+				SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
+			} else {
+				currentLine.RemoveAt (currentColumn);
+				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) {
+					wrapNeeded = true;
+				}
+				var r = currentRow - topRow;
+				SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
+			}
+
+			return false;
+		}
+
+		bool DeleteTextBackwards ()
+		{
+			if (currentColumn > 0) {
+				// Delete backwards 
+				var currentLine = GetCurrentLine ();
+				currentLine.RemoveAt (currentColumn - 1);
+				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) {
+					wrapNeeded = true;
+				}
+				currentColumn--;
+				if (currentColumn < leftColumn) {
+					leftColumn--;
+					SetNeedsDisplay ();
+				} else
+					SetNeedsDisplay (new Rect (0, currentRow - topRow, 1, Frame.Width));
+			} else {
+				// Merges the current line with the previous one.
+				if (currentRow == 0)
+					return true;
+				var prowIdx = currentRow - 1;
+				var prevRow = model.GetLine (prowIdx);
+				var prevCount = prevRow.Count;
+				model.GetLine (prowIdx).AddRange (GetCurrentLine ());
+				model.RemoveLine (currentRow);
+				bool lineRemoved = false;
+				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out lineRemoved, false)) {
+					wrapNeeded = true;
+				}
+				currentRow--;
+				if (wrapNeeded && !lineRemoved) {
+					currentColumn = Math.Max (prevCount - 1, 0);
+				} else {
+					currentColumn = prevCount;
+				}
+				Adjust ();
+			}
+
+			return false;
+		}
+
+		bool copyWithoutSelection;
+
 		/// <summary>
 		/// <summary>
 		/// Copy the selected text to the clipboard contents.
 		/// Copy the selected text to the clipboard contents.
 		/// </summary>
 		/// </summary>
 		public void Copy ()
 		public void Copy ()
 		{
 		{
 			SetWrapModel ();
 			SetWrapModel ();
-			SetClipboard (GetRegion ());
+			if (selecting) {
+				SetClipboard (GetRegion ());
+				copyWithoutSelection = false;
+			} else {
+				var currentLine = GetCurrentLine ();
+				SetClipboard (ustring.Make (currentLine));
+				copyWithoutSelection = true;
+			}
 			UpdateWrapModel ();
 			UpdateWrapModel ();
 			DoNeededAction ();
 			DoNeededAction ();
 		}
 		}
@@ -2291,10 +2418,17 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			SetWrapModel ();
 			SetWrapModel ();
-			if (selecting) {
-				ClearRegion ();
+			if (copyWithoutSelection) {
+				var runeList = Clipboard.Contents == null ? new List<Rune> () : Clipboard.Contents.ToRuneList ();
+				model.AddLine (currentRow, runeList);
+				currentRow++;
+			} else {
+				if (selecting) {
+					ClearRegion ();
+				}
+				InsertText (Clipboard.Contents);
+				copyWithoutSelection = false;
 			}
 			}
-			InsertText (Clipboard.Contents);
 			UpdateWrapModel ();
 			UpdateWrapModel ();
 			selecting = false;
 			selecting = false;
 			DoNeededAction ();
 			DoNeededAction ();
@@ -2538,15 +2672,22 @@ namespace Terminal.Gui {
 							}
 							}
 						}
 						}
 						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) {
 						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) {
+							if (lastValidCol > -1) {
+								nCol = lastValidCol;
+							}
 							return;
 							return;
 						}
 						}
 						while (MovePrev (ref nCol, ref nRow, out nRune)) {
 						while (MovePrev (ref nCol, ref nRow, out nRune)) {
 							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
 							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
 								break;
 								break;
+							if (nRow != fromRow) {
+								break;
+							}
 							lastValidCol = nCol;
 							lastValidCol = nCol;
 						}
 						}
 						if (lastValidCol > -1) {
 						if (lastValidCol > -1) {
 							nCol = lastValidCol;
 							nCol = lastValidCol;
+							nRow = fromRow;
 						}
 						}
 					} else {
 					} else {
 						if (!MovePrev (ref nCol, ref nRow, out nRune)) {
 						if (!MovePrev (ref nCol, ref nRow, out nRune)) {

+ 563 - 1
UnitTests/TextViewTests.cs

@@ -1,4 +1,5 @@
-using Xunit;
+using System;
+using Xunit;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	public class TextViewTests {
 	public class TextViewTests {
@@ -648,6 +649,549 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		[Fact]
+		public void WordBackward_Multiline_With_Selection ()
+		{
+			//		          4         3          2         1
+			//		  87654321098765432109876 54321098765432109876543210-Length
+			//			    1         2              1         2
+			//                01234567890123456789012  0123456789012345678901234
+			_textView.Text = "This is the first line.\nThis is the second line.";
+
+			_textView.MoveEnd ();
+			_textView.SelectionStartColumn = _textView.CurrentColumn;
+			_textView.SelectionStartRow = _textView.CurrentRow;
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (19, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (5, _textView.SelectedLength);
+					Assert.Equal ("line.", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (12, _textView.SelectedLength);
+					Assert.Equal ("second line.", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (16, _textView.SelectedLength);
+					Assert.Equal ("the second line.", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (19, _textView.SelectedLength);
+					Assert.Equal ("is the second line.", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (24, _textView.SelectedLength);
+					Assert.Equal ("This is the second line.", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (25, _textView.SelectedLength);
+					Assert.Equal ("\nThis is the second line.", _textView.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (18, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (30, _textView.SelectedLength);
+					Assert.Equal ("line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (36, _textView.SelectedLength);
+					Assert.Equal ("first line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (40, _textView.SelectedLength);
+					Assert.Equal ("the first line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (43, _textView.SelectedLength);
+					Assert.Equal ("is the first line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				case 10:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (48, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_Multiline_With_Selection ()
+		{
+			//			    1         2          3         4
+			//		  01234567890123456789012 34567890123456789012345678-Length
+			//			    1         2              1         2
+			//                01234567890123456789012  0123456789012345678901234
+			_textView.Text = "This is the first line.\nThis is the second line.";
+
+			_textView.SelectionStartColumn = _textView.CurrentColumn;
+			_textView.SelectionStartRow = _textView.CurrentRow;
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (5, _textView.SelectedLength);
+					Assert.Equal ("This ", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (8, _textView.SelectedLength);
+					Assert.Equal ("This is ", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (12, _textView.SelectedLength);
+					Assert.Equal ("This is the ", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (18, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (18, _textView.SelectedLength);
+					Assert.Equal ("This is the first ", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (23, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (24, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\n", _textView.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (29, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis ", _textView.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (32, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis is ", _textView.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (36, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis is the ", _textView.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (19, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (43, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis is the second ", _textView.SelectedText);
+					break;
+				case 10:
+					Assert.Equal (24, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (48, _textView.SelectedLength);
+					Assert.Equal ("This is the first line.\nThis is the second line.", _textView.SelectedText);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_To_End_Delete_Forwards_And_Copy_To_The_Clipboard ()
+		{
+			_textView.Text = "This is the first line.\nThis is the second line.";
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ($"{System.Environment.NewLine}This is the second line.", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the second line.", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					_textView.Paste ();
+					Assert.Equal ("This is the second line.", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_To_Start_Delete_Backwards_And_Copy_To_The_Clipboard ()
+		{
+			_textView.Text = "This is the first line.\nThis is the second line.";
+			_textView.MoveEnd ();
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ($"This is the first line.{System.Environment.NewLine}", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line.", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					_textView.Paste ();
+					Assert.Equal ("This is the first line.", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_Delete_WordForward ()
+		{
+			_textView.Text = "This is the first line.";
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("is the first line.", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("the first line.", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("first line.", _textView.Text);
+					break;
+				case 3:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("line.", _textView.Text);
+					break;
+				case 4:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_Delete_WordBackward ()
+		{
+			_textView.Text = "This is the first line.";
+			_textView.MoveEnd ();
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (18, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first ", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the ", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is ", _textView.Text);
+					break;
+				case 3:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This ", _textView.Text);
+					break;
+				case 4:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_Delete_WordForward_Multiline ()
+		{
+			_textView.Text = "This is the first line.\nThis is the second line.";
+			_textView.Width = 4;
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("is the first line." + Environment.NewLine
+						+ "This is the second line.", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("the first line." + Environment.NewLine
+						+ "This is the second line.", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("first line." + Environment.NewLine
+						+ "This is the second line.", _textView.Text);
+					break;
+				case 3:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("line." + Environment.NewLine
+						+ "This is the second line.", _textView.Text);
+					break;
+				case 4:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("" + Environment.NewLine
+						+ "This is the second line.", _textView.Text);
+					break;
+				case 5:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the second line.", _textView.Text);
+					break;
+				case 6:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("is the second line.", _textView.Text);
+					break;
+				case 7:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("the second line.", _textView.Text);
+					break;
+				case 8:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("second line.", _textView.Text);
+					break;
+				case 9:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("line.", _textView.Text);
+					break;
+				case 10:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Kill_Delete_WordBackward_Multiline ()
+		{
+			_textView.Text = "This is the first line.\nThis is the second line.";
+			_textView.Width = 4;
+			_textView.MoveEnd ();
+			var iteration = 0;
+			bool iterationsFinished = false;
+
+			while (!iterationsFinished) {
+				_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (19, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine
+						+ "This is the second ", _textView.Text);
+					break;
+				case 1:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine
+						+ "This is the ", _textView.Text);
+					break;
+				case 2:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine
+						+ "This is ", _textView.Text);
+					break;
+				case 3:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine
+						+ "This ", _textView.Text);
+					break;
+				case 4:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine, _textView.Text);
+					break;
+				case 5:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line.", _textView.Text);
+					break;
+				case 6:
+					Assert.Equal (18, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first ", _textView.Text);
+					break;
+				case 7:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the ", _textView.Text);
+					break;
+				case 8:
+					Assert.Equal (8, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is ", _textView.Text);
+					break;
+				case 9:
+					Assert.Equal (5, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This ", _textView.Text);
+					break;
+				case 10:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("", _textView.Text);
+					break;
+				default:
+					iterationsFinished = true;
+					break;
+				}
+				iteration++;
+			}
+		}
+
 		[Fact]
 		[Fact]
 		public void Copy_Or_Cut_Null_If_No_Selection ()
 		public void Copy_Or_Cut_Null_If_No_Selection ()
 		{
 		{
@@ -793,5 +1337,23 @@ namespace Terminal.Gui {
 			_textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
 			_textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
 			Assert.Equal ("TAB to jumusedtween text fields.", _textView.Text);
 			Assert.Equal ("TAB to jumusedtween text fields.", _textView.Text);
 		}
 		}
+
+		[Fact]
+		public void Copy_Without_Selection ()
+		{
+			_textView.Text = "This is the first line.\nThis is the second line.\n";
+			_textView.CursorPosition = new Point (0, _textView.Lines - 1);
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ()));
+			_textView.Paste ();
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
+			_textView.CursorPosition = new Point (3, 1);
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ()));
+			_textView.Paste ();
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
+			Assert.Equal (new Point (3, 2), _textView.CursorPosition);
+			_textView.Paste ();
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
+			Assert.Equal (new Point (3, 3), _textView.CursorPosition);
+		}
 	}
 	}
 }
 }