瀏覽代碼

Fixes #2523. WordBackward and WordForward should be moved to the TextModel class. (#2524)

* Move WordBackward and WordForward to TextModel class.

* Improving code and changing unit tests.

* Remove commented code.

* Fix unit test.
BDisp 2 年之前
父節點
當前提交
8dbb345225
共有 4 個文件被更改,包括 528 次插入406 次删除
  1. 49 117
      Terminal.Gui/Views/TextField.cs
  2. 242 193
      Terminal.Gui/Views/TextView.cs
  3. 97 59
      UnitTests/Views/TextFieldTests.cs
  4. 140 37
      UnitTests/Views/TextViewTests.cs

+ 49 - 117
Terminal.Gui/Views/TextField.cs

@@ -34,13 +34,13 @@ namespace Terminal.Gui {
 		/// been entered yet and the <see cref="View"/> does not yet have
 		/// input focus.
 		/// </summary>
-		public ustring Caption {get;set;}
+		public ustring Caption { get; set; }
 
 		/// <summary>
 		/// Gets or sets the foreground <see cref="Color"/> to use when 
 		/// rendering <see cref="Caption"/>.
 		/// </summary>
-		public Color CaptionColor {get;set;} = Color.DarkGray;
+		public Color CaptionColor { get; set; } = Color.DarkGray;
 
 		/// <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
@@ -481,8 +481,8 @@ namespace Terminal.Gui {
 
 			PositionCursor ();
 
-			RenderCaption();
-			
+			RenderCaption ();
+
 			if (SelectedLength > 0)
 				return;
 
@@ -498,7 +498,7 @@ namespace Terminal.Gui {
 
 		private void RenderCaption ()
 		{
-			
+
 			if (HasFocus || Caption == null || Caption.Length == 0
 				|| Text?.Length > 0) {
 				return;
@@ -516,14 +516,14 @@ namespace Terminal.Gui {
 
 			Driver.AddStr (render);
 		}
-    
+
 		private void GenerateSuggestions ()
 		{
 			var currentLine = Text.ToRuneList ();
 			var cursorPosition = Math.Min (this.CursorPosition, currentLine.Count);
 
-			Autocomplete.GenerateSuggestions(
-				new AutocompleteContext(currentLine,cursorPosition)
+			Autocomplete.GenerateSuggestions (
+				new AutocompleteContext (currentLine, cursorPosition)
 				);
 		}
 
@@ -675,16 +675,24 @@ namespace Terminal.Gui {
 			SetNeedsDisplay ();
 		}
 
+		TextModel GetModel ()
+		{
+			var model = new TextModel ();
+			model.LoadString (Text);
+			return model;
+		}
+
 		/// <summary>
 		/// Deletes word backwards.
 		/// </summary>
 		public virtual void KillWordBackwards ()
 		{
 			ClearAllSelection ();
-			int bw = WordBackward (point);
-			if (bw != -1) {
-				SetText (text.GetRange (0, bw).Concat (text.GetRange (point, text.Count - point)));
-				point = bw;
+			var newPos = GetModel ().WordBackward (point, 0);
+			if (newPos == null) return;
+			if (newPos.Value.col != -1) {
+				SetText (text.GetRange (0, newPos.Value.col).Concat (text.GetRange (point, text.Count - point)));
+				point = newPos.Value.col;
 			}
 			Adjust ();
 		}
@@ -695,9 +703,10 @@ namespace Terminal.Gui {
 		public virtual void KillWordForwards ()
 		{
 			ClearAllSelection ();
-			int fw = WordForward (point);
-			if (fw != -1) {
-				SetText (text.GetRange (0, point).Concat (text.GetRange (fw, text.Count - fw)));
+			var newPos = GetModel ().WordForward (point, 0);
+			if (newPos == null) return;
+			if (newPos.Value.col != -1) {
+				SetText (text.GetRange (0, point).Concat (text.GetRange (newPos.Value.col, text.Count - newPos.Value.col)));
 			}
 			Adjust ();
 		}
@@ -705,18 +714,20 @@ namespace Terminal.Gui {
 		void MoveWordRight ()
 		{
 			ClearAllSelection ();
-			int fw = WordForward (point);
-			if (fw != -1)
-				point = fw;
+			var newPos = GetModel ().WordForward (point, 0);
+			if (newPos == null) return;
+			if (newPos.Value.col != -1)
+				point = newPos.Value.col;
 			Adjust ();
 		}
 
 		void MoveWordLeft ()
 		{
 			ClearAllSelection ();
-			int bw = WordBackward (point);
-			if (bw != -1)
-				point = bw;
+			var newPos = GetModel ().WordBackward (point, 0);
+			if (newPos == null) return;
+			if (newPos.Value.col != -1)
+				point = newPos.Value.col;
 			Adjust ();
 		}
 
@@ -810,10 +821,11 @@ namespace Terminal.Gui {
 		{
 			if (point < text.Count) {
 				int x = start > -1 && start > point ? start : point;
-				int sfw = WordForward (x);
-				if (sfw != -1)
-					point = sfw;
-				PrepareSelection (x, sfw - x);
+				var newPos = GetModel ().WordForward (x, 0);
+				if (newPos == null) return;
+				if (newPos.Value.col != -1)
+					point = newPos.Value.col;
+				PrepareSelection (x, newPos.Value.col - x);
 			}
 		}
 
@@ -822,10 +834,11 @@ namespace Terminal.Gui {
 			if (point > 0) {
 				int x = Math.Min (start > -1 && start > point ? start : point, text.Count);
 				if (x > 0) {
-					int sbw = WordBackward (x);
-					if (sbw != -1)
-						point = sbw;
-					PrepareSelection (x, sbw - x);
+					var newPos = GetModel ().WordBackward (x, 0);
+					if (newPos == null) return;
+					if (newPos.Value.col != -1)
+						point = newPos.Value.col;
+					PrepareSelection (x, newPos.Value.col - x);
 				}
 			}
 		}
@@ -923,88 +936,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		int WordForward (int p)
-		{
-			if (p >= text.Count)
-				return -1;
-
-			int i = p + 1;
-			if (i == text.Count)
-				return text.Count;
-
-			var ti = text [i];
-			if (Rune.IsLetterOrDigit (ti) && Rune.IsWhiteSpace (text [p]))
-				return i;
-
-			if (Rune.IsPunctuation (ti) || Rune.IsSymbol (ti) || Rune.IsWhiteSpace (ti)) {
-				for (; i < text.Count; i++) {
-					if (Rune.IsLetterOrDigit (text [i]))
-						return i;
-				}
-			} else {
-				for (; i < text.Count; i++) {
-					if (!Rune.IsLetterOrDigit (text [i]))
-						break;
-				}
-				for (; i < text.Count; i++) {
-					if (Rune.IsLetterOrDigit (text [i]) ||
-						(Rune.IsPunctuation (text [i]) && Rune.IsWhiteSpace (text [i - 1])))
-						break;
-				}
-			}
-
-			if (i != p)
-				return Math.Min (i, text.Count);
-
-			return -1;
-		}
-
-		int WordBackward (int p)
-		{
-			if (p == 0)
-				return -1;
-
-			int i = p - 1;
-			if (i == 0)
-				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])) {
-						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;
-				}
-			}
-
-			if (i != p)
-				return Math.Max (i, 0);
-
-			return -1;
-		}
-
 		void ShowContextMenu ()
 		{
 			if (currentCulture != Thread.CurrentThread.CurrentUICulture) {
@@ -1127,18 +1058,19 @@ namespace Terminal.Gui {
 				if (x == text.Count || (x > 0 && (char)text [x - 1] != ' ')
 					|| (x > 0 && (char)text [x] == ' ')) {
 
-					sbw = WordBackward (x);
+					var newPosBw = GetModel ().WordBackward (x, 0);
+					sbw = newPosBw.Value.col;
 				}
 				if (sbw != -1) {
 					x = sbw;
 					PositionCursor (x);
 				}
-				int sfw = WordForward (x);
+				var newPosFw = GetModel ().WordForward (x, 0);
 				ClearAllSelection ();
-				if (sfw != -1 && sbw != -1) {
-					point = sfw;
+				if (newPosFw.Value.col != -1 && sbw != -1) {
+					point = newPosFw.Value.col;
 				}
-				PrepareSelection (sbw, sfw - sbw);
+				PrepareSelection (sbw, newPosFw.Value.col - sbw);
 			} else if (ev.Flags == MouseFlags.Button1TripleClicked) {
 				EnsureHasFocus ();
 				PositionCursor (0);
@@ -1375,7 +1307,7 @@ namespace Terminal.Gui {
 		/// at the start of the <see cref="TextField"/>.
 		/// </summary>
 		/// <returns></returns>
-		internal bool CursorIsAtStart()
+		internal bool CursorIsAtStart ()
 		{
 			return CursorPosition <= 0;
 		}

+ 242 - 193
Terminal.Gui/Views/TextView.cs

@@ -512,6 +512,240 @@ namespace Terminal.Gui {
 			toFind.startPointToFind = toFind.currentPointToFind = point;
 			toFind.found = false;
 		}
+
+		Rune RuneAt (int col, int row)
+		{
+			var line = GetLine (row);
+			if (line.Count > 0) {
+				return line [col > line.Count - 1 ? line.Count - 1 : col];
+			} else {
+				return 0;
+			}
+		}
+
+		bool MoveNext (ref int col, ref int row, out Rune rune)
+		{
+			var line = GetLine (row);
+			if (col + 1 < line.Count) {
+				col++;
+				rune = line [col];
+				if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune)
+					&& !Rune.IsWhiteSpace (line [col - 1])) {
+					col++;
+				}
+				return true;
+			} else if (col + 1 == line.Count) {
+				col++;
+			}
+			while (row + 1 < Count) {
+				col = 0;
+				row++;
+				line = GetLine (row);
+				if (line.Count > 0) {
+					rune = line [0];
+					return true;
+				}
+			}
+			rune = 0;
+			return false;
+		}
+
+		bool MovePrev (ref int col, ref int row, out Rune rune)
+		{
+			var line = GetLine (row);
+
+			if (col > 0) {
+				col--;
+				rune = line [col];
+				return true;
+			}
+			if (row == 0) {
+				rune = 0;
+				return false;
+			}
+			while (row > 0) {
+				row--;
+				line = GetLine (row);
+				col = line.Count - 1;
+				if (col >= 0) {
+					rune = line [col];
+					return true;
+				}
+			}
+			rune = 0;
+			return false;
+		}
+
+		enum RuneType {
+			IsSymbol,
+			IsWhiteSpace,
+			IsLetterOrDigit,
+			IsPunctuation,
+			IsUnknow
+		}
+
+		RuneType GetRuneType (Rune rune)
+		{
+			if (Rune.IsSymbol (rune)) {
+				return RuneType.IsSymbol;
+			} else if (Rune.IsWhiteSpace (rune)) {
+				return RuneType.IsWhiteSpace;
+			} else if (Rune.IsLetterOrDigit (rune)) {
+				return RuneType.IsLetterOrDigit;
+			} else if (Rune.IsPunctuation (rune)) {
+				return RuneType.IsPunctuation;
+			}
+			return RuneType.IsUnknow;
+		}
+
+		bool IsSameRuneType (Rune newRune, RuneType runeType)
+		{
+			var rt = GetRuneType (newRune);
+			return rt == runeType;
+		}
+
+		public (int col, int row)? WordForward (int fromCol, int fromRow)
+		{
+			if (fromRow == lines.Count - 1 && fromCol == GetLine (lines.Count - 1).Count)
+				return null;
+
+			var col = fromCol;
+			var row = fromRow;
+			try {
+				var rune = RuneAt (col, row);
+				var runeType = GetRuneType (rune);
+				int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1;
+
+				void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune)
+				{
+					if (Rune.IsWhiteSpace (nRune)) {
+						while (MoveNext (ref nCol, ref nRow, out nRune)) {
+							if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) {
+								lastValidCol = nCol;
+								return;
+							}
+						}
+						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) {
+							if (lastValidCol > -1) {
+								nCol = lastValidCol;
+							}
+							return;
+						}
+						while (MoveNext (ref nCol, ref nRow, out nRune)) {
+							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
+								break;
+							if (nRow != fromRow) {
+								break;
+							}
+							lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol;
+						}
+						if (lastValidCol > -1) {
+							nCol = lastValidCol;
+							nRow = fromRow;
+						}
+					} else {
+						if (!MoveNext (ref nCol, ref nRow, out nRune)) {
+							return;
+						}
+						if (!IsSameRuneType (nRune, runeType) && !Rune.IsWhiteSpace (nRune)) {
+							return;
+						}
+						var line = GetLine (nRow);
+						if (nCol == line.Count && nRow == fromRow && (Rune.IsLetterOrDigit (line [0]) || Rune.IsPunctuation (line [0]) || Rune.IsSymbol (line [0]))) {
+							return;
+						}
+						lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol;
+						if (fromRow != nRow) {
+							nCol = 0;
+							return;
+						}
+						ProcMoveNext (ref nCol, ref nRow, nRune);
+					}
+				}
+
+				ProcMoveNext (ref col, ref row, rune);
+
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
+			}
+		}
+
+		public (int col, int row)? WordBackward (int fromCol, int fromRow)
+		{
+			if (fromRow == 0 && fromCol == 0)
+				return null;
+
+			var col = Math.Max (fromCol - 1, 0);
+			var row = fromRow;
+			try {
+				var rune = RuneAt (col, row);
+				var runeType = GetRuneType (rune);
+				int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune)) ? col : -1;
+
+				void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune)
+				{
+					if (Rune.IsWhiteSpace (nRune)) {
+						while (MovePrev (ref nCol, ref nRow, out nRune)) {
+							if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)) {
+								lastValidCol = nCol;
+								if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknow) {
+									runeType = GetRuneType (nRune);
+								}
+								break;
+							}
+						}
+						if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))) {
+							if (lastValidCol > -1) {
+								nCol = lastValidCol;
+							}
+							return;
+						}
+						while (MovePrev (ref nCol, ref nRow, out nRune)) {
+							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
+								break;
+							if (nRow != fromRow) {
+								break;
+							}
+							lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol;
+						}
+						if (lastValidCol > -1) {
+							nCol = lastValidCol;
+							nRow = fromRow;
+						}
+					} else {
+						if (!MovePrev (ref nCol, ref nRow, out nRune)) {
+							return;
+						}
+
+						var line = GetLine (nRow);
+						if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0]) || Rune.IsPunctuation (line [0]) || Rune.IsSymbol (line [0]))) {
+							return;
+						}
+						lastValidCol = IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune) ? nCol : lastValidCol;
+						if (lastValidCol > -1 && Rune.IsWhiteSpace (nRune)) {
+							nCol = lastValidCol;
+							return;
+						}
+						if (fromRow != nRow) {
+							nCol = line.Count;
+							return;
+						}
+						ProcMovePrev (ref nCol, ref nRow, nRune);
+					}
+				}
+
+				ProcMovePrev (ref col, ref row, rune);
+
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
+			}
+		}
 	}
 
 	partial class HistoryText {
@@ -2442,8 +2676,8 @@ namespace Terminal.Gui {
 		{
 			var currentLine = this.GetCurrentLine ();
 			var cursorPosition = Math.Min (this.CurrentColumn, currentLine.Count);
-			Autocomplete.GenerateSuggestions(
-				new AutocompleteContext(currentLine,cursorPosition)
+			Autocomplete.GenerateSuggestions (
+				new AutocompleteContext (currentLine, cursorPosition)
 				);
 		}
 
@@ -3212,7 +3446,7 @@ namespace Terminal.Gui {
 
 				return;
 			}
-			var newPos = WordBackward (currentColumn, currentRow);
+			var newPos = model.WordBackward (currentColumn, currentRow);
 			if (newPos.HasValue && currentRow == newPos.Value.row) {
 				var restCount = currentColumn - newPos.Value.col;
 				currentLine.RemoveRange (newPos.Value.col, restCount);
@@ -3264,7 +3498,7 @@ namespace Terminal.Gui {
 
 				return;
 			}
-			var newPos = WordForward (currentColumn, currentRow);
+			var newPos = model.WordForward (currentColumn, currentRow);
 			var restCount = 0;
 			if (newPos.HasValue && currentRow == newPos.Value.row) {
 				restCount = newPos.Value.col - currentColumn;
@@ -3292,7 +3526,7 @@ namespace Terminal.Gui {
 
 		void MoveWordForward ()
 		{
-			var newPos = WordForward (currentColumn, currentRow);
+			var newPos = model.WordForward (currentColumn, currentRow);
 			if (newPos.HasValue) {
 				currentColumn = newPos.Value.col;
 				currentRow = newPos.Value.row;
@@ -3303,7 +3537,7 @@ namespace Terminal.Gui {
 
 		void MoveWordBackward ()
 		{
-			var newPos = WordBackward (currentColumn, currentRow);
+			var newPos = model.WordBackward (currentColumn, currentRow);
 			if (newPos.HasValue) {
 				currentColumn = newPos.Value.col;
 				currentRow = newPos.Value.row;
@@ -4028,16 +4262,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		Rune RuneAt (int col, int row)
-		{
-			var line = model.GetLine (row);
-			if (line.Count > 0) {
-				return line [col > line.Count - 1 ? line.Count - 1 : col];
-			} else {
-				return 0;
-			}
-		}
-
 		/// <summary>
 		/// Will scroll the <see cref="TextView"/> to the last line and position the cursor there.
 		/// </summary>
@@ -4063,181 +4287,6 @@ namespace Terminal.Gui {
 			PositionCursor ();
 		}
 
-		bool MoveNext (ref int col, ref int row, out Rune rune)
-		{
-			var line = model.GetLine (row);
-			if (col + 1 < line.Count) {
-				col++;
-				rune = line [col];
-				if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune)
-					&& !Rune.IsWhiteSpace (line [col - 1])) {
-					col++;
-				}
-				return true;
-			} else if (col + 1 == line.Count) {
-				col++;
-			}
-			while (row + 1 < model.Count) {
-				col = 0;
-				row++;
-				line = model.GetLine (row);
-				if (line.Count > 0) {
-					rune = line [0];
-					return true;
-				}
-			}
-			rune = 0;
-			return false;
-		}
-
-		bool MovePrev (ref int col, ref int row, out Rune rune)
-		{
-			var line = model.GetLine (row);
-
-			if (col > 0) {
-				col--;
-				rune = line [col];
-				return true;
-			}
-			if (row == 0) {
-				rune = 0;
-				return false;
-			}
-			while (row > 0) {
-				row--;
-				line = model.GetLine (row);
-				col = line.Count - 1;
-				if (col >= 0) {
-					rune = line [col];
-					return true;
-				}
-			}
-			rune = 0;
-			return false;
-		}
-
-		(int col, int row)? WordForward (int fromCol, int fromRow)
-		{
-			var col = fromCol;
-			var row = fromRow;
-			try {
-				var rune = RuneAt (col, row);
-
-				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, row);
-				return null;
-			} catch (Exception) {
-				return null;
-			}
-		}
-
-		(int col, int row)? WordBackward (int fromCol, int fromRow)
-		{
-			if (fromRow == 0 && fromCol == 0)
-				return null;
-
-			var col = Math.Max (fromCol - 1, 0);
-			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))) {
-							if (lastValidCol > -1) {
-								nCol = lastValidCol;
-							}
-							return;
-						}
-						while (MovePrev (ref nCol, ref nRow, out nRune)) {
-							if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune))
-								break;
-							if (nRow != fromRow) {
-								break;
-							}
-							lastValidCol = nCol;
-						}
-						if (lastValidCol > -1) {
-							nCol = lastValidCol;
-							nRow = fromRow;
-						}
-					} else {
-						if (!MovePrev (ref nCol, ref nRow, out nRune)) {
-							return;
-						}
-
-						var line = model.GetLine (nRow);
-						if (nCol == 0 && nRow == fromRow && (Rune.IsLetterOrDigit (line [0]) || Rune.IsPunctuation (line [0]))) {
-							return;
-						}
-						lastValidCol = Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) ? nCol : lastValidCol;
-						if (lastValidCol > -1 && (Rune.IsSymbol (nRune) || Rune.IsWhiteSpace (nRune))) {
-							nCol = lastValidCol;
-							return;
-						}
-						if (fromRow != nRow) {
-							nCol = line.Count;
-							return;
-						}
-						ProcMovePrev (ref nCol, ref nRow, nRune);
-					}
-				}
-
-				ProcMovePrev (ref col, ref row, rune);
-
-				if (fromCol != col || fromRow != row)
-					return (col, row);
-				return null;
-			} catch (Exception) {
-				return null;
-			}
-		}
-
 		bool isButtonShift;
 		bool clickWithSelecting;
 
@@ -4361,7 +4410,7 @@ namespace Terminal.Gui {
 				if (currentColumn == line.Count || (currentColumn > 0 && (line [currentColumn - 1] != ' '
 					|| line [currentColumn] == ' '))) {
 
-					newPos = WordBackward (currentColumn, currentRow);
+					newPos = model.WordBackward (currentColumn, currentRow);
 					if (newPos.HasValue) {
 						currentColumn = currentRow == newPos.Value.row ? newPos.Value.col : 0;
 					}
@@ -4369,7 +4418,7 @@ namespace Terminal.Gui {
 				if (!selecting) {
 					StartSelecting ();
 				}
-				newPos = WordForward (currentColumn, currentRow);
+				newPos = model.WordForward (currentColumn, currentRow);
 				if (newPos != null && newPos.HasValue) {
 					currentColumn = currentRow == newPos.Value.row ? newPos.Value.col : line.Count;
 				}

+ 97 - 59
UnitTests/Views/TextFieldTests.cs

@@ -181,36 +181,42 @@ namespace Terminal.Gui.ViewsTests {
 				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
-					Assert.Equal (25, _textField.CursorPosition);
+					Assert.Equal (31, _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 (25, _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 (20, _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 (12, _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 (7, _textField.CursorPosition);
 					Assert.Equal (-1, _textField.SelectedStart);
 					Assert.Equal (0, _textField.SelectedLength);
 					Assert.Null (_textField.SelectedText);
 					break;
 				case 5:
+					Assert.Equal (4, _textField.CursorPosition);
+					Assert.Equal (-1, _textField.SelectedStart);
+					Assert.Equal (0, _textField.SelectedLength);
+					Assert.Null (_textField.SelectedText);
+					break;
+				case 6:
 					Assert.Equal (0, _textField.CursorPosition);
 					Assert.Equal (-1, _textField.SelectedStart);
 					Assert.Equal (0, _textField.SelectedLength);
@@ -285,36 +291,42 @@ namespace Terminal.Gui.ViewsTests {
 				_textField.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
+					Assert.Equal (31, _textField.CursorPosition);
+					Assert.Equal (32, _textField.SelectedStart);
+					Assert.Equal (1, _textField.SelectedLength);
+					Assert.Equal (".", _textField.SelectedText);
+					break;
+				case 1:
 					Assert.Equal (25, _textField.CursorPosition);
 					Assert.Equal (32, _textField.SelectedStart);
 					Assert.Equal (7, _textField.SelectedLength);
 					Assert.Equal ("fields.", _textField.SelectedText);
 					break;
-				case 1:
+				case 2:
 					Assert.Equal (20, _textField.CursorPosition);
 					Assert.Equal (32, _textField.SelectedStart);
 					Assert.Equal (12, _textField.SelectedLength);
 					Assert.Equal ("text fields.", _textField.SelectedText);
 					break;
-				case 2:
+				case 3:
 					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:
+				case 4:
 					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:
+				case 5:
 					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:
+				case 6:
 					Assert.Equal (0, _textField.CursorPosition);
 					Assert.Equal (32, _textField.SelectedStart);
 					Assert.Equal (32, _textField.SelectedLength);
@@ -1101,9 +1113,9 @@ namespace Terminal.Gui.ViewsTests {
 			Assert.Equal ("to jump between text fields.", tf.Text.ToString ());
 			tf.CursorPosition = tf.Text.Length;
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("to jump between text ", tf.Text.ToString ());
+			Assert.Equal ("to jump between text fields", tf.Text.ToString ());
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.T | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ("to jump between text ", tf.SelectedText);
+			Assert.Equal ("to jump between text fields", tf.SelectedText);
 			Assert.True (tf.ProcessKey (new KeyEvent (Key.D | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ())));
 			Assert.Equal ("", tf.Text.ToString ());
 		}
@@ -1441,93 +1453,92 @@ Les Miśerables", output);
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ắ", output);
 		}
-	
+
 		[Fact, AutoInitShutdown]
 		public void CaptionedTextField_RendersCaption_WhenNotFocused ()
 		{
-			var tf = GetTextFieldsInView();
+			var tf = GetTextFieldsInView ();
 
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("",output);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("", output);
 
 			// Caption has no effect when focused
-			tf.Caption  = "Enter txt";
-			Assert.True(tf.HasFocus);
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("",output);
+			tf.Caption = "Enter txt";
+			Assert.True (tf.HasFocus);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("", output);
 
-			Application.Driver.SendKeys('\t',ConsoleKey.Tab,false,false,false);
+			Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
 
-			Assert.False(tf.HasFocus);
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("Enter txt",output);		
+			Assert.False (tf.HasFocus);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("Enter txt", output);
 		}
 
 		[Theory, AutoInitShutdown]
-		[InlineData("blah")]
-		[InlineData(" ")]
+		[InlineData ("blah")]
+		[InlineData (" ")]
 		public void CaptionedTextField_DoNotRenderCaption_WhenTextPresent (string content)
 		{
-			var tf = GetTextFieldsInView();
+			var tf = GetTextFieldsInView ();
 
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("",output);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("", output);
 
-			tf.Caption  = "Enter txt";
-			Application.Driver.SendKeys('\t',ConsoleKey.Tab,false,false,false);
+			tf.Caption = "Enter txt";
+			Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
 
 			// Caption should appear when not focused and no text
-			Assert.False(tf.HasFocus);
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("Enter txt",output);
+			Assert.False (tf.HasFocus);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("Enter txt", output);
 
 			// but disapear when text is added
 			tf.Text = content;
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre(content,output);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre (content, output);
 		}
 
-
 		[Fact, AutoInitShutdown]
 		public void CaptionedTextField_DoesNotOverspillBounds_Unicode ()
 		{
 			var caption = "Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
 
-			Assert.Equal(11,caption.Length);
-			Assert.Equal(10,caption.Sum(c => Rune.ColumnWidth(c)));
+			Assert.Equal (11, caption.Length);
+			Assert.Equal (10, caption.Sum (c => Rune.ColumnWidth (c)));
 
-			var tf = GetTextFieldsInView();
-			
-			tf.Caption  = caption;
-			Application.Driver.SendKeys('\t',ConsoleKey.Tab,false,false,false);
-			Assert.False(tf.HasFocus);
+			var tf = GetTextFieldsInView ();
 
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre("Misérables",output);
+			tf.Caption = caption;
+			Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+			Assert.False (tf.HasFocus);
+
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre ("Misérables", output);
 		}
 
 		[Theory, AutoInitShutdown]
-		[InlineData("0123456789","0123456789")]
-		[InlineData("01234567890","0123456789")]
+		[InlineData ("0123456789", "0123456789")]
+		[InlineData ("01234567890", "0123456789")]
 		public void CaptionedTextField_DoesNotOverspillBounds (string caption, string expectedRender)
 		{
-			var tf = GetTextFieldsInView();
+			var tf = GetTextFieldsInView ();
 			// Caption has no effect when focused
-			tf.Caption  = caption;
-			Application.Driver.SendKeys('\t',ConsoleKey.Tab,false,false,false);
-			Assert.False(tf.HasFocus);
+			tf.Caption = caption;
+			Application.Driver.SendKeys ('\t', ConsoleKey.Tab, false, false, false);
+			Assert.False (tf.HasFocus);
 
-			tf.Redraw(tf.Bounds);
-			TestHelpers.AssertDriverContentsAre(expectedRender,output);
+			tf.Redraw (tf.Bounds);
+			TestHelpers.AssertDriverContentsAre (expectedRender, output);
 		}
 
 
 		private TextField GetTextFieldsInView ()
 		{
-            var tf = new TextField{
+			var tf = new TextField {
 				Width = 10
 			};
-            var tf2 = new TextField{
+			var tf2 = new TextField {
 				Y = 1,
 				Width = 10
 			};
@@ -1537,11 +1548,11 @@ Les Miśerables", output);
 			top.Add (tf2);
 
 			Application.Begin (top);
-			
-			Assert.Same(tf,top.Focused);
+
+			Assert.Same (tf, top.Focused);
 
 			return tf;
-    	}
+		}
 
 		[Fact]
 		public void OnEnter_Does_Not_Throw_If_Not_IsInitialized_SetCursorVisibility ()
@@ -1553,5 +1564,32 @@ Les Miśerables", output);
 			var exception = Record.Exception (tf.SetFocus);
 			Assert.Null (exception);
 		}
+
+		[Fact]
+		public void WordBackward_WordForward_Mixed ()
+		{
+			var tf = new TextField ("Test with0. and!.?;-@+") { Width = 30 };
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorLeft, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (15, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorLeft, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (12, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorLeft, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (10, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorLeft, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (5, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorLeft, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (0, tf.CursorPosition);
+
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorRight, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (5, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorRight, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (10, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorRight, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (12, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorRight, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (15, tf.CursorPosition);
+			tf.ProcessKey (new KeyEvent (Key.CtrlMask | Key.CursorRight, new KeyModifiers () { Ctrl = true }));
+			Assert.Equal (22, tf.CursorPosition);
+		}
 	}
 }

+ 140 - 37
UnitTests/Views/TextViewTests.cs

@@ -214,7 +214,7 @@ namespace Terminal.Gui.ViewsTests {
 				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
-					Assert.Equal (25, _textView.CursorPosition.X);
+					Assert.Equal (31, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
 					Assert.Equal (0, _textView.SelectionStartRow);
@@ -222,7 +222,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 1:
-					Assert.Equal (20, _textView.CursorPosition.X);
+					Assert.Equal (25, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
 					Assert.Equal (0, _textView.SelectionStartRow);
@@ -230,7 +230,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 2:
-					Assert.Equal (12, _textView.CursorPosition.X);
+					Assert.Equal (20, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
 					Assert.Equal (0, _textView.SelectionStartRow);
@@ -238,7 +238,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 3:
-					Assert.Equal (7, _textView.CursorPosition.X);
+					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
 					Assert.Equal (0, _textView.SelectionStartRow);
@@ -246,7 +246,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 4:
-					Assert.Equal (4, _textView.CursorPosition.X);
+					Assert.Equal (7, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
 					Assert.Equal (0, _textView.SelectionStartRow);
@@ -254,6 +254,14 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal ("", _textView.SelectedText);
 					break;
 				case 5:
+					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 6:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (0, _textView.SelectionStartColumn);
@@ -344,6 +352,14 @@ namespace Terminal.Gui.ViewsTests {
 				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
+					Assert.Equal (31, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (32, _textView.SelectionStartColumn);
+					Assert.Equal (0, _textView.SelectionStartRow);
+					Assert.Equal (1, _textView.SelectedLength);
+					Assert.Equal (".", _textView.SelectedText);
+					break;
+				case 1:
 					Assert.Equal (25, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -351,7 +367,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (7, _textView.SelectedLength);
 					Assert.Equal ("fields.", _textView.SelectedText);
 					break;
-				case 1:
+				case 2:
 					Assert.Equal (20, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -359,7 +375,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (12, _textView.SelectedLength);
 					Assert.Equal ("text fields.", _textView.SelectedText);
 					break;
-				case 2:
+				case 3:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -367,7 +383,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (20, _textView.SelectedLength);
 					Assert.Equal ("between text fields.", _textView.SelectedText);
 					break;
-				case 3:
+				case 4:
 					Assert.Equal (7, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -375,7 +391,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (25, _textView.SelectedLength);
 					Assert.Equal ("jump between text fields.", _textView.SelectedText);
 					break;
-				case 4:
+				case 5:
 					Assert.Equal (4, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -383,7 +399,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (28, _textView.SelectedLength);
 					Assert.Equal ("to jump between text fields.", _textView.SelectedText);
 					break;
-				case 5:
+				case 6:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (32, _textView.SelectionStartColumn);
@@ -718,6 +734,14 @@ namespace Terminal.Gui.ViewsTests {
 				_textView.ProcessKey (new KeyEvent (Key.CursorLeft | Key.CtrlMask | Key.ShiftMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (1, _textView.SelectedLength);
+					Assert.Equal (".", _textView.SelectedText);
+					break;
+				case 1:
 					Assert.Equal (19, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -725,7 +749,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (5, _textView.SelectedLength);
 					Assert.Equal ("line.", _textView.SelectedText);
 					break;
-				case 1:
+				case 2:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -733,7 +757,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (12, _textView.SelectedLength);
 					Assert.Equal ("second line.", _textView.SelectedText);
 					break;
-				case 2:
+				case 3:
 					Assert.Equal (8, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -741,7 +765,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (16, _textView.SelectedLength);
 					Assert.Equal ("the second line.", _textView.SelectedText);
 					break;
-				case 3:
+				case 4:
 					Assert.Equal (5, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -749,7 +773,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (19, _textView.SelectedLength);
 					Assert.Equal ("is the second line.", _textView.SelectedText);
 					break;
-				case 4:
+				case 5:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -757,7 +781,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (24, _textView.SelectedLength);
 					Assert.Equal ("This is the second line.", _textView.SelectedText);
 					break;
-				case 5:
+				case 6:
 					Assert.Equal (23, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -765,7 +789,15 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (24 + Environment.NewLine.Length, _textView.SelectedLength);
 					Assert.Equal ($"{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
-				case 6:
+				case 7:
+					Assert.Equal (22, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal (24, _textView.SelectionStartColumn);
+					Assert.Equal (1, _textView.SelectionStartRow);
+					Assert.Equal (25 + Environment.NewLine.Length, _textView.SelectedLength);
+					Assert.Equal ($".{Environment.NewLine}This is the second line.", _textView.SelectedText);
+					break;
+				case 8:
 					Assert.Equal (18, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -773,7 +805,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (29 + Environment.NewLine.Length, _textView.SelectedLength);
 					Assert.Equal ($"line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
-				case 7:
+				case 9:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -781,7 +813,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (35 + Environment.NewLine.Length, _textView.SelectedLength);
 					Assert.Equal ($"first line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
-				case 8:
+				case 10:
 					Assert.Equal (8, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -789,7 +821,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (39 + Environment.NewLine.Length, _textView.SelectedLength);
 					Assert.Equal ($"the first line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
-				case 9:
+				case 11:
 					Assert.Equal (5, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -797,7 +829,7 @@ namespace Terminal.Gui.ViewsTests {
 					Assert.Equal (42 + Environment.NewLine.Length, _textView.SelectedLength);
 					Assert.Equal ($"is the first line.{Environment.NewLine}This is the second line.", _textView.SelectedText);
 					break;
-				case 10:
+				case 12:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal (24, _textView.SelectionStartColumn);
@@ -1071,26 +1103,31 @@ namespace Terminal.Gui.ViewsTests {
 				_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
+					Assert.Equal (22, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line", _textView.Text);
+					break;
+				case 1:
 					Assert.Equal (18, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first ", _textView.Text);
 					break;
-				case 1:
+				case 2:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the ", _textView.Text);
 					break;
-				case 2:
+				case 3:
 					Assert.Equal (8, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is ", _textView.Text);
 					break;
-				case 3:
+				case 4:
 					Assert.Equal (5, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This ", _textView.Text);
 					break;
-				case 4:
+				case 5:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
@@ -1197,60 +1234,71 @@ namespace Terminal.Gui.ViewsTests {
 				_textView.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ()));
 				switch (iteration) {
 				case 0:
+					Assert.Equal (23, _textView.CursorPosition.X);
+					Assert.Equal (1, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line." + Environment.NewLine
+						+ "This is the second line", _textView.Text);
+					break;
+				case 1:
 					Assert.Equal (19, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line." + Environment.NewLine
 						+ "This is the second ", _textView.Text);
 					break;
-				case 1:
+				case 2:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line." + Environment.NewLine
 						+ "This is the ", _textView.Text);
 					break;
-				case 2:
+				case 3:
 					Assert.Equal (8, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line." + Environment.NewLine
 						+ "This is ", _textView.Text);
 					break;
-				case 3:
+				case 4:
 					Assert.Equal (5, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line." + Environment.NewLine
 						+ "This ", _textView.Text);
 					break;
-				case 4:
+				case 5:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (1, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line." + Environment.NewLine, _textView.Text);
 					break;
-				case 5:
+				case 6:
 					Assert.Equal (23, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first line.", _textView.Text);
 					break;
-				case 6:
+				case 7:
+					Assert.Equal (22, _textView.CursorPosition.X);
+					Assert.Equal (0, _textView.CursorPosition.Y);
+					Assert.Equal ("This is the first line", _textView.Text);
+					break;
+				case 8:
 					Assert.Equal (18, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the first ", _textView.Text);
 					break;
-				case 7:
+				case 9:
 					Assert.Equal (12, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is the ", _textView.Text);
 					break;
-				case 8:
+				case 10:
 					Assert.Equal (8, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This is ", _textView.Text);
 					break;
-				case 9:
+				case 11:
 					Assert.Equal (5, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("This ", _textView.Text);
 					break;
-				case 10:
+				case 12:
 					Assert.Equal (0, _textView.CursorPosition.X);
 					Assert.Equal (0, _textView.CursorPosition.Y);
 					Assert.Equal ("", _textView.Text);
@@ -2549,12 +2597,12 @@ line.
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.F", tv.Text);
 			Assert.Equal (new Point (24, 2), tv.CursorPosition);
 			Assert.Single (tv.Autocomplete.Suggestions);
-			Assert.Equal ("first", tv.Autocomplete.Suggestions[0].Replacement);
+			Assert.Equal ("first", tv.Autocomplete.Suggestions [0].Replacement);
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 			Assert.Equal (new Point (28, 2), tv.CursorPosition);
 			Assert.Single (tv.Autocomplete.Suggestions);
-			Assert.Equal ("first", tv.Autocomplete.Suggestions[0].Replacement);
+			Assert.Equal ("first", tv.Autocomplete.Suggestions [0].Replacement);
 			g.AllSuggestions = new List<string> ();
 			tv.Autocomplete.ClearSuggestions ();
 			Assert.Empty (g.AllSuggestions);
@@ -2834,6 +2882,18 @@ line.
 			Assert.True (tv.Selecting);
 			Assert.True (tv.ProcessKey (new KeyEvent ((Key)((int)'F' + Key.AltMask), new KeyModifiers ())));
 			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
+			Assert.Equal (new Point (22, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.False (tv.Selecting);
+			Assert.True (tv.ProcessKey (new KeyEvent ((Key)((int)'F' + Key.AltMask), new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
+			Assert.Equal (new Point (23, 2), tv.CursorPosition);
+			Assert.Equal (0, tv.SelectedLength);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.False (tv.Selecting);
+			Assert.True (tv.ProcessKey (new KeyEvent ((Key)((int)'F' + Key.AltMask), new KeyModifiers ())));
+			Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 			Assert.Equal (new Point (28, 2), tv.CursorPosition);
 			Assert.Equal (0, tv.SelectedLength);
 			Assert.Equal ("", tv.SelectedText);
@@ -4937,6 +4997,12 @@ line.
 			Assert.Equal (2, tv.Lines);
 			Assert.Equal (new Point (12, 1), tv.CursorPosition);
 
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (11, 1), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
 			Assert.Equal ("", tv.SelectedText);
@@ -4955,6 +5021,12 @@ line.
 			Assert.Equal (1, tv.Lines);
 			Assert.Equal (new Point (11, 0), tv.CursorPosition);
 
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line", tv.Text);
+			Assert.Equal ("", tv.SelectedText);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (10, 0), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ("First ", tv.Text);
 			Assert.Equal ("", tv.SelectedText);
@@ -4973,6 +5045,11 @@ line.
 			Assert.Equal (1, tv.Lines);
 			Assert.Equal (new Point (6, 0), tv.CursorPosition);
 
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (10, 0), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ("First line.", tv.Text);
 			Assert.Equal (1, tv.Lines);
@@ -4988,12 +5065,22 @@ line.
 			Assert.Equal (2, tv.Lines);
 			Assert.Equal (new Point (7, 1), tv.CursorPosition);
 
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (11, 1), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ($"First line.{Environment.NewLine}Second line.", tv.Text);
 			Assert.Equal (2, tv.Lines);
 			Assert.Equal (new Point (12, 1), tv.CursorPosition);
 
 			// Redo
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ($"First line.{Environment.NewLine}Second line", tv.Text);
+			Assert.Equal (2, tv.Lines);
+			Assert.Equal (new Point (11, 1), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ($"First line.{Environment.NewLine}Second ", tv.Text);
 			Assert.Equal (2, tv.Lines);
@@ -5009,6 +5096,11 @@ line.
 			Assert.Equal (1, tv.Lines);
 			Assert.Equal (new Point (11, 0), tv.CursorPosition);
 
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
+			Assert.Equal ("First line", tv.Text);
+			Assert.Equal (1, tv.Lines);
+			Assert.Equal (new Point (10, 0), tv.CursorPosition);
+
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ("First ", tv.Text);
 			Assert.Equal (1, tv.Lines);
@@ -6809,7 +6901,7 @@ This is the second line.
 				Height = 10,
 			};
 			tv.BeginInit (); tv.EndInit ();
-			
+
 			tv.ContentsChanged += (s, e) => {
 				eventcount++;
 			};
@@ -6833,5 +6925,16 @@ This is the second line.
 			Assert.Null (exception);
 			Assert.Equal (textToReplace, tv.Text);
 		}
+
+		[Fact]
+		public void WordBackward_WordForward_Limits_Return_Null ()
+		{
+			var model = new TextModel ();
+			model.LoadString ("Test");
+			var newPos = model.WordBackward (0, 0);
+			Assert.Null (newPos);
+			newPos = model.WordForward (4, 0);
+			Assert.Null (newPos);
+		}
 	}
 }