Pārlūkot izejas kodu

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 gadi atpakaļ
vecāks
revīzija
5bd8be83ca
2 mainītis faili ar 759 papildinājumiem un 61 dzēšanām
  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 {
 		List<Rune> text;
 		int first, point;
+		int selectedStart = -1; // -1 represents there is no text selection.
+		ustring selectedText;
 
 		/// <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
@@ -104,8 +106,8 @@ namespace Terminal.Gui {
 		{
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 				Application.UngrabMouse ();
-			if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
-				ClearAllSelection ();
+			//if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
+			//	ClearAllSelection ();
 
 			return base.OnLeave (view);
 		}
@@ -177,9 +179,14 @@ namespace Terminal.Gui {
 		public int CursorPosition {
 			get { return point; }
 			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/>
 		public override void Redraw (Rect bounds)
 		{
-			ColorScheme color = Colors.Menu;
+			var selColor = new Attribute (ColorScheme.Focus.Background, ColorScheme.Focus.Foreground);
 			SetSelectedStartSelectedLength ();
 
 			Driver.SetAttribute (ColorScheme.Focus);
@@ -215,12 +222,14 @@ namespace Terminal.Gui {
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = text [idx];
 				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);
 				} 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 {
-					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) {
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
@@ -316,13 +325,12 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 					return true;
 
-				if (SelectedLength == 0) {
+				if (length == 0) {
 					if (text.Count == 0 || text.Count == point)
 						return true;
 
 					SetText (text.GetRange (0, point).Concat (text.GetRange (point + 1, text.Count - (point + 1))));
 					Adjust ();
-
 				} else {
 					DeleteSelectedText ();
 				}
@@ -332,7 +340,7 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 					return true;
 
-				if (SelectedLength == 0) {
+				if (length == 0) {
 					if (point == 0)
 						return true;
 
@@ -391,7 +399,7 @@ namespace Terminal.Gui {
 			case Key.CursorUp | Key.ShiftMask | Key.CtrlMask:
 			case (Key)((int)'B' + Key.ShiftMask | Key.AltMask):
 				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) {
 						int sbw = WordBackward (x);
 						if (sbw != -1)
@@ -556,7 +564,7 @@ namespace Terminal.Gui {
 				if (ReadOnly)
 					return true;
 
-				if (SelectedLength != 0) {
+				if (length > 0) {
 					DeleteSelectedText ();
 					oldCursorPos = point;
 				}
@@ -586,27 +594,30 @@ namespace Terminal.Gui {
 			if (p >= text.Count)
 				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++) {
-					var r = text [i];
-					if (Rune.IsLetterOrDigit (r))
-						break;
+					if (Rune.IsLetterOrDigit (text [i]))
+						return i;
 				}
+			} else {
 				for (; i < text.Count; i++) {
-					var r = text [i];
-					if (!Rune.IsLetterOrDigit (r))
+					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
 				}
-			} else {
 				for (; i < text.Count; i++) {
-					var r = text [i];
-					if (!Rune.IsLetterOrDigit (r))
+					if (Rune.IsLetterOrDigit (text [i]))
 						break;
 				}
 			}
+
 			if (i != p)
-				return i + 1;
+				return Math.Min (i, text.Count);
+
 			return -1;
 		}
 
@@ -620,25 +631,38 @@ namespace Terminal.Gui {
 				return 0;
 
 			var ti = text [i];
+			var lastValidCol = -1;
 			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
 				for (; i >= 0; i--) {
-					if (Rune.IsLetterOrDigit (text [i]))
+					if (Rune.IsLetterOrDigit (text [i])) {
+						lastValidCol = i;
 						break;
+					}
+					if (i - 1 > 0 && !Rune.IsWhiteSpace (text [i]) && Rune.IsWhiteSpace (text [i - 1])) {
+						return i;
+					}
 				}
 				for (; i >= 0; i--) {
 					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
+					lastValidCol = i;
+				}
+				if (lastValidCol > -1) {
+					return lastValidCol;
 				}
 			} else {
 				for (; i >= 0; i--) {
 					if (!Rune.IsLetterOrDigit (text [i]))
 						break;
+					lastValidCol = i;
+				}
+				if (lastValidCol > -1) {
+					return lastValidCol;
 				}
 			}
-			i++;
 
 			if (i != p)
-				return i;
+				return Math.Max (i, 0);
 
 			return -1;
 		}
@@ -646,17 +670,32 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Start position of the selected text.
 		/// </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>
 		/// Length of the selected text.
 		/// </summary>
-		public int SelectedLength { get; set; } = 0;
+		public int SelectedLength { get => length; }
 
 		/// <summary>
 		/// The selected text.
 		/// </summary>
-		public ustring SelectedText { get; set; }
+		public ustring SelectedText {
+			get => Secret ? null : selectedText;
+			private set => selectedText = value;
+		}
 
 		int start, length;
 		bool isButtonPressed;
@@ -707,6 +746,9 @@ namespace Terminal.Gui {
 				}
 				int sfw = WordForward (x);
 				ClearAllSelection ();
+				if (sfw != -1 && sbw != -1) {
+					point = sfw;
+				}
 				PrepareSelection (sbw, sfw - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
 				PositionCursor (0);
@@ -750,20 +792,20 @@ namespace Terminal.Gui {
 
 		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 ();
-				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) : "";
 					if (first > start) {
 						first = start;
 					}
-				} else {
-					ClearAllSelection ();
 				}
+			} else if (length > 0) {
+				ClearAllSelection ();
 			}
 			Adjust ();
 		}
@@ -773,11 +815,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void ClearAllSelection ()
 		{
-			if (SelectedStart == -1 && SelectedLength == 0)
+			if (selectedStart == -1 && length == 0)
 				return;
-			SelectedStart = -1;
-			SelectedLength = 0;
-			SelectedText = "";
+
+			selectedStart = -1;
+			length = 0;
+			selectedText = null;
 			start = 0;
 			length = 0;
 			SetNeedsDisplay ();
@@ -785,12 +828,10 @@ namespace Terminal.Gui {
 
 		void SetSelectedStartSelectedLength ()
 		{
-			if (SelectedLength < 0) {
-				start = SelectedLength + SelectedStart;
-				length = Math.Abs (SelectedLength);
+			if (SelectedStart > -1 && point < SelectedStart) {
+				start = point - SelectedStart + SelectedStart;
 			} else {
 				start = SelectedStart;
-				length = SelectedLength;
 			}
 		}
 
@@ -799,12 +840,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public virtual void Copy ()
 		{
-			if (Secret)
+			if (Secret || length == 0)
 				return;
 
-			if (SelectedLength != 0) {
-				Clipboard.Contents = SelectedText;
-			}
+			Clipboard.Contents = SelectedText;
 		}
 
 		/// <summary>
@@ -812,20 +851,20 @@ namespace Terminal.Gui {
 		/// </summary>
 		public virtual void Cut ()
 		{
-			if (SelectedLength != 0) {
-				Clipboard.Contents = SelectedText;
-				DeleteSelectedText ();
-			}
+			if (Secret || length == 0)
+				return;
+
+			Clipboard.Contents = SelectedText;
+			DeleteSelectedText ();
 		}
 
 		void DeleteSelectedText ()
 		{
 			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 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] +
 				actualText [len + len2, len + len2 + len3];
 			ClearAllSelection ();
@@ -838,7 +877,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public virtual void Paste ()
 		{
-			if (ReadOnly) {
+			if (ReadOnly || Clipboard.Contents == null) {
 				return;
 			}
 
@@ -869,7 +908,7 @@ namespace Terminal.Gui {
 			return ev;
 		}
 
-		private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
+		CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
 
 		/// <summary>
 		/// 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);
+
+		}
+	}
+}