Bläddra i källkod

Fixes #1187. Prevents WordBackward throwing an exception if point is greater than the text length. (#1188)

* Fixes #1187. Prevents WordBackward throwing an exception if point is greater than the text length.

* Always uses inverted color for selected text to avoid same colors.

* Prevents throw an exception if the clipboard content is null.

* The selected text should be maintained when losing focus.

* Fixes WordForward/WordBackward on text with more than one whitespace or with only one digit or letter.

* Forgot to replace the hacking.

* Added unit tests for the TextField view. Fixed some more bugs.

* Redraw should only show the selected text if it is focused.

* Fixes cursor position on double click and ensures the setting of the selected text.

* Ensures the SelectedLength property to be always with positive value.

* Fixes the WordBackward when at the end of the text has a character between two whitespace.
BDisp 4 år sedan
förälder
incheckning
5bd8be83ca
2 ändrade filer med 759 tillägg och 61 borttagningar
  1. 100 61
      Terminal.Gui/Views/TextField.cs
  2. 659 0
      UnitTests/TextFieldTests.cs

+ 100 - 61
Terminal.Gui/Views/TextField.cs

@@ -20,6 +20,8 @@ namespace Terminal.Gui {
 	public class TextField : View {
 	public class TextField : View {
 		List<Rune> text;
 		List<Rune> text;
 		int first, point;
 		int first, point;
+		int selectedStart = -1; // -1 represents there is no text selection.
+		ustring selectedText;
 
 
 		/// <summary>
 		/// <summary>
 		/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
 		/// Tracks whether the text field should be considered "used", that is, that the user has moved in the entry, so new input should be appended at the cursor position, rather than clearing the entry
@@ -104,8 +106,8 @@ namespace Terminal.Gui {
 		{
 		{
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 				Application.UngrabMouse ();
 				Application.UngrabMouse ();
-			if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
-				ClearAllSelection ();
+			//if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
+			//	ClearAllSelection ();
 
 
 			return base.OnLeave (view);
 			return base.OnLeave (view);
 		}
 		}
@@ -177,9 +179,14 @@ namespace Terminal.Gui {
 		public int CursorPosition {
 		public int CursorPosition {
 			get { return point; }
 			get { return point; }
 			set {
 			set {
-				point = value;
-				Adjust ();
-				SetNeedsDisplay ();
+				if (value < 0) {
+					point = 0;
+				} else if (value > text.Count) {
+					point = text.Count;
+				} else {
+					point = value;
+				}
+				PrepareSelection (selectedStart, point - selectedStart);
 			}
 			}
 		}
 		}
 
 
@@ -201,7 +208,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
-			ColorScheme color = Colors.Menu;
+			var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
 			SetSelectedStartSelectedLength ();
 			SetSelectedStartSelectedLength ();
 
 
 			Driver.SetAttribute (ColorScheme.Focus);
 			Driver.SetAttribute (ColorScheme.Focus);
@@ -215,12 +222,14 @@ namespace Terminal.Gui {
 			for (int idx = p; idx < tcount; idx++) {
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = text [idx];
 				var rune = text [idx];
 				var cols = Rune.ColumnWidth (rune);
 				var cols = Rune.ColumnWidth (rune);
-				if (idx == point && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) {
+				if (idx == point && HasFocus && !Used && length == 0 && !ReadOnly) {
 					Driver.SetAttribute (Colors.Menu.HotFocus);
 					Driver.SetAttribute (Colors.Menu.HotFocus);
 				} else if (ReadOnly) {
 				} else if (ReadOnly) {
-					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? color.Focus : roc);
+					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : roc);
+				} else if (!HasFocus) {
+					Driver.SetAttribute (ColorScheme.Focus);
 				} else {
 				} else {
-					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? color.Focus : ColorScheme.Focus);
+					Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : ColorScheme.Focus);
 				}
 				}
 				if (col + cols <= width) {
 				if (col + cols <= width) {
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
@@ -316,13 +325,12 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 				if (ReadOnly)
 					return true;
 					return true;
 
 
-				if (SelectedLength == 0) {
+				if (length == 0) {
 					if (text.Count == 0 || text.Count == point)
 					if (text.Count == 0 || text.Count == point)
 						return true;
 						return true;
 
 
 					SetText (text.GetRange (0, point).Concat (text.GetRange (point + 1, text.Count - (point + 1))));
 					SetText (text.GetRange (0, point).Concat (text.GetRange (point + 1, text.Count - (point + 1))));
 					Adjust ();
 					Adjust ();
-
 				} else {
 				} else {
 					DeleteSelectedText ();
 					DeleteSelectedText ();
 				}
 				}
@@ -332,7 +340,7 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 				if (ReadOnly)
 					return true;
 					return true;
 
 
-				if (SelectedLength == 0) {
+				if (length == 0) {
 					if (point == 0)
 					if (point == 0)
 						return true;
 						return true;
 
 
@@ -391,7 +399,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 = start > -1 && start > point ? start : point;
+					int x = Math.Min (start > -1 && start > point ? start : point, text.Count - 1);
 					if (x > 0) {
 					if (x > 0) {
 						int sbw = WordBackward (x);
 						int sbw = WordBackward (x);
 						if (sbw != -1)
 						if (sbw != -1)
@@ -556,7 +564,7 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 				if (ReadOnly)
 					return true;
 					return true;
 
 
-				if (SelectedLength != 0) {
+				if (length > 0) {
 					DeleteSelectedText ();
 					DeleteSelectedText ();
 					oldCursorPos = point;
 					oldCursorPos = point;
 				}
 				}
@@ -586,27 +594,30 @@ namespace Terminal.Gui {
 			if (p >= text.Count)
 			if (p >= text.Count)
 				return -1;
 				return -1;
 
 
-			int i = p;
-			if (Rune.IsPunctuation (text [p]) || Rune.IsWhiteSpace (text [p])) {
+			int i = p + 1;
+			if (i == text.Count)
+				return text.Count;
+
+			var ti = text [i];
+			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
 				for (; i < text.Count; i++) {
 				for (; i < text.Count; i++) {
-					var r = text [i];
-					if (Rune.IsLetterOrDigit (r))
-						break;
+					if (Rune.IsLetterOrDigit (text [i]))
+						return i;
 				}
 				}
+			} else {
 				for (; i < text.Count; i++) {
 				for (; i < text.Count; i++) {
-					var r = text [i];
-					if (!Rune.IsLetterOrDigit (r))
+					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
 						break;
 				}
 				}
-			} else {
 				for (; i < text.Count; i++) {
 				for (; i < text.Count; i++) {
-					var r = text [i];
-					if (!Rune.IsLetterOrDigit (r))
+					if (Rune.IsLetterOrDigit (text [i]))
 						break;
 						break;
 				}
 				}
 			}
 			}
+
 			if (i != p)
 			if (i != p)
-				return i + 1;
+				return Math.Min (i, text.Count);
+
 			return -1;
 			return -1;
 		}
 		}
 
 
@@ -620,25 +631,38 @@ namespace Terminal.Gui {
 				return 0;
 				return 0;
 
 
 			var ti = text [i];
 			var ti = text [i];
+			var lastValidCol = -1;
 			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
 			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
 				for (; i >= 0; i--) {
 				for (; i >= 0; i--) {
-					if (Rune.IsLetterOrDigit (text [i]))
+					if (Rune.IsLetterOrDigit (text [i])) {
+						lastValidCol = i;
 						break;
 						break;
+					}
+					if (i - 1 > 0 && !Rune.IsWhiteSpace (text [i]) && Rune.IsWhiteSpace (text [i - 1])) {
+						return i;
+					}
 				}
 				}
 				for (; i >= 0; i--) {
 				for (; i >= 0; i--) {
 					if (!Rune.IsLetterOrDigit (text [i]))
 					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
 						break;
+					lastValidCol = i;
+				}
+				if (lastValidCol > -1) {
+					return lastValidCol;
 				}
 				}
 			} else {
 			} else {
 				for (; i >= 0; i--) {
 				for (; i >= 0; i--) {
 					if (!Rune.IsLetterOrDigit (text [i]))
 					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
 						break;
+					lastValidCol = i;
+				}
+				if (lastValidCol > -1) {
+					return lastValidCol;
 				}
 				}
 			}
 			}
-			i++;
 
 
 			if (i != p)
 			if (i != p)
-				return i;
+				return Math.Max (i, 0);
 
 
 			return -1;
 			return -1;
 		}
 		}
@@ -646,17 +670,32 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Start position of the selected text.
 		/// Start position of the selected text.
 		/// </summary>
 		/// </summary>
-		public int SelectedStart { get; set; } = -1;
+		public int SelectedStart {
+			get => selectedStart;
+			set {
+				if (value < -1) {
+					selectedStart = -1;
+				} else if (value > text.Count) {
+					selectedStart = text.Count;
+				} else {
+					selectedStart = value;
+				}
+				PrepareSelection (selectedStart, point - selectedStart);
+			}
+		}
 
 
 		/// <summary>
 		/// <summary>
 		/// Length of the selected text.
 		/// Length of the selected text.
 		/// </summary>
 		/// </summary>
-		public int SelectedLength { get; set; } = 0;
+		public int SelectedLength { get => length; }
 
 
 		/// <summary>
 		/// <summary>
 		/// The selected text.
 		/// The selected text.
 		/// </summary>
 		/// </summary>
-		public ustring SelectedText { get; set; }
+		public ustring SelectedText {
+			get => Secret ? null : selectedText;
+			private set => selectedText = value;
+		}
 
 
 		int start, length;
 		int start, length;
 		bool isButtonPressed;
 		bool isButtonPressed;
@@ -707,6 +746,9 @@ namespace Terminal.Gui {
 				}
 				}
 				int sfw = WordForward (x);
 				int sfw = WordForward (x);
 				ClearAllSelection ();
 				ClearAllSelection ();
+				if (sfw != -1 && sbw != -1) {
+					point = sfw;
+				}
 				PrepareSelection (sbw, sfw - sbw);
 				PrepareSelection (sbw, sfw - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
 				PositionCursor (0);
 				PositionCursor (0);
@@ -750,20 +792,20 @@ namespace Terminal.Gui {
 
 
 		void PrepareSelection (int x, int direction = 0)
 		void PrepareSelection (int x, int direction = 0)
 		{
 		{
-			x = x + first < 0 ? 0 : x;
-			SelectedStart = SelectedStart == -1 && text.Count > 0 && x >= 0 && x <= text.Count ? x : SelectedStart;
-			if (SelectedStart > -1) {
-				SelectedLength = x + direction <= text.Count ? x + direction - SelectedStart : text.Count - SelectedStart;
+			x = x + first < -1 ? 0 : x;
+			selectedStart = selectedStart == -1 && text.Count > 0 && x >= 0 && x <= text.Count ? x : selectedStart;
+			if (selectedStart > -1) {
+				length = Math.Abs (x + direction <= text.Count ? x + direction - selectedStart : text.Count - selectedStart);
 				SetSelectedStartSelectedLength ();
 				SetSelectedStartSelectedLength ();
-				if (start > -1) {
-					SelectedText = length > 0 ? ustring.Make (text).ToString ().Substring (
+				if (start > -1 && length > 0) {
+					selectedText = length > 0 ? ustring.Make (text).ToString ().Substring (
 						start < 0 ? 0 : start, length > text.Count ? text.Count : length) : "";
 						start < 0 ? 0 : start, length > text.Count ? text.Count : length) : "";
 					if (first > start) {
 					if (first > start) {
 						first = start;
 						first = start;
 					}
 					}
-				} else {
-					ClearAllSelection ();
 				}
 				}
+			} else if (length > 0) {
+				ClearAllSelection ();
 			}
 			}
 			Adjust ();
 			Adjust ();
 		}
 		}
@@ -773,11 +815,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public void ClearAllSelection ()
 		public void ClearAllSelection ()
 		{
 		{
-			if (SelectedStart == -1 && SelectedLength == 0)
+			if (selectedStart == -1 && length == 0)
 				return;
 				return;
-			SelectedStart = -1;
-			SelectedLength = 0;
-			SelectedText = "";
+
+			selectedStart = -1;
+			length = 0;
+			selectedText = null;
 			start = 0;
 			start = 0;
 			length = 0;
 			length = 0;
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
@@ -785,12 +828,10 @@ namespace Terminal.Gui {
 
 
 		void SetSelectedStartSelectedLength ()
 		void SetSelectedStartSelectedLength ()
 		{
 		{
-			if (SelectedLength < 0) {
-				start = SelectedLength + SelectedStart;
-				length = Math.Abs (SelectedLength);
+			if (SelectedStart > -1 && point < SelectedStart) {
+				start = point - SelectedStart + SelectedStart;
 			} else {
 			} else {
 				start = SelectedStart;
 				start = SelectedStart;
-				length = SelectedLength;
 			}
 			}
 		}
 		}
 
 
@@ -799,12 +840,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public virtual void Copy ()
 		public virtual void Copy ()
 		{
 		{
-			if (Secret)
+			if (Secret || length == 0)
 				return;
 				return;
 
 
-			if (SelectedLength != 0) {
-				Clipboard.Contents = SelectedText;
-			}
+			Clipboard.Contents = SelectedText;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -812,20 +851,20 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public virtual void Cut ()
 		public virtual void Cut ()
 		{
 		{
-			if (SelectedLength != 0) {
-				Clipboard.Contents = SelectedText;
-				DeleteSelectedText ();
-			}
+			if (Secret || length == 0)
+				return;
+
+			Clipboard.Contents = SelectedText;
+			DeleteSelectedText ();
 		}
 		}
 
 
 		void DeleteSelectedText ()
 		void DeleteSelectedText ()
 		{
 		{
 			ustring actualText = Text;
 			ustring actualText = Text;
-			int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
-			int selLength = Math.Abs (SelectedLength);
+			int selStart = point < SelectedStart ? SelectedStart - point + SelectedStart : SelectedStart;
 			(var _, var len) = TextModel.DisplaySize (text, 0, selStart, false);
 			(var _, var len) = TextModel.DisplaySize (text, 0, selStart, false);
-			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + selLength, false);
-			(var _, var len3) = TextModel.DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
+			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + length, false);
+			(var _, var len3) = TextModel.DisplaySize (text, selStart + length, actualText.RuneCount, false);
 			Text = actualText [0, len] +
 			Text = actualText [0, len] +
 				actualText [len + len2, len + len2 + len3];
 				actualText [len + len2, len + len2 + len3];
 			ClearAllSelection ();
 			ClearAllSelection ();
@@ -838,7 +877,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public virtual void Paste ()
 		public virtual void Paste ()
 		{
 		{
-			if (ReadOnly) {
+			if (ReadOnly || Clipboard.Contents == null) {
 				return;
 				return;
 			}
 			}
 
 
@@ -869,7 +908,7 @@ namespace Terminal.Gui {
 			return ev;
 			return ev;
 		}
 		}
 
 
-		private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
+		CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
 
 
 		/// <summary>
 		/// <summary>
 		/// Get / Set the wished cursor when the field is focused
 		/// Get / Set the wished cursor when the field is focused

+ 659 - 0
UnitTests/TextFieldTests.cs

@@ -0,0 +1,659 @@
+using Xunit;
+
+namespace Terminal.Gui {
+	public class TextFieldTests {
+		private TextField _textField;
+
+		public TextFieldTests ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			//                                     1         2         3 
+			//                           01234567890123456789012345678901=32 (Length)
+			_textField = new TextField ("TAB to jump between text fields.");
+		}
+
+		[Fact]
+		public void Changing_SelectedStart_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
+		{
+			_textField.SelectedStart = 2;
+			Assert.Equal (32, _textField.CursorPosition);
+			Assert.Equal (30, _textField.SelectedLength);
+			Assert.Equal ("B to jump between text fields.", _textField.SelectedText);
+			_textField.CursorPosition = 20;
+			Assert.Equal (2, _textField.SelectedStart);
+			Assert.Equal (18, _textField.SelectedLength);
+			Assert.Equal ("B to jump between ", _textField.SelectedText);
+		}
+
+		[Fact]
+		public void SelectedStart_With_Value_Less_Than_Minus_One_Changes_To_Minus_One ()
+		{
+			_textField.SelectedStart = -2;
+			Assert.Equal (-1, _textField.SelectedStart);
+			Assert.Equal (0, _textField.SelectedLength);
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void SelectedStart_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
+		{
+			_textField.CursorPosition = 2;
+			_textField.SelectedStart = 33;
+			Assert.Equal (32, _textField.SelectedStart);
+			Assert.Equal (30, _textField.SelectedLength);
+			Assert.Equal ("B to jump between text fields.", _textField.SelectedText);
+		}
+
+		[Fact]
+		public void SelectedStart_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length ()
+		{
+			_textField.CursorPosition = 33;
+			_textField.SelectedStart = 33;
+			Assert.Equal (32, _textField.CursorPosition);
+			Assert.Equal (32, _textField.SelectedStart);
+			Assert.Equal (0, _textField.SelectedLength);
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero ()
+		{
+			_textField.CursorPosition = -1;
+			Assert.Equal (0, _textField.CursorPosition);
+			Assert.Equal (0, _textField.SelectedLength);
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
+		{
+			_textField.CursorPosition = 33;
+			Assert.Equal (32, _textField.CursorPosition);
+			Assert.Equal (0, _textField.SelectedLength);
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void WordForward_With_No_Selection ()
+		{
+			_textField.CursorPosition = 0;
+			var iteration = 0;
+
+			while (_textField.CursorPosition < _textField.Text.Length) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (7, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (20, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (32, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_No_Selection ()
+		{
+			_textField.CursorPosition = _textField.Text.Length;
+			var iteration = 0;
+
+			while (_textField.CursorPosition > 0) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (7, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (0, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_With_Selection ()
+		{
+			_textField.CursorPosition = 0;
+			_textField.SelectedStart = 0;
+			var iteration = 0;
+
+			while (_textField.CursorPosition < _textField.Text.Length) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (4, _textField.SelectedLength);
+					Assert.Equal ("TAB ", _textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (7, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (7, _textField.SelectedLength);
+					Assert.Equal ("TAB to ", _textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (12, _textField.SelectedLength);
+					Assert.Equal ("TAB to jump ", _textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (20, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (20, _textField.SelectedLength);
+					Assert.Equal ("TAB to jump between ", _textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (25, _textField.SelectedLength);
+					Assert.Equal ("TAB to jump between text ", _textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (32, _textField.CursorPosition);
+					Assert.Equal (0, _textField.SelectedStart);
+					Assert.Equal (32, _textField.SelectedLength);
+					Assert.Equal ("TAB to jump between text fields.", _textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_Selection ()
+		{
+			_textField.CursorPosition = _textField.Text.Length;
+			_textField.SelectedStart = _textField.Text.Length;
+			var iteration = 0;
+
+			while (_textField.CursorPosition > 0) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (7, _textField.SelectedLength);
+					Assert.Equal ("fields.", _textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (12, _textField.SelectedLength);
+					Assert.Equal ("text fields.", _textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (20, _textField.SelectedLength);
+					Assert.Equal ("between text fields.", _textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (7, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (25, _textField.SelectedLength);
+					Assert.Equal ("jump between text fields.", _textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (28, _textField.SelectedLength);
+					Assert.Equal ("to jump between text fields.", _textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (0, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (32, _textField.SelectedLength);
+					Assert.Equal ("TAB to jump between text fields.", _textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
+		{
+			_textField.CursorPosition = 10;
+			_textField.SelectedStart = 10;
+			var iteration = 0;
+
+			while (_textField.CursorPosition < _textField.Text.Length) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (2, _textField.SelectedLength);
+					Assert.Equal ("p ", _textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (10, _textField.SelectedLength);
+					Assert.Equal ("p between ", _textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (15, _textField.SelectedLength);
+					Assert.Equal ("p between text ", _textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (32, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (22, _textField.SelectedLength);
+					Assert.Equal ("p between text fields.", _textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
+		{
+			_textField.CursorPosition = 10;
+			_textField.SelectedStart = 10;
+			var iteration = 0;
+
+			while (_textField.CursorPosition > 0) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (7, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (3, _textField.SelectedLength);
+					Assert.Equal ("jum", _textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (6, _textField.SelectedLength);
+					Assert.Equal ("to jum", _textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (0, _textField.CursorPosition);
+					Assert.Equal (10, _textField.SelectedStart);
+					Assert.Equal (10, _textField.SelectedLength);
+					Assert.Equal ("TAB to jum", _textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
+		{
+			//                           1         2         3         4         5    
+			//                 0123456789012345678901234567890123456789012345678901234=55 (Length)
+			_textField.Text = "TAB   t  o  jump         b  etween    t ext   f ields .";
+			_textField.CursorPosition = 0;
+			var iteration = 0;
+
+			while (_textField.CursorPosition < _textField.Text.Length) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (6, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (9, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (28, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (38, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (40, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (46, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (48, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (55, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
+		{
+			//                           1         2         3         4         5    
+			//                 0123456789012345678901234567890123456789012345678901234=55 (Length)
+			_textField.Text = "TAB   t  o  jump         b  etween    t ext   f ields .";
+			_textField.CursorPosition = _textField.Text.Length;
+			var iteration = 0;
+
+			while (_textField.CursorPosition > 0) {
+				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (54, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (48, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (46, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (40, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (38, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (28, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (12, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (9, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (6, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 10:
+					Assert.Equal (0, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_Null_If_No_Selection ()
+		{
+			_textField.SelectedStart = -1;
+			_textField.Copy ();
+			Assert.Null (_textField.SelectedText);
+			_textField.Cut ();
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_Not_Null_If_Has_Selection ()
+		{
+			_textField.SelectedStart = 20;
+			_textField.CursorPosition = 24;
+			_textField.Copy ();
+			Assert.Equal ("text", _textField.SelectedText);
+			_textField.Cut ();
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_And_Paste_With_Selection ()
+		{
+			_textField.SelectedStart = 20;
+			_textField.CursorPosition = 24;
+			_textField.Copy ();
+			Assert.Equal ("text", _textField.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.Paste ();
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.SelectedStart = 20;
+			_textField.Cut ();
+			_textField.Paste ();
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_And_Paste_With_No_Selection ()
+		{
+			_textField.SelectedStart = 20;
+			_textField.CursorPosition = 24;
+			_textField.Copy ();
+			Assert.Equal ("text", _textField.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.SelectedStart = -1;
+			_textField.Paste ();
+			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text);
+			_textField.SelectedStart = 24;
+			_textField.Cut ();
+			Assert.Null (_textField.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.SelectedStart = -1;
+			_textField.Paste ();
+			Assert.Equal ("TAB to jump between texttext fields.", _textField.Text);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut__Not_Allowed_If_Secret_Is_True ()
+		{
+			_textField.Secret = true;
+			_textField.SelectedStart = 20;
+			_textField.CursorPosition = 24;
+			_textField.Copy ();
+			Assert.Null (_textField.SelectedText);
+			_textField.Cut ();
+			Assert.Null (_textField.SelectedText);
+			_textField.Secret = false;
+			_textField.Copy ();
+			Assert.Equal ("text", _textField.SelectedText);
+			_textField.Cut ();
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void Paste_Always_Clear_The_SelectedText ()
+		{
+			_textField.SelectedStart = 20;
+			_textField.CursorPosition = 24;
+			_textField.Copy ();
+			Assert.Equal ("text", _textField.SelectedText);
+			_textField.Paste ();
+			Assert.Null (_textField.SelectedText);
+		}
+
+		[Fact]
+		public void TextChanging_Event ()
+		{
+			bool cancel = true;
+
+			_textField.TextChanging += (e) => {
+				Assert.Equal ("changing", e.NewText);
+				if (cancel) {
+					e.Cancel = true;
+				}
+			};
+
+			_textField.Text = "changing";
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			cancel = false;
+			_textField.Text = "changing";
+			Assert.Equal ("changing", _textField.Text);
+		}
+
+		[Fact]
+		public void TextChanged_Event ()
+		{
+			_textField.TextChanged += (e) => {
+				Assert.Equal ("TAB to jump between text fields.", e);
+			};
+
+			_textField.Text = "changed";
+			Assert.Equal ("changed", _textField.Text);
+		}
+
+		[Fact]
+		public void Used_Is_True_By_Default ()
+		{
+			_textField.CursorPosition = 10;
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
+			Assert.Equal ("TAB to jumup between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
+			Assert.Equal ("TAB to jumusp between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
+			Assert.Equal ("TAB to jumusep between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
+			Assert.Equal ("TAB to jumusedp between text fields.", _textField.Text);
+
+		}
+
+		[Fact]
+		public void Used_Is_False ()
+		{
+			_textField.Used = false;
+			_textField.CursorPosition = 10;
+			Assert.Equal ("TAB to jump between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
+			Assert.Equal ("TAB to jumu between text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
+			Assert.Equal ("TAB to jumusbetween text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
+			Assert.Equal ("TAB to jumuseetween text fields.", _textField.Text);
+			_textField.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
+			Assert.Equal ("TAB to jumusedtween text fields.", _textField.Text);
+
+		}
+	}
+}