瀏覽代碼

Implementing HistoryText class for undo and redo text. (#1590)

* Implementing HistotyText class for undo and redo text.

* Untying newline based on 'n' but on Environment.NewLine.

* Prevent throwing exception if value is null.

* Also adding history text feature to the return key.

* Fixes WSL unit test failure.
BDisp 3 年之前
父節點
當前提交
6c21b1ee6e

+ 4 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -1481,7 +1481,10 @@ namespace Terminal.Gui {
 					Curses.raw ();
 					Curses.noecho ();
 				}
-				return result.TrimEnd ();
+				if (result.EndsWith ("\r\n")) {
+					result = result.Substring (0, result.Length - 2);
+				}
+				return result;
 			}
 		}
 

+ 1 - 2
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1902,8 +1902,7 @@ namespace Terminal.Gui {
 					Marshal.Copy (pointer, buff, 0, size);
 
 					return System.Text.Encoding.Unicode.GetString (buff)
-						.TrimEnd ('\0')
-						.Replace ("\r\n", "\n");
+						.TrimEnd ('\0');
 				} finally {
 					if (pointer != IntPtr.Zero)
 						GlobalUnlock (handle);

+ 1 - 1
Terminal.Gui/Core/Clipboard/Clipboard.cs

@@ -25,7 +25,7 @@ namespace Terminal.Gui {
 			}
 			set {
 				try {
-					if (IsSupported) {
+					if (IsSupported && value != null) {
 						Application.Driver.Clipboard.SetClipboardData (value.ToString ());
 					}
 					contents = value;

+ 44 - 33
Terminal.Gui/Views/TextField.cs

@@ -23,6 +23,7 @@ namespace Terminal.Gui {
 		int first, point;
 		int selectedStart = -1; // -1 represents there is no text selection.
 		ustring selectedText;
+		HistoryText historyText = new HistoryText ();
 
 		/// <summary>
 		/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
@@ -96,6 +97,8 @@ namespace Terminal.Gui {
 			Used = true;
 			WantMousePositionReports = true;
 
+			historyText.ChangeText += HistoryText_ChangeText;
+
 			Initialized += TextField_Initialized;
 
 			// Things this view knows how to do
@@ -193,6 +196,12 @@ namespace Terminal.Gui {
 			AddKeyBinding (Key.V | Key.CtrlMask, Command.Paste);
 		}
 
+		private void HistoryText_ChangeText (HistoryText.HistoryTextItem obj)
+		{
+			Text = ustring.Make (obj.Lines [obj.CursorPosition.Y]);
+			CursorPosition = obj.CursorPosition.X;
+			Adjust ();
+		}
 
 		void TextField_Initialized (object sender, EventArgs e)
 		{
@@ -226,10 +235,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		List<ustring> historyText;
-		int idxhistoryText;
-		bool isFromHistory;
-
 		/// <summary>
 		///   Sets or gets the text held by the view.
 		/// </summary>
@@ -254,14 +259,14 @@ namespace Terminal.Gui {
 					return;
 				}
 				text = TextModel.ToRunes (newText.NewText);
-				if (!Secret && !isFromHistory) {
-					if (historyText == null)
-						historyText = new List<ustring> () { oldText };
-					if (idxhistoryText > 0 && idxhistoryText + 1 < historyText.Count)
-						historyText.RemoveRange (idxhistoryText + 1, historyText.Count - idxhistoryText - 1);
-					historyText.Add (ustring.Make (text));
-					idxhistoryText++;
+
+				if (!Secret && !historyText.IsFromHistory) {
+					historyText.Add (new List<List<Rune>> () { oldText.ToRuneList () },
+						new Point (point, 0));
+					historyText.Add (new List<List<Rune>> () { text }, new Point (point, 0)
+						, HistoryText.LineStatus.Replaced);
 				}
+
 				TextChanged?.Invoke (oldText);
 
 				if (point > text.Count) {
@@ -303,6 +308,18 @@ namespace Terminal.Gui {
 		/// </summary>
 		public int ScrollOffset => first;
 
+		/// <summary>
+		/// Indicates whatever the text was changed or not.
+		/// <see langword="true"/> if the text was changed <see langword="false"/> otherwise.
+		/// </summary>
+		public bool IsDirty => historyText.IsDirty (Text);
+
+		/// <summary>
+		/// Indicates whatever the text has history changes or not.
+		/// <see langword="true"/> if the text has history changes <see langword="false"/> otherwise.
+		/// </summary>
+		public bool HasHistoryChanges => historyText.HasHistoryChanges;
+
 		/// <summary>
 		///   Sets the cursor position.
 		/// </summary>
@@ -480,6 +497,8 @@ namespace Terminal.Gui {
 
 		void InsertText (KeyEvent kb, bool useOldCursorPos = true)
 		{
+			historyText.Add (new List<List<Rune>> () { text }, new Point (point, 0));
+
 			if (length > 0) {
 				DeleteSelectedText ();
 				oldCursorPos = point;
@@ -561,19 +580,7 @@ namespace Terminal.Gui {
 			if (ReadOnly)
 				return;
 
-			if (historyText != null && historyText.Count > 0) {
-				isFromHistory = true;
-				if (idxhistoryText < historyText.Count - 1) {
-					idxhistoryText++;
-					if (idxhistoryText < historyText.Count) {
-						Text = historyText [idxhistoryText];
-					} else if (idxhistoryText == historyText.Count - 1) {
-						Text = historyText [historyText.Count - 1];
-					}
-					point = text.Count;
-				}
-				isFromHistory = false;
-			}
+			historyText.Redo ();
 
 			//if (Clipboard.Contents == null)
 			//	return true;
@@ -596,15 +603,7 @@ namespace Terminal.Gui {
 			if (ReadOnly)
 				return;
 
-			if (historyText != null && historyText.Count > 0) {
-				isFromHistory = true;
-				if (idxhistoryText > 0)
-					idxhistoryText--;
-				if (idxhistoryText > -1)
-					Text = historyText [idxhistoryText];
-				point = text.Count;
-				isFromHistory = false;
-			}
+			historyText.Undo ();
 		}
 
 		void KillToStart ()
@@ -730,6 +729,8 @@ namespace Terminal.Gui {
 			if (ReadOnly)
 				return;
 
+			historyText.Add (new List<List<Rune>> () { text }, new Point (point, 0));
+
 			if (length == 0) {
 				if (point == 0)
 					return;
@@ -757,6 +758,8 @@ namespace Terminal.Gui {
 			if (ReadOnly)
 				return;
 
+			historyText.Add (new List<List<Rune>> () { text }, new Point (point, 0));
+
 			if (length == 0) {
 				if (text.Count == 0 || text.Count == point)
 					return;
@@ -1156,6 +1159,14 @@ namespace Terminal.Gui {
 				InsertText (new KeyEvent () { Key = key }, useOldCursorPos);
 			}
 		}
+
+		/// <summary>
+		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
+		/// </summary>
+		public void ClearHistoryChanges ()
+		{
+			historyText.Clear (Text);
+		}
 	}
 
 	/// <summary>

+ 558 - 31
Terminal.Gui/Views/TextView.cs

@@ -36,6 +36,8 @@ namespace Terminal.Gui {
 	class TextModel {
 		List<List<Rune>> lines = new List<List<Rune>> ();
 
+		public event Action LinesLoaded;
+
 		public bool LoadFile (string file)
 		{
 			FilePath = file ?? throw new ArgumentNullException (nameof (file));
@@ -125,11 +127,20 @@ namespace Terminal.Gui {
 			if (line.Count > 0 || wasNewLine)
 				Append (line);
 			buff.Dispose ();
+
+			OnLinesLoaded ();
 		}
 
 		public void LoadString (ustring content)
 		{
 			lines = StringToRunes (content);
+
+			OnLinesLoaded ();
+		}
+
+		void OnLinesLoaded ()
+		{
+			LinesLoaded?.Invoke ();
 		}
 
 		public override string ToString ()
@@ -194,6 +205,15 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public void ReplaceLine (int pos, List<Rune> runes)
+		{
+			if (lines.Count > 0 && pos < lines.Count) {
+				lines [pos] = new List<Rune> (runes);
+			} else if (lines.Count == 0 || (lines.Count > 0 && pos >= lines.Count)) {
+				lines.Add (runes);
+			}
+		}
+
 		/// <summary>
 		/// Returns the maximum line length of the visible lines.
 		/// </summary>
@@ -515,6 +535,218 @@ namespace Terminal.Gui {
 		}
 	}
 
+	class HistoryText {
+		public enum LineStatus {
+			Original,
+			Replaced,
+			Removed,
+			Added
+		}
+
+		public class HistoryTextItem {
+			public List<List<Rune>> Lines;
+			public Point CursorPosition;
+			public LineStatus LineStatus;
+			public bool IsUndoing;
+			public Point FinalCursorPosition;
+			public HistoryTextItem RemovedOnAdded;
+
+			public HistoryTextItem (List<List<Rune>> lines, Point curPos, LineStatus linesStatus)
+			{
+				Lines = lines;
+				CursorPosition = curPos;
+				LineStatus = linesStatus;
+			}
+
+			public HistoryTextItem (HistoryTextItem historyTextItem)
+			{
+				Lines = new List<List<Rune>> (historyTextItem.Lines);
+				CursorPosition = new Point (historyTextItem.CursorPosition.X, historyTextItem.CursorPosition.Y);
+				LineStatus = historyTextItem.LineStatus;
+			}
+
+			public override string ToString ()
+			{
+				return $"(Count: {Lines.Count}, Cursor: {CursorPosition}, Status: {LineStatus})";
+			}
+		}
+
+		List<HistoryTextItem> historyTextItems = new List<HistoryTextItem> ();
+		int idxHistoryText = -1;
+		ustring originalText;
+
+		public bool IsFromHistory { get; private set; }
+
+		public bool HasHistoryChanges => idxHistoryText > -1;
+
+		public event Action<HistoryTextItem> ChangeText;
+
+		public void Add (List<List<Rune>> lines, Point curPos, LineStatus lineStatus = LineStatus.Original)
+		{
+			if (lineStatus == LineStatus.Original && historyTextItems.Count > 0
+				&& historyTextItems.Last ().LineStatus == LineStatus.Original) {
+				return;
+			}
+			if (lineStatus == LineStatus.Replaced && historyTextItems.Count > 0
+				&& historyTextItems.Last ().LineStatus == LineStatus.Replaced) {
+				return;
+			}
+
+			if (historyTextItems.Count == 0 && lineStatus != LineStatus.Original)
+				throw new ArgumentException ("The first item must be the original.");
+
+			if (idxHistoryText >= 0 && idxHistoryText + 1 < historyTextItems.Count)
+				historyTextItems.RemoveRange (idxHistoryText + 1, historyTextItems.Count - idxHistoryText - 1);
+
+			historyTextItems.Add (new HistoryTextItem (lines, curPos, lineStatus));
+			idxHistoryText++;
+		}
+
+		public void ReplaceLast (List<List<Rune>> lines, Point curPos, LineStatus lineStatus)
+		{
+			var found = historyTextItems.FindLast (x => x.LineStatus == lineStatus);
+			if (found != null) {
+				found.Lines = lines;
+				found.CursorPosition = curPos;
+			}
+		}
+
+		public void Undo ()
+		{
+			if (historyTextItems?.Count > 0 && idxHistoryText > 0) {
+				IsFromHistory = true;
+
+				idxHistoryText--;
+
+				var historyTextItem = new HistoryTextItem (historyTextItems [idxHistoryText]) {
+					IsUndoing = true
+				};
+
+				ProcessChanges (ref historyTextItem);
+
+				IsFromHistory = false;
+			}
+		}
+
+		public void Redo ()
+		{
+			if (historyTextItems?.Count > 0 && idxHistoryText < historyTextItems.Count - 1) {
+				IsFromHistory = true;
+
+				idxHistoryText++;
+
+				var historyTextItem = new HistoryTextItem (historyTextItems [idxHistoryText]) {
+					IsUndoing = false
+				};
+
+				ProcessChanges (ref historyTextItem);
+
+				IsFromHistory = false;
+			}
+		}
+
+		void ProcessChanges (ref HistoryTextItem historyTextItem)
+		{
+			if (historyTextItem.IsUndoing) {
+				if (idxHistoryText - 1 > -1 && ((historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Added)
+					|| historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Removed
+					|| (historyTextItem.LineStatus == LineStatus.Replaced &&
+					historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Original))) {
+
+					idxHistoryText--;
+
+					while (historyTextItems [idxHistoryText].LineStatus == LineStatus.Added
+						&& historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Removed) {
+
+						idxHistoryText--;
+					}
+					historyTextItem = new HistoryTextItem (historyTextItems [idxHistoryText]);
+					historyTextItem.IsUndoing = true;
+					historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
+				}
+
+				if (historyTextItem.LineStatus == LineStatus.Removed && historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Added) {
+					historyTextItem.RemovedOnAdded = new HistoryTextItem (historyTextItems [idxHistoryText + 1]);
+				}
+
+				if ((historyTextItem.LineStatus == LineStatus.Added && historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Original)
+					|| (historyTextItem.LineStatus == LineStatus.Removed && historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Original)
+					|| (historyTextItem.LineStatus == LineStatus.Added && historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Removed)) {
+
+					if (!historyTextItem.Lines [0].SequenceEqual (historyTextItems [idxHistoryText - 1].Lines [0])
+						&& historyTextItem.CursorPosition == historyTextItems [idxHistoryText - 1].CursorPosition) {
+						historyTextItem.Lines [0] = new List<Rune> (historyTextItems [idxHistoryText - 1].Lines [0]);
+					}
+					if (historyTextItem.LineStatus == LineStatus.Added && historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Removed) {
+						historyTextItem.FinalCursorPosition = historyTextItems [idxHistoryText - 2].CursorPosition;
+					} else {
+						historyTextItem.FinalCursorPosition = historyTextItems [idxHistoryText - 1].CursorPosition;
+					}
+				} else {
+					historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
+				}
+
+				OnChangeText (historyTextItem);
+				while (historyTextItems [idxHistoryText].LineStatus == LineStatus.Removed
+					|| historyTextItems [idxHistoryText].LineStatus == LineStatus.Added) {
+
+					idxHistoryText--;
+				}
+			} else if (!historyTextItem.IsUndoing) {
+				if (idxHistoryText + 1 < historyTextItems.Count && (historyTextItem.LineStatus == LineStatus.Original
+					|| historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Added
+					|| historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Removed)) {
+
+					idxHistoryText++;
+					historyTextItem = new HistoryTextItem (historyTextItems [idxHistoryText]);
+					historyTextItem.IsUndoing = false;
+					historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
+				}
+
+				if (historyTextItem.LineStatus == LineStatus.Added && historyTextItems [idxHistoryText - 1].LineStatus == LineStatus.Removed) {
+					historyTextItem.RemovedOnAdded = new HistoryTextItem (historyTextItems [idxHistoryText - 1]);
+				}
+
+				if ((historyTextItem.LineStatus == LineStatus.Removed && historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Replaced)
+					|| (historyTextItem.LineStatus == LineStatus.Removed && historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Original)
+					|| (historyTextItem.LineStatus == LineStatus.Added && historyTextItems [idxHistoryText + 1].LineStatus == LineStatus.Replaced)) {
+
+					if (historyTextItem.LineStatus == LineStatus.Removed
+						&& !historyTextItem.Lines [0].SequenceEqual (historyTextItems [idxHistoryText + 1].Lines [0])) {
+						historyTextItem.Lines [0] = new List<Rune> (historyTextItems [idxHistoryText + 1].Lines [0]);
+					}
+					historyTextItem.FinalCursorPosition = historyTextItems [idxHistoryText + 1].CursorPosition;
+				} else {
+					historyTextItem.FinalCursorPosition = historyTextItem.CursorPosition;
+				}
+
+				OnChangeText (historyTextItem);
+				while (historyTextItems [idxHistoryText].LineStatus == LineStatus.Removed
+					|| historyTextItems [idxHistoryText].LineStatus == LineStatus.Added) {
+
+					idxHistoryText++;
+				}
+			}
+		}
+
+		void OnChangeText (HistoryTextItem lines)
+		{
+			ChangeText?.Invoke (lines);
+		}
+
+		public void Clear (ustring text)
+		{
+			historyTextItems.Clear ();
+			idxHistoryText = -1;
+			originalText = text;
+		}
+
+		public bool IsDirty (ustring text)
+		{
+			return originalText != text;
+		}
+	}
+
 	class WordWrapManager {
 		class WrappedLine {
 			public int ModelLine;
@@ -903,6 +1135,7 @@ namespace Terminal.Gui {
 		bool allowsTab = true;
 		bool allowsReturn = true;
 		bool multiline = true;
+		HistoryText historyText = new HistoryText ();
 
 		/// <summary>
 		/// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
@@ -949,6 +1182,9 @@ namespace Terminal.Gui {
 			CanFocus = true;
 			Used = true;
 
+			model.LinesLoaded += Model_LinesLoaded;
+			historyText.ChangeText += HistoryText_ChangeText;
+
 			Initialized += TextView_Initialized;
 
 			// Things this view knows how to do
@@ -995,6 +1231,8 @@ namespace Terminal.Gui {
 			AddCommand (Command.BackTab, () => ProcessBackTab ());
 			AddCommand (Command.NextView, () => ProcessMoveNextView ());
 			AddCommand (Command.PreviousView, () => ProcessMovePreviousView ());
+			AddCommand (Command.Undo, () => { UndoChanges (); return true; });
+			AddCommand (Command.Redo, () => { RedoChanges (); return true; });
 
 			// Default keybindings for this view
 			AddKeyBinding (Key.PageDown, Command.PageDown);
@@ -1086,6 +1324,50 @@ namespace Terminal.Gui {
 
 			AddKeyBinding (Key.Tab | Key.CtrlMask | Key.ShiftMask, Command.PreviousView);
 			AddKeyBinding (Application.AlternateBackwardKey, Command.PreviousView);
+
+			AddKeyBinding (Key.Z | Key.CtrlMask, Command.Undo);
+			AddKeyBinding (Key.R | Key.CtrlMask, Command.Redo);
+		}
+
+		private void Model_LinesLoaded ()
+		{
+			historyText.Clear (Text);
+		}
+
+		private void HistoryText_ChangeText (HistoryText.HistoryTextItem obj)
+		{
+			var startLine = obj.CursorPosition.Y;
+
+			if (obj.RemovedOnAdded != null) {
+				int offset;
+				if (obj.IsUndoing) {
+					offset = Math.Max (obj.RemovedOnAdded.Lines.Count - obj.Lines.Count, 1);
+				} else {
+					offset = obj.RemovedOnAdded.Lines.Count - 1;
+				}
+				for (int i = 0; i < offset; i++) {
+					if (Lines > obj.RemovedOnAdded.CursorPosition.Y) {
+						model.RemoveLine (obj.RemovedOnAdded.CursorPosition.Y);
+					} else {
+						break;
+					}
+				}
+			}
+
+			for (int i = 0; i < obj.Lines.Count; i++) {
+				if (i == 0) {
+					model.ReplaceLine (startLine, obj.Lines [i]);
+				} else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed)
+						|| !obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added) {
+					model.AddLine (startLine, obj.Lines [i]);
+				} else if (Lines > obj.CursorPosition.Y + 1) {
+					model.RemoveLine (obj.CursorPosition.Y + 1);
+				}
+				startLine++;
+			}
+
+			CursorPosition = obj.FinalCursorPosition;
+			Adjust ();
 		}
 
 		void TextView_Initialized (object sender, EventArgs e)
@@ -1143,6 +1425,8 @@ namespace Terminal.Gui {
 				}
 				TextChanged?.Invoke ();
 				SetNeedsDisplay ();
+
+				historyText.Clear (Text);
 			}
 		}
 
@@ -1418,6 +1702,18 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Indicates whatever the text was changed or not.
+		/// <see langword="true"/> if the text was changed <see langword="false"/> otherwise.
+		/// </summary>
+		public bool IsDirty => historyText.IsDirty (Text);
+
+		/// <summary>
+		/// Indicates whatever the text has history changes or not.
+		/// <see langword="true"/> if the text has history changes <see langword="false"/> otherwise.
+		/// </summary>
+		public bool HasHistoryChanges => historyText.HasHistoryChanges;
+
 		int GetSelectedLength ()
 		{
 			return SelectedText.Length;
@@ -1671,10 +1967,10 @@ namespace Terminal.Gui {
 			ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
 
 			for (int row = startRow + 1; row < maxrow; row++) {
-				res = res + ustring.Make ((Rune)10) + StringFromRunes (model.GetLine (row));
+				res = res + ustring.Make (Environment.NewLine) + StringFromRunes (model.GetLine (row));
 			}
 			line = model.GetLine (maxrow);
-			res = res + ustring.Make ((Rune)10) + StringFromRunes (line.GetRange (0, endCol));
+			res = res + ustring.Make (Environment.NewLine) + StringFromRunes (line.GetRange (0, endCol));
 			return res;
 		}
 
@@ -1692,7 +1988,13 @@ namespace Terminal.Gui {
 			var endCol = (int)(end & 0xffffffff);
 			var line = model.GetLine (startRow);
 
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, new Point (startCol, startRow));
+
+			List<List<Rune>> removedLines = new List<List<Rune>> ();
+
 			if (startRow == maxrow) {
+				removedLines.Add (new List<Rune> (line));
+
 				line.RemoveRange (startCol, endCol - startCol);
 				currentColumn = startCol;
 				if (wordWrap) {
@@ -1700,13 +2002,21 @@ namespace Terminal.Gui {
 				} else {
 					SetNeedsDisplay (new Rect (0, startRow - topRow, Frame.Width, startRow - topRow + 1));
 				}
+
+				historyText.Add (new List<List<Rune>> (removedLines), CursorPosition, HistoryText.LineStatus.Removed);
+
 				return;
 			}
 
+			removedLines.Add (new List<Rune> (line));
+
 			line.RemoveRange (startCol, line.Count - startCol);
 			var line2 = model.GetLine (maxrow);
 			line.AddRange (line2.Skip (endCol));
 			for (int row = startRow + 1; row <= maxrow; row++) {
+
+				removedLines.Add (new List<Rune> (model.GetLine (startRow + 1)));
+
 				model.RemoveLine (startRow + 1);
 			}
 			if (currentEncoded == end) {
@@ -1714,6 +2024,9 @@ namespace Terminal.Gui {
 			}
 			currentColumn = startCol;
 
+			historyText.Add (new List<List<Rune>> (removedLines), CursorPosition,
+				HistoryText.LineStatus.Removed);
+
 			SetNeedsDisplay ();
 		}
 
@@ -2071,10 +2384,16 @@ namespace Terminal.Gui {
 
 			var line = GetCurrentLine ();
 
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, CursorPosition);
+
 			// Optimize single line
 			if (lines.Count == 1) {
 				line.InsertRange (currentColumn, lines [0]);
 				currentColumn += lines [0].Count;
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				if (!wordWrap && currentColumn - leftColumn > Frame.Width) {
 					leftColumn = Math.Max (currentColumn - Frame.Width + 1, 0);
 				}
@@ -2089,7 +2408,7 @@ namespace Terminal.Gui {
 			List<Rune> rest = null;
 			int lastp = 0;
 
-			if (model.Count > 0 && currentColumn > 0) {
+			if (model.Count > 0 && line.Count > 0 && !copyWithoutSelection) {
 				// Keep a copy of the rest of the line
 				var restCount = line.Count - currentColumn;
 				rest = line.GetRange (currentColumn, restCount);
@@ -2100,20 +2419,31 @@ namespace Terminal.Gui {
 			line.InsertRange (currentColumn, lines [0]);
 			//model.AddLine (currentRow, lines [0]);
 
+			var addedLines = new List<List<Rune>> () { new List<Rune> (line) };
+
 			for (int i = 1; i < lines.Count; i++) {
 				model.AddLine (currentRow + i, lines [i]);
+
+				addedLines.Add (new List<Rune> (lines [i]));
 			}
 
 			if (rest != null) {
 				var last = model.GetLine (currentRow + lines.Count - 1);
 				lastp = last.Count;
 				last.InsertRange (last.Count, rest);
+
+				addedLines.Last ().InsertRange (addedLines.Last ().Count, rest);
 			}
 
+			historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added);
+
 			// Now adjust column and row positions
 			currentRow += lines.Count - 1;
 			currentColumn = rest != null ? lastp : lines [lines.Count - 1].Count;
 			Adjust ();
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
 		}
 
 		// The column we are tracking, or -1 if we are not tracking any column
@@ -2160,6 +2490,8 @@ namespace Terminal.Gui {
 			} else if (currentRow - topRow + BottomOffset >= Frame.Height + offB.height) {
 				topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1 + BottomOffset, 0), currentRow);
 				need = true;
+			} else if (topRow > 0 && currentRow == topRow) {
+				topRow = Math.Max (topRow - 1, 0);
 			}
 			if (need) {
 				if (wrapNeeded) {
@@ -2238,6 +2570,22 @@ namespace Terminal.Gui {
 			return true;
 		}
 
+		void RedoChanges ()
+		{
+			if (ReadOnly || wordWrap)
+				return;
+
+			historyText.Redo ();
+		}
+
+		void UndoChanges ()
+		{
+			if (ReadOnly || wordWrap)
+				return;
+
+			historyText.Undo ();
+		}
+
 		bool ProcessMovePreviousView ()
 		{
 			ResetColumnTrack ();
@@ -2588,22 +2936,43 @@ namespace Terminal.Gui {
 			}
 			if (isReadOnly)
 				return true;
+
 			var currentLine = GetCurrentLine ();
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
+			if (selecting) {
+				ClearSelectedRegion ();
+				currentLine = GetCurrentLine ();
+			}
 			var restCount = currentLine.Count - currentColumn;
 			var rest = currentLine.GetRange (currentColumn, restCount);
 			currentLine.RemoveRange (currentColumn, restCount);
+
+			var addedLines = new List<List<Rune>> () { new List<Rune> (currentLine) };
+
 			model.AddLine (currentRow + 1, rest);
+
+			addedLines.Add (new List<Rune> (model.GetLine (currentRow + 1)));
+
+			historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added);
+
 			if (wordWrap) {
 				wrapManager.AddLine (currentRow, currentColumn);
 				wrapNeeded = true;
 			}
 			currentRow++;
+
 			bool fullNeedsDisplay = false;
 			if (currentRow >= topRow + Frame.Height) {
 				topRow++;
 				fullNeedsDisplay = true;
 			}
 			currentColumn = 0;
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			if (!wordWrap && currentColumn < leftColumn) {
 				fullNeedsDisplay = true;
 				leftColumn = 0;
@@ -2623,8 +2992,15 @@ namespace Terminal.Gui {
 			if (isReadOnly)
 				return;
 			var currentLine = GetCurrentLine ();
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
+
 			if (currentColumn == 0) {
 				DeleteTextBackwards ();
+
+				historyText.ReplaceLast (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				return;
 			}
 			var newPos = WordBackward (currentColumn, currentRow);
@@ -2644,6 +3020,10 @@ namespace Terminal.Gui {
 				currentColumn = newPos.Value.col;
 				currentRow = newPos.Value.row;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			if (wrapNeeded) {
 				SetNeedsDisplay ();
 			} else {
@@ -2657,8 +3037,15 @@ namespace Terminal.Gui {
 			if (isReadOnly)
 				return;
 			var currentLine = GetCurrentLine ();
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
+
 			if (currentLine.Count == 0 || currentColumn == currentLine.Count) {
 				DeleteTextForwards ();
+
+				historyText.ReplaceLast (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				return;
 			}
 			var newPos = WordForward (currentColumn, currentRow);
@@ -2673,6 +3060,10 @@ namespace Terminal.Gui {
 			if (wordWrap && wrapManager.RemoveRange (currentRow, currentColumn, restCount)) {
 				wrapNeeded = true;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			if (wrapNeeded) {
 				SetNeedsDisplay ();
 			} else {
@@ -2705,29 +3096,54 @@ namespace Terminal.Gui {
 
 		void KillToStartOfLine ()
 		{
-			ResetColumnTrack ();
 			if (isReadOnly)
 				return;
+			if (model.Count == 1 && GetCurrentLine ().Count == 0) {
+				// Prevents from adding line feeds if there is no more lines.
+				return;
+			}
+
 			var currentLine = GetCurrentLine ();
 			var setLastWasKill = true;
 			if (currentLine.Count > 0 && currentColumn == 0) {
 				DeleteTextBackwards ();
 				return;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
 			if (currentLine.Count == 0) {
 				if (currentRow > 0) {
 					model.RemoveLine (currentRow);
+
+					if (model.Count > 0 || lastWasKill) {
+						var val = ustring.Make (Environment.NewLine);
+						if (lastWasKill) {
+							AppendClipboard (val);
+						} else {
+							SetClipboard (val);
+						}
+					}
+					if (model.Count == 0) {
+						// Prevents from adding line feeds if there is no more lines.
+						setLastWasKill = false;
+					}
+
 					currentRow--;
 					currentLine = model.GetLine (currentRow);
+
+					var removedLine = new List<List<Rune>> () { new List<Rune> (currentLine) };
+
+					removedLine.Add (new List<Rune> ());
+
+					historyText.Add (new List<List<Rune>> (removedLine), CursorPosition, HistoryText.LineStatus.Removed);
+
 					currentColumn = currentLine.Count;
 				}
 			} else {
 				var restCount = currentColumn;
 				var 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);
@@ -2737,6 +3153,10 @@ namespace Terminal.Gui {
 				currentLine.RemoveRange (0, restCount);
 				currentColumn = 0;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 			lastWasKill = setLastWasKill;
 			DoNeededAction ();
@@ -2744,21 +3164,35 @@ namespace Terminal.Gui {
 
 		void KillToEndOfLine ()
 		{
-			ResetColumnTrack ();
 			if (isReadOnly)
 				return;
+			if (model.Count == 1 && GetCurrentLine ().Count == 0) {
+				// Prevents from adding line feeds if there is no more lines.
+				return;
+			}
+
 			var currentLine = GetCurrentLine ();
 			var setLastWasKill = true;
 			if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
 				DeleteTextForwards ();
 				return;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
 			if (currentLine.Count == 0) {
 				if (currentRow < model.Count - 1) {
+					var removedLines = new List<List<Rune>> () { new List<Rune> (currentLine) };
+
 					model.RemoveLine (currentRow);
+
+					removedLines.Add (new List<Rune> (GetCurrentLine ()));
+
+					historyText.Add (new List<List<Rune>> (removedLines), CursorPosition,
+						HistoryText.LineStatus.Removed);
 				}
 				if (model.Count > 0 || lastWasKill) {
-					var val = ustring.Make ((Rune)'\n');
+					var val = ustring.Make (Environment.NewLine);
 					if (lastWasKill) {
 						AppendClipboard (val);
 					} else {
@@ -2773,9 +3207,6 @@ namespace Terminal.Gui {
 				var restCount = currentLine.Count - currentColumn;
 				var rest = currentLine.GetRange (currentColumn, restCount);
 				var val = ustring.Empty;
-				if (currentColumn == 0 && lastWasKill && currentLine.Count > 0) {
-					val = ustring.Make ((Rune)'\n');
-				}
 				val += StringFromRunes (rest);
 				if (lastWasKill) {
 					AppendClipboard (val);
@@ -2784,6 +3215,10 @@ namespace Terminal.Gui {
 				}
 				currentLine.RemoveRange (currentColumn, restCount);
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 			lastWasKill = setLastWasKill;
 			DoNeededAction ();
@@ -2805,12 +3240,24 @@ namespace Terminal.Gui {
 			DoNeededAction ();
 		}
 
-		void DeleteCharRight ()
+		/// <summary>
+		/// Deletes all the selected or a single character at right from the position of the cursor.
+		/// </summary>
+		public void DeleteCharRight ()
 		{
 			if (isReadOnly)
 				return;
 			if (selecting) {
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Original);
+
 				ClearSelectedRegion ();
+
+				var currentLine = GetCurrentLine ();
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				return;
 			}
 			if (DeleteTextForwards ()) {
@@ -2819,12 +3266,24 @@ namespace Terminal.Gui {
 			DoNeededAction ();
 		}
 
-		void DeleteCharLeft ()
+		/// <summary>
+		/// Deletes all the selected or a single character at left from the position of the cursor.
+		/// </summary>
+		public void DeleteCharLeft ()
 		{
 			if (isReadOnly)
 				return;
 			if (selecting) {
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Original);
+
 				ClearSelectedRegion ();
+
+				var currentLine = GetCurrentLine ();
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				return;
 			}
 			if (DeleteTextBackwards ()) {
@@ -2937,6 +3396,11 @@ namespace Terminal.Gui {
 			//So that special keys like tab can be processed
 			if (isReadOnly)
 				return true;
+
+			var curPos = CursorPosition;
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
+
 			if (selecting) {
 				ClearSelectedRegion ();
 			}
@@ -2951,6 +3415,10 @@ namespace Terminal.Gui {
 				Insert ((uint)kb.Key);
 				currentColumn++;
 			}
+
+			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+				HistoryText.LineStatus.Replaced);
+
 			return true;
 		}
 
@@ -2980,16 +3448,36 @@ namespace Terminal.Gui {
 			if (currentColumn == currentLine.Count) {
 				if (currentRow + 1 == model.Count)
 					return true;
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
+				var removedLines = new List<List<Rune>> () { new List<Rune> (currentLine) };
+
 				var nextLine = model.GetLine (currentRow + 1);
+
+				removedLines.Add (new List<Rune> (nextLine));
+
+				historyText.Add (removedLines, CursorPosition, HistoryText.LineStatus.Removed);
+
 				currentLine.AddRange (nextLine);
 				model.RemoveLine (currentRow + 1);
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) {
 					wrapNeeded = true;
 				}
 				var sr = currentRow - topRow;
 				SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
 			} else {
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
 				currentLine.RemoveAt (currentColumn);
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) {
 					wrapNeeded = true;
 				}
@@ -3000,20 +3488,23 @@ namespace Terminal.Gui {
 			return false;
 		}
 
-		/// <summary>
-		/// Deletes a single character from the position of the cursor
-		/// </summary>
-		/// <returns></returns>
-		public bool DeleteTextBackwards ()
+		bool DeleteTextBackwards ()
 		{
 			if (currentColumn > 0) {
 				// Delete backwards 
 				var currentLine = GetCurrentLine ();
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
 				currentLine.RemoveAt (currentColumn - 1);
 				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn - 1)) {
 					wrapNeeded = true;
 				}
 				currentColumn--;
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
+
 				if (currentColumn < leftColumn) {
 					leftColumn--;
 					SetNeedsDisplay ();
@@ -3025,6 +3516,16 @@ namespace Terminal.Gui {
 					return true;
 				var prowIdx = currentRow - 1;
 				var prevRow = model.GetLine (prowIdx);
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (prevRow) }, CursorPosition);
+
+				List<List<Rune>> removedLines = new List<List<Rune>> () { new List<Rune> (prevRow) };
+
+				removedLines.Add (new List<Rune> (GetCurrentLine ()));
+
+				historyText.Add (removedLines, new Point (currentColumn, prowIdx),
+					HistoryText.LineStatus.Removed);
+
 				var prevCount = prevRow.Count;
 				model.GetLine (prowIdx).AddRange (GetCurrentLine ());
 				model.RemoveLine (currentRow);
@@ -3033,6 +3534,10 @@ namespace Terminal.Gui {
 					wrapNeeded = true;
 				}
 				currentRow--;
+
+				historyText.Add (new List<List<Rune>> () { GetCurrentLine () }, new Point (currentColumn, prowIdx),
+					HistoryText.LineStatus.Replaced);
+
 				if (wrapNeeded && !lineRemoved) {
 					currentColumn = Math.Max (prevCount - 1, 0);
 				} else {
@@ -3073,6 +3578,9 @@ namespace Terminal.Gui {
 			SetClipboard (GetRegion ());
 			if (!isReadOnly) {
 				ClearRegion ();
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
 			}
 			UpdateWrapModel ();
 			selecting = false;
@@ -3090,23 +3598,34 @@ namespace Terminal.Gui {
 
 			SetWrapModel ();
 			var contents = Clipboard.Contents;
-			if (copyWithoutSelection) {
+			if (copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0) {
 				var runeList = contents == null ? new List<Rune> () : contents.ToRuneList ();
-				if ((runeList?.Count > 0 && runeList [0] == '\n')
-					|| (runeList?.Count > 1 && runeList [0] == '\r'
-					&& runeList [1] == '\n')) {
+				var currentLine = GetCurrentLine ();
 
-					InsertText (contents);
-				} else {
-					model.AddLine (currentRow, runeList);
-					currentRow++;
-				}
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
+
+				var addedLine = new List<List<Rune>> () { new List<Rune> (currentLine) };
+
+				addedLine.Add (runeList);
+
+				historyText.Add (new List<List<Rune>> (addedLine), CursorPosition, HistoryText.LineStatus.Added);
+
+				model.AddLine (currentRow, runeList);
+				currentRow++;
+
+				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+					HistoryText.LineStatus.Replaced);
 			} else {
 				if (selecting) {
 					ClearRegion ();
 				}
-				InsertText (contents);
 				copyWithoutSelection = false;
+				InsertText (contents);
+
+				if (selecting) {
+					historyText.ReplaceLast (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
+						HistoryText.LineStatus.Original);
+				}
 			}
 			UpdateWrapModel ();
 			selecting = false;
@@ -3590,6 +4109,14 @@ namespace Terminal.Gui {
 
 			return base.OnLeave (view);
 		}
+
+		/// <summary>
+		/// Allows clearing the <see cref="HistoryText.HistoryTextItem"/> items updating the original text.
+		/// </summary>
+		public void ClearHistoryChanges ()
+		{
+			historyText.Clear (Text);
+		}
 	}
 
 	/// <summary>
@@ -3598,7 +4125,7 @@ namespace Terminal.Gui {
 	/// An implementation on a TextView.
 	/// </summary>
 	public class TextViewAutocomplete : Autocomplete {
-		
+
 		///<inheritdoc/>
 		protected override string GetCurrentWord ()
 		{
@@ -3611,7 +4138,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		protected override void DeleteTextBackwards ()
 		{
-			((TextView)HostControl).DeleteTextBackwards ();
+			((TextView)HostControl).DeleteCharLeft ();
 		}
 
 		/// <inheritdoc/>

+ 4 - 0
UICatalog/Scenarios/Editor.cs

@@ -321,9 +321,12 @@ namespace UICatalog.Scenarios {
 		private bool CanCloseFile ()
 		{
 			if (_textView.Text == _originalText) {
+				System.Diagnostics.Debug.Assert (!_textView.IsDirty);
 				return true;
 			}
 
+			System.Diagnostics.Debug.Assert (_textView.IsDirty);
+
 			var r = MessageBox.ErrorQuery ("Save File",
 				$"Do you want save changes in {Win.Title}?", "Yes", "No", "Cancel");
 			if (r == 0) {
@@ -394,6 +397,7 @@ namespace UICatalog.Scenarios {
 				System.IO.File.WriteAllText (_fileName, _textView.Text.ToString ());
 				_originalText = _textView.Text.ToByteArray ();
 				_saved = true;
+				_textView.ClearHistoryChanges ();
 				MessageBox.Query ("Save File", "File was successfully saved.", "Ok");
 
 			} catch (Exception ex) {

+ 2913 - 40
UnitTests/TextViewTests.cs

@@ -766,48 +766,48 @@ namespace Terminal.Gui.Views {
 					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);
+					Assert.Equal (24 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"{Environment.NewLine}This 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);
+					Assert.Equal (29 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"line.{Environment.NewLine}This 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);
+					Assert.Equal (35 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"first line.{Environment.NewLine}This 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);
+					Assert.Equal (39 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"the first line.{Environment.NewLine}This 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);
+					Assert.Equal (42 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"is the first line.{Environment.NewLine}This 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);
+					Assert.Equal (47 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
 				default:
 					iterationsFinished = true;
@@ -880,48 +880,48 @@ namespace Terminal.Gui.Views {
 					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);
+					Assert.Equal (23 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}", _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);
+					Assert.Equal (28 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This ", _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);
+					Assert.Equal (31 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This 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);
+					Assert.Equal (35 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This 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);
+					Assert.Equal (42 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This 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);
+					Assert.Equal (47 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
 				default:
 					iterationsFinished = true;
@@ -945,21 +945,26 @@ namespace Terminal.Gui.Views {
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
-					Assert.Equal ($"{System.Environment.NewLine}This is the second line.", _textView.Text);
+					Assert.Equal ($"{Environment.NewLine}This is the second line.", _textView.Text);
+					Assert.Equal ("This is the first line.", Clipboard.Contents);
 					break;
 				case 1:
 					_textView.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the second line.", _textView.Text);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}", Clipboard.Contents);
 					break;
 				case 2:
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
-					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
-					Assert.Equal ("This is the second line.", _textView.Text);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", Clipboard.Contents);
+
+					// Paste
+					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ()));
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", _textView.Text);
 					break;
 				default:
 					iterationsFinished = true;
@@ -984,21 +989,26 @@ namespace Terminal.Gui.Views {
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
-					Assert.Equal ($"This is the first line.{System.Environment.NewLine}", _textView.Text);
+					Assert.Equal ($"This is the first line.{Environment.NewLine}", _textView.Text);
+					Assert.Equal ($"This is the second line.", Clipboard.Contents);
 					break;
 				case 1:
 					_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 					Assert.Equal (23, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line.", _textView.Text);
+					Assert.Equal ($"This is the second line.{Environment.NewLine}", Clipboard.Contents);
 					break;
 				case 2:
 					_textView.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ()));
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
-					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
-					Assert.Equal ("This is the first line.", _textView.Text);
+					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", Clipboard.Contents);
+
+					// Paste inverted
+					_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ()));
+					Assert.Equal ($"This is the second line.{Environment.NewLine}This is the first line.", _textView.Text);
 					break;
 				default:
 					iterationsFinished = true;
@@ -2337,6 +2347,16 @@ line.
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F", tv.Text);
 			Assert.Equal (new Point (24, 2), tv.CursorPosition);
 			Assert.Empty (tv.Autocomplete.Suggestions);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Empty (tv.Autocomplete.Suggestions);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F", tv.Text);
+			Assert.Equal (new Point (24, 2), tv.CursorPosition);
+			Assert.Empty (tv.Autocomplete.Suggestions);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
 			Assert.Equal (new Point (23, 2), tv.CursorPosition);
@@ -2383,8 +2403,8 @@ line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.PageUp | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal (24, tv.GetCurrentLine ().Count);
 			Assert.Equal (new Point (23, 1), tv.CursorPosition); // gets the previous length
-			Assert.Equal (25, tv.SelectedLength);
-			Assert.Equal (".\nThis is the third line.", tv.SelectedText);
+			Assert.Equal (24 + Environment.NewLine.Length, tv.SelectedLength);
+			Assert.Equal ($".{Environment.NewLine}This is the third line.", tv.SelectedText);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.PageDown | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal (28, tv.GetCurrentLine ().Count);
 			Assert.Equal (new Point (23, 2), tv.CursorPosition); // gets the previous length
@@ -2410,8 +2430,8 @@ line.
 			Assert.Equal ("", tv.SelectedText);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
-			Assert.Equal (24, tv.SelectedLength);
-			Assert.Equal ("This is the first line.\n", tv.SelectedText);
+			Assert.Equal (23 + Environment.NewLine.Length, tv.SelectedLength);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}", tv.SelectedText);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal (new Point (0, 0), tv.CursorPosition);
 			Assert.Equal (0, tv.SelectedLength);
@@ -2691,20 +2711,20 @@ line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.CtrlMask | Key.End | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.Text);
 			Assert.Equal (new Point (18, 2), tv.CursorPosition);
-			Assert.Equal (43, tv.SelectedLength);
-			Assert.Equal ("This is the second line.\nThis is the third ", tv.SelectedText);
+			Assert.Equal (42 + Environment.NewLine.Length, tv.SelectedLength);
+			Assert.Equal ($"This is the second line.{Environment.NewLine}This is the third ", tv.SelectedText);
 			Assert.True (tv.Selecting);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Home | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.Text);
 			Assert.Equal (new Point (0, 0), tv.CursorPosition);
-			Assert.Equal (1, tv.SelectedLength);
-			Assert.Equal ("\n", tv.SelectedText);
+			Assert.Equal (Environment.NewLine.Length, tv.SelectedLength);
+			Assert.Equal ($"{Environment.NewLine}", tv.SelectedText);
 			Assert.True (tv.Selecting);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.T | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.Text);
 			Assert.Equal (new Point (18, 2), tv.CursorPosition);
-			Assert.Equal (44, tv.SelectedLength);
-			Assert.Equal ("\nThis is the second line.\nThis is the third ", tv.SelectedText);
+			Assert.Equal (42 + Environment.NewLine.Length * 2, tv.SelectedLength);
+			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.SelectedText);
 			Assert.True (tv.Selecting);
 			Assert.True (tv.Used);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.InsertChar, new KeyModifiers ())));
@@ -2735,5 +2755,2858 @@ line.
 			Assert.False (tv.ProcessKey (new KeyEvent (Key.Tab | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
 			Assert.False (tv.ProcessKey (new KeyEvent (Application.AlternateBackwardKey, new KeyModifiers ())));
 		}
+
+		[Fact]
+		public void HistoryText_Exceptions ()
+		{
+			var ht = new HistoryText ();
+
+			foreach (var ls in Enum.GetValues (typeof (HistoryText.LineStatus))) {
+				if ((HistoryText.LineStatus)ls != HistoryText.LineStatus.Original) {
+					Assert.Throws<ArgumentException> (() => ht.Add (new List<List<Rune>> () { new List<Rune> () }, Point.Empty,
+						(HistoryText.LineStatus)ls));
+				}
+			}
+
+			Assert.Null (Record.Exception (() => ht.Add (new List<List<Rune>> () { new List<Rune> () }, Point.Empty,
+				HistoryText.LineStatus.Original)));
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var messy = " messy";
+			tv.CursorPosition = new Point (7, 1);
+			tv.InsertText (messy);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_DeleteCharLeft ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharLeft ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_DeleteCharRight ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharRight ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_Selected_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var messy = " messy";
+			tv.CursorPosition = new Point (7, 1);
+			tv.SelectionStartColumn = 11;
+			tv.SelectionStartRow = 1;
+			Assert.Equal (4, tv.SelectedLength);
+			tv.InsertText (messy);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_Selected_DeleteCharLeft ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			tv.SelectionStartColumn = 11;
+			tv.SelectionStartRow = 1;
+			Assert.Equal (4, tv.SelectedLength);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharLeft ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This  second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This  second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Single_Line_Selected_DeleteCharRight ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			tv.SelectionStartColumn = 11;
+			tv.SelectionStartRow = 1;
+			Assert.Equal (4, tv.SelectedLength);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharRight ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This isecond line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This isecond line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (1, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var messy = " messy";
+			tv.CursorPosition = new Point (7, 1);
+			tv.InsertText (messy);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 0);
+			tv.InsertText (messy);
+			Assert.Equal ($"This is messy the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 0), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 2);
+			tv.InsertText (messy);
+			Assert.Equal ($"This is messy the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is messy the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 2), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is messy the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 2), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 1), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is messy the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 0), tv.CursorPosition);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is messy the first line.{Environment.NewLine}This is messy the second line.{Environment.NewLine}This is messy the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (13, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_DeleteCharLeft ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharLeft ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 1), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 0);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharLeft ();
+			}
+			Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 0), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 2);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharLeft ();
+			}
+			Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+
+				switch (i) {
+				case 0:
+					Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This  the third line.", tv.Text);
+					Assert.Equal (new Point (5, 2), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This i the third line.", tv.Text);
+					Assert.Equal (new Point (6, 2), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (7, 2), tv.CursorPosition);
+					break;
+				}
+			}
+			Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 2), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+
+				switch (i) {
+				case 0:
+					Assert.Equal ($"This  the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (5, 0), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ($"This i the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (6, 0), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (7, 0), tv.CursorPosition);
+					break;
+				}
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+
+				switch (i) {
+				case 0:
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This  the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (5, 1), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This i the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (6, 1), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (7, 1), tv.CursorPosition);
+					break;
+				}
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 0), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This the first line.{Environment.NewLine}This the second line.{Environment.NewLine}This the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_DeleteCharRight ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var ntimes = 3;
+			tv.CursorPosition = new Point (7, 1);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharRight ();
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 0);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharRight ();
+			}
+			Assert.Equal ($"This ise first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (7, 2);
+			for (int i = 0; i < ntimes; i++) {
+				tv.DeleteCharRight ();
+			}
+			Assert.Equal ($"This ise first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This ise third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 2), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This ise first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 2), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This ise first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+
+			for (int i = 0; i < ntimes; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			}
+			Assert.Equal ($"This ise first line.{Environment.NewLine}This ise second line.{Environment.NewLine}This ise third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_InsertText ()
+		{
+			var text = $"This is the first line.{Environment.NewLine}This is the second line.\nThis is the third line.";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+
+			var messy = " messy";
+			tv.CursorPosition = new Point (7, 0);
+			tv.SelectionStartColumn = 11;
+			tv.SelectionStartRow = 2;
+			Assert.Equal (51 + Environment.NewLine.Length * 2, tv.SelectedLength);
+			for (int i = 0; i < messy.Length; i++) {
+				tv.InsertText (messy [i].ToString ());
+
+				switch (i) {
+				case 0:
+					Assert.Equal ("This is  third line.", tv.Text);
+					Assert.Equal (new Point (8, 0), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ("This is m third line.", tv.Text);
+					Assert.Equal (new Point (9, 0), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ("This is me third line.", tv.Text);
+					Assert.Equal (new Point (10, 0), tv.CursorPosition);
+					break;
+				case 3:
+					Assert.Equal ("This is mes third line.", tv.Text);
+					Assert.Equal (new Point (11, 0), tv.CursorPosition);
+					break;
+				case 4:
+					Assert.Equal ("This is mess third line.", tv.Text);
+					Assert.Equal (new Point (12, 0), tv.CursorPosition);
+					break;
+				case 5:
+					Assert.Equal ("This is messy third line.", tv.Text);
+					Assert.Equal (new Point (13, 0), tv.CursorPosition);
+					break;
+				}
+			}
+
+			Assert.Equal ($"This is messy third line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (13, 0), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (2, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+
+				switch (i) {
+				case 0:
+					Assert.Equal ("This is mess third line.", tv.Text);
+					Assert.Equal (new Point (12, 0), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ("This is mes third line.", tv.Text);
+					Assert.Equal (new Point (11, 0), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ("This is me third line.", tv.Text);
+					Assert.Equal (new Point (10, 0), tv.CursorPosition);
+					break;
+				case 3:
+					Assert.Equal ("This is m third line.", tv.Text);
+					Assert.Equal (new Point (9, 0), tv.CursorPosition);
+					break;
+				case 4:
+					Assert.Equal ("This is  third line.", tv.Text);
+					Assert.Equal (new Point (8, 0), tv.CursorPosition);
+					break;
+				case 5:
+					Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+					Assert.Equal (new Point (7, 0), tv.CursorPosition);
+					break;
+				}
+			}
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 0), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (2, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+
+			for (int i = 0; i < messy.Length; i++) {
+				Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+
+				switch (i) {
+				case 0:
+					Assert.Equal ("This is  third line.", tv.Text);
+					Assert.Equal (new Point (8, 0), tv.CursorPosition);
+					break;
+				case 1:
+					Assert.Equal ("This is m third line.", tv.Text);
+					Assert.Equal (new Point (9, 0), tv.CursorPosition);
+					break;
+				case 2:
+					Assert.Equal ("This is me third line.", tv.Text);
+					Assert.Equal (new Point (10, 0), tv.CursorPosition);
+					break;
+				case 3:
+					Assert.Equal ("This is mes third line.", tv.Text);
+					Assert.Equal (new Point (11, 0), tv.CursorPosition);
+					break;
+				case 4:
+					Assert.Equal ("This is mess third line.", tv.Text);
+					Assert.Equal (new Point (12, 0), tv.CursorPosition);
+					break;
+				case 5:
+					Assert.Equal ("This is messy third line.", tv.Text);
+					Assert.Equal (new Point (13, 0), tv.CursorPosition);
+					break;
+				}
+			}
+			Assert.Equal ("This is messy third line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (13, 0), tv.CursorPosition);
+			Assert.Equal (11, tv.SelectionStartColumn);
+			Assert.Equal (2, tv.SelectionStartRow);
+			Assert.Equal (0, tv.SelectedLength);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_With_Empty_Text ()
+		{
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.O, new KeyModifiers ())));
+			Assert.Equal ("O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.n, new KeyModifiers ())));
+			Assert.Equal ("On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ("One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.T, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.w, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.o, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.T, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.h, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.r, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			// Undoing
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redoing
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_With_Empty_Text ()
+		{
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.O, new KeyModifiers ())));
+			Assert.Equal ("O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.n, new KeyModifiers ())));
+			Assert.Equal ("On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ("One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.T, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.w, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.o, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.T, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.h, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.r, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.e, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			tv.SelectionStartColumn = 0;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (0, 1);
+			Assert.Equal (3 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			tv.SelectionStartColumn = 1;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (1, 1);
+			Assert.Equal (4 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+			Assert.Equal ($"12hree{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			// Undoing
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+
+			// Redoing
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"O", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"On", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}T", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Tw", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (3, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}T", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Th", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (2, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thr", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (3, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Thre", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"12hree{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_InsertText_Twice_On_Same_Line ()
+		{
+			var text = "One\nTwo\nThree";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			tv.SelectionStartColumn = 0;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (0, 1);
+			Assert.Equal (3 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			tv.SelectionStartColumn = 1;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (1, 1);
+			Assert.Equal (4 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+			Assert.Equal ("12hree", tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three", tv.Text);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("12hree", tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_InsertText_Twice_On_Same_Line_With_End_Line ()
+		{
+			var text = "One\nTwo\nThree\n";
+			var tv = new TextView () {
+				Width = 10,
+				Height = 2,
+				Text = text
+			};
+			var top = Application.Top;
+			top.Add (tv);
+			Application.Begin (top);
+
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			tv.SelectionStartColumn = 0;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (0, 1);
+			Assert.Equal (3 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			tv.SelectionStartColumn = 1;
+			tv.SelectionStartRow = 0;
+			tv.CursorPosition = new Point (1, 1);
+			Assert.Equal (4 + Environment.NewLine.Length, tv.SelectedLength);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+			Assert.Equal ($"12hree{Environment.NewLine}", tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"One{Environment.NewLine}Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"1Two{Environment.NewLine}Three{Environment.NewLine}", tv.Text);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"12hree{Environment.NewLine}", tv.Text);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+		}
+
+		[Fact]
+		public void HistoryText_IsDirty_HasHistoryChanges ()
+		{
+			var tv = new TextView ();
+
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+			Assert.Equal ("1", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"1{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+			Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Assert.Equal ($"1{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Assert.Equal ($"1", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Assert.Equal ($"", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			// IsDirty cannot be based on HasHistoryChanges because HasHistoryChanges is greater than 0
+			// The only way is comparing from the original text
+			Assert.False (tv.IsDirty);
+			// Still true because HasHistoryChanges is greater than 0
+			Assert.True (tv.HasHistoryChanges);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_DeleteCharLeft_All ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Equal (70 + Environment.NewLine.Length * 2, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_DeleteCharRight_All ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Equal (70 + Environment.NewLine.Length * 2, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.False (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Copy_Without_Selection_Multi_Line_Paste ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.CursorPosition = new Point (23, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ("This is the first line.", Clipboard.Contents);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (23, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (23, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Simple_Copy_Multi_Line_Selected_Paste ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("first", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (11, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_Copy_Simple_Paste_Starting_On_Space ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ($"first line.{Environment.NewLine}This is the second", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+
+			tv.Selecting = false;
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the secondfirst line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (18, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the secondfirst line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (18, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_Copy_Simple_Paste_Starting_On_Letter ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ($"first line.{Environment.NewLine}This is the second", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+
+			tv.Selecting = false;
+			tv.CursorPosition = new Point (17, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the seconfirst line.{Environment.NewLine}This is the secondd line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (18, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 1), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the seconfirst line.{Environment.NewLine}This is the secondd line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (18, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Empty_Copy_Without_Selection_Multi_Line_Selected_Paste ()
+		{
+			var text = "\nThis is the first line.\nThis is the second line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Setting_Clipboard_Multi_Line_Selected_Paste ()
+		{
+			var text = "This is the first line.\nThis is the second line.";
+			var tv = new TextView () { Text = text };
+
+			Clipboard.Contents = "Inserted\nNewLine";
+
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"Inserted{Environment.NewLine}NewLineThis is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"Inserted{Environment.NewLine}NewLineThis is the first line.{Environment.NewLine}This is the second line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Cut_Multi_Line_Selected_Paste ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (11, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Cut_Simple_Paste_Starting ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			tv.Selecting = false;
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_Cut_Multi_Line_Another_Selected_Paste ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			tv.SelectionStartColumn = 12;
+			tv.SelectionStartRow = 1;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the first line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the  line.{Environment.NewLine}This is the first line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_KillWordBackward ()
+		{
+			var text = "First line.\nSecond line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First ", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (6, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First ", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (6, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 1), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (7, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First ", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (6, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_KillWordForward ()
+		{
+			var text = "First line.\nSecond line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_KillToStartOfLine ()
+		{
+			var text = "First line.\nSecond line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ("Second line.", Clipboard.Contents);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ())));
+			Assert.Equal ("First line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ($"Second line.{Environment.NewLine}", Clipboard.Contents);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.AltMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ($"Second line.{Environment.NewLine}First line.", Clipboard.Contents);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (12, 1), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (11, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void HistoryText_Undo_Redo_KillToEndOfLine ()
+		{
+			var text = "First line.\nSecond line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ("First line.", Clipboard.Contents);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ($"First line.{Environment.NewLine}", Clipboard.Contents);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.K | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", Clipboard.Contents);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("Second line.", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Changing_On_Middle_Clear_History_Forwards ()
+		{
+			var tv = new TextView ();
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+			Assert.Equal ("1", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (1, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+			Assert.Equal ("12", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D3, new KeyModifiers ())));
+			Assert.Equal ("123", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("12", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (2, 0), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+			Assert.Equal ("124", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("124", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (3, 0), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Single_Line_Selected_Return ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Two_Line_Selected_Return ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Three_Line_Selected_Return ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 2);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Single_Second_Line_Selected_Return ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.SelectionStartRow = 1;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_First_Line_Selected_Return_And_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (17, 0);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.a, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (17, 0), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine} line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}a line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Single_Second_Line_Selected_Return_And_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.SelectionStartRow = 1;
+			tv.CursorPosition = new Point (18, 1);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.a, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (18, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine} line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 2), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the {Environment.NewLine}a line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (1, 2), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Multi_Line_Selected_All_Return_And_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.a, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}a", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Ending_With_Newline_Multi_Line_Selected_Almost_All_Return_And_InsertText ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.\n";
+			var tv = new TextView () { Text = text };
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (12, 2);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.a, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (12, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (12, 2), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+
+			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_Undo_Redo_Disabled_On_WordWrap ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.\n";
+			var tv = new TextView () { Width = 80, Height = 5, Text = text };
+
+			Assert.False (tv.WordWrap);
+			tv.WordWrap = true;
+
+			tv.SelectionStartColumn = 12;
+			tv.CursorPosition = new Point (12, 2);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.a, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+
+			// Undo is disabled
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+
+			// Redo is disabled
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal (3, tv.Lines);
+			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+		}
+
+		[Fact]
+		public void HistoryText_ClearHistoryChanges ()
+		{
+			var text = "This is the first line.\nThis is the second line.\nThis is the third line.";
+			var tv = new TextView () { Text = text };
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.True (tv.IsDirty);
+			Assert.True (tv.HasHistoryChanges);
+
+			tv.ClearHistoryChanges ();
+			Assert.Equal ($"{Environment.NewLine}This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.", tv.Text);
+			Assert.Equal (4, tv.Lines);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
+			Assert.False (tv.IsDirty);
+			Assert.False (tv.HasHistoryChanges);
+		}
+
+		[Fact]
+		public void GetRegion_StringFromRunes_Environment_NewLine ()
+		{
+			var tv = new TextView () { Text = $"1{Environment.NewLine}2" };
+
+			Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+
+			tv.SelectAll ();
+			Assert.Equal ($"1{Environment.NewLine}2", tv.Text);
+			Assert.Equal ($"1{Environment.NewLine}2", tv.SelectedText);
+		}
 	}
 }

+ 2 - 2
UnitTests/UnitTests.csproj

@@ -16,14 +16,14 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
-    <PackageReference Include="ReportGenerator" Version="5.0.3" />
+    <PackageReference Include="ReportGenerator" Version="5.0.4" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="coverlet.collector" Version="3.1.1">
+    <PackageReference Include="coverlet.collector" Version="3.1.2">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>