Browse Source

Fixes #1179. TextView does not copy to the clipboard on deleting. (#1180)

* Fixes #1133. Flaw in LayoutSubviews/TopologicalSort.

* Toplevel can't be used on Pos/Dim but only his subviews. Was not caught before because the LayoutSubviews  method never gone so deep before.

* Fixed the error that is triggered when the Pos/Dim is the current Application.Top.

* Application.Top is the only exception in the TopologicalSort method check.

* Fixes #1179. TextView does not copy to the clipboard on deleting.

* Added Button DoubleClick and fixed WordForward/WordBackward issues.

* Prevents a negative height.

* Fixes the enter key line feed.

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

* Fixes #1189. Prevents negative index.

* Fixes #1193. A  non auto size default Button now preserves his width and thus the text alignment now work.

* Fixing the Width and Height checks of the Dim class with AutoSize dependence.

* Fixes #1197. Prevents width negative value if added directly to the Application.Top

* Fixes #1199. Normalize views constructors and did some typo fixing.

* Fixing the Application.Top Pos/Dim settings.

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

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

* Added Find and Replace (next/previous). Replace All and Select All. A non modal dialog box.

* Keeps tracking the selected replaced text.

* Fixes #1202. CheckBox now deals with a functional '_' underscore hotkey.

* The selected text should be maintained when losing focus.

* Fixes an extra line on page down.

* Fixes the WordBackward if it text has more than one whitespaces or when has only one digit or letter.

* 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.

* Added match whole word checking.

* Added missing parameters documentation.

* 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.

* Added unit tests to the TextView, Used property and fixed some more bugs.

* Fixed Used to only show if it has focus.

* Fixed ReplaceAll and prevents Debug.Assert from showing.
BDisp 4 years ago
parent
commit
e784765094

+ 5 - 5
Terminal.Gui/Core/PosDim.cs

@@ -36,7 +36,7 @@ namespace Terminal.Gui {
 			return 0;
 		}
 
-		class PosFactor : Pos {
+		internal class PosFactor : Pos {
 			float factor;
 
 			public PosFactor (float n)
@@ -82,7 +82,7 @@ namespace Terminal.Gui {
 
 		static PosAnchorEnd endNoMargin;
 
-		class PosAnchorEnd : Pos {
+		internal class PosAnchorEnd : Pos {
 			int n;
 
 			public PosAnchorEnd (int n)
@@ -205,8 +205,8 @@ namespace Terminal.Gui {
 			return new PosAbsolute (n);
 		}
 
-		class PosCombine : Pos {
-			Pos left, right;
+		internal class PosCombine : Pos {
+			internal Pos left, right;
 			bool add;
 			public PosCombine (bool add, Pos left, Pos right)
 			{
@@ -500,7 +500,7 @@ namespace Terminal.Gui {
 		}
 
 		internal class DimCombine : Dim {
-			Dim left, right;
+			internal Dim left, right;
 			bool add;
 			public DimCombine (bool add, Dim left, Dim right)
 			{

+ 2 - 1
Terminal.Gui/Core/TextFormatter.cs

@@ -625,7 +625,8 @@ namespace Terminal.Gui {
 						Application.Driver?.AddRune (rune);
 					}
 					col += Rune.ColumnWidth (rune);
-					if (idx + 1 < runes.Length && col + Rune.ColumnWidth (runes [idx + 1]) > bounds.Width) {
+					if (idx + 1 > - 1 && idx + 1 < runes.Length && col
+						+ Rune.ColumnWidth (runes [idx + 1]) > bounds.Width) {
 						break;
 					}
 				}

+ 2 - 2
Terminal.Gui/Core/Toplevel.cs

@@ -370,7 +370,7 @@ namespace Terminal.Gui {
 				l = SuperView.Frame.Width;
 			}
 			nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx;
-			SetWidth (top.Frame.Width, out int rWidth);
+			SetWidth (top.Frame.Width, out int rWidth, out _);
 			if (rWidth < 0 && nx >= top.Frame.X) {
 				nx = Math.Max (top.Frame.Right - 2, 0);
 			}
@@ -399,7 +399,7 @@ namespace Terminal.Gui {
 			}
 			ny = Math.Min (ny, l);
 			ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
-			SetHeight (top.Frame.Height, out int rHeight);
+			SetHeight (top.Frame.Height, out int rHeight, out _);
 			if (rHeight < 0 && ny >= top.Frame.Y) {
 				ny = Math.Max (top.Frame.Bottom - 2, 0);
 			}

+ 65 - 18
Terminal.Gui/Core/View.cs

@@ -1753,7 +1753,7 @@ namespace Terminal.Gui {
 			var result = new List<View> ();
 
 			// Set of all nodes with no incoming edges
-			var S = new HashSet<View> (nodes.Where (n => edges.All (e => e.To.Equals (n) == false)));
+			var S = new HashSet<View> (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
 
 			while (S.Any ()) {
 				//  remove a node n from S
@@ -1772,15 +1772,15 @@ namespace Terminal.Gui {
 					edges.Remove (e);
 
 					// if m has no other incoming edges then
-					if (edges.All (me => me.To.Equals (m) == false) && m != this?.SuperView) {
+					if (edges.All (me => !me.To.Equals (m)) && m != this?.SuperView) {
 						// insert m into S
 						S.Add (m);
 					}
 				}
 			}
 
-			if (edges.Any ()) {
-				if (!object.ReferenceEquals (edges.First ().From, edges.First ().To)) {
+			if (edges.Any () && edges.First ().From != Application.Top) {
+				if (!ReferenceEquals (edges.First ().From, edges.First ().To)) {
 					throw new InvalidOperationException ($"TopologicalSort (for Pos/Dim) cannot find {edges.First ().From}. Did you forget to add it to {this}?");
 				} else {
 					throw new InvalidOperationException ("TopologicalSort encountered a recursive cycle in the relative Pos/Dim in the views of " + this);
@@ -1863,20 +1863,60 @@ namespace Terminal.Gui {
 			var nodes = new HashSet<View> ();
 			var edges = new HashSet<(View, View)> ();
 
-			foreach (var v in InternalSubviews) {
-				nodes.Add (v);
-				if (v.LayoutStyle == LayoutStyle.Computed) {
-					if (v.X is Pos.PosView vX)
-						edges.Add ((vX.Target, v));
-					if (v.Y is Pos.PosView vY)
-						edges.Add ((vY.Target, v));
-					if (v.Width is Dim.DimView vWidth)
-						edges.Add ((vWidth.Target, v));
-					if (v.Height is Dim.DimView vHeight)
-						edges.Add ((vHeight.Target, v));
+			void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+			{
+				if (pos is Pos.PosView pv) {
+					if (pv.Target != this) {
+						nEdges.Add ((pv.Target, from));
+					}
+					foreach (var v in from.InternalSubviews) {
+						CollectAll (v, ref nNodes, ref nEdges);
+					}
+					return;
+				}
+				if (pos is Pos.PosCombine pc) {
+					foreach (var v in from.InternalSubviews) {
+						CollectPos (pc.left, from, ref nNodes, ref nEdges);
+						CollectPos (pc.right, from, ref nNodes, ref nEdges);
+					}
+				}
+			}
+
+			void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+			{
+				if (dim is Dim.DimView dv) {
+					if (dv.Target != this) {
+						nEdges.Add ((dv.Target, from));
+					}
+					foreach (var v in from.InternalSubviews) {
+						CollectAll (v, ref nNodes, ref nEdges);
+					}
+					return;
+				}
+				if (dim is Dim.DimCombine dc) {
+					foreach (var v in from.InternalSubviews) {
+						CollectDim (dc.left, from, ref nNodes, ref nEdges);
+						CollectDim (dc.right, from, ref nNodes, ref nEdges);
+					}
+				}
+			}
+
+			void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+			{
+				foreach (var v in from.InternalSubviews) {
+					nNodes.Add (v);
+					if (v.layoutStyle != LayoutStyle.Computed) {
+						continue;
+					}
+					CollectPos (v.X, v, ref nNodes, ref nEdges);
+					CollectPos (v.Y, v, ref nNodes, ref nEdges);
+					CollectDim (v.Width, v, ref nNodes, ref nEdges);
+					CollectDim (v.Height, v, ref nNodes, ref nEdges);
 				}
 			}
 
+			CollectAll (this, ref nodes, ref edges);
+
 			var ordered = TopologicalSort (nodes, edges);
 
 			foreach (var v in ordered) {
@@ -1886,7 +1926,6 @@ namespace Terminal.Gui {
 
 				v.LayoutSubviews ();
 				v.LayoutNeeded = false;
-
 			}
 
 			if (SuperView == Application.Top && LayoutNeeded && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
@@ -2154,14 +2193,17 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="desiredWidth">The desired width.</param>
 		/// <param name="resultWidth">The real result width.</param>
+		/// <param name="currentWidth">The real current width.</param>
 		/// <returns>True if the width can be directly assigned, false otherwise.</returns>
-		public bool SetWidth (int desiredWidth, out int resultWidth)
+		public bool SetWidth (int desiredWidth, out int resultWidth, out int currentWidth)
 		{
 			int w = desiredWidth;
+			currentWidth = Width != null ? Width.Anchor (0) : 0;
 			bool canSetWidth;
 			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
 				w = Width.Anchor (w);
+				currentWidth = Width.Anchor (w);
 				canSetWidth = false;
 			} else if (Width is Dim.DimFactor factor) {
 				// Tries to get the SuperView width otherwise the view width.
@@ -2170,6 +2212,7 @@ namespace Terminal.Gui {
 					sw -= Frame.X;
 				}
 				w = Width.Anchor (sw);
+				currentWidth = Width.Anchor (sw);
 				canSetWidth = false;
 			} else {
 				canSetWidth = true;
@@ -2184,14 +2227,17 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="desiredHeight">The desired height.</param>
 		/// <param name="resultHeight">The real result height.</param>
+		/// <param name="currentHeight">The real current height.</param>
 		/// <returns>True if the height can be directly assigned, false otherwise.</returns>
-		public bool SetHeight (int desiredHeight, out int resultHeight)
+		public bool SetHeight (int desiredHeight, out int resultHeight, out int currentHeight)
 		{
 			int h = desiredHeight;
+			currentHeight = Height != null ? Height.Anchor (0) : 0;
 			bool canSetHeight;
 			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
 				h = Height.Anchor (h);
+				currentHeight = Height.Anchor (h);
 				canSetHeight = false;
 			} else if (Height is Dim.DimFactor factor) {
 				// Tries to get the SuperView height otherwise the view height.
@@ -2200,6 +2246,7 @@ namespace Terminal.Gui {
 					sh -= Frame.Y;
 				}
 				h = Height.Anchor (sh);
+				currentHeight = Height.Anchor (sh);
 				canSetHeight = false;
 			} else {
 				canSetHeight = true;

+ 5 - 2
Terminal.Gui/Views/Button.cs

@@ -146,10 +146,13 @@ namespace Terminal.Gui {
 				base.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
 
 			int w = base.Text.RuneCount - (base.Text.Contains (HotKeySpecifier) ? 1 : 0);
-			if (SetWidth (w, out int rWidth)) {
+			var canSetWidth = SetWidth (w, out int rWidth, out int cWidth);
+			if (canSetWidth && (cWidth < rWidth || AutoSize)) {
 				Width = rWidth;
+				w = rWidth;
+			} else if (!canSetWidth || !AutoSize) {
+				w = cWidth;
 			}
-			w = rWidth;
 			var layout = LayoutStyle;
 			bool layoutChanged = false;
 			if (!(Height is Dim.DimAbsolute)) {

+ 633 - 62
Terminal.Gui/Views/TextView.cs

@@ -279,6 +279,184 @@ namespace Terminal.Gui {
 			}
 			return col;
 		}
+
+		(Point startPointToFind, Point currentPointToFind, bool found) toFind;
+
+		internal (Point current, bool found) FindNextText (ustring text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false)
+		{
+			if (text == null || lines.Count == 0) {
+				gaveFullTurn = false;
+				return (Point.Empty, false);
+			}
+
+			if (toFind.found) {
+				toFind.currentPointToFind.X++;
+			}
+			var foundPos = GetFoundNextTextPoint (text, lines.Count, matchCase, matchWholeWord, toFind.currentPointToFind);
+			if (!foundPos.found && toFind.currentPointToFind != toFind.startPointToFind) {
+				foundPos = GetFoundNextTextPoint (text, toFind.startPointToFind.Y + 1, matchCase, matchWholeWord, Point.Empty);
+			}
+			gaveFullTurn = ApplyToFind (foundPos);
+
+			return foundPos;
+		}
+
+		internal (Point current, bool found) FindPreviousText (ustring text, out bool gaveFullTurn, bool matchCase = false, bool matchWholeWord = false)
+		{
+			if (text == null || lines.Count == 0) {
+				gaveFullTurn = false;
+				return (Point.Empty, false);
+			}
+
+			if (toFind.found) {
+				toFind.currentPointToFind.X++;
+			}
+			var foundPos = GetFoundPreviousTextPoint (text, toFind.currentPointToFind.Y, matchCase, matchWholeWord, toFind.currentPointToFind);
+			if (!foundPos.found && toFind.currentPointToFind != toFind.startPointToFind) {
+				foundPos = GetFoundPreviousTextPoint (text, lines.Count - 1, matchCase, matchWholeWord,
+					new Point (lines [lines.Count - 1].Count, lines.Count));
+			}
+			gaveFullTurn = ApplyToFind (foundPos);
+
+			return foundPos;
+		}
+
+		internal (Point current, bool found) ReplaceAllText (ustring text, bool matchCase = false, bool matchWholeWord = false, ustring textToReplace = null)
+		{
+			bool found = false;
+			Point pos = Point.Empty;
+
+			for (int i = 0; i < lines.Count; i++) {
+				var x = lines [i];
+				var txt = ustring.Make (x).ToString ();
+				if (!matchCase) {
+					txt = txt.ToUpper ();
+				}
+				var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString ();
+				var col = txt.IndexOf (matchText);
+				if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) {
+					continue;
+				}
+				if (col > -1) {
+					if (!found) {
+						found = true;
+					}
+					pos = new Point (col, i);
+					lines [i] = ReplaceText (x, textToReplace, matchText, col).ToRuneList ();
+					i--;
+				}
+			}
+
+			return (pos, found);
+		}
+
+		ustring ReplaceText (List<Rune> source, ustring textToReplace, string matchText, int col)
+		{
+			var origTxt = ustring.Make (source);
+			(int _, int len) = TextModel.DisplaySize (source, 0, col, false);
+			(var _, var len2) = TextModel.DisplaySize (source, col, col + matchText.Length, false);
+			(var _, var len3) = TextModel.DisplaySize (source, col + matchText.Length, origTxt.RuneCount, false);
+
+			return origTxt [0, len] +
+				textToReplace.ToString () +
+				origTxt [len + len2, len + len2 + len3];
+		}
+
+		bool ApplyToFind ((Point current, bool found) foundPos)
+		{
+			bool gaveFullTurn = false;
+			if (foundPos.found) {
+				toFind.currentPointToFind = foundPos.current;
+				if (toFind.found && toFind.currentPointToFind == toFind.startPointToFind) {
+					gaveFullTurn = true;
+				}
+				if (!toFind.found) {
+					toFind.startPointToFind = toFind.currentPointToFind = foundPos.current;
+					toFind.found = foundPos.found;
+				}
+			}
+
+			return gaveFullTurn;
+		}
+
+		(Point current, bool found) GetFoundNextTextPoint (ustring text, int linesCount, bool matchCase, bool matchWholeWord, Point start)
+		{
+			for (int i = start.Y; i < linesCount; i++) {
+				var x = lines [i];
+				var txt = ustring.Make (x).ToString ();
+				if (!matchCase) {
+					txt = txt.ToUpper ();
+				}
+				var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString ();
+				var col = txt.IndexOf (matchText, Math.Min (start.X, txt.Length));
+				if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) {
+					continue;
+				}
+				if (col > -1 && ((i == start.Y && col >= start.X)
+					|| i > start.Y)
+					&& txt.Contains (matchText)) {
+					return (new Point (col, i), true);
+				} else if (col == -1 && start.X > 0) {
+					start.X = 0;
+				}
+			}
+
+			return (Point.Empty, false);
+		}
+
+		(Point current, bool found) GetFoundPreviousTextPoint (ustring text, int linesCount, bool matchCase, bool matchWholeWord, Point start)
+		{
+			for (int i = linesCount; i >= 0; i--) {
+				var x = lines [i];
+				var txt = ustring.Make (x).ToString ();
+				if (!matchCase) {
+					txt = txt.ToUpper ();
+				}
+				if (start.Y != i) {
+					start.X = Math.Max (x.Count - 1, 0);
+				}
+				var matchText = !matchCase ? text.ToUpper ().ToString () : text.ToString ();
+				var col = txt.LastIndexOf (matchText, start.X);
+				if (col > -1 && matchWholeWord && !MatchWholeWord (txt, matchText, col)) {
+					continue;
+				}
+				if (col > -1 && ((i == linesCount && col <= start.X)
+					|| i < start.Y)
+					&& txt.Contains (matchText)) {
+					return (new Point (col, i), true);
+				}
+			}
+
+			return (Point.Empty, false);
+		}
+
+		bool MatchWholeWord (string source, string matchText, int index = 0)
+		{
+			if (string.IsNullOrEmpty (source) || string.IsNullOrEmpty (matchText)) {
+				return false;
+			}
+
+			var txt = matchText.Trim ();
+			var start = index > 0 ? index - 1 : 0;
+			var end = index + txt.Length;
+
+			if ((start == 0 || Rune.IsWhiteSpace (source [start]))
+				&& (end == source.Length || Rune.IsWhiteSpace (source [end]))) {
+				return true;
+			}
+
+			return false;
+		}
+
+		/// <summary>
+		/// Redefine column and line tracking.
+		/// </summary>
+		/// <param name="point">Contains the column and line.</param>
+		internal void ResetContinuousFind (Point point)
+		{
+			toFind.startPointToFind = toFind.currentPointToFind = point;
+			toFind.found = false;
+		}
 	}
 
 	class WordWrapManager {
@@ -645,9 +823,9 @@ namespace Terminal.Gui {
 		int currentColumn;
 		int selectionStartColumn, selectionStartRow;
 		bool selecting;
-		//bool used;
 		bool wordWrap;
 		WordWrapManager wrapManager;
+		bool continuousFind;
 
 		/// <summary>
 		/// Raised when the <see cref="Text"/> of the <see cref="TextView"/> changes.
@@ -671,7 +849,7 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public TextView (Rect frame) : base (frame)
 		{
-			CanFocus = true;
+			Initialize ();
 		}
 
 		/// <summary>
@@ -679,10 +857,22 @@ namespace Terminal.Gui {
 		///   with dimensions controlled with the X, Y, Width and Height properties.
 		/// </summary>
 		public TextView () : base ()
+		{
+			Initialize ();
+		}
+
+		void Initialize ()
 		{
 			CanFocus = true;
+			Used = true;
 		}
 
+		/// <summary>
+		/// Tracks whether the text view 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
+		/// </summary>
+		public bool Used { get; set; }
+
 		void ResetPosition ()
 		{
 			topRow = leftColumn = currentRow = currentColumn = 0;
@@ -763,6 +953,79 @@ namespace Terminal.Gui {
 		/// </summary>
 		public int Lines => model.Count;
 
+		/// <summary>
+		///    Sets or gets the current cursor position.
+		/// </summary>
+		public Point CursorPosition {
+			get => new Point (currentColumn, currentRow);
+			set {
+				var line = model.GetLine (Math.Max (Math.Min (value.Y, model.Count - 1), 0));
+				currentColumn = value.X < 0 ? 0 : value.X > line.Count ? line.Count : value.X;
+				currentRow = value.Y < 0 ? 0 : value.Y > model.Count - 1
+					? Math.Max (model.Count - 1, 0) : value.Y;
+				SetNeedsDisplay ();
+				Adjust ();
+			}
+		}
+
+		/// <summary>
+		/// Start column position of the selected text.
+		/// </summary>
+		public int SelectionStartColumn {
+			get => selectionStartColumn;
+			set {
+				var line = model.GetLine (currentRow);
+				selectionStartColumn = value < 0 ? 0 : value > line.Count ? line.Count : value;
+				selecting = true;
+				SetNeedsDisplay ();
+				Adjust ();
+			}
+		}
+
+		/// <summary>
+		/// Start row position of the selected text.
+		/// </summary>
+		public int SelectionStartRow {
+			get => selectionStartRow;
+			set {
+				selectionStartRow = value < 0 ? 0 : value > model.Count - 1
+					? Math.Max (model.Count - 1, 0) : value;
+				selecting = true;
+				SetNeedsDisplay ();
+				Adjust ();
+			}
+		}
+
+		/// <summary>
+		/// The selected text.
+		/// </summary>
+		public ustring SelectedText {
+			get {
+				if (!selecting || (model.Count == 1 && model.GetLine (0).Count == 0)) {
+					return ustring.Empty;
+				}
+
+				SetWrapModel ();
+				var sel = GetRegion ();
+				UpdateWrapModel ();
+				Adjust ();
+
+				return sel;
+			}
+		}
+
+		/// <summary>
+		/// Length of the selected text.
+		/// </summary>
+		public int SelectedLength => GetSelectedLength ();
+
+		/// <summary>
+		/// Get or sets the selecting.
+		/// </summary>
+		public bool Selecting {
+			get => selecting;
+			set => selecting = value;
+		}
 		/// <summary>
 		/// Allows word wrap the to fit the available container width.
 		/// </summary>
@@ -796,6 +1059,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public int RightOffset { get; set; }
 
+		int GetSelectedLength ()
+		{
+			return SelectedText.Length;
+		}
+
 		CursorVisibility savedCursorVisibility = CursorVisibility.Default;
 
 		void SaveCursorVisibility ()
@@ -884,7 +1152,7 @@ namespace Terminal.Gui {
 			var col = 0;
 			if (line.Count > 0) {
 				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)
 						break;
@@ -892,7 +1160,7 @@ namespace Terminal.Gui {
 					col += cols - 1;
 				}
 			}
-			var ccol = currentColumn - leftColumn - retreat + col;
+			var ccol = currentColumn - leftColumn + retreat + col;
 			if (leftColumn <= currentColumn && ccol < Frame.Width
 				&& topRow <= currentRow && currentRow - topRow < Frame.Height) {
 				ResetCursorVisibility ();
@@ -921,6 +1189,11 @@ namespace Terminal.Gui {
 			Driver.SetAttribute (ColorScheme.Focus);
 		}
 
+		void ColorUsed ()
+		{
+			Driver.SetAttribute (ColorScheme.HotFocus);
+		}
+
 		bool isReadOnly = false;
 
 		/// <summary>
@@ -1050,6 +1323,145 @@ namespace Terminal.Gui {
 			SetNeedsDisplay ();
 		}
 
+		/// <summary>
+		/// Select all text.
+		/// </summary>
+		public void SelectAll ()
+		{
+			if (model.Count == 0) {
+				return;
+			}
+
+			StartSelecting ();
+			selectionStartColumn = 0;
+			selectionStartRow = 0;
+			currentColumn = model.GetLine (model.Count - 1).Count;
+			currentRow = model.Count - 1;
+			SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Find the next text based on the match case with the option to replace it.
+		/// </summary>
+		/// <param name="textToFind">The text to find.</param>
+		/// <param name="gaveFullTurn"><c>true</c>If all the text was forward searched.<c>false</c>otherwise.</param>
+		/// <param name="matchCase">The match case setting.</param>
+		/// <param name="matchWholeWord">The match whole word setting.</param>
+		/// <param name="textToReplace">The text to replace.</param>
+		/// <param name="replace"><c>true</c>If is replacing.<c>false</c>otherwise.</param>
+		/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+		public bool FindNextText (ustring textToFind, out bool gaveFullTurn, bool matchCase = false,
+			bool matchWholeWord = false, ustring textToReplace = null, bool replace = false)
+		{
+			if (model.Count == 0) {
+				gaveFullTurn = false;
+				return false;
+			}
+
+			SetWrapModel ();
+			ResetContinuousFind ();
+			var foundPos = model.FindNextText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
+
+			return SetFoundText (textToFind, foundPos, textToReplace, replace);
+		}
+
+		/// <summary>
+		/// Find the previous text based on the match case with the option to replace it.
+		/// </summary>
+		/// <param name="textToFind">The text to find.</param>
+		/// <param name="gaveFullTurn"><c>true</c>If all the text was backward searched.<c>false</c>otherwise.</param>
+		/// <param name="matchCase">The match case setting.</param>
+		/// <param name="matchWholeWord">The match whole word setting.</param>
+		/// <param name="textToReplace">The text to replace.</param>
+		/// <param name="replace"><c>true</c>If the text was found.<c>false</c>otherwise.</param>
+		/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+		public bool FindPreviousText (ustring textToFind, out bool gaveFullTurn, bool matchCase = false,
+			bool matchWholeWord = false, ustring textToReplace = null, bool replace = false)
+		{
+			if (model.Count == 0) {
+				gaveFullTurn = false;
+				return false;
+			}
+
+			SetWrapModel ();
+			ResetContinuousFind ();
+			var foundPos = model.FindPreviousText (textToFind, out gaveFullTurn, matchCase, matchWholeWord);
+
+			return SetFoundText (textToFind, foundPos, textToReplace, replace);
+		}
+
+		/// <summary>
+		/// Reset the flag to stop continuous find.
+		/// </summary>
+		public void FindTextChanged ()
+		{
+			continuousFind = false;
+		}
+
+		/// <summary>
+		/// Replaces all the text based on the match case.
+		/// </summary>
+		/// <param name="textToFind">The text to find.</param>
+		/// <param name="matchCase">The match case setting.</param>
+		/// <param name="matchWholeWord">The match whole word setting.</param>
+		/// <param name="textToReplace">The text to replace.</param>
+		/// <returns><c>true</c>If the text was found.<c>false</c>otherwise.</returns>
+		public bool ReplaceAllText (ustring textToFind, bool matchCase = false, bool matchWholeWord = false,
+			ustring textToReplace = null)
+		{
+			if (isReadOnly || model.Count == 0) {
+				return false;
+			}
+
+			SetWrapModel ();
+			ResetContinuousFind ();
+			var foundPos = model.ReplaceAllText (textToFind, matchCase, matchWholeWord, textToReplace);
+
+			return SetFoundText (textToFind, foundPos, textToReplace, false, true);
+		}
+
+		bool SetFoundText (ustring text, (Point current, bool found) foundPos,
+			ustring textToReplace = null, bool replace = false, bool replaceAll = false)
+		{
+			if (foundPos.found) {
+				StartSelecting ();
+				selectionStartColumn = foundPos.current.X;
+				selectionStartRow = foundPos.current.Y;
+				if (!replaceAll) {
+					currentColumn = selectionStartColumn + text.RuneCount;
+				} else {
+					currentColumn = selectionStartColumn + textToReplace.RuneCount;
+				}
+				currentRow = foundPos.current.Y;
+				if (!isReadOnly && replace) {
+					Adjust ();
+					ClearSelectedRegion ();
+					InsertText (textToReplace);
+					StartSelecting ();
+					selectionStartColumn = currentColumn - textToReplace.RuneCount;
+				} else {
+					UpdateWrapModel ();
+					SetNeedsDisplay ();
+					Adjust ();
+				}
+				continuousFind = true;
+				return foundPos.found;
+			}
+			UpdateWrapModel ();
+			continuousFind = false;
+
+			return foundPos.found;
+		}
+
+		void ResetContinuousFind ()
+		{
+			if (!continuousFind) {
+				var col = selecting ? selectionStartColumn : currentColumn;
+				var row = selecting ? selectionStartRow : currentRow;
+				model.ResetContinuousFind (new Point (col, row));
+			}
+		}
+
 		/// <summary>
 		/// Restore from original model.
 		/// </summary>
@@ -1088,7 +1500,7 @@ namespace Terminal.Gui {
 			ColorNormal ();
 
 			int bottom = bounds.Bottom;
-			int right = bounds.Right + 1;
+			int right = bounds.Right;
 			for (int row = bounds.Top; row < bottom; row++) {
 				int textLine = topRow + row;
 				if (textLine >= model.Count) {
@@ -1111,14 +1523,27 @@ namespace Terminal.Gui {
 					var cols = Rune.ColumnWidth (rune);
 					if (lineCol < line.Count && selecting && PointInSelection (idx + leftColumn, row + topRow)) {
 						ColorSelection ();
+					} else if (lineCol == currentColumn && textLine == currentRow && !selecting && !Used
+						&& HasFocus && lineCol < lineRuneCount) {
+						ColorUsed ();
 					} else {
 						ColorNormal ();
 					}
 
 					if (!SpecialRune (rune)) {
 						AddRune (col, row, rune);
+					} else {
+						col++;
 					}
 					col = TextModel.SetCol (col, bounds.Right, cols);
+					if (idx + 1 < lineRuneCount && col + Rune.ColumnWidth (line [idx + 1]) > right) {
+						break;
+					} else if (idx == lineRuneCount - 1) {
+						ColorNormal ();
+						for (int i = col; i < right; i++) {
+							Driver.AddRune (' ');
+						}
+					}
 				}
 			}
 			PositionCursor ();
@@ -1156,9 +1581,21 @@ namespace Terminal.Gui {
 		void Insert (Rune rune)
 		{
 			var line = GetCurrentLine ();
-			line.Insert (Math.Min (currentColumn, line.Count), rune);
+			if (Used) {
+				line.Insert (Math.Min (currentColumn, line.Count), rune);
+			} else {
+				if (currentColumn < line.Count) {
+					line.RemoveAt (currentColumn);
+				}
+				line.Insert (Math.Min (currentColumn, line.Count), rune);
+			}
 			if (wordWrap) {
-				wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune);
+				if (Used) {
+					wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune);
+				} else {
+					wrapNeeded = wrapManager.RemoveAt (currentRow, currentColumn);
+					wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune);
+				}
 				if (wrapNeeded) {
 					SetNeedsDisplay ();
 				}
@@ -1211,7 +1648,7 @@ namespace Terminal.Gui {
 				if (wordWrap) {
 					SetNeedsDisplay ();
 				} else {
-					SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
+					SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0)));
 				}
 				return;
 			}
@@ -1348,9 +1785,35 @@ namespace Terminal.Gui {
 			case Key.P | Key.CtrlMask:
 			case Key.CursorUp:
 				lastWasKill = false;
+				continuousFind = false;
 				break;
 			case Key.K | Key.CtrlMask:
 				break;
+			case Key.F | Key.CtrlMask:
+			case Key.B | Key.CtrlMask:
+			case (Key)((int)'B' + Key.AltMask):
+			case Key.A | Key.CtrlMask:
+			case Key.E | Key.CtrlMask:
+			case Key.CursorRight:
+			case Key.CursorLeft:
+			case Key.CursorRight | Key.CtrlMask:
+			case Key.CursorLeft | Key.CtrlMask:
+			case Key.CursorRight | Key.ShiftMask:
+			case Key.CursorLeft | Key.ShiftMask:
+			case Key.CursorRight | Key.CtrlMask | Key.ShiftMask:
+			case Key.CursorLeft | Key.CtrlMask | Key.ShiftMask:
+			case Key.Home:
+			case Key.Home | Key.CtrlMask:
+			case Key.Home | Key.ShiftMask:
+			case Key.Home | Key.CtrlMask | Key.ShiftMask:
+			case Key.End:
+			case Key.End | Key.CtrlMask:
+			case Key.End | Key.ShiftMask:
+			case Key.End | Key.CtrlMask | Key.ShiftMask:
+				lastWasKill = false;
+				columnTrack = -1;
+				continuousFind = false;
+				break;
 			default:
 				lastWasKill = false;
 				columnTrack = -1;
@@ -1371,7 +1834,9 @@ namespace Terminal.Gui {
 				if (currentRow < model.Count) {
 					if (columnTrack == -1)
 						columnTrack = currentColumn;
-					currentRow = (currentRow + nPageDnShift) > model.Count ? model.Count : currentRow + nPageDnShift;
+					currentRow = (currentRow + nPageDnShift) > model.Count
+						? model.Count > 0 ? model.Count - 1 : 0
+						: currentRow + nPageDnShift;
 					if (topRow < currentRow - nPageDnShift) {
 						topRow = currentRow >= model.Count ? currentRow - nPageDnShift : topRow + nPageDnShift;
 						SetNeedsDisplay ();
@@ -1478,7 +1943,7 @@ namespace Terminal.Gui {
 				if (isReadOnly)
 					break;
 				if (selecting) {
-					Cut ();
+					ClearSelectedRegion ();
 					return true;
 				}
 				if (currentColumn > 0) {
@@ -1533,7 +1998,7 @@ namespace Terminal.Gui {
 				if (isReadOnly)
 					break;
 				if (selecting) {
-					Cut ();
+					ClearSelectedRegion ();
 					return true;
 				}
 				currentLine = GetCurrentLine ();
@@ -1733,6 +2198,15 @@ namespace Terminal.Gui {
 				MoveHome ();
 				break;
 
+			case Key.T | Key.CtrlMask:
+				SelectAll ();
+				break;
+
+			case Key.InsertChar:
+				Used = !Used;
+				SetNeedsDisplay ();
+				break;
+
 			default:
 				// Ignore control characters and other special keys
 				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
@@ -1741,13 +2215,18 @@ namespace Terminal.Gui {
 				if (isReadOnly)
 					return true;
 				if (selecting) {
-					Cut ();
+					ClearSelectedRegion ();
 				}
-				Insert ((uint)kb.Key);
-				currentColumn++;
-				if (currentColumn >= leftColumn + Frame.Width) {
-					leftColumn++;
-					SetNeedsDisplay ();
+				if (Used) {
+					Insert ((uint)kb.Key);
+					currentColumn++;
+					if (currentColumn >= leftColumn + Frame.Width) {
+						leftColumn++;
+						SetNeedsDisplay ();
+					}
+				} else {
+					Insert ((uint)kb.Key);
+					currentColumn++;
 				}
 				break;
 			}
@@ -1838,6 +2317,17 @@ namespace Terminal.Gui {
 			selecting = false;
 		}
 
+		void ClearSelectedRegion ()
+		{
+			SetWrapModel ();
+			if (!isReadOnly) {
+				ClearRegion ();
+			}
+			UpdateWrapModel ();
+			selecting = false;
+			DoNeededAction ();
+		}
+
 		void MoveUp ()
 		{
 			if (currentRow > 0) {
@@ -1895,7 +2385,11 @@ namespace Terminal.Gui {
 		Rune RuneAt (int col, int row)
 		{
 			var line = model.GetLine (row);
-			return line [col > line.Count - 1 ? line.Count - 1 : col];
+			if (line.Count > 0) {
+				return line [col > line.Count - 1 ? line.Count - 1 : col];
+			} else {
+				return 0;
+			}
 		}
 
 		/// <summary>
@@ -1927,6 +2421,9 @@ namespace Terminal.Gui {
 			if (col + 1 < line.Count) {
 				col++;
 				rune = line [col];
+				if (col + 1 == line.Count) {
+					col++;
+				}
 				return true;
 			}
 			while (row + 1 < model.Count) {
@@ -1975,27 +2472,45 @@ namespace Terminal.Gui {
 			try {
 				var rune = RuneAt (col, row);
 
-				var srow = row;
-				if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
-					while (MoveNext (ref col, ref row, out rune)) {
-						if (Rune.IsLetterOrDigit (rune))
-							break;
-					}
-					if (row != fromRow && Rune.IsLetterOrDigit (rune)) {
-						return (col, row);
-					}
-					while (MoveNext (ref col, ref row, out rune)) {
-						if (!Rune.IsLetterOrDigit (rune))
-							break;
-					}
-				} else {
-					while (MoveNext (ref col, ref row, out rune)) {
-						if (!Rune.IsLetterOrDigit (rune))
-							break;
+				void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune)
+				{
+					if (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune)) {
+						while (MoveNext (ref nCol, ref nRow, out nRune)) {
+							if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))
+								return;
+						}
+						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) {
+							return;
+						}
+						while (MoveNext (ref nCol, ref nRow, out nRune)) {
+							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
+								break;
+						}
+					} else {
+						if (!MoveNext (ref nCol, ref nRow, out nRune)) {
+							return;
+						}
+
+						var line = model.GetLine (fromRow);
+						if ((nRow != fromRow && fromCol < line.Count)
+							|| (nRow == fromRow && nCol == line.Count - 1)) {
+							nCol = line.Count;
+							nRow = fromRow;
+							return;
+						} else if (nRow != fromRow && fromCol == line.Count) {
+							line = model.GetLine (nRow);
+							if (Rune.IsLetterOrDigit (line [nCol]) || Rune.IsPunctuation (line [nCol])) {
+								return;
+							}
+						}
+						ProcMoveNext (ref nCol, ref nRow, nRune);
 					}
 				}
+
+				ProcMoveNext (ref col, ref row, rune);
+
 				if (fromCol != col || fromRow != row)
-					return (col + 1, row);
+					return (col, row);
 				return null;
 			} catch (Exception) {
 				return null;
@@ -2011,36 +2526,54 @@ namespace Terminal.Gui {
 			var row = fromRow;
 			try {
 				var rune = RuneAt (col, row);
+				int lastValidCol = Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) ? col : -1;
+
+				void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune)
+				{
+					if (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune)) {
+						while (MovePrev (ref nCol, ref nRow, out nRune)) {
+							if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune)) {
+								lastValidCol = nCol;
+								break;
+							}
+						}
+						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune))) {
+							return;
+						}
+						while (MovePrev (ref nCol, ref nRow, out nRune)) {
+							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
+								break;
+							lastValidCol = nCol;
+						}
+						if (lastValidCol > -1) {
+							nCol = lastValidCol;
+						}
+					} else {
+						if (!MovePrev (ref nCol, ref nRow, out nRune)) {
+							return;
+						}
 
-				if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
-					while (MovePrev (ref col, ref row, out rune)) {
-						if (Rune.IsLetterOrDigit (rune))
-							break;
-					}
-					int lastValidCol = -1;
-					while (MovePrev (ref col, ref row, out rune)) {
-						if (col == 0 && Rune.IsLetterOrDigit (rune)) {
-							return (col, row);
-						} else if (col == 0 && !Rune.IsLetterOrDigit (rune) && lastValidCol > -1) {
-							col = lastValidCol;
-							return (col, row);
+						var line = model.GetLine (nRow);
+						if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0]) || Rune.IsPunctuation (line [0]))) {
+							return;
 						}
-						if (!Rune.IsLetterOrDigit (rune)) {
-							break;
+						lastValidCol = Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) ? nCol : lastValidCol;
+						if (lastValidCol > -1 && (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune))) {
+							nCol = lastValidCol;
+							return;
 						}
-						lastValidCol = Rune.IsLetterOrDigit (rune) ? col : -1;
-					}
-				} else {
-					while (MovePrev (ref col, ref row, out rune)) {
-						if (!Rune.IsLetterOrDigit (rune))
-							break;
+						if (fromRow != nRow) {
+							nCol = line.Count;
+							return;
+						}
+						ProcMovePrev (ref nCol, ref nRow, nRune);
 					}
 				}
-				if (fromCol != col && fromRow == row) {
-					return (col == 0 ? col : col + 1, row);
-				} else if (fromCol != col && fromRow != row) {
-					return (col + 1, row);
-				}
+
+				ProcMovePrev (ref col, ref row, rune);
+
+				if (fromCol != col || fromRow != row)
+					return (col, row);
 				return null;
 			} catch (Exception) {
 				return null;
@@ -2054,7 +2587,9 @@ namespace Terminal.Gui {
 				&& !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)
 				&& !ev.Flags.HasFlag (MouseFlags.Button1Released)
 				&& !ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)
-				&& !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)) {
+				&& !ev.Flags.HasFlag (MouseFlags.WheeledDown) && !ev.Flags.HasFlag (MouseFlags.WheeledUp)
+				&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
+				&& !ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked | MouseFlags.ButtonShift)) {
 				return false;
 			}
 
@@ -2066,6 +2601,8 @@ namespace Terminal.Gui {
 				SetFocus ();
 			}
 
+			continuousFind = false;
+
 			if (ev.Flags == MouseFlags.Button1Clicked) {
 				if (shiftSelecting) {
 					shiftSelecting = false;
@@ -2143,6 +2680,40 @@ namespace Terminal.Gui {
 				}
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) {
 				Application.UngrabMouse ();
+			} else if (ev.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) {
+				if (ev.Flags.HasFlag (MouseFlags.ButtonShift)) {
+					if (!selecting) {
+						StartSelecting ();
+					}
+				} else if (selecting) {
+					StopSelecting ();
+				}
+				ProcessMouseClick (ev, out List<Rune> line);
+				(int col, int row)? newPos = null;
+				if (currentColumn > 0 && line [currentColumn - 1] != ' ') {
+					newPos = WordBackward (currentColumn, currentRow);
+					if (newPos.HasValue) {
+						currentColumn = newPos.Value.col;
+						currentRow = newPos.Value.row;
+					}
+				}
+				if (!selecting) {
+					StartSelecting ();
+				}
+				if (currentRow < selectionStartRow || currentRow == selectionStartRow && currentColumn < selectionStartColumn) {
+					if (currentColumn > 0 && line [currentColumn - 1] != ' ') {
+						newPos = WordBackward (currentColumn, currentRow);
+					}
+				} else {
+					newPos = WordForward (currentColumn, currentRow);
+				}
+				if (newPos != null && newPos.HasValue) {
+					currentColumn = newPos.Value.col;
+					currentRow = newPos.Value.row;
+				}
+				PositionCursor ();
+				lastWasKill = false;
+				columnTrack = currentColumn;
 			}
 
 			return true;

+ 2 - 7
UICatalog/Scenarios/Dialogs.cs

@@ -93,13 +93,8 @@ namespace UICatalog {
 			};
 			frame.Add (numButtonsEdit);
 
-			void Top_Loaded ()
-			{
-				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
-					+ Dim.Height (numButtonsEdit) + 2;
-				Top.Loaded -= Top_Loaded;
-			}
-			Top.Loaded += Top_Loaded;
+			frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
+				+ Dim.Height (numButtonsEdit) + 2;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 368 - 1
UICatalog/Scenarios/Editor.cs

@@ -15,6 +15,11 @@ namespace UICatalog {
 		private bool _saved = true;
 		private ScrollBarView _scrollBar;
 		private byte [] _originalText;
+		private string _textToFind;
+		private string _textToReplace;
+		private bool _matchCase;
+		private bool _matchWholeWord;
+		Window winDialog;
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
@@ -36,7 +41,17 @@ namespace UICatalog {
 				new MenuBarItem ("_Edit", new MenuItem [] {
 					new MenuItem ("_Copy", "", () => Copy(),null,null, Key.CtrlMask | Key.C),
 					new MenuItem ("C_ut", "", () => Cut(),null,null, Key.CtrlMask | Key.W),
-					new MenuItem ("_Paste", "", () => Paste(),null,null, Key.CtrlMask | Key.Y)
+					new MenuItem ("_Paste", "", () => Paste(),null,null, Key.CtrlMask | Key.Y),
+					null,
+					new MenuItem ("_Find", "", () => Find(),null,null, Key.CtrlMask | Key.S),
+					new MenuItem ("Find _Next", "", () => FindNext(),null,null, Key.CtrlMask | Key.ShiftMask | Key.S),
+					new MenuItem ("Find P_revious", "", () => FindPrevious(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.S),
+					new MenuItem ("_Replace", "", () => Replace(),null,null, Key.CtrlMask | Key.R),
+					new MenuItem ("Replace Ne_xt", "", () => ReplaceNext(),null,null, Key.CtrlMask | Key.ShiftMask | Key.R),
+					new MenuItem ("Replace Pre_vious", "", () => ReplacePrevious(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.R),
+					new MenuItem ("Replace _All", "", () => ReplaceAll(),null,null, Key.CtrlMask | Key.ShiftMask | Key.AltMask | Key.A),
+					null,
+					new MenuItem ("_Select All", "", () => SelectAll(),null,null, Key.CtrlMask | Key.T)
 				}),
 				new MenuBarItem ("_ScrollBarView", CreateKeepChecked ()),
 				new MenuBarItem ("_Cursor", new MenuItem [] {
@@ -115,6 +130,20 @@ namespace UICatalog {
 				_scrollBar.LayoutSubviews ();
 				_scrollBar.Refresh ();
 			};
+
+			Win.KeyPress += (e) => {
+				if (winDialog != null && (e.KeyEvent.Key == Key.Esc
+					|| e.KeyEvent.Key.HasFlag (Key.Q | Key.CtrlMask))) {
+					DisposeWinDialog ();
+				}
+			};
+		}
+
+		private void DisposeWinDialog ()
+		{
+			winDialog.Dispose ();
+			Win.Remove (winDialog);
+			winDialog = null;
 		}
 
 		public override void Setup ()
@@ -166,6 +195,91 @@ namespace UICatalog {
 			}
 		}
 
+		private void SelectAll ()
+		{
+			_textView.SelectAll ();
+		}
+
+		private void Find ()
+		{
+			CreateFindReplace ();
+		}
+
+		private void FindNext ()
+		{
+			ContinueFind ();
+		}
+
+		private void FindPrevious ()
+		{
+			ContinueFind (false);
+		}
+
+		private void ContinueFind (bool next = true, bool replace = false)
+		{
+			if (!replace && string.IsNullOrEmpty (_textToFind)) {
+				Find ();
+				return;
+			} else if (replace && (string.IsNullOrEmpty (_textToFind)
+				|| (winDialog == null && string.IsNullOrEmpty (_textToReplace)))) {
+				Replace ();
+				return;
+			}
+
+			bool found;
+			bool gaveFullTurn;
+
+			if (next) {
+				if (!replace) {
+					found = _textView.FindNextText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord);
+				} else {
+					found = _textView.FindNextText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord,
+						_textToReplace, true);
+				}
+			} else {
+				if (!replace) {
+					found = _textView.FindPreviousText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord);
+				} else {
+					found = _textView.FindPreviousText (_textToFind, out gaveFullTurn, _matchCase, _matchWholeWord,
+						_textToReplace, true);
+				}
+			}
+			if (!found) {
+				MessageBox.Query ("Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
+			} else if (gaveFullTurn) {
+				MessageBox.Query ("Find", $"No more occurrences were found for the following specified text: '{_textToFind}'", "Ok");
+			}
+		}
+
+		private void Replace ()
+		{
+			CreateFindReplace (false);
+		}
+
+		private void ReplaceNext ()
+		{
+			ContinueFind (true, true);
+		}
+
+		private void ReplacePrevious ()
+		{
+			ContinueFind (false, true);
+		}
+
+		private void ReplaceAll ()
+		{
+			if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && winDialog == null)) {
+				Replace ();
+				return;
+			}
+
+			if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace)) {
+				MessageBox.Query ("Replace All", $"All occurrences were replaced for the following specified text: '{_textToReplace}'", "Ok");
+			} else {
+				MessageBox.Query ("Replace All", $"None of the following specified text was found: '{_textToFind}'", "Ok");
+			}
+		}
+
 		private void SetCursor (CursorVisibility visibility)
 		{
 			_textView.DesiredCursorVisibility = visibility;
@@ -307,6 +421,259 @@ namespace UICatalog {
 			return new MenuItem [] { item };
 		}
 
+		private void CreateFindReplace (bool isFind = true)
+		{
+			winDialog = new Window (isFind ? "Find" : "Replace") {
+				X = Win.Bounds.Width / 2 - 30,
+				Y = Win.Bounds.Height / 2 - 10,
+				ColorScheme = Colors.Menu
+			};
+
+			var tabView = new TabView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+
+			tabView.AddTab (new Tab ("Find", FindTab ()), isFind);
+			var replace = ReplaceTab ();
+			tabView.AddTab (new Tab ("Replace", replace), !isFind);
+			tabView.SelectedTabChanged += (s, e) => tabView.SelectedTab.View.FocusFirst ();
+			winDialog.Add (tabView);
+
+			Win.Add (winDialog);
+
+			winDialog.Width = replace.Width + 4;
+			winDialog.Height = replace.Height + 4;
+
+			winDialog.SuperView.BringSubviewToFront (winDialog);
+			winDialog.SetFocus ();
+		}
+
+		private void SetFindText ()
+		{
+			_textToFind = !_textView.SelectedText.IsEmpty
+				? _textView.SelectedText.ToString ()
+				: string.IsNullOrEmpty (_textToFind) ? "" : _textToFind;
+
+			_textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace;
+		}
+
+		private View FindTab ()
+		{
+			var d = new View () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			d.DrawContent += (e) => {
+				foreach (var v in d.Subviews) {
+					v.SetNeedsDisplay ();
+				}
+			};
+
+			var lblWidth = "Replace:".Length;
+
+			var label = new Label (0, 1, "Find:") {
+				Width = lblWidth,
+				TextAlignment = TextAlignment.Right,
+				LayoutStyle = LayoutStyle.Computed
+			};
+			d.Add (label);
+
+			SetFindText ();
+			var txtToFind = new TextField (_textToFind) {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 20
+			};
+			txtToFind.Enter += (_) => txtToFind.Text = _textToFind;
+			d.Add (txtToFind);
+
+			var btnFindNext = new Button ("Find _Next") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (label),
+				Width = 20,
+				CanFocus = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered,
+				IsDefault = true
+			};
+			btnFindNext.Clicked += () => FindNext ();
+			d.Add (btnFindNext);
+
+			var btnFindPrevious = new Button ("Find _Previous") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindNext) + 1,
+				Width = 20,
+				CanFocus = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered
+			};
+			btnFindPrevious.Clicked += () => FindPrevious ();
+			d.Add (btnFindPrevious);
+
+			txtToFind.TextChanged += (e) => {
+				_textToFind = txtToFind.Text.ToString ();
+				_textView.FindTextChanged ();
+				btnFindNext.CanFocus = !txtToFind.Text.IsEmpty;
+				btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty;
+			};
+
+			var btnCancel = new Button ("Cancel") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindPrevious) + 2,
+				Width = 20,
+				TextAlignment = TextAlignment.Centered
+			};
+			btnCancel.Clicked += () => {
+				DisposeWinDialog ();
+			};
+			d.Add (btnCancel);
+
+			var ckbMatchCase = new CheckBox ("Match c_ase") {
+				X = 0,
+				Y = Pos.Top (txtToFind) + 2,
+				Checked = _matchCase
+			};
+			ckbMatchCase.Toggled += (e) => _matchCase = ckbMatchCase.Checked;
+			d.Add (ckbMatchCase);
+
+			var ckbMatchWholeWord = new CheckBox ("Match _whole word") {
+				X = 0,
+				Y = Pos.Top (ckbMatchCase) + 1,
+				Checked = _matchWholeWord
+			};
+			ckbMatchWholeWord.Toggled += (e) => _matchWholeWord = ckbMatchWholeWord.Checked;
+			d.Add (ckbMatchWholeWord);
+
+			d.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2;
+			d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
+
+			return d;
+		}
+
+		private View ReplaceTab ()
+		{
+			var d = new View () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			d.DrawContent += (e) => {
+				foreach (var v in d.Subviews) {
+					v.SetNeedsDisplay ();
+				}
+			};
+
+			var lblWidth = "Replace:".Length;
+
+			var label = new Label (0, 1, "Find:") {
+				Width = lblWidth,
+				TextAlignment = TextAlignment.Right,
+				LayoutStyle = LayoutStyle.Computed
+			};
+			d.Add (label);
+
+			SetFindText ();
+			var txtToFind = new TextField (_textToFind) {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 20
+			};
+			txtToFind.Enter += (_) => txtToFind.Text = _textToFind;
+			d.Add (txtToFind);
+
+			var btnFindNext = new Button ("Replace _Next") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (label),
+				Width = 20,
+				CanFocus = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered,
+				IsDefault = true
+			};
+			btnFindNext.Clicked += () => ReplaceNext ();
+			d.Add (btnFindNext);
+
+			label = new Label ("Replace:") {
+				X = Pos.Left (label),
+				Y = Pos.Top (label) + 1,
+				Width = lblWidth,
+				TextAlignment = TextAlignment.Right
+			};
+			d.Add (label);
+
+			SetFindText ();
+			var txtToReplace = new TextField (_textToReplace) {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 20
+			};
+			txtToReplace.TextChanged += (e) => _textToReplace = txtToReplace.Text.ToString ();
+			d.Add (txtToReplace);
+
+			var btnFindPrevious = new Button ("Replace _Previous") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindNext) + 1,
+				Width = 20,
+				CanFocus = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered
+			};
+			btnFindPrevious.Clicked += () => ReplacePrevious ();
+			d.Add (btnFindPrevious);
+
+			var btnReplaceAll = new Button ("Replace _All") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindPrevious) + 1,
+				Width = 20,
+				CanFocus = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered
+			};
+			btnReplaceAll.Clicked += () => ReplaceAll ();
+			d.Add (btnReplaceAll);
+
+			txtToFind.TextChanged += (e) => {
+				_textToFind = txtToFind.Text.ToString ();
+				_textView.FindTextChanged ();
+				btnFindNext.CanFocus = !txtToFind.Text.IsEmpty;
+				btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty;
+				btnReplaceAll.CanFocus = !txtToFind.Text.IsEmpty;
+			};
+
+			var btnCancel = new Button ("Cancel") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnReplaceAll) + 1,
+				Width = 20,
+				TextAlignment = TextAlignment.Centered
+			};
+			btnCancel.Clicked += () => {
+				DisposeWinDialog ();
+			};
+			d.Add (btnCancel);
+
+			var ckbMatchCase = new CheckBox ("Match c_ase") {
+				X = 0,
+				Y = Pos.Top (txtToFind) + 2,
+				Checked = _matchCase
+			};
+			ckbMatchCase.Toggled += (e) => _matchCase = ckbMatchCase.Checked;
+			d.Add (ckbMatchCase);
+
+			var ckbMatchWholeWord = new CheckBox ("Match _whole word") {
+				X = 0,
+				Y = Pos.Top (ckbMatchCase) + 1,
+				Checked = _matchWholeWord
+			};
+			ckbMatchWholeWord.Toggled += (e) => _matchWholeWord = ckbMatchWholeWord.Checked;
+			d.Add (ckbMatchWholeWord);
+
+			d.Width = lblWidth + txtToFind.Width + btnFindNext.Width + 2;
+			d.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
+
+			return d;
+		}
+
 		public override void Run ()
 		{
 			base.Run ();

+ 2 - 7
UICatalog/Scenarios/MessageBoxes.cs

@@ -140,13 +140,8 @@ namespace UICatalog {
 			};
 			frame.Add (styleRadioGroup);
 
-			void Top_Loaded ()
-			{
-				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit)
-					+ Dim.Height (numButtonsEdit) + Dim.Height (defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2;
-				Top.Loaded -= Top_Loaded;
-			}
-			Top.Loaded += Top_Loaded;
+			frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + Dim.Height (messageEdit)
+				+ Dim.Height (numButtonsEdit) + Dim.Height(defaultButtonEdit) + Dim.Height (styleRadioGroup) + 2;
 
 			label = new Label ("Button Pressed:") {
 				X = Pos.Center (),

+ 83 - 1
UnitTests/DimTests.cs

@@ -483,6 +483,10 @@ namespace Terminal.Gui {
 				Assert.Equal (1, v3.Frame.Height); // 1 because is Dim.DimAbsolute
 
 				v4.Text = "Button4";
+				v4.AutoSize = false;
+				Assert.Equal ("Dim.Absolute(50)", v4.Width.ToString ());
+				Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ());
+				v4.AutoSize = true;
 				Assert.Equal ("Dim.Absolute(11)", v4.Width.ToString ());
 				Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ());
 				Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute
@@ -507,6 +511,84 @@ namespace Terminal.Gui {
 			Application.Shutdown ();
 		}
 
-		// TODO: Test operators
+		// DONE: Test operators
+		[Fact]
+		public void DimCombine_Do_Not_Throws ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var t = Application.Top;
+
+			var w = new Window ("w") {
+				Width = Dim.Width (t) - 2,
+				Height = Dim.Height (t) - 2
+			};
+			var f = new FrameView ("f");
+			var v1 = new View ("v1") {
+				Width = Dim.Width (w) - 2,
+				Height = Dim.Height (w) - 2
+			};
+			var v2 = new View ("v2") {
+				Width = Dim.Width (v1) - 2,
+				Height = Dim.Height (v1) - 2
+			};
+
+			f.Add (v1, v2);
+			w.Add (f);
+			t.Add (w);
+
+			f.Width = Dim.Width (t) - Dim.Width (v2);
+			f.Height = Dim.Height (t) - Dim.Height (v2);
+
+			t.Ready += () => {
+				Assert.Equal (80, t.Frame.Width);
+				Assert.Equal (25, t.Frame.Height);
+				Assert.Equal (78, w.Frame.Width);
+				Assert.Equal (23, w.Frame.Height);
+				Assert.Equal (6, f.Frame.Width);
+				Assert.Equal (6, f.Frame.Height);
+				Assert.Equal (76, v1.Frame.Width);
+				Assert.Equal (21, v1.Frame.Height);
+				Assert.Equal (74, v2.Frame.Width);
+				Assert.Equal (19, v2.Frame.Height);
+			};
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void PosCombine_Will_Throws ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var t = Application.Top;
+
+			var w = new Window ("w") {
+				Width = Dim.Width (t) - 2,
+				Height = Dim.Height (t) - 2
+			};
+			var f = new FrameView ("f");
+			var v1 = new View ("v1") {
+				Width = Dim.Width (w) - 2,
+				Height = Dim.Height (w) - 2
+			};
+			var v2 = new View ("v2") {
+				Width = Dim.Width (v1) - 2,
+				Height = Dim.Height (v1) - 2
+			};
+
+			f.Add (v1); // v2 not added
+			w.Add (f);
+			t.Add (w);
+
+			f.Width = Dim.Width (t) - Dim.Width (v2);
+			f.Height = Dim.Height (t) - Dim.Height (v2);
+
+			Assert.Throws<InvalidOperationException> (() => Application.Run ());
+			Application.Shutdown ();
+		}
 	}
 }

+ 81 - 5
UnitTests/PosTests.cs

@@ -79,7 +79,7 @@ namespace Terminal.Gui {
 			Assert.Equal (pos1, pos2);
 		}
 
-		[Fact] 
+		[Fact]
 		public void SetSide_Null_Throws ()
 		{
 			var pos = Pos.Left (null);
@@ -91,7 +91,7 @@ namespace Terminal.Gui {
 			pos = Pos.Top (null);
 			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-			pos = Pos.Y(null);
+			pos = Pos.Y (null);
 			Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
 			pos = Pos.Bottom (null);
@@ -112,7 +112,7 @@ namespace Terminal.Gui {
 			string side; // used in format string
 			var testRect = Rect.Empty;
 			var testInt = 0;
-			Pos pos; 
+			Pos pos;
 
 			// Pos.Left
 			side = "x";
@@ -456,9 +456,85 @@ namespace Terminal.Gui {
 			Application.Shutdown ();
 		}
 
-		// TODO: Test PosCombine
+		// DONE: Test PosCombine
+		// DONE: Test operators
+		[Fact]
+		public void PosCombine_Do_Not_Throws ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
+			var t = Application.Top;
 
-		// TODO: Test operators
+			var w = new Window ("w") {
+				X = Pos.Left (t) + 2,
+				Y = Pos.Top (t) + 2
+			};
+			var f = new FrameView ("f");
+			var v1 = new View ("v1") {
+				X = Pos.Left (w) + 2,
+				Y = Pos.Top (w) + 2
+			};
+			var v2 = new View ("v2") {
+				X = Pos.Left (v1) + 2,
+				Y = Pos.Top (v1) + 2
+			};
+
+			f.Add (v1, v2);
+			w.Add (f);
+			t.Add (w);
+
+			f.X = Pos.X (t) + Pos.X (v2) - Pos.X (v1);
+			f.Y = Pos.Y (t) + Pos.Y (v2) - Pos.Y (v1);
+
+			t.Ready += () => {
+				Assert.Equal (0, t.Frame.X);
+				Assert.Equal (0, t.Frame.Y);
+				Assert.Equal (2, w.Frame.X);
+				Assert.Equal (2, w.Frame.Y);
+				Assert.Equal (2, f.Frame.X);
+				Assert.Equal (2, f.Frame.Y);
+				Assert.Equal (4, v1.Frame.X);
+				Assert.Equal (4, v1.Frame.Y);
+				Assert.Equal (6, v2.Frame.X);
+				Assert.Equal (6, v2.Frame.Y);
+			};
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void PosCombine_Will_Throws ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var t = Application.Top;
+
+			var w = new Window ("w") {
+				X = Pos.Left (t) + 2,
+				Y = Pos.Top (t) + 2
+			};
+			var f = new FrameView ("f");
+			var v1 = new View ("v1") {
+				X = Pos.Left (w) + 2,
+				Y = Pos.Top (w) + 2
+			};
+			var v2 = new View ("v2") {
+				X = Pos.Left (v1) + 2,
+				Y = Pos.Top (v1) + 2
+			};
+
+			f.Add (v1); // v2 not added
+			w.Add (f);
+			t.Add (w);
+
+			f.X = Pos.X (v2) - Pos.X (v1);
+			f.Y = Pos.Y (v2) - Pos.Y (v1);
+
+			Assert.Throws<InvalidOperationException> (() => Application.Run ());
+			Application.Shutdown ();
+		}
 	}
 }

+ 797 - 0
UnitTests/TextViewTests.cs

@@ -0,0 +1,797 @@
+using Xunit;
+
+namespace Terminal.Gui {
+	public class TextViewTests {
+		private TextView _textView;
+
+		public TextViewTests ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			//                   1         2         3 
+			//         01234567890123456789012345678901=32 (Length)
+			var txt = "TAB to jump between text fields.";
+			var buff = new byte [txt.Length];
+			for (int i = 0; i < txt.Length; i++) {
+				buff [i] = (byte)txt [i];
+			}
+			var ms = new System.IO.MemoryStream (buff).ToArray ();
+			_textView = new TextView () { Width = 30, Height = 10 };
+			_textView.Text = ms;
+		}
+
+		[Fact]
+		public void Changing_Selection_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
+		{
+			_textView.SelectionStartColumn = 2;
+			_textView.SelectionStartRow = 0;
+			Assert.Equal (0, _textView.CursorPosition.X);
+			Assert.Equal (0, _textView.CursorPosition.Y);
+			Assert.Equal (2, _textView.SelectedLength);
+			Assert.Equal ("TA", _textView.SelectedText);
+			_textView.CursorPosition = new Point (20, 0);
+			Assert.Equal (2, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (18, _textView.SelectedLength);
+			Assert.Equal ("B to jump between ", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Selection_With_Value_Less_Than_Zero_Changes_To_Zero ()
+		{
+			_textView.SelectionStartColumn = -2;
+			_textView.SelectionStartRow = -2;
+			Assert.Equal (0, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectedLength);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Selection_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
+		{
+			_textView.CursorPosition = new Point (2, 0);
+			_textView.SelectionStartColumn = 33;
+			_textView.SelectionStartRow = 1;
+			Assert.Equal (32, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (30, _textView.SelectedLength);
+			Assert.Equal ("B to jump between text fields.", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Selection_With_Empty_Text ()
+		{
+			_textView = new TextView ();
+			_textView.CursorPosition = new Point (2, 0);
+			_textView.SelectionStartColumn = 33;
+			_textView.SelectionStartRow = 1;
+			Assert.Equal (0, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectedLength);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Selection_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length ()
+		{
+			_textView.CursorPosition = new Point (33, 2);
+			_textView.SelectionStartColumn = 33;
+			_textView.SelectionStartRow = 33;
+			Assert.Equal (32, _textView.CursorPosition.X);
+			Assert.Equal (0, _textView.CursorPosition.Y);
+			Assert.Equal (32, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectedLength);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero ()
+		{
+			_textView.CursorPosition = new Point (-1, -1);
+			Assert.Equal (0, _textView.CursorPosition.X);
+			Assert.Equal (0, _textView.CursorPosition.Y);
+			Assert.Equal (0, _textView.SelectedLength);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
+		{
+			_textView.CursorPosition = new Point (33, 1);
+			Assert.Equal (32, _textView.CursorPosition.X);
+			Assert.Equal (0, _textView.CursorPosition.Y);
+			Assert.Equal (0, _textView.SelectedLength);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void WordForward_With_No_Selection ()
+		{
+			_textView.CursorPosition = new Point (0, 0);
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X < _textView.Text.Length) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (32, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_No_Selection ()
+		{
+			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X > 0) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_With_Selection ()
+		{
+			_textView.CursorPosition = new Point (0, 0);
+			_textView.SelectionStartColumn = 0;
+			_textView.SelectionStartRow = 0;
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X < _textView.Text.Length) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (4, _textView.SelectedLength);
+					Assert.Equal ("TAB ", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (7, _textView.SelectedLength);
+					Assert.Equal ("TAB to ", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (12, _textView.SelectedLength);
+					Assert.Equal ("TAB to jump ", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (20, _textView.SelectedLength);
+					Assert.Equal ("TAB to jump between ", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (25, _textView.SelectedLength);
+					Assert.Equal ("TAB to jump between text ", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (32, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (32, _textView.SelectedLength);
+					Assert.Equal ("TAB to jump between text fields.", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_Selection ()
+		{
+			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
+			_textView.SelectionStartColumn = _textView.Text.Length;
+			_textView.SelectionStartRow = 0;
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X > 0) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (7, _textView.SelectedLength);
+					Assert.Equal ("fields.", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (12, _textView.SelectedLength);
+					Assert.Equal ("text fields.", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (20, _textView.SelectedLength);
+					Assert.Equal ("between text fields.", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (25, _textView.SelectedLength);
+					Assert.Equal ("jump between text fields.", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (28, _textView.SelectedLength);
+					Assert.Equal ("to jump between text fields.", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (32, _textView.SelectedLength);
+					Assert.Equal ("TAB to jump between text fields.", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
+		{
+			_textView.CursorPosition = new Point (10, 0);
+			_textView.SelectionStartColumn = 10;
+			_textView.SelectionStartRow = 0;
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X < _textView.Text.Length) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (2, _textView.SelectedLength);
+					Assert.Equal ("p ", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (10, _textView.SelectedLength);
+					Assert.Equal ("p between ", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (15, _textView.SelectedLength);
+					Assert.Equal ("p between text ", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (32, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (22, _textView.SelectedLength);
+					Assert.Equal ("p between text fields.", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
+		{
+			_textView.CursorPosition = new Point (10, 0);
+			_textView.SelectionStartColumn = 10;
+			_textView.SelectionStartRow = 0;
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X > 0) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (3, _textView.SelectedLength);
+					Assert.Equal ("jum", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (6, _textView.SelectedLength);
+					Assert.Equal ("to jum", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (10, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (10, _textView.SelectedLength);
+					Assert.Equal ("TAB to jum", _textView.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)
+			_textView.Text = "TAB   t  o  jump         b  etween    t ext   f ields .";
+			_textView.CursorPosition = new Point (0, 0);
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X < _textView.Text.Length) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (6, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (9, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (28, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (38, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (40, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (46, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (48, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (55, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.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)
+			_textView.Text = "TAB   t  o  jump         b  etween    t ext   f ields .";
+			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
+			var iteration = 0;
+
+			while (_textView.CursorPosition.X > 0) {
+				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
+				switch (iteration) {
+				case 0:
+					Assert.Equal (54, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 1:
+					Assert.Equal (48, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 2:
+					Assert.Equal (46, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 3:
+					Assert.Equal (40, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 4:
+					Assert.Equal (38, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 5:
+					Assert.Equal (28, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 6:
+					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 7:
+					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 8:
+					Assert.Equal (9, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 9:
+					Assert.Equal (6, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				case 10:
+					Assert.Equal (0, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (0, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (0, _textView.SelectedLength);
+					Assert.Equal ("", _textView.SelectedText);
+					break;
+				}
+				iteration++;
+			}
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_Null_If_No_Selection ()
+		{
+			_textView.SelectionStartColumn = 0;
+			_textView.SelectionStartRow = 0;
+			_textView.Copy ();
+			Assert.Equal ("", _textView.SelectedText);
+			_textView.Cut ();
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_Not_Null_If_Has_Selection ()
+		{
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.CursorPosition = new Point (24, 0);
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			_textView.Cut ();
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_And_Paste_With_Selection ()
+		{
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.CursorPosition = new Point (24, 0);
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.Paste ();
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.Cut ();
+			_textView.Paste ();
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+		}
+
+		[Fact]
+		public void Copy_Or_Cut_And_Paste_With_No_Selection ()
+		{
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.CursorPosition = new Point (24, 0);
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.SelectionStartColumn = 0;
+			_textView.SelectionStartRow = 0;
+			Assert.True (_textView.Selecting);
+			_textView.Selecting = false;
+			_textView.Paste ();
+			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
+			_textView.SelectionStartColumn = 24;
+			_textView.SelectionStartRow = 0;
+			_textView.Cut ();
+			Assert.Equal ("", _textView.SelectedText);
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.SelectionStartColumn = 0;
+			_textView.SelectionStartRow = 0;
+			Assert.True (_textView.Selecting);
+			_textView.Selecting = false;
+			_textView.Paste ();
+			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
+		}
+
+		[Fact]
+		public void Cut_Not_Allowed_If_ReadOnly_Is_True ()
+		{
+			_textView.ReadOnly = true;
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.CursorPosition = new Point (24, 0);
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			_textView.Cut (); // Selecting is set to false after Cut.
+			Assert.Equal ("", _textView.SelectedText);
+			_textView.ReadOnly = false;
+			Assert.False (_textView.Selecting);
+			_textView.Selecting = true; // Needed to set Selecting to true.
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			_textView.Cut ();
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void Paste_Always_Clear_The_SelectedText ()
+		{
+			_textView.SelectionStartColumn = 20;
+			_textView.SelectionStartRow = 0;
+			_textView.CursorPosition = new Point (24, 0);
+			_textView.Copy ();
+			Assert.Equal ("text", _textView.SelectedText);
+			_textView.Paste ();
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact]
+		public void TextChanged_Event ()
+		{
+			_textView.TextChanged += () => {
+				if (_textView.Text == "changing") {
+					Assert.Equal ("changing", _textView.Text);
+					_textView.Text = "changed";
+				}
+			};
+
+			_textView.Text = "changing";
+			Assert.Equal ("changed", _textView.Text);
+		}
+
+		[Fact]
+		public void Used_Is_True_By_Default ()
+		{
+			_textView.CursorPosition = new Point (10, 0);
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
+			Assert.Equal ("TAB to jumup between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
+			Assert.Equal ("TAB to jumusp between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
+			Assert.Equal ("TAB to jumusep between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
+			Assert.Equal ("TAB to jumusedp between text fields.", _textView.Text);
+		}
+
+		[Fact]
+		public void Used_Is_False ()
+		{
+			_textView.Used = false;
+			_textView.CursorPosition = new Point (10, 0);
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x75, new KeyModifiers ())); // u
+			Assert.Equal ("TAB to jumu between text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x73, new KeyModifiers ())); // s
+			Assert.Equal ("TAB to jumusbetween text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x65, new KeyModifiers ())); // e
+			Assert.Equal ("TAB to jumuseetween text fields.", _textView.Text);
+			_textView.ProcessKey (new KeyEvent ((Key)0x64, new KeyModifiers ())); // d
+			Assert.Equal ("TAB to jumusedtween text fields.", _textView.Text);
+		}
+	}
+}