浏览代码

Fixes #1210. Added AllowsReturn, AllowsTab and Multiline into the TextView. (#1249)

* Fixes #1234. Setting Handled to true in the KeyPress event avoids ProcessKey from running.

* Using literals values in Assert.Equal.

* Can't use numbers variables on the left side of an Assert.Equal/NotEqual, it must be literal (Linux only).

* Fixes #1241. Added SendKeys feature to the ConsoleDriver.

* Fixes SendKeys unit test.

* Changed Key.Null to '\0' (null string).

* Fixes the space bar when there are another previous char.

* Now the drivers are reading up to 255 from ASCII table.

* Fixes CursesDriver and WindowsDriver SendKeys.

* Fixes NetDriver SendKeys.

* Fixed unit test.

* Fixes #1210.  Added AllowsReturn, AllowsTab and Multiline into the TextView.

* Added a Single Line and Multiline toggle to the Text.cs scenario.

* Removing comment.
BDisp 4 年之前
父节点
当前提交
c27da1b982

+ 24 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -674,12 +674,14 @@ namespace Terminal.Gui {
 			//}
 			//}
 		}
 		}
 
 
+		Action<KeyEvent> keyHandler;
 		Action<MouseEvent> mouseHandler;
 		Action<MouseEvent> mouseHandler;
 
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 		{
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			Curses.timeout (0);
 			Curses.timeout (0);
+			this.keyHandler = keyHandler;
 			this.mouseHandler = mouseHandler;
 			this.mouseHandler = mouseHandler;
 
 
 			var mLoop = mainLoop.Driver as UnixMainLoop;
 			var mLoop = mainLoop.Driver as UnixMainLoop;
@@ -946,6 +948,28 @@ namespace Terminal.Gui {
 		{
 		{
 			return false;
 			return false;
 		}
 		}
+
+		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+		{
+			Key k;
+
+			if ((shift || alt || control)
+				&& keyChar - (int)Key.Space >= (uint)Key.A && keyChar - (int)Key.Space <= (uint)Key.Z) {
+				k = (Key)(keyChar - (uint)Key.Space);
+			} else {
+				k = (Key)keyChar;
+			}
+			if (shift) {
+				k |= Key.ShiftMask;
+			}
+			if (alt) {
+				k |= Key.AltMask;
+			}
+			if (control) {
+				k |= Key.CtrlMask;
+			}
+			keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+		}
 	}
 	}
 
 
 	internal static class Platform {
 	internal static class Platform {

+ 1 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -798,7 +798,7 @@ namespace Terminal.Gui {
 			if (MockKeyPresses.Count > 0) {
 			if (MockKeyPresses.Count > 0) {
 				return MockKeyPresses.Pop();
 				return MockKeyPresses.Pop();
 			} else {
 			} else {
-				return new ConsoleKeyInfo ('~', ConsoleKey.Oem3, false,false,false);
+				return new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', false,false,false);
 			}
 			}
 		}
 		}
 
 

+ 55 - 38
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -282,7 +282,7 @@ namespace Terminal.Gui {
 			case ConsoleKey.Enter:
 			case ConsoleKey.Enter:
 				return MapKeyModifiers (keyInfo, Key.Enter);
 				return MapKeyModifiers (keyInfo, Key.Enter);
 			case ConsoleKey.Spacebar:
 			case ConsoleKey.Spacebar:
-				return MapKeyModifiers (keyInfo, Key.Space);
+				return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
 			case ConsoleKey.Backspace:
 			case ConsoleKey.Backspace:
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 			case ConsoleKey.Delete:
 			case ConsoleKey.Delete:
@@ -348,6 +348,9 @@ namespace Terminal.Gui {
 
 
 				return (Key)((uint)Key.F1 + delta);
 				return (Key)((uint)Key.F1 + delta);
 			}
 			}
+			if (keyInfo.KeyChar != 0) {
+				return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
+			}
 
 
 			return (Key)(0xffffffff);
 			return (Key)(0xffffffff);
 		}
 		}
@@ -367,55 +370,46 @@ namespace Terminal.Gui {
 			return keyMod != Key.Null ? keyMod | key : key;
 			return keyMod != Key.Null ? keyMod | key : key;
 		}
 		}
 
 
+		Action<KeyEvent> keyHandler;
+		Action<KeyEvent> keyUpHandler;
+
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 		{
-			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			(mainLoop.Driver as FakeMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
-				var map = MapKey (consoleKey);
-				if (map == (Key)0xffffffff)
-					return;
-
-				if (keyModifiers == null)
-					keyModifiers = new KeyModifiers ();
-				switch (consoleKey.Modifiers) {
-				case ConsoleModifiers.Alt:
-					keyModifiers.Alt = true;
-					break;
-				case ConsoleModifiers.Shift:
-					keyModifiers.Shift = true;
-					break;
-				case ConsoleModifiers.Control:
-					keyModifiers.Ctrl = true;
-					break;
-				}
+			this.keyHandler = keyHandler;
+			this.keyUpHandler = keyUpHandler;
 
 
-				keyHandler (new KeyEvent (map, keyModifiers));
-				keyUpHandler (new KeyEvent (map, keyModifiers));
-				keyModifiers = new KeyModifiers ();
-			};
+			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
+			(mainLoop.Driver as FakeMainLoop).KeyPressed += (consoleKey) => ProcessInput (consoleKey);
 		}
 		}
 
 
-		public override Attribute GetAttribute ()
+		void ProcessInput (ConsoleKeyInfo consoleKey)
 		{
 		{
-			return currentAttribute;
-		}
+			var map = MapKey (consoleKey);
+			if (map == (Key)0xffffffff)
+				return;
 
 
-		#region Unused
-		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-		{
-		}
+			if (keyModifiers == null) {
+				keyModifiers = new KeyModifiers ();
+			}
 
 
-		public override void SetColors (short foregroundColorId, short backgroundColorId)
-		{
-			throw new NotImplementedException ();
-		}
+			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
+				keyModifiers.Alt = true;
+			}
+			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
+				keyModifiers.Shift = true;
+			}
+			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+				keyModifiers.Ctrl = true;
+			}
 
 
-		public override void CookMouse ()
-		{
+			keyHandler (new KeyEvent (map, keyModifiers));
+			keyUpHandler (new KeyEvent (map, keyModifiers));
+			keyModifiers = new KeyModifiers ();
 		}
 		}
 
 
-		public override void UncookMouse ()
+		public override Attribute GetAttribute ()
 		{
 		{
+			return currentAttribute;
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
@@ -438,6 +432,29 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
+		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+		{
+			ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
+		}
+
+		#region Unused
+		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
+		{
+		}
+
+		public override void SetColors (short foregroundColorId, short backgroundColorId)
+		{
+			throw new NotImplementedException ();
+		}
+
+		public override void CookMouse ()
+		{
+		}
+
+		public override void UncookMouse ()
+		{
+		}
+
 		#endregion
 		#endregion
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
 	}
 	}

+ 24 - 7
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -226,7 +226,7 @@ namespace Terminal.Gui {
 		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
 		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
 		{
 		{
 			InputResult inputResult = new InputResult {
 			InputResult inputResult = new InputResult {
-				EventType = EventType.key
+				EventType = EventType.Key
 			};
 			};
 			ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
 			ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
 			ConsoleKey key = 0;
 			ConsoleKey key = 0;
@@ -280,7 +280,7 @@ namespace Terminal.Gui {
 				newConsoleKeyInfo = consoleKeyInfo;
 				newConsoleKeyInfo = consoleKeyInfo;
 				break;
 				break;
 			}
 			}
-			if (inputResult.EventType == EventType.key) {
+			if (inputResult.EventType == EventType.Key) {
 				inputResult.ConsoleKeyInfo = newConsoleKeyInfo;
 				inputResult.ConsoleKeyInfo = newConsoleKeyInfo;
 			} else {
 			} else {
 				inputResult.MouseEvent = mouseEvent;
 				inputResult.MouseEvent = mouseEvent;
@@ -440,7 +440,7 @@ namespace Terminal.Gui {
 				GetMouseEvent (cki);
 				GetMouseEvent (cki);
 				return;
 				return;
 			}
 			}
-			if (inputResult.EventType == EventType.key) {
+			if (inputResult.EventType == EventType.Key) {
 				inputResult.ConsoleKeyInfo = newConsoleKeyInfo;
 				inputResult.ConsoleKeyInfo = newConsoleKeyInfo;
 			} else {
 			} else {
 				inputResult.MouseEvent = mouseEvent;
 				inputResult.MouseEvent = mouseEvent;
@@ -1010,7 +1010,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public enum EventType {
 		public enum EventType {
-			key = 1,
+			Key = 1,
 			Mouse = 2,
 			Mouse = 2,
 			WindowSize = 3,
 			WindowSize = 3,
 			WindowPosition = 4
 			WindowPosition = 4
@@ -1441,7 +1441,7 @@ namespace Terminal.Gui {
 			case ConsoleKey.Enter:
 			case ConsoleKey.Enter:
 				return MapKeyModifiers (keyInfo, Key.Enter);
 				return MapKeyModifiers (keyInfo, Key.Enter);
 			case ConsoleKey.Spacebar:
 			case ConsoleKey.Spacebar:
-				return MapKeyModifiers (keyInfo, Key.Space);
+				return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
 			case ConsoleKey.Backspace:
 			case ConsoleKey.Backspace:
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 			case ConsoleKey.Delete:
 			case ConsoleKey.Delete:
@@ -1506,7 +1506,7 @@ namespace Terminal.Gui {
 				return (Key)((uint)Key.F1 + delta);
 				return (Key)((uint)Key.F1 + delta);
 			}
 			}
 			if (keyInfo.KeyChar != 0) {
 			if (keyInfo.KeyChar != 0) {
-				return (Key)((uint)keyInfo.KeyChar);
+				return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
 			}
 			}
 
 
 			return (Key)(0xffffffff);
 			return (Key)(0xffffffff);
@@ -1557,7 +1557,7 @@ namespace Terminal.Gui {
 		void ProcessInput (NetEvents.InputResult inputEvent)
 		void ProcessInput (NetEvents.InputResult inputEvent)
 		{
 		{
 			switch (inputEvent.EventType) {
 			switch (inputEvent.EventType) {
-			case NetEvents.EventType.key:
+			case NetEvents.EventType.Key:
 				var map = MapKey (inputEvent.ConsoleKeyInfo);
 				var map = MapKey (inputEvent.ConsoleKeyInfo);
 				if (map == (Key)0xffffffff) {
 				if (map == (Key)0xffffffff) {
 					return;
 					return;
@@ -1744,6 +1744,23 @@ namespace Terminal.Gui {
 		{
 		{
 			return false;
 			return false;
 		}
 		}
+
+		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+		{
+			NetEvents.InputResult input = new NetEvents.InputResult ();
+			ConsoleKey ck;
+			if (char.IsLetter (keyChar)) {
+				ck = key;
+			} else {
+				ck = (ConsoleKey)'\0';
+			}
+			input.EventType = NetEvents.EventType.Key;
+			input.ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, ck, shift, alt, control);
+
+			try {
+				ProcessInput (input);
+			} catch (OverflowException) { }
+		}
 		#endregion
 		#endregion
 
 
 		//
 		//

+ 64 - 10
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -116,23 +116,23 @@ namespace Terminal.Gui {
 				var err = Marshal.GetLastWin32Error ();
 				var err = Marshal.GetLastWin32Error ();
 				if (err != 0) {
 				if (err != 0) {
 					throw new System.ComponentModel.Win32Exception (err);
 					throw new System.ComponentModel.Win32Exception (err);
-				}				
+				}
 				visibility = Gui.CursorVisibility.Default;
 				visibility = Gui.CursorVisibility.Default;
 
 
 				return false;
 				return false;
 			}
 			}
 
 
-			if (!info.bVisible)        
+			if (!info.bVisible)
 				visibility = CursorVisibility.Invisible;
 				visibility = CursorVisibility.Invisible;
-			else if (info.dwSize > 50) 
+			else if (info.dwSize > 50)
 				visibility = CursorVisibility.Box;
 				visibility = CursorVisibility.Box;
-			else                       
+			else
 				visibility = CursorVisibility.Underline;
 				visibility = CursorVisibility.Underline;
 
 
 			return true;
 			return true;
 		}
 		}
 
 
-		public bool EnsureCursorVisibility () 
+		public bool EnsureCursorVisibility ()
 		{
 		{
 			if (initialCursorVisibility.HasValue && pendingCursorVisibility.HasValue && SetCursorVisibility (pendingCursorVisibility.Value)) {
 			if (initialCursorVisibility.HasValue && pendingCursorVisibility.HasValue && SetCursorVisibility (pendingCursorVisibility.Value)) {
 				pendingCursorVisibility = null;
 				pendingCursorVisibility = null;
@@ -161,11 +161,11 @@ namespace Terminal.Gui {
 
 
 			if (currentCursorVisibility.HasValue == false || currentCursorVisibility.Value != visibility) {
 			if (currentCursorVisibility.HasValue == false || currentCursorVisibility.Value != visibility) {
 				ConsoleCursorInfo info = new ConsoleCursorInfo {
 				ConsoleCursorInfo info = new ConsoleCursorInfo {
-					dwSize   =  (uint) visibility & 0x00FF,
-					bVisible = ((uint) visibility & 0xFF00) != 0
+					dwSize = (uint)visibility & 0x00FF,
+					bVisible = ((uint)visibility & 0xFF00) != 0
 				};
 				};
 
 
-				if (!SetConsoleCursorInfo (ScreenBuffer, ref info)) 
+				if (!SetConsoleCursorInfo (ScreenBuffer, ref info))
 					return false;
 					return false;
 
 
 				currentCursorVisibility = visibility;
 				currentCursorVisibility = visibility;
@@ -1063,7 +1063,7 @@ namespace Terminal.Gui {
 			case ConsoleKey.Enter:
 			case ConsoleKey.Enter:
 				return MapKeyModifiers (keyInfo, Key.Enter);
 				return MapKeyModifiers (keyInfo, Key.Enter);
 			case ConsoleKey.Spacebar:
 			case ConsoleKey.Spacebar:
-				return MapKeyModifiers (keyInfo, Key.Space);
+				return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
 			case ConsoleKey.Backspace:
 			case ConsoleKey.Backspace:
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 				return MapKeyModifiers (keyInfo, Key.Backspace);
 			case ConsoleKey.Delete:
 			case ConsoleKey.Delete:
@@ -1154,7 +1154,7 @@ namespace Terminal.Gui {
 				return (Key)((uint)Key.F1 + delta);
 				return (Key)((uint)Key.F1 + delta);
 			}
 			}
 			if (keyInfo.KeyChar != 0) {
 			if (keyInfo.KeyChar != 0) {
-				return (Key)((uint)keyInfo.KeyChar);
+				return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
 			}
 			}
 
 
 			return (Key)(0xffffffff);
 			return (Key)(0xffffffff);
@@ -1419,6 +1419,60 @@ namespace Terminal.Gui {
 		public override void CookMouse ()
 		public override void CookMouse ()
 		{
 		{
 		}
 		}
+
+		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+		{
+			WindowsConsole.InputRecord input = new WindowsConsole.InputRecord ();
+			input.EventType = WindowsConsole.EventType.Key;
+
+			WindowsConsole.KeyEventRecord keyEvent = new WindowsConsole.KeyEventRecord ();
+			keyEvent.bKeyDown = true;
+			WindowsConsole.ControlKeyState controlKey = new WindowsConsole.ControlKeyState ();
+			if (shift) {
+				controlKey |= WindowsConsole.ControlKeyState.ShiftPressed;
+				keyEvent.UnicodeChar = '\0';
+				keyEvent.wVirtualKeyCode = 16;
+			}
+			if (alt) {
+				controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed;
+				controlKey |= WindowsConsole.ControlKeyState.RightAltPressed;
+				keyEvent.UnicodeChar = '\0';
+				keyEvent.wVirtualKeyCode = 18;
+			}
+			if (control) {
+				controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed;
+				controlKey |= WindowsConsole.ControlKeyState.RightControlPressed;
+				keyEvent.UnicodeChar = '\0';
+				keyEvent.wVirtualKeyCode = 17;
+			}
+			keyEvent.dwControlKeyState = controlKey;
+
+			input.KeyEvent = keyEvent;
+
+			if (shift || alt || control) {
+				ProcessInput (input);
+			}
+
+			keyEvent.UnicodeChar = keyChar;
+			if ((shift || alt || control)
+				&& (key >= ConsoleKey.A && key <= ConsoleKey.Z
+				|| key >= ConsoleKey.D0 && key <= ConsoleKey.D9)) {
+				keyEvent.wVirtualKeyCode = (ushort)key;
+			} else {
+				keyEvent.wVirtualKeyCode = '\0';
+			}
+
+			input.KeyEvent = keyEvent;
+
+			try {
+				ProcessInput (input);
+			} catch (OverflowException) { }
+			finally {
+				keyEvent.bKeyDown = false;
+				input.KeyEvent = keyEvent;
+				ProcessInput (input);
+			}
+		}
 		#endregion
 		#endregion
 	}
 	}
 
 

+ 10 - 0
Terminal.Gui/Core/ConsoleDriver.cs

@@ -739,6 +739,16 @@ namespace Terminal.Gui {
 		/// <param name="backgroundColorId">Background color identifier.</param>
 		/// <param name="backgroundColorId">Background color identifier.</param>
 		public abstract void SetColors (short foregroundColorId, short backgroundColorId);
 		public abstract void SetColors (short foregroundColorId, short backgroundColorId);
 
 
+		/// <summary>
+		/// Allows sending keys without typing on a keyboard.
+		/// </summary>
+		/// <param name="keyChar">The character key.</param>
+		/// <param name="key">The key.</param>
+		/// <param name="shift">If shift key is sending.</param>
+		/// <param name="alt">If alt key is sending.</param>
+		/// <param name="control">If control key is sending.</param>
+		public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control);
+
 		/// <summary>
 		/// <summary>
 		/// Set the handler when the terminal is resized.
 		/// Set the handler when the terminal is resized.
 		/// </summary>
 		/// </summary>

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

@@ -75,7 +75,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// The key code representing null or empty
 		/// The key code representing null or empty
 		/// </summary>
 		/// </summary>
-		Null = 0,
+		Null = '\0',
 
 
 		/// <summary>
 		/// <summary>
 		/// The key code for the user pressing the return key.
 		/// The key code for the user pressing the return key.

+ 62 - 16
Terminal.Gui/Core/TextFormatter.cs

@@ -273,7 +273,7 @@ namespace Terminal.Gui {
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
 		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
 		/// Upon a 'get' of this property, if the text needs to be formatted (if <see cref="NeedsFormat"/> is <c>true</c>)
-		/// <see cref="Format(ustring, int, bool, bool, bool)"/> will be called internally. 
+		/// <see cref="Format(ustring, int, bool, bool, bool, int)"/> will be called internally. 
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public List<ustring> Lines {
 		public List<ustring> Lines {
@@ -371,6 +371,7 @@ namespace Terminal.Gui {
 		/// <param name="width">The width to contain the text to</param>
 		/// <param name="width">The width to contain the text to</param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c>, the wrapped text will keep the trailing spaces.
 		/// <param name="preserveTrailingSpaces">If <c>true</c>, the wrapped text will keep the trailing spaces.
 		///  If <c>false</c>, the trailing spaces will be trimmed.</param>
 		///  If <c>false</c>, the trailing spaces will be trimmed.</param>
+		/// <param name="tabWidth">The tab width.</param>
 		/// <returns>Returns a list of word wrapped lines.</returns>
 		/// <returns>Returns a list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
@@ -380,7 +381,7 @@ namespace Terminal.Gui {
 		/// This method strips Newline ('\n' and '\r\n') sequences before processing.
 		/// This method strips Newline ('\n' and '\r\n') sequences before processing.
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
-		public static List<ustring> WordWrap (ustring text, int width, bool preserveTrailingSpaces = false)
+		public static List<ustring> WordWrap (ustring text, int width, bool preserveTrailingSpaces = false, int tabWidth = 0)
 		{
 		{
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
 				throw new ArgumentOutOfRangeException ("Width cannot be negative.");
@@ -394,16 +395,58 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			var runes = StripCRLF (text).ToRuneList ();
 			var runes = StripCRLF (text).ToRuneList ();
+			if (!preserveTrailingSpaces) {
+				while ((end = start + width) < runes.Count) {
+					while (runes [end] != ' ' && end > start)
+						end--;
+					if (end == start)
+						end = start + width;
+					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+					start = end;
+					if (runes [end] == ' ') {
+						start++;
+					}
+				}
+			} else {
+				while ((end = start) < runes.Count) {
+					end = GetNextWhiteSpace (start, width);
+					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
+					start = end;
+				}
+			}
 
 
-			while ((end = start + width) < runes.Count) {
-				while (runes [end] != ' ' && end > start)
-					end -= 1;
-				if (end == start)
-					end = start + width;
-				lines.Add (ustring.Make (runes.GetRange (start, end - start)));
-				start = end;
-				if (runes [end] == ' ' && !preserveTrailingSpaces) {
-					start++;
+			int GetNextWhiteSpace (int from, int cWidth, int cLength = 0)
+			{
+				var to = from;
+				var length = cLength;
+
+				while (length < cWidth && to < runes.Count) {
+					var rune = runes [to];
+					length += Rune.ColumnWidth (rune);
+					if (rune == ' ') {
+						if (length == cWidth) {
+							return to + 1;
+						} else if (length > cWidth) {
+							return to;
+						} else {
+							return GetNextWhiteSpace (to + 1, cWidth, length);
+						}
+					} else if (rune == '\t') {
+						length += tabWidth + 1;
+						if (length == tabWidth && tabWidth > cWidth) {
+							return to + 1;
+						} else if (length > cWidth && tabWidth > cWidth) {
+							return to;
+						} else {
+							return GetNextWhiteSpace (to + 1, cWidth, length);
+						}
+					}
+					to++;
+				}
+				if (cLength > 0 && to < runes.Count && runes [to] != ' ') {
+					return from;
+				} else {
+					return to;
 				}
 				}
 			}
 			}
 
 
@@ -502,6 +545,7 @@ namespace Terminal.Gui {
 		/// <param name="talign">Specifies how the text will be aligned horizontally.</param>
 		/// <param name="talign">Specifies how the text will be aligned horizontally.</param>
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
+		/// <param name="tabWidth">The tab width.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
@@ -514,9 +558,9 @@ namespace Terminal.Gui {
 		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
-		public static List<ustring> Format (ustring text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false)
+		public static List<ustring> Format (ustring text, int width, TextAlignment talign, bool wordWrap, bool preserveTrailingSpaces = false, int tabWidth = 0)
 		{
 		{
-			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces);
+			return Format (text, width, talign == TextAlignment.Justified, wordWrap, preserveTrailingSpaces, tabWidth);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -527,6 +571,7 @@ namespace Terminal.Gui {
 		/// <param name="justify">Specifies whether the text should be justified.</param>
 		/// <param name="justify">Specifies whether the text should be justified.</param>
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="wordWrap">If <c>true</c>, the text will be wrapped to new lines as need. If <c>false</c>, forces text to fit a single line. Line breaks are converted to spaces. The text will be clipped to <c>width</c></param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
 		/// <param name="preserveTrailingSpaces">If <c>true</c> and 'wordWrap' also true, the wrapped text will keep the trailing spaces. If <c>false</c>, the trailing spaces will be trimmed.</param>
+		/// <param name="tabWidth">The tab width.</param>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <returns>A list of word wrapped lines.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <para>
@@ -539,7 +584,8 @@ namespace Terminal.Gui {
 		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// If <c>width</c> is int.MaxValue, the text will be formatted to the maximum width possible. 
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
-		public static List<ustring> Format (ustring text, int width, bool justify, bool wordWrap, bool preserveTrailingSpaces = false)
+		public static List<ustring> Format (ustring text, int width, bool justify, bool wordWrap,
+			bool preserveTrailingSpaces = false, int tabWidth = 0)
 		{
 		{
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
@@ -566,7 +612,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < runeCount; i++) {
 			for (int i = 0; i < runeCount; i++) {
 				Rune c = runes [i];
 				Rune c = runes [i];
 				if (c == '\n') {
 				if (c == '\n') {
-					var wrappedLines = WordWrap (ustring.Make (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces);
+					var wrappedLines = WordWrap (ustring.Make (runes.GetRange (lp, i - lp)), width, preserveTrailingSpaces, tabWidth);
 					foreach (var line in wrappedLines) {
 					foreach (var line in wrappedLines) {
 						lineResult.Add (ClipAndJustify (line, width, justify));
 						lineResult.Add (ClipAndJustify (line, width, justify));
 					}
 					}
@@ -576,7 +622,7 @@ namespace Terminal.Gui {
 					lp = i + 1;
 					lp = i + 1;
 				}
 				}
 			}
 			}
-			foreach (var line in WordWrap (ustring.Make (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces)) {
+			foreach (var line in WordWrap (ustring.Make (runes.GetRange (lp, runeCount - lp)), width, preserveTrailingSpaces, tabWidth)) {
 				lineResult.Add (ClipAndJustify (line, width, justify));
 				lineResult.Add (ClipAndJustify (line, width, justify));
 			}
 			}
 
 

+ 7 - 4
Terminal.Gui/Views/TextField.cs

@@ -200,7 +200,7 @@ namespace Terminal.Gui {
 				if (idx == point)
 				if (idx == point)
 					break;
 					break;
 				var cols = Rune.ColumnWidth (text [idx]);
 				var cols = Rune.ColumnWidth (text [idx]);
-				col = TextModel.SetCol (col, Frame.Width - 1, cols);
+				TextModel.SetCol (ref col, Frame.Width - 1, cols);
 			}
 			}
 			Move (col, 0);
 			Move (col, 0);
 		}
 		}
@@ -234,7 +234,9 @@ namespace Terminal.Gui {
 				if (col + cols <= width) {
 				if (col + cols <= width) {
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 				}
 				}
-				col = TextModel.SetCol (col, width, cols);
+				if (!TextModel.SetCol (ref col, width, cols)) {
+					break;
+				}
 				if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
 				if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
 					break;
 					break;
 				}
 				}
@@ -256,7 +258,7 @@ namespace Terminal.Gui {
 			} else if (first + point - (Frame.Width + offB) == 0 ||
 			} else if (first + point - (Frame.Width + offB) == 0 ||
 				  TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) {
 				  TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) {
 				first = Math.Max (TextModel.CalculateLeftColumn (text, first,
 				first = Math.Max (TextModel.CalculateLeftColumn (text, first,
-					point, Frame.Width - 1 + offB, point), 0);
+					point, Frame.Width + offB), 0);
 			}
 			}
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
@@ -399,7 +401,7 @@ namespace Terminal.Gui {
 			case Key.CursorUp | Key.ShiftMask | Key.CtrlMask:
 			case Key.CursorUp | Key.ShiftMask | Key.CtrlMask:
 			case (Key)((int)'B' + Key.ShiftMask | Key.AltMask):
 			case (Key)((int)'B' + Key.ShiftMask | Key.AltMask):
 				if (point > 0) {
 				if (point > 0) {
-					int x = Math.Min (start > -1 && start > point ? start : point, text.Count - 1);
+					int x = Math.Min (start > -1 && start > point ? start : point, text.Count);
 					if (x > 0) {
 					if (x > 0) {
 						int sbw = WordBackward (x);
 						int sbw = WordBackward (x);
 						if (sbw != -1)
 						if (sbw != -1)
@@ -895,6 +897,7 @@ namespace Terminal.Gui {
 			point = selStart + cbTxt.RuneCount;
 			point = selStart + cbTxt.RuneCount;
 			ClearAllSelection ();
 			ClearAllSelection ();
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
+			Adjust ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>

+ 274 - 89
Terminal.Gui/Views/TextView.cs

@@ -181,6 +181,9 @@ namespace Terminal.Gui {
 		public void RemoveLine (int pos)
 		public void RemoveLine (int pos)
 		{
 		{
 			if (lines.Count > 0) {
 			if (lines.Count > 0) {
+				if (lines.Count == 1 && lines [0].Count == 0) {
+					return;
+				}
 				lines.RemoveAt (pos);
 				lines.RemoveAt (pos);
 			}
 			}
 		}
 		}
@@ -190,12 +193,15 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		/// <param name="first">The first line.</param>
 		/// <param name="first">The first line.</param>
 		/// <param name="last">The last line.</param>
 		/// <param name="last">The last line.</param>
-		public int GetMaxVisibleLine (int first, int last)
+		/// <param name="tabWidth">The tab width.</param>
+		public int GetMaxVisibleLine (int first, int last, int tabWidth)
 		{
 		{
 			int maxLength = 0;
 			int maxLength = 0;
 			last = last < lines.Count ? last : lines.Count;
 			last = last < lines.Count ? last : lines.Count;
 			for (int i = first; i < last; i++) {
 			for (int i = first; i < last; i++) {
-				var l = GetLine (i).Count;
+				var line = GetLine (i);
+				var tabSum = line.Sum (r => r == '\t' ? tabWidth - 1 : 0);
+				var l = line.Count + tabSum;
 				if (l > maxLength) {
 				if (l > maxLength) {
 					maxLength = l;
 					maxLength = l;
 				}
 				}
@@ -204,16 +210,17 @@ namespace Terminal.Gui {
 			return maxLength;
 			return maxLength;
 		}
 		}
 
 
-		internal static int SetCol (int col, int width, int cols)
+		internal static bool SetCol (ref int col, int width, int cols)
 		{
 		{
 			if (col + cols <= width) {
 			if (col + cols <= width) {
 				col += cols;
 				col += cols;
+				return true;
 			}
 			}
 
 
-			return col;
+			return false;
 		}
 		}
 
 
-		internal static int GetColFromX (List<Rune> t, int start, int x)
+		internal static int GetColFromX (List<Rune> t, int start, int x, int tabWidth = 0)
 		{
 		{
 			if (x < 0) {
 			if (x < 0) {
 				return x;
 				return x;
@@ -223,6 +230,9 @@ namespace Terminal.Gui {
 			for (int i = start; i < t.Count; i++) {
 			for (int i = start; i < t.Count; i++) {
 				var r = t [i];
 				var r = t [i];
 				size += Rune.ColumnWidth (r);
 				size += Rune.ColumnWidth (r);
+				if (r == '\t' && tabWidth > 0) {
+					size += tabWidth + 1;
+				}
 				if (i == pX || (size > pX)) {
 				if (i == pX || (size > pX)) {
 					return i - start;
 					return i - start;
 				}
 				}
@@ -231,7 +241,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		// Returns the size and length in a range of the string.
 		// Returns the size and length in a range of the string.
-		internal static (int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
+		internal static (int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1,
+			bool checkNextRune = true, int tabWidth = 0)
 		{
 		{
 			if (t == null || t.Count == 0) {
 			if (t == null || t.Count == 0) {
 				return (0, 0);
 				return (0, 0);
@@ -244,39 +255,59 @@ namespace Terminal.Gui {
 				var rune = t [i];
 				var rune = t [i];
 				size += Rune.ColumnWidth (rune);
 				size += Rune.ColumnWidth (rune);
 				len += Rune.RuneLen (rune);
 				len += Rune.RuneLen (rune);
-				if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
-					size += Rune.ColumnWidth (t [i + 1]);
-					len += Rune.RuneLen (t [i + 1]);
+				if (rune == '\t' && tabWidth > 0) {
+					size += tabWidth + 1;
+					len += tabWidth - 1;
+				}
+				if (checkNextRune && i == tcount - 1 && t.Count > tcount
+					&& IsWideRune (t [i + 1], tabWidth, out int s, out int l)) {
+					size += s;
+					len += l;
+				}
+			}
+
+			bool IsWideRune (Rune r, int tWidth, out int s, out int l)
+			{
+				s = Rune.ColumnWidth (r);
+				l = Rune.RuneLen (r);
+				if (r == '\t' && tWidth > 0) {
+					s += tWidth + 1;
+					l += tWidth - 1;
 				}
 				}
+
+				return s > 1;
 			}
 			}
+
 			return (size, len);
 			return (size, len);
 		}
 		}
 
 
 		// Returns the left column in a range of the string.
 		// Returns the left column in a range of the string.
-		internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int currentColumn)
+		internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int tabWidth = 0)
 		{
 		{
-			if (t == null) {
+			if (t == null || t.Count == 0) {
 				return 0;
 				return 0;
 			}
 			}
-			(var dSize, _) = TextModel.DisplaySize (t, start, end);
-			if (dSize < width) {
-				return start;
-			}
 			int size = 0;
 			int size = 0;
 			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
 			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
 			int col = 0;
 			int col = 0;
-			for (int i = tcount; i > start; i--) {
+
+			for (int i = tcount; i >= 0; i--) {
 				var rune = t [i];
 				var rune = t [i];
-				var s = Rune.ColumnWidth (rune);
-				size += s;
-				if (size >= dSize - width) {
-					col = tcount - i + start;
-					if (start == 0 || col == start || (currentColumn == t.Count && (currentColumn - col > width))) {
+				size += Rune.ColumnWidth (rune);
+				if (rune == '\t') {
+					size += tabWidth + 1;
+				}
+				if (size > width) {
+					if (end == t.Count) {
 						col++;
 						col++;
 					}
 					}
 					break;
 					break;
+				} else if (end < t.Count && col > 0 && start < end && col == start) {
+					break;
 				}
 				}
+				col = i;
 			}
 			}
+
 			return col;
 			return col;
 		}
 		}
 
 
@@ -479,7 +510,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
 		public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
-			int row = 0, int col = 0, int startRow = 0, int startCol = 0)
+			int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0)
 		{
 		{
 			frameWidth = width;
 			frameWidth = width;
 
 
@@ -500,7 +531,8 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Model.Count; i++) {
 			for (int i = 0; i < Model.Count; i++) {
 				var line = Model.GetLine (i);
 				var line = Model.GetLine (i);
 				var wrappedLines = ToListRune (
 				var wrappedLines = ToListRune (
-					TextFormatter.Format (ustring.Make (line), width, TextAlignment.Left, true, true));
+					TextFormatter.Format (ustring.Make (line), width,
+					TextAlignment.Left, true, true, tabWidth));
 				int sumColWidth = 0;
 				int sumColWidth = 0;
 				for (int j = 0; j < wrappedLines.Count; j++) {
 				for (int j = 0; j < wrappedLines.Count; j++) {
 					var wrapLine = wrappedLines [j];
 					var wrapLine = wrappedLines [j];
@@ -841,6 +873,10 @@ namespace Terminal.Gui {
 		bool wordWrap;
 		bool wordWrap;
 		WordWrapManager wrapManager;
 		WordWrapManager wrapManager;
 		bool continuousFind;
 		bool continuousFind;
+		int tabWidth = 4;
+		bool allowsTab = true;
+		bool allowsReturn = true;
+		bool multiline = true;
 
 
 		/// <summary>
 		/// <summary>
 		/// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
 		/// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
@@ -939,7 +975,8 @@ namespace Terminal.Gui {
 					out int nRow, out int nCol,
 					out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
 					currentRow, currentColumn,
-					selectionStartRow, selectionStartColumn);
+					selectionStartRow, selectionStartColumn,
+					tabWidth);
 				currentRow = nRow;
 				currentRow = nRow;
 				currentColumn = nCol;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
 				selectionStartRow = nStartRow;
@@ -961,7 +998,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Gets the maximum visible length line.
 		/// Gets the maximum visible length line.
 		/// </summary>
 		/// </summary>
-		public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
+		public int Maxlength => model.GetMaxVisibleLine (topRow, topRow + Frame.Height, TabWidth);
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets the  number of lines.
 		/// Gets the  number of lines.
@@ -1074,6 +1111,104 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public int RightOffset { get; set; }
 		public int RightOffset { get; set; }
 
 
+		/// <summary>
+		/// Gets or sets a value indicating whether pressing ENTER in a <see cref="TextView"/>
+		/// creates a new line of text in the view or activates the default button for the toplevel.
+		/// </summary>
+		public bool AllowsReturn {
+			get => allowsReturn;
+			set {
+				allowsReturn = value;
+				if (allowsReturn && !multiline) {
+					Multiline = true;
+				}
+				if (!allowsReturn && multiline) {
+					Multiline = false;
+					AllowsTab = false;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets a value indicating whether pressing the TAB key in a <see cref="TextView"/>
+		/// types a TAB character in the view instead of moving the focus to the next view in the tab order.
+		/// </summary>
+		public bool AllowsTab {
+			get => allowsTab;
+			set {
+				allowsTab = value;
+				if (allowsTab && tabWidth == 0) {
+					tabWidth = 4;
+				}
+				if (allowsTab && !multiline) {
+					Multiline = true;
+				}
+				if (!allowsTab && multiline) {
+					Multiline = false;
+				}
+				if (!allowsTab && tabWidth > 0) {
+					tabWidth = 0;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets a value indicating the number of whitespace when pressing the TAB key.
+		/// </summary>
+		public int TabWidth {
+			get => tabWidth;
+			set {
+				tabWidth = Math.Max (value, 0);
+				if (tabWidth == 0 && AllowsTab) {
+					AllowsTab = false;
+				}
+				if (tabWidth > 0 && !AllowsTab) {
+					AllowsTab = true;
+				}
+			}
+		}
+
+		Dim savedHeight = null;
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="TextView"/> is a multiline text view.
+		/// </summary>
+		public bool Multiline {
+			get => multiline;
+			set {
+				multiline = value;
+				if (multiline && !allowsTab) {
+					AllowsTab = true;
+				}
+				if (multiline && !allowsReturn) {
+					AllowsReturn = true;
+				}
+
+				if (!multiline) {
+					AllowsReturn = false;
+					AllowsTab = false;
+					currentColumn = 0;
+					currentRow = 0;
+					savedHeight = Height;
+					var lyout = LayoutStyle;
+					if (LayoutStyle == LayoutStyle.Computed) {
+						LayoutStyle = LayoutStyle.Absolute;
+					}
+					Height = 1;
+					LayoutStyle = lyout;
+					SetNeedsDisplay ();
+				} else if (multiline && savedHeight != null) {
+					var lyout = LayoutStyle;
+					if (LayoutStyle == LayoutStyle.Computed) {
+						LayoutStyle = LayoutStyle.Absolute;
+					}
+					Height = savedHeight;
+					LayoutStyle = lyout;
+					SetNeedsDisplay ();
+				}
+			}
+		}
+
 		int GetSelectedLength ()
 		int GetSelectedLength ()
 		{
 		{
 			return SelectedText.Length;
 			return SelectedText.Length;
@@ -1168,18 +1303,22 @@ namespace Terminal.Gui {
 			if (line.Count > 0) {
 			if (line.Count > 0) {
 				retreat = Math.Max (SpecialRune (line [Math.Min (Math.Max (currentColumn - leftColumn - 1, 0), line.Count - 1)])
 				retreat = Math.Max (SpecialRune (line [Math.Min (Math.Max (currentColumn - leftColumn - 1, 0), line.Count - 1)])
 					? 1 : 0, 0);
 					? 1 : 0, 0);
-				for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
-					if (idx == currentColumn)
+
+				for (int idx = leftColumn; idx < line.Count; idx++) {
+					if (idx >= currentColumn)
 						break;
 						break;
 					var cols = Rune.ColumnWidth (line [idx]);
 					var cols = Rune.ColumnWidth (line [idx]);
-					col += cols - 1;
+					if (line [idx] == '\t' && TabWidth > 0) {
+						cols += TabWidth + 1;
+					}
+					TextModel.SetCol (ref col, Frame.Width, cols);
 				}
 				}
 			}
 			}
-			var ccol = currentColumn - leftColumn + retreat + col;
-			if (leftColumn <= currentColumn && ccol < Frame.Width
-				&& topRow <= currentRow && currentRow - topRow < Frame.Height) {
+			col += retreat;
+			if ((col >= leftColumn || col < Frame.Width)
+				&& topRow <= currentRow && currentRow - topRow + BottomOffset < Frame.Height) {
 				ResetCursorVisibility ();
 				ResetCursorVisibility ();
-				Move (ccol, currentRow - topRow);
+				Move (col, currentRow - topRow);
 			} else {
 			} else {
 				SaveCursorVisibility ();
 				SaveCursorVisibility ();
 			}
 			}
@@ -1514,53 +1653,61 @@ namespace Terminal.Gui {
 		{
 		{
 			ColorNormal ();
 			ColorNormal ();
 
 
-			int bottom = bounds.Bottom;
-			int right = bounds.Right;
-			for (int row = bounds.Top; row < bottom; row++) {
-				int textLine = topRow + row;
-				if (textLine >= model.Count) {
-					ColorNormal ();
-					ClearRegion (bounds.Left, row, bounds.Right, row + 1);
-					continue;
-				}
-				var line = model.GetLine (textLine);
+			var offB = OffSetBackground ();
+			int right = Frame.Width + offB.width + RightOffset;
+			int bottom = Frame.Height + offB.height + BottomOffset;
+			var row = 0;
+			for (int idxRow = topRow; idxRow < model.Count; idxRow++) {
+				var line = model.GetLine (idxRow);
 				int lineRuneCount = line.Count;
 				int lineRuneCount = line.Count;
-				if (line.Count < bounds.Left) {
-					ClearRegion (bounds.Left, row, bounds.Right, row + 1);
-					continue;
-				}
-
-				Move (bounds.Left, row);
 				var col = 0;
 				var col = 0;
-				for (int idx = bounds.Left; idx < right; idx++) {
-					var lineCol = leftColumn + idx;
-					var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
+
+				Move (0, idxRow);
+				for (int idxCol = leftColumn; idxCol < lineRuneCount; idxCol++) {
+					var rune = idxCol >= lineRuneCount ? ' ' : line [idxCol];
 					var cols = Rune.ColumnWidth (rune);
 					var cols = Rune.ColumnWidth (rune);
-					if (lineCol < line.Count && selecting && PointInSelection (idx + leftColumn, row + topRow)) {
+					if (idxCol < line.Count && selecting && PointInSelection (idxCol, idxRow)) {
 						ColorSelection ();
 						ColorSelection ();
-					} else if (lineCol == currentColumn && textLine == currentRow && !selecting && !Used
-						&& HasFocus && lineCol < lineRuneCount) {
+					} else if (idxCol == currentColumn && idxRow == currentRow && !selecting && !Used
+						&& HasFocus && idxCol < lineRuneCount) {
 						ColorUsed ();
 						ColorUsed ();
 					} else {
 					} else {
 						ColorNormal ();
 						ColorNormal ();
 					}
 					}
 
 
-					if (!SpecialRune (rune)) {
+					if (rune == '\t' && TabWidth > 0) {
+						cols += TabWidth + 1;
+						if (col + cols > right) {
+							cols = right - col;
+						}
+						for (int i = 0; i < cols; i++) {
+							if (col + i < right) {
+								AddRune (col + i, row, ' ');
+							}
+						}
+					} else if (!SpecialRune (rune)) {
 						AddRune (col, row, rune);
 						AddRune (col, row, rune);
 					} else {
 					} else {
 						col++;
 						col++;
 					}
 					}
-					col = TextModel.SetCol (col, bounds.Right, cols);
-					if (idx + 1 < lineRuneCount && col + Rune.ColumnWidth (line [idx + 1]) > right) {
+					if (!TextModel.SetCol (ref col, bounds.Right, cols)) {
+						break;
+					}
+					if (idxCol + 1 < lineRuneCount && col + Rune.ColumnWidth (line [idxCol + 1]) > right) {
 						break;
 						break;
-					} else if (idx == lineRuneCount - 1) {
-						ColorNormal ();
-						for (int i = col; i < right; i++) {
-							Driver.AddRune (' ');
-						}
 					}
 					}
 				}
 				}
+				if (col < right) {
+					ColorNormal ();
+					ClearRegion (col, row, right, row + 1);
+				}
+				row++;
 			}
 			}
+			if (row < bottom) {
+				ColorNormal ();
+				ClearRegion (bounds.Left, row, right, bottom);
+			}
+
 			PositionCursor ();
 			PositionCursor ();
 		}
 		}
 
 
@@ -1720,15 +1867,21 @@ namespace Terminal.Gui {
 			var offB = OffSetBackground ();
 			var offB = OffSetBackground ();
 			var line = GetCurrentLine ();
 			var line = GetCurrentLine ();
 			bool need = !NeedDisplay.IsEmpty || wrapNeeded;
 			bool need = !NeedDisplay.IsEmpty || wrapNeeded;
+			var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
+			var dSize = TextModel.DisplaySize (line, leftColumn, currentColumn, true, TabWidth);
 			if (!wordWrap && currentColumn < leftColumn) {
 			if (!wordWrap && currentColumn < leftColumn) {
 				leftColumn = currentColumn;
 				leftColumn = currentColumn;
 				need = true;
 				need = true;
-			} else if (!wordWrap && (currentColumn - leftColumn + RightOffset > Frame.Width + offB.width ||
-				TextModel.DisplaySize (line, leftColumn, currentColumn).size + RightOffset >= Frame.Width + offB.width)) {
-				leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn,
-					currentColumn + RightOffset, Frame.Width - 1 + offB.width, currentColumn), 0);
+			} else if (!wordWrap && (currentColumn - leftColumn + RightOffset > Frame.Width + offB.width
+				|| dSize.size + RightOffset >= Frame.Width + offB.width)) {
+				leftColumn = TextModel.CalculateLeftColumn (line, leftColumn, currentColumn,
+					Frame.Width + offB.width - RightOffset, TabWidth);
 				need = true;
 				need = true;
+			} else if (dSize.size + RightOffset < Frame.Width + offB.width
+				&& tSize.size + RightOffset < Frame.Width + offB.width) {
+				leftColumn = 0;
 			}
 			}
+
 			if (currentRow < topRow) {
 			if (currentRow < topRow) {
 				topRow = currentRow;
 				topRow = currentRow;
 				need = true;
 				need = true;
@@ -1775,7 +1928,7 @@ namespace Terminal.Gui {
 			if (isRow) {
 			if (isRow) {
 				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
 				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
 			} else if (!wordWrap) {
 			} else if (!wordWrap) {
-				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height + RightOffset);
+				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height + RightOffset, TabWidth);
 				leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
 				leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
 			}
 			}
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
@@ -1845,7 +1998,7 @@ namespace Terminal.Gui {
 					StopSelecting ();
 					StopSelecting ();
 				}
 				}
 				int nPageDnShift = Frame.Height - 1;
 				int nPageDnShift = Frame.Height - 1;
-				if (currentRow < model.Count) {
+				if (currentRow > 0 && currentRow < model.Count) {
 					if (columnTrack == -1)
 					if (columnTrack == -1)
 						columnTrack = currentColumn;
 						columnTrack = currentColumn;
 					currentRow = (currentRow + nPageDnShift) > model.Count
 					currentRow = (currentRow + nPageDnShift) > model.Count
@@ -1926,7 +2079,7 @@ namespace Terminal.Gui {
 					}
 					}
 				}
 				}
 				Adjust ();
 				Adjust ();
-				break;
+				return true;
 
 
 			case Key.B | Key.CtrlMask:
 			case Key.B | Key.CtrlMask:
 			case Key.CursorLeft:
 			case Key.CursorLeft:
@@ -1975,6 +2128,7 @@ namespace Terminal.Gui {
 					StopSelecting ();
 					StopSelecting ();
 				}
 				}
 				currentColumn = 0;
 				currentColumn = 0;
+				leftColumn = 0;
 				Adjust ();
 				Adjust ();
 				break;
 				break;
 			case Key.DeleteChar:
 			case Key.DeleteChar:
@@ -2000,9 +2154,8 @@ namespace Terminal.Gui {
 				}
 				}
 				currentLine = GetCurrentLine ();
 				currentLine = GetCurrentLine ();
 				currentColumn = currentLine.Count;
 				currentColumn = currentLine.Count;
-				int pcol = leftColumn;
 				Adjust ();
 				Adjust ();
-				break;
+				return true;
 
 
 			case Key.K | Key.CtrlMask: // kill-to-end
 			case Key.K | Key.CtrlMask: // kill-to-end
 			case Key.DeleteChar | Key.CtrlMask | Key.ShiftMask:
 			case Key.DeleteChar | Key.CtrlMask | Key.ShiftMask:
@@ -2201,6 +2354,9 @@ namespace Terminal.Gui {
 				break;
 				break;
 
 
 			case Key.Enter:
 			case Key.Enter:
+				if (!AllowsReturn) {
+					return false;
+				}
 				if (isReadOnly)
 				if (isReadOnly)
 					break;
 					break;
 				currentLine = GetCurrentLine ();
 				currentLine = GetCurrentLine ();
@@ -2259,34 +2415,61 @@ namespace Terminal.Gui {
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 				break;
 				break;
 
 
-			default:
-				// Ignore control characters and other special keys
-				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+			case Key _ when ShortcutHelper.GetModifiersKey (kb) == Key.Tab:
+				if (!AllowsTab) {
 					return false;
 					return false;
-				//So that special keys like tab can be processed
-				if (isReadOnly)
-					return true;
-				if (selecting) {
-					ClearSelectedRegion ();
 				}
 				}
-				if (Used) {
-					Insert ((uint)kb.Key);
-					currentColumn++;
-					if (currentColumn >= leftColumn + Frame.Width) {
-						leftColumn++;
-						SetNeedsDisplay ();
+				InsertText (new KeyEvent ((Key)'\t', null));
+				break;
+
+			case Key _ when (ShortcutHelper.GetModifiersKey (kb) == (Key.BackTab | Key.ShiftMask)):
+				if (!AllowsTab) {
+					return false;
+				}
+				if (currentColumn > 0) {
+					currentLine = GetCurrentLine ();
+					if (currentLine.Count > 0 && currentLine [currentColumn - 1] == '\t') {
+						currentLine.RemoveAt (currentColumn - 1);
+						currentColumn--;
 					}
 					}
-				} else {
-					Insert ((uint)kb.Key);
-					currentColumn++;
 				}
 				}
 				break;
 				break;
+
+			default:
+				// Ignore control characters and other special keys
+				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+					return false;
+
+				InsertText (kb);
+				break;
 			}
 			}
 			DoNeededAction ();
 			DoNeededAction ();
 
 
 			return true;
 			return true;
 		}
 		}
 
 
+		bool InsertText (KeyEvent kb)
+		{
+			//So that special keys like tab can be processed
+			if (isReadOnly)
+				return true;
+			if (selecting) {
+				ClearSelectedRegion ();
+			}
+			if (Used) {
+				Insert ((uint)kb.Key);
+				currentColumn++;
+				if (currentColumn >= leftColumn + Frame.Width) {
+					leftColumn++;
+					SetNeedsDisplay ();
+				}
+			} else {
+				Insert ((uint)kb.Key);
+				currentColumn++;
+			}
+			return true;
+		}
+
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool OnKeyUp (KeyEvent kb)
 		public override bool OnKeyUp (KeyEvent kb)
 		{
 		{
@@ -2544,7 +2727,9 @@ namespace Terminal.Gui {
 		public void MoveHome ()
 		public void MoveHome ()
 		{
 		{
 			currentRow = 0;
 			currentRow = 0;
+			topRow = 0;
 			currentColumn = 0;
 			currentColumn = 0;
+			leftColumn = 0;
 			TrackColumn ();
 			TrackColumn ();
 			PositionCursor ();
 			PositionCursor ();
 		}
 		}
@@ -2871,7 +3056,7 @@ namespace Terminal.Gui {
 					currentRow = Math.Max (ev.Y + topRow, 0);
 					currentRow = Math.Max (ev.Y + topRow, 0);
 				}
 				}
 				r = GetCurrentLine ();
 				r = GetCurrentLine ();
-				var idx = TextModel.GetColFromX (r, leftColumn, Math.Max (ev.X, 0));
+				var idx = TextModel.GetColFromX (r, leftColumn, Math.Max (ev.X, 0), TabWidth);
 				if (idx - leftColumn >= r.Count + RightOffset) {
 				if (idx - leftColumn >= r.Count + RightOffset) {
 					currentColumn = Math.Max (r.Count - leftColumn + RightOffset, 0);
 					currentColumn = Math.Max (r.Count - leftColumn + RightOffset, 0);
 				} else {
 				} else {

+ 127 - 0
UICatalog/Scenarios/SendKeys.cs

@@ -0,0 +1,127 @@
+using System;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "SendKeys", Description: "SendKeys sample - Send key combinations.")]
+	[ScenarioCategory ("Input")]
+	class SendKeys : Scenario {
+		public override void Setup ()
+		{
+			var label = new Label ("Insert the text to send:") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 6
+			};
+			Win.Add (label);
+
+			var txtInput = new TextField ("MockKeyPresses") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 5,
+				Width = 20
+			};
+			Win.Add (txtInput);
+
+			var ckbShift = new CheckBox ("Shift") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 4
+			};
+			Win.Add (ckbShift);
+
+			var ckbAlt = new CheckBox ("Alt") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 3
+			};
+			Win.Add (ckbAlt);
+
+			var ckbControl = new CheckBox ("Control") {
+				X = Pos.Center (),
+				Y = Pos.Center () - 2
+			};
+			Win.Add (ckbControl);
+
+			label = new Label ("Result keys:") {
+				X = Pos.Center (),
+				Y = Pos.Center () + 1
+			};
+			Win.Add (label);
+
+			var txtResult = new TextField () {
+				X = Pos.Center (),
+				Y = Pos.Center () + 2,
+				Width = 20,
+			};
+			Win.Add (txtResult);
+
+			var rKeys = "";
+			var rControlKeys = "";
+			var IsShift = false;
+			var IsAlt = false;
+			var IsCtrl = false;
+
+			txtResult.KeyPress += (e) => {
+				rKeys += (char)e.KeyEvent.Key;
+				if (!IsShift && e.KeyEvent.IsShift) {
+					rControlKeys += " Shift ";
+					IsShift = true;
+				}
+				if (!IsAlt && e.KeyEvent.IsAlt) {
+					rControlKeys += " Alt ";
+					IsAlt = true;
+				}
+				if (!IsCtrl && e.KeyEvent.IsCtrl) {
+					rControlKeys += " Ctrl ";
+					IsCtrl = true;
+				}
+			};
+
+			var lblShippedKeys = new Label () {
+				X = Pos.Center (),
+				Y = Pos.Center () + 3,
+				AutoSize = true
+			};
+			Win.Add (lblShippedKeys);
+
+			var lblShippedControlKeys = new Label () {
+				X = Pos.Center (),
+				Y = Pos.Center () + 5,
+				AutoSize = true
+			};
+			Win.Add (lblShippedControlKeys);
+
+			var button = new Button ("Process keys") {
+				X = Pos.Center (),
+				Y = Pos.Center () + 7,
+				IsDefault = true
+			};
+			Win.Add (button);
+
+			void ProcessInput ()
+			{
+				rKeys = "";
+				rControlKeys = "";
+				txtResult.Text = "";
+				IsShift = false;
+				IsAlt = false;
+				IsCtrl = false;
+				txtResult.SetFocus ();
+				foreach (var r in txtInput.Text.ToString ()) {
+					var ck = char.IsLetter (r)
+						? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
+					Application.Driver.SendKeys (r, ck, ckbShift.Checked,
+						ckbAlt.Checked, ckbControl.Checked);
+				}
+				lblShippedKeys.Text = rKeys;
+				lblShippedControlKeys.Text = rControlKeys;
+				txtInput.SetFocus ();
+			}
+
+			button.Clicked += () => ProcessInput ();
+
+			Win.KeyPress += (e) => {
+				if (e.KeyEvent.Key == Key.Enter) {
+					ProcessInput ();
+					e.Handled = true;
+				}
+			};
+		}
+	}
+}

+ 7 - 0
UICatalog/Scenarios/Text.cs

@@ -54,6 +54,13 @@ namespace UICatalog {
 				labelMirroringTextView.Text = textView.Text;
 				labelMirroringTextView.Text = textView.Text;
 			};
 			};
 
 
+			var btnMultiline = new Button ("Toggle Multiline") {
+				X = Pos.Right (textView) + 1,
+				Y = Pos.Top (textView) + 1
+			};
+			btnMultiline.Clicked += () => textView.Multiline = !textView.Multiline;
+			Win.Add (btnMultiline);
+
 			// BUGBUG: 531 - TAB doesn't go to next control from HexView
 			// BUGBUG: 531 - TAB doesn't go to next control from HexView
 			var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (s))) {
 			var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (s))) {
 				X = 1,
 				X = 1,

+ 162 - 1
UnitTests/ConsoleDriverTests.cs

@@ -1,8 +1,10 @@
 using System;
 using System;
+using System.Collections.Generic;
+using System.Linq;
 using Terminal.Gui;
 using Terminal.Gui;
 using Xunit;
 using Xunit;
 
 
-// Alais Console to MockConsole so we don't accidentally use Console
+// Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
 namespace Terminal.Gui.ConsoleDrivers {
 namespace Terminal.Gui.ConsoleDrivers {
@@ -66,5 +68,164 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
 			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
 			driver.End ();
 			driver.End ();
 		}
 		}
+
+		[Fact]
+		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = Application.Top;
+			var view = new View ();
+			var count = 0;
+			var wasKeyPressed = false;
+
+			view.KeyPress += (e) => {
+				wasKeyPressed = true;
+			};
+			top.Add (view);
+
+			Application.Iteration += () => {
+				count++;
+				if (count == 10) {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.False (wasKeyPressed);
+		}
+
+		[Fact]
+		public void FakeDriver_MockKeyPresses ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var text = "MockKeyPresses";
+			var mKeys = new Stack<ConsoleKeyInfo> ();
+			foreach (var r in text.Reverse ()) {
+				var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
+				var cki = new ConsoleKeyInfo (r, ck, false, false, false);
+				mKeys.Push (cki);
+			}
+			FakeConsole.MockKeyPresses = mKeys;
+
+			var top = Application.Top;
+			var view = new View ();
+			var rText = "";
+			var idx = 0;
+
+			view.KeyPress += (e) => {
+				Assert.Equal (text [idx], (char)e.KeyEvent.Key);
+				rText += (char)e.KeyEvent.Key;
+				Assert.Equal (rText, text.Substring (0, idx + 1));
+				e.Handled = true;
+				idx++;
+			};
+			top.Add (view);
+
+			Application.Iteration += () => {
+				if (mKeys.Count == 0) {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.Equal ("MockKeyPresses", rText);
+		}
+
+		[Fact]
+		public void SendKeys_Test ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = Application.Top;
+			var view = new View ();
+			var shift = false; var alt = false; var control = false;
+			Key key = default;
+			Key lastKey = default;
+			List<Key> keyEnums = GetKeys ();
+			int i = 0;
+			int idxKey = 0;
+			var PushIterations = 0;
+			var PopIterations = 0;
+
+			List<Key> GetKeys ()
+			{
+				List<Key> keys = new List<Key> ();
+
+				foreach (Key k in Enum.GetValues (typeof (Key))) {
+					if ((uint)k <= 0xff) {
+						keys.Add (k);
+					} else if ((uint)k > 0xff) {
+						break;
+					}
+				}
+
+				return keys;
+			}
+
+			view.KeyPress += (e) => {
+				e.Handled = true;
+				PopIterations++;
+				var rMk = new KeyModifiers () {
+					Shift = e.KeyEvent.IsShift,
+					Alt = e.KeyEvent.IsAlt,
+					Ctrl = e.KeyEvent.IsCtrl
+				};
+				lastKey = ShortcutHelper.GetModifiersKey (new KeyEvent (e.KeyEvent.Key, rMk));
+				Assert.Equal (key, lastKey);
+			};
+			top.Add (view);
+
+			Application.Iteration += () => {
+				switch (i) {
+				case 0:
+					SendKeys ();
+					break;
+				case 1:
+					shift = true;
+					SendKeys ();
+					break;
+				case 2:
+					alt = true;
+					SendKeys ();
+					break;
+				case 3:
+					control = true;
+					SendKeys ();
+					break;
+				}
+				if (PushIterations == keyEnums.Count * 4) {
+					Application.RequestStop ();
+				}
+			};
+
+			void SendKeys ()
+			{
+				var k = keyEnums [idxKey];
+				var c = (char)k;
+				var ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
+				var mk = new KeyModifiers () {
+					Shift = shift,
+					Alt = alt,
+					Ctrl = control
+				};
+				key = ShortcutHelper.GetModifiersKey (new KeyEvent (k, mk));
+				Application.Driver.SendKeys (c, ck, shift, alt, control);
+				PushIterations++;
+				if (idxKey + 1 < keyEnums.Count) {
+					idxKey++;
+				} else {
+					idxKey = 0;
+					i++;
+				}
+			}
+
+			Application.Run ();
+
+			Assert.Equal (key, lastKey);
+		}
 	}
 	}
 }
 }

+ 178 - 93
UnitTests/TextFormatterTests.cs

@@ -1861,8 +1861,156 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("e", wrappedLines [2].ToString ());
 			Assert.Equal ("e", wrappedLines [2].ToString ());
 			Assert.Equal ("n", wrappedLines [3].ToString ());
 			Assert.Equal ("n", wrappedLines [3].ToString ());
 			Assert.Equal (".", wrappedLines [^1].ToString ());
 			Assert.Equal (".", wrappedLines [^1].ToString ());
+		}
+
+		[Fact]
+		public void WordWrap_preserveTrailingSpaces ()
+		{
+			var text = ustring.Empty;
+			int maxWidth = 1;
+			int expectedClippedWidth = 1;
+
+			List<ustring> wrappedLines;
+
+			text = "A sentence has words.";
+			maxWidth = 14;
+			expectedClippedWidth = 14;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A sentence has", wrappedLines [0].ToString ());
+			Assert.Equal (" words.", wrappedLines [1].ToString ());
+			Assert.True (wrappedLines.Count == 2);
+
+			maxWidth = 3;
+			expectedClippedWidth = 3;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A ", wrappedLines [0].ToString ());
+			Assert.Equal ("sen", wrappedLines [1].ToString ());
+			Assert.Equal ("ten", wrappedLines [2].ToString ());
+			Assert.Equal ("ce ", wrappedLines [3].ToString ());
+			Assert.Equal ("has", wrappedLines [4].ToString ());
+			Assert.Equal (" ", wrappedLines [5].ToString ());
+			Assert.Equal ("wor", wrappedLines [6].ToString ());
+			Assert.Equal ("ds.", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 8);
+
+			maxWidth = 2;
+			expectedClippedWidth = 2;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A ", wrappedLines [0].ToString ());
+			Assert.Equal ("se", wrappedLines [1].ToString ());
+			Assert.Equal ("nt", wrappedLines [2].ToString ());
+			Assert.Equal ("en", wrappedLines [3].ToString ());
+			Assert.Equal ("ce", wrappedLines [4].ToString ());
+			Assert.Equal (" ", wrappedLines [5].ToString ());
+			Assert.Equal ("ha", wrappedLines [6].ToString ());
+			Assert.Equal ("s ", wrappedLines [7].ToString ());
+			Assert.Equal ("wo", wrappedLines [8].ToString ());
+			Assert.Equal ("rd", wrappedLines [9].ToString ());
+			Assert.Equal ("s.", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 11);
+
+			maxWidth = 1;
+			expectedClippedWidth = 1;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A", wrappedLines [0].ToString ());
+			Assert.Equal (" ", wrappedLines [1].ToString ());
+			Assert.Equal ("s", wrappedLines [2].ToString ());
+			Assert.Equal ("e", wrappedLines [3].ToString ());
+			Assert.Equal ("n", wrappedLines [4].ToString ());
+			Assert.Equal ("t", wrappedLines [5].ToString ());
+			Assert.Equal ("e", wrappedLines [6].ToString ());
+			Assert.Equal ("n", wrappedLines [7].ToString ());
+			Assert.Equal ("c", wrappedLines [8].ToString ());
+			Assert.Equal ("e", wrappedLines [9].ToString ());
+			Assert.Equal (" ", wrappedLines [10].ToString ());
+			Assert.Equal (".", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == text.Length);
+		}
 
 
+		[Fact]
+		public void WordWrap_preserveTrailingSpaces_With_Tab ()
+		{
+			var text = ustring.Empty;
+			int maxWidth = 1;
+			int expectedClippedWidth = 1;
+
+			List<ustring> wrappedLines;
+
+			text = "A sentence\t\t\t has words.";
+			var tabWidth = 4;
+			maxWidth = 14;
+			expectedClippedWidth = 11;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A sentence\t", wrappedLines [0].ToString ());
+			Assert.Equal ("\t\t has ", wrappedLines [1].ToString ());
+			Assert.Equal ("words.", wrappedLines [2].ToString ());
+			Assert.True (wrappedLines.Count == 3);
+
+			maxWidth = 3;
+			expectedClippedWidth = 3;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A ", wrappedLines [0].ToString ());
+			Assert.Equal ("sen", wrappedLines [1].ToString ());
+			Assert.Equal ("ten", wrappedLines [2].ToString ());
+			Assert.Equal ("ce", wrappedLines [3].ToString ());
+			Assert.Equal ("\t", wrappedLines [4].ToString ());
+			Assert.Equal ("\t", wrappedLines [5].ToString ());
+			Assert.Equal ("\t", wrappedLines [6].ToString ());
+			Assert.Equal (" ", wrappedLines [7].ToString ());
+			Assert.Equal ("has", wrappedLines [8].ToString ());
+			Assert.Equal (" ", wrappedLines [9].ToString ());
+			Assert.Equal ("wor", wrappedLines [10].ToString ());
+			Assert.Equal ("ds.", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 12);
+
+			maxWidth = 2;
+			expectedClippedWidth = 2;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A ", wrappedLines [0].ToString ());
+			Assert.Equal ("se", wrappedLines [1].ToString ());
+			Assert.Equal ("nt", wrappedLines [2].ToString ());
+			Assert.Equal ("en", wrappedLines [3].ToString ());
+			Assert.Equal ("ce", wrappedLines [4].ToString ());
+			Assert.Equal ("\t", wrappedLines [5].ToString ());
+			Assert.Equal ("\t", wrappedLines [6].ToString ());
+			Assert.Equal ("\t", wrappedLines [7].ToString ());
+			Assert.Equal (" ", wrappedLines [8].ToString ());
+			Assert.Equal ("ha", wrappedLines [9].ToString ());
+			Assert.Equal ("s ", wrappedLines [10].ToString ());
+			Assert.Equal ("wo", wrappedLines [11].ToString ());
+			Assert.Equal ("rd", wrappedLines [12].ToString ());
+			Assert.Equal ("s.", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == 14);
+
+			maxWidth = 1;
+			expectedClippedWidth = 1;
+			wrappedLines = TextFormatter.WordWrap (text, maxWidth, true, tabWidth);
+			Assert.True (expectedClippedWidth >= wrappedLines.Max (l => l.RuneCount));
+			Assert.Equal ("A", wrappedLines [0].ToString ());
+			Assert.Equal (" ", wrappedLines [1].ToString ());
+			Assert.Equal ("s", wrappedLines [2].ToString ());
+			Assert.Equal ("e", wrappedLines [3].ToString ());
+			Assert.Equal ("n", wrappedLines [4].ToString ());
+			Assert.Equal ("t", wrappedLines [5].ToString ());
+			Assert.Equal ("e", wrappedLines [6].ToString ());
+			Assert.Equal ("n", wrappedLines [7].ToString ());
+			Assert.Equal ("c", wrappedLines [8].ToString ());
+			Assert.Equal ("e", wrappedLines [9].ToString ());
+			Assert.Equal ("\t", wrappedLines [10].ToString ());
+			Assert.Equal ("\t", wrappedLines [11].ToString ());
+			Assert.Equal ("\t", wrappedLines [12].ToString ());
+			Assert.Equal (" ", wrappedLines [13].ToString ());
+			Assert.Equal (".", wrappedLines [^1].ToString ());
+			Assert.True (wrappedLines.Count == text.Length);
 		}
 		}
+
 		[Fact]
 		[Fact]
 		public void ReplaceHotKeyWithTag ()
 		public void ReplaceHotKeyWithTag ()
 		{
 		{
@@ -2307,115 +2455,52 @@ namespace Terminal.Gui.Core {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void Format_WordWrap_Keep_End_Spaces ()
+		public void Format_WordWrap_preserveTrailingSpaces ()
 		{
 		{
 			ustring text = " A sentence has words. \n This is the second Line - 2. ";
 			ustring text = " A sentence has words. \n This is the second Line - 2. ";
 
 
 			// With preserveTrailingSpaces = false by default.
 			// With preserveTrailingSpaces = false by default.
 			var list1 = TextFormatter.Format (text, 4, TextAlignment.Left, true);
 			var list1 = TextFormatter.Format (text, 4, TextAlignment.Left, true);
 			ustring wrappedText1 = ustring.Empty;
 			ustring wrappedText1 = ustring.Empty;
-			var idx = 0;
+			Assert.Equal (" A", list1 [0].ToString ());
+			Assert.Equal ("sent", list1 [1].ToString ());
+			Assert.Equal ("ence", list1 [2].ToString ());
+			Assert.Equal ("has", list1 [3].ToString ());
+			Assert.Equal ("word", list1 [4].ToString ());
+			Assert.Equal ("s. ", list1 [5].ToString ());
+			Assert.Equal (" Thi", list1 [6].ToString ());
+			Assert.Equal ("s is", list1 [7].ToString ());
+			Assert.Equal ("the", list1 [8].ToString ());
+			Assert.Equal ("seco", list1 [9].ToString ());
+			Assert.Equal ("nd", list1 [10].ToString ());
+			Assert.Equal ("Line", list1 [11].ToString ());
+			Assert.Equal ("- 2.", list1 [^1].ToString ());
 			foreach (var txt in list1) {
 			foreach (var txt in list1) {
 				wrappedText1 += txt;
 				wrappedText1 += txt;
-				switch (idx) {
-				case 0:
-					Assert.Equal (" A", txt);
-					break;
-				case 1:
-					Assert.Equal ("sent", txt);
-					break;
-				case 2:
-					Assert.Equal ("ence", txt);
-					break;
-				case 3:
-					Assert.Equal ("has", txt);
-					break;
-				case 4:
-					Assert.Equal ("word", txt);
-					break;
-				case 5:
-					Assert.Equal ("s. ", txt);
-					break;
-				case 6:
-					Assert.Equal (" Thi", txt);
-					break;
-				case 7:
-					Assert.Equal ("s is", txt);
-					break;
-				case 8:
-					Assert.Equal ("the", txt);
-					break;
-				case 9:
-					Assert.Equal ("seco", txt);
-					break;
-				case 10:
-					Assert.Equal ("nd", txt);
-					break;
-				case 11:
-					Assert.Equal ("Line", txt);
-					break;
-				case 12:
-					Assert.Equal ("- 2.", txt);
-					break;
-				}
-				idx++;
 			}
 			}
 			Assert.Equal (" Asentencehaswords.  This isthesecondLine- 2.", wrappedText1);
 			Assert.Equal (" Asentencehaswords.  This isthesecondLine- 2.", wrappedText1);
 
 
 			// With preserveTrailingSpaces = true.
 			// With preserveTrailingSpaces = true.
 			var list2 = TextFormatter.Format (text, 4, TextAlignment.Left, true, true);
 			var list2 = TextFormatter.Format (text, 4, TextAlignment.Left, true, true);
 			ustring wrappedText2 = ustring.Empty;
 			ustring wrappedText2 = ustring.Empty;
-			idx = 0;
+			Assert.Equal (" A ", list2 [0].ToString ());
+			Assert.Equal ("sent", list2 [1].ToString ());
+			Assert.Equal ("ence", list2 [2].ToString ());
+			Assert.Equal (" has", list2 [3].ToString ());
+			Assert.Equal (" ", list2 [4].ToString ());
+			Assert.Equal ("word", list2 [5].ToString ());
+			Assert.Equal ("s. ", list2 [6].ToString ());
+			Assert.Equal (" ", list2 [7].ToString ());
+			Assert.Equal ("This", list2 [8].ToString ());
+			Assert.Equal (" is ", list2 [9].ToString ());
+			Assert.Equal ("the ", list2 [10].ToString ());
+			Assert.Equal ("seco", list2 [11].ToString ());
+			Assert.Equal ("nd ", list2 [12].ToString ());
+			Assert.Equal ("Line", list2 [13].ToString ());
+			Assert.Equal (" - ", list2 [14].ToString ());
+			Assert.Equal ("2. ", list2 [^1].ToString ());
 			foreach (var txt in list2) {
 			foreach (var txt in list2) {
 				wrappedText2 += txt;
 				wrappedText2 += txt;
-				switch (idx) {
-				case 0:
-					Assert.Equal (" A", txt);
-					break;
-				case 1:
-					Assert.Equal (" sen", txt);
-					break;
-				case 2:
-					Assert.Equal ("tenc", txt);
-					break;
-				case 3:
-					Assert.Equal ("e", txt);
-					break;
-				case 4:
-					Assert.Equal (" has", txt);
-					break;
-				case 5:
-					Assert.Equal (" wor", txt);
-					break;
-				case 6:
-					Assert.Equal ("ds. ", txt);
-					break;
-				case 7:
-					Assert.Equal (" Thi", txt);
-					break;
-				case 8:
-					Assert.Equal ("s is", txt);
-					break;
-				case 9:
-					Assert.Equal (" the", txt);
-					break;
-				case 10:
-					Assert.Equal (" sec", txt);
-					break;
-				case 11:
-					Assert.Equal ("ond", txt);
-					break;
-				case 12:
-					Assert.Equal (" Lin", txt);
-					break;
-				case 13:
-					Assert.Equal ("e -", txt);
-					break;
-				case 14:
-					Assert.Equal (" 2. ", txt);
-					break;
-				}
-				idx++;
 			}
 			}
 			Assert.Equal (" A sentence has words.  This is the second Line - 2. ", wrappedText2);
 			Assert.Equal (" A sentence has words.  This is the second Line - 2. ", wrappedText2);
 		}
 		}

+ 401 - 25
UnitTests/TextViewTests.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Linq;
 using Xunit;
 using Xunit;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
@@ -900,7 +901,7 @@ namespace Terminal.Gui.Views {
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
 					Assert.Equal ("", _textView.Text);
-					_textView.Paste ();
+					_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 second line.", _textView.Text);
 					break;
 					break;
 				default:
 				default:
@@ -936,7 +937,7 @@ namespace Terminal.Gui.Views {
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
 					Assert.Equal ("", _textView.Text);
-					_textView.Paste ();
+					_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 first line.", _textView.Text);
 					break;
 					break;
 				default:
 				default:
@@ -1197,9 +1198,9 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
-			_textView.Cut ();
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 		}
 		}
 
 
@@ -1209,9 +1210,9 @@ namespace Terminal.Gui.Views {
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.CursorPosition = new Point (24, 0);
 			_textView.CursorPosition = new Point (24, 0);
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
-			_textView.Cut ();
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 		}
 		}
 
 
@@ -1221,15 +1222,15 @@ namespace Terminal.Gui.Views {
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.CursorPosition = new Point (24, 0);
 			_textView.CursorPosition = new Point (24, 0);
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
-			_textView.Cut ();
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 		}
 		}
 
 
@@ -1239,25 +1240,25 @@ namespace Terminal.Gui.Views {
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.CursorPosition = new Point (24, 0);
 			_textView.CursorPosition = new Point (24, 0);
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			Assert.True (_textView.Selecting);
 			Assert.True (_textView.Selecting);
 			_textView.Selecting = false;
 			_textView.Selecting = false;
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 			_textView.SelectionStartColumn = 24;
 			_textView.SelectionStartColumn = 24;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
-			_textView.Cut ();
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			Assert.True (_textView.Selecting);
 			Assert.True (_textView.Selecting);
 			_textView.Selecting = false;
 			_textView.Selecting = false;
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 		}
 		}
 
 
@@ -1268,16 +1269,16 @@ namespace Terminal.Gui.Views {
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.CursorPosition = new Point (24, 0);
 			_textView.CursorPosition = new Point (24, 0);
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
-			_textView.Cut (); // Selecting is set to false after Cut.
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Selecting is set to false after Cut.
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 			_textView.ReadOnly = false;
 			_textView.ReadOnly = false;
 			Assert.False (_textView.Selecting);
 			Assert.False (_textView.Selecting);
 			_textView.Selecting = true; // Needed to set Selecting to true.
 			_textView.Selecting = true; // Needed to set Selecting to true.
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
-			_textView.Cut ();
+			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 		}
 		}
 
 
@@ -1287,9 +1288,9 @@ namespace Terminal.Gui.Views {
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartColumn = 20;
 			_textView.SelectionStartRow = 0;
 			_textView.SelectionStartRow = 0;
 			_textView.CursorPosition = new Point (24, 0);
 			_textView.CursorPosition = new Point (24, 0);
-			_textView.Copy ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
 			Assert.Equal ("text", _textView.SelectedText);
 			Assert.Equal ("text", _textView.SelectedText);
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ("", _textView.SelectedText);
 			Assert.Equal ("", _textView.SelectedText);
 		}
 		}
 
 
@@ -1343,17 +1344,392 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.\n";
 			_textView.Text = "This is the first line.\nThis is the second line.\n";
 			_textView.CursorPosition = new Point (0, _textView.Lines - 1);
 			_textView.CursorPosition = new Point (0, _textView.Lines - 1);
-			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ()));
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			_textView.CursorPosition = new Point (3, 1);
 			_textView.CursorPosition = new Point (3, 1);
-			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ()));
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.C | Key.CtrlMask, new KeyModifiers ())); // Copy
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			Assert.Equal (new Point (3, 2), _textView.CursorPosition);
 			Assert.Equal (new Point (3, 2), _textView.CursorPosition);
-			_textView.Paste ();
+			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the second line.{Environment.NewLine}{Environment.NewLine}", _textView.Text);
 			Assert.Equal (new Point (3, 3), _textView.CursorPosition);
 			Assert.Equal (new Point (3, 3), _textView.CursorPosition);
 		}
 		}
+
+		[Fact]
+		public void TabWidth_Setting_To_Zero_Changes_AllowsTab_To_False_If_True ()
+		{
+			Assert.Equal (4, _textView.TabWidth);
+			Assert.True (_textView.AllowsTab);
+			Assert.True (_textView.AllowsReturn);
+			Assert.True (_textView.Multiline);
+			_textView.TabWidth = -1;
+			Assert.Equal (0, _textView.TabWidth);
+			Assert.False (_textView.AllowsTab);
+			Assert.False (_textView.AllowsReturn);
+			Assert.False (_textView.Multiline);
+			_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+		}
+
+		[Fact]
+		public void AllowsTab_Setting_To_True_Changes_TabWidth_To_Default_If_It_Is_Zero ()
+		{
+			_textView.TabWidth = 0;
+			Assert.Equal (0, _textView.TabWidth);
+			Assert.False (_textView.AllowsTab);
+			Assert.False (_textView.AllowsReturn);
+			Assert.False (_textView.Multiline);
+			_textView.AllowsTab = true;
+			Assert.True (_textView.AllowsTab);
+			Assert.Equal (4, _textView.TabWidth);
+			Assert.True (_textView.AllowsReturn);
+			Assert.True (_textView.Multiline);
+		}
+
+		[Fact]
+		public void AllowsReturn_Setting_To_True_Changes_Multiline_To_True_If_It_Is_False ()
+		{
+			Assert.True (_textView.AllowsReturn);
+			Assert.True (_textView.Multiline);
+			Assert.Equal (4, _textView.TabWidth);
+			Assert.True (_textView.AllowsTab);
+			_textView.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
+			Assert.Equal (Environment.NewLine +
+				"TAB to jump between text fields.", _textView.Text);
+
+			_textView.AllowsReturn = false;
+			Assert.False (_textView.AllowsReturn);
+			Assert.False (_textView.Multiline);
+			Assert.Equal (0, _textView.TabWidth);
+			Assert.False (_textView.AllowsTab);
+			_textView.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ()));
+			Assert.Equal (Environment.NewLine +
+				"TAB to jump between text fields.", _textView.Text);
+		}
+
+		[Fact]
+		public void Multiline_Setting_Changes_AllowsReturn_And_AllowsTab_And_Height ()
+		{
+			Assert.True (_textView.Multiline);
+			Assert.True (_textView.AllowsReturn);
+			Assert.Equal (4, _textView.TabWidth);
+			Assert.True (_textView.AllowsTab);
+			Assert.Equal ("Dim.Absolute(30)", _textView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(10)", _textView.Height.ToString ());
+
+			_textView.Multiline = false;
+			Assert.False (_textView.Multiline);
+			Assert.False (_textView.AllowsReturn);
+			Assert.Equal (0, _textView.TabWidth);
+			Assert.False (_textView.AllowsTab);
+			Assert.Equal ("Dim.Absolute(30)", _textView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", _textView.Height.ToString ());
+
+			_textView.Multiline = true;
+			Assert.True (_textView.Multiline);
+			Assert.True (_textView.AllowsReturn);
+			Assert.Equal (4, _textView.TabWidth);
+			Assert.True (_textView.AllowsTab);
+			Assert.Equal ("Dim.Absolute(30)", _textView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(10)", _textView.Height.ToString ());
+		}
+
+		[Fact]
+		public void Tab_Test_Follow_By_BackTab ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				_textView.Text = "";
+				var col = 0;
+				var leftCol = 0;
+				var tabWidth = _textView.TabWidth;
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		public void BackTab_Test_Follow_By_Tab ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				_textView.Text = "";
+				for (int i = 0; i < 100; i++) {
+					_textView.Text += "\t";
+				}
+				var col = 100;
+				var tabWidth = _textView.TabWidth;
+				var leftCol = _textView.LeftColumn;
+				_textView.MoveEnd ();
+				Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+				leftCol = GetLeftCol (leftCol);
+				Assert.Equal (leftCol, _textView.LeftColumn);
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				_textView.Text = "";
+				var col = 0;
+				var leftCol = 0;
+				var tabWidth = _textView.TabWidth;
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		public void Tab_Test_Follow_By_BackTab_With_Text ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				var col = 0;
+				var leftCol = 0;
+				Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+				Assert.Equal (leftCol, _textView.LeftColumn);
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		public void Tab_Test_Follow_By_Home_And_Then_Follow_By_End_And_Then_Follow_By_BackTab_With_Text ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				var col = 0;
+				var leftCol = 0;
+				Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+				Assert.Equal (leftCol, _textView.LeftColumn);
+				Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+				Assert.Equal (32, _textView.Text.Length);
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				_textView.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers ()));
+				col = 0;
+				Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+				leftCol = 0;
+				Assert.Equal (leftCol, _textView.LeftColumn);
+
+				_textView.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ()));
+				col = _textView.Text.Length;
+				Assert.Equal (132, _textView.Text.Length);
+				Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+				leftCol = GetLeftCol (leftCol);
+				Assert.Equal (leftCol, _textView.LeftColumn);
+				var txt = _textView.Text;
+				while (col - 1 > 0 && txt [col - 1] != '\t') {
+					col--;
+				}
+				_textView.CursorPosition = new Point (col, 0);
+				leftCol = GetLeftCol (leftCol);
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+				Assert.Equal (32, _textView.Text.Length);
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		[Fact]
+		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight_With_Text ()
+		{
+			Application.Top.Add (_textView);
+
+			Application.Iteration += () => {
+				var width = _textView.Bounds.Width - 1;
+				Assert.Equal (30, width + 1);
+				Assert.Equal (10, _textView.Height);
+				Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+				var col = 0;
+				var leftCol = 0;
+				var tabWidth = _textView.TabWidth;
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				Assert.Equal (132, _textView.Text.Length);
+				while (col > 0) {
+					col--;
+					_textView.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+				while (col < 100) {
+					col++;
+					_textView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()));
+					Assert.Equal (new Point (col, 0), _textView.CursorPosition);
+					leftCol = GetLeftCol (leftCol);
+					Assert.Equal (leftCol, _textView.LeftColumn);
+				}
+
+				Application.Top.Remove (_textView);
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+		}
+
+		private int GetLeftCol (int start)
+		{
+			var lines = _textView.Text.Split (Environment.NewLine);
+			if (lines == null || lines.Length == 0) {
+				return 0;
+			}
+			if (start == _textView.LeftColumn) {
+				return start;
+			}
+			if (_textView.LeftColumn == _textView.CurrentColumn) {
+				return _textView.CurrentColumn;
+			}
+			var cCol = _textView.CurrentColumn;
+			var line = lines [_textView.CurrentRow];
+			var lCount = cCol > line.Length - 1 ? line.Length - 1 : cCol;
+			var width = _textView.Frame.Width;
+			var tabWidth = _textView.TabWidth;
+			var sumLength = 0;
+			var col = 0;
+
+			for (int i = lCount; i >= 0; i--) {
+				var r = line [i];
+				sumLength += Rune.ColumnWidth (r);
+				if (r == '\t') {
+					sumLength += tabWidth + 1;
+				}
+				if (sumLength > width) {
+					if (cCol == line.Length) {
+						col++;
+					}
+					break;
+				} else if (cCol < line.Length && col > 0 && start < cCol && col == start) {
+					break;
+				}
+				col = i;
+			}
+
+			return col;
+		}
 	}
 	}
 }
 }