Browse Source

Merge branch 'main' of tig:migueldeicaza/gui.cs

Charlie Kindel 4 years ago
parent
commit
60dc242f26

+ 17 - 0
.github/dependabot.yml

@@ -0,0 +1,17 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    # default location of `.github/workflows`
+    directory: "/"
+    open-pull-requests-limit: 10
+    schedule:
+      interval: "weekly"
+
+  - package-ecosystem: "nuget"
+    # location of package manifests
+    directory: "/"
+    open-pull-requests-limit: 10
+    schedule:
+      interval: "daily"
+
+# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation)

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -206,7 +206,7 @@
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="true" />
     <PackageReference Include="NStack.Core" Version="0.16.0" />
     <PackageReference Include="NStack.Core" Version="0.16.0" />
-    <PackageReference Include="MinVer" Version="2.4.0">
+    <PackageReference Include="MinVer" Version="2.5.0">
       <PrivateAssets>all</PrivateAssets>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
     </PackageReference>  
     </PackageReference>  

+ 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);
+		}
 	}
 	}
 }
 }

+ 1 - 1
docfx/articles/overview.md

@@ -44,7 +44,7 @@ In the example above, you can see that we have initialized the runtime by callin
 [`Init`](../api/Terminal.Gui/Terminal.Gui.Application.html#Terminal_Gui_Application_Init) method in the Application class - this sets up the environment, initializes the color
 [`Init`](../api/Terminal.Gui/Terminal.Gui.Application.html#Terminal_Gui_Application_Init) method in the Application class - this sets up the environment, initializes the color
 schemes available for your application and clears the screen to start your application.
 schemes available for your application and clears the screen to start your application.
 
 
-The [`Application`](../api/Terminal.Gui/Terminal.Gui.Application.html) class, additionally creates an instance of the [Toplevel]((../api/Terminal.Gui/Terminal.Gui.Toplevel.html) class that is ready to be consumed, 
+The [`Application`](../api/Terminal.Gui/Terminal.Gui.Application.html) class, additionally creates an instance of the [`Toplevel`](../api/Terminal.Gui/Terminal.Gui.Toplevel.html) class that is ready to be consumed, 
 this instance is available in the `Application.Top` property, and can be used like this:
 this instance is available in the `Application.Top` property, and can be used like this:
 
 
 ```csharp
 ```csharp