浏览代码

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;
 		}
 
+		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,
 			int row, int col, int startRow, int startCol)
 		{
@@ -1568,7 +1583,7 @@ namespace Terminal.Gui {
 
 		void SetClipboard (ustring text)
 		{
-			if (!text.IsEmpty) {
+			if (text != null) {
 				Clipboard.Contents = text;
 			}
 		}
@@ -1775,7 +1790,6 @@ namespace Terminal.Gui {
 		{
 			int restCount;
 			List<Rune> rest;
-			bool lineRemoved = false;
 
 			// Handle some state here - whether the last command was a kill
 			// operation and the column tracking (up/down)
@@ -1946,38 +1960,8 @@ namespace Terminal.Gui {
 					ClearSelectedRegion ();
 					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;
 
@@ -2001,25 +1985,8 @@ namespace Terminal.Gui {
 					ClearSelectedRegion ();
 					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;
 
@@ -2038,14 +2005,19 @@ namespace Terminal.Gui {
 				break;
 
 			case Key.K | Key.CtrlMask: // kill-to-end
+			case Key.DeleteChar | Key.CtrlMask | Key.ShiftMask:
 				if (isReadOnly)
 					break;
-				var cRow = currentRow;
-				SetWrapModel ();
 				currentLine = GetCurrentLine ();
 				var setLastWasKill = true;
+				if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
+					DeleteTextForwards ();
+					return true;
+				}
 				if (currentLine.Count == 0) {
-					model.RemoveLine (currentRow);
+					if (currentRow < model.Count - 1) {
+						model.RemoveLine (currentRow);
+					}
 					if (model.Count > 0 || lastWasKill) {
 						var val = ustring.Make ((Rune)'\n');
 						if (lastWasKill) {
@@ -2058,9 +2030,6 @@ namespace Terminal.Gui {
 						// Prevents from adding line feeds if there is no more lines.
 						setLastWasKill = false;
 					}
-					if (currentRow > 0) {
-						currentRow--;
-					}
 				} else {
 					restCount = currentLine.Count - currentColumn;
 					rest = currentLine.GetRange (currentColumn, restCount);
@@ -2074,20 +2043,44 @@ namespace Terminal.Gui {
 					} else {
 						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);
-						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));
 				lastWasKill = setLastWasKill;
 				break;
@@ -2148,6 +2141,65 @@ namespace Terminal.Gui {
 				Adjust ();
 				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:
 				if (isReadOnly)
 					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>
 		/// Copy the selected text to the clipboard contents.
 		/// </summary>
 		public void Copy ()
 		{
 			SetWrapModel ();
-			SetClipboard (GetRegion ());
+			if (selecting) {
+				SetClipboard (GetRegion ());
+				copyWithoutSelection = false;
+			} else {
+				var currentLine = GetCurrentLine ();
+				SetClipboard (ustring.Make (currentLine));
+				copyWithoutSelection = true;
+			}
 			UpdateWrapModel ();
 			DoNeededAction ();
 		}
@@ -2291,10 +2418,17 @@ namespace Terminal.Gui {
 			}
 
 			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 ();
 			selecting = false;
 			DoNeededAction ();
@@ -2538,15 +2672,22 @@ namespace Terminal.Gui {
 							}
 						}
 						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) {
+							if (lastValidCol > -1) {
+								nCol = lastValidCol;
+							}
 							return;
 						}
 						while (MovePrev (ref nCol, ref nRow, out nRune)) {
 							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
 								break;
+							if (nRow != fromRow) {
+								break;
+							}
 							lastValidCol = nCol;
 						}
 						if (lastValidCol > -1) {
 							nCol = lastValidCol;
+							nRow = fromRow;
 						}
 					} else {
 						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 {
 	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]
 		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
 			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);
+		}
 	}
 }