Преглед изворни кода

Merge pull request #1047 from BDisp/textview-unicode-support

Fixes #93. Audit TextView just like we did TextField to ensure proper treatment of Unicode
Charlie Kindel пре 4 година
родитељ
комит
287d2c1b05
2 измењених фајлова са 268 додато и 216 уклоњено
  1. 14 90
      Terminal.Gui/Views/TextField.cs
  2. 254 126
      Terminal.Gui/Views/TextView.cs

+ 14 - 90
Terminal.Gui/Views/TextField.cs

@@ -115,8 +115,6 @@ namespace Terminal.Gui {
 			get => base.Frame;
 			set {
 				base.Frame = value;
-				var w = base.Frame.Width;
-				first = point > w ? point - w : 0;
 				Adjust ();
 			}
 		}
@@ -157,7 +155,7 @@ namespace Terminal.Gui {
 				TextChanged?.Invoke (oldText);
 
 				if (point > text.Count) {
-					point = Math.Max (DisplaySize (text, 0).size - 1, 0);
+					point = Math.Max (TextModel.DisplaySize (text, 0).size - 1, 0);
 				}
 
 				Adjust ();
@@ -195,7 +193,7 @@ namespace Terminal.Gui {
 				if (idx == point)
 					break;
 				var cols = Rune.ColumnWidth (text [idx]);
-				col = SetCol (col, Frame.Width - 1, cols);
+				col = TextModel.SetCol (col, Frame.Width - 1, cols);
 			}
 			Move (col, 0);
 		}
@@ -227,7 +225,7 @@ namespace Terminal.Gui {
 				if (col + cols <= width) {
 					Driver.AddRune ((Rune)(Secret ? '*' : rune));
 				}
-				col = SetCol (col, width, cols);
+				col = TextModel.SetCol (col, width, cols);
 				if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
 					break;
 				}
@@ -241,45 +239,15 @@ namespace Terminal.Gui {
 			PositionCursor ();
 		}
 
-		static int SetCol (int col, int width, int cols)
-		{
-			if (col + cols <= width) {
-				col += cols;
-			}
-
-			return col;
-		}
-
-		// Returns the size and length in a range of the string.
-		(int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
-		{
-			if (t == null || t.Count == 0) {
-				return (0, 0);
-			}
-			int size = 0;
-			int len = 0;
-			int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
-			int i = start == -1 ? 0 : start;
-			for (; i < tcount; i++) {
-				var rune = t [i];
-				size += Rune.ColumnWidth (rune);
-				len += Rune.RuneLen (rune);
-				if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
-					size += Rune.ColumnWidth (t [i + 1]);
-					len += Rune.RuneLen (t [i + 1]);
-				}
-			}
-			return (size, len);
-		}
-
 		void Adjust ()
 		{
 			int offB = OffSetBackground ();
 			if (point < first) {
 				first = point;
 			} else if (first + point - (Frame.Width + offB) == 0 ||
-				  DisplaySize (text, first, point).size >= Frame.Width + offB) {
-				first = Math.Max (CalculateFirst (text, first, point, Frame.Width - 1 + offB), 0);
+				  TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) {
+				first = Math.Max (TextModel.CalculateLeftColumn (text, first,
+					point, Frame.Width - 1 + offB, point), 0);
 			}
 			SetNeedsDisplay ();
 		}
@@ -294,33 +262,6 @@ namespace Terminal.Gui {
 			return offB;
 		}
 
-		int CalculateFirst (List<Rune> t, int start, int end, int width)
-		{
-			if (t == null) {
-				return 0;
-			}
-			(var dSize, _) = DisplaySize (t, start, end);
-			if (dSize < width) {
-				return start;
-			}
-			int size = 0;
-			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
-			int col = 0;
-			for (int i = tcount; i > start; i--) {
-				var rune = t [i];
-				var s = Rune.ColumnWidth (rune);
-				size += s;
-				if (size >= dSize - width) {
-					col = tcount - i + start;
-					if (start == 0 || col == start || (point == t.Count && (point - col > width))) {
-						col++;
-					}
-					break;
-				}
-			}
-			return col;
-		}
-
 		void SetText (List<Rune> newText)
 		{
 			Text = ustring.Make (newText);
@@ -779,7 +720,7 @@ namespace Terminal.Gui {
 		{
 			// We could also set the cursor position.
 			int x;
-			var pX = GetPointFromX (text, first, ev.X);
+			var pX = TextModel.GetColFromX (text, first, ev.X);
 			if (text.Count == 0) {
 				x = pX - ev.OfX;
 			} else {
@@ -792,7 +733,7 @@ namespace Terminal.Gui {
 		{
 			int pX = x;
 			if (getX) {
-				pX = GetPointFromX (text, first, x);
+				pX = TextModel.GetColFromX (text, first, x);
 			}
 			if (first + pX > text.Count) {
 				point = text.Count;
@@ -805,23 +746,6 @@ namespace Terminal.Gui {
 			return point;
 		}
 
-		int GetPointFromX (List<Rune> t, int start, int x)
-		{
-			if (x < 0) {
-				return x;
-			}
-			int size = start;
-			var pX = x + start;
-			for (int i = start; i < t.Count; i++) {
-				var r = t [i];
-				size += Rune.ColumnWidth (r);
-				if (i == pX || (size > pX)) {
-					return i - start;
-				}
-			}
-			return t.Count - start;
-		}
-
 		void PrepareSelection (int x, int direction = 0)
 		{
 			x = x + first < 0 ? 0 : x;
@@ -897,9 +821,9 @@ namespace Terminal.Gui {
 			ustring actualText = Text;
 			int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
 			int selLength = Math.Abs (SelectedLength);
-			(var _, var len) = DisplaySize (text, 0, selStart, false);
-			(var _, var len2) = DisplaySize (text, selStart, selStart + selLength, false);
-			(var _, var len3) = DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
+			(var _, var len) = TextModel.DisplaySize (text, 0, selStart, false);
+			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + selLength, false);
+			(var _, var len3) = TextModel.DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
 			Text = actualText[0, len] +
 				actualText[len + len2, len + len2 + len3];
 			ClearAllSelection ();
@@ -919,9 +843,9 @@ namespace Terminal.Gui {
 			SetSelectedStartSelectedLength ();
 			int selStart = start == -1 ? CursorPosition : start;
 			ustring actualText = Text;
-			(int _, int len) = DisplaySize (text, 0, selStart, false);
-			(var _, var len2) = DisplaySize (text, selStart, selStart + length, false);
-			(var _, var len3) = DisplaySize (text, selStart + length, actualText.RuneCount, false);
+			(int _, int len) = TextModel.DisplaySize (text, 0, selStart, false);
+			(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + length, false);
+			(var _, var len3) = TextModel.DisplaySize (text, selStart + length, actualText.RuneCount, false);
 			ustring cbTxt = Clipboard.Contents ?? "";
 			Text = actualText [0, len] +
 				cbTxt +

+ 254 - 126
Terminal.Gui/Views/TextView.cs

@@ -171,6 +171,101 @@ namespace Terminal.Gui {
 		{
 			lines.RemoveAt (pos);
 		}
+
+		/// <summary>
+		/// Returns the maximum line length of the visible lines.
+		/// </summary>
+		/// <param name="first">The first line.</param>
+		/// <param name="last">The last line.</param>
+		public int GetMaxVisibleLine (int first, int last)
+		{
+			int maxLength = 0;
+			last = last < lines.Count ? last : lines.Count;
+			for (int i = first; i < last; i++) {
+				var l = GetLine (i).Count;
+				if (l > maxLength) {
+					maxLength = l;
+				}
+			}
+
+			return maxLength;
+		}
+
+		internal static int SetCol (int col, int width, int cols)
+		{
+			if (col + cols <= width) {
+				col += cols;
+			}
+
+			return col;
+		}
+
+		internal static int GetColFromX (List<Rune> t, int start, int x)
+		{
+			if (x < 0) {
+				return x;
+			}
+			int size = start;
+			var pX = x + start;
+			for (int i = start; i < t.Count; i++) {
+				var r = t [i];
+				size += Rune.ColumnWidth (r);
+				if (i == pX || (size > pX)) {
+					return i - start;
+				}
+			}
+			return t.Count - start;
+		}
+
+		// Returns the size and length in a range of the string.
+		internal static (int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
+		{
+			if (t == null || t.Count == 0) {
+				return (0, 0);
+			}
+			int size = 0;
+			int len = 0;
+			int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
+			int i = start == -1 ? 0 : start;
+			for (; i < tcount; i++) {
+				var rune = t [i];
+				size += Rune.ColumnWidth (rune);
+				len += Rune.RuneLen (rune);
+				if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
+					size += Rune.ColumnWidth (t [i + 1]);
+					len += Rune.RuneLen (t [i + 1]);
+				}
+			}
+			return (size, len);
+		}
+
+		// Returns the left column in a range of the string.
+		internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int currentColumn)
+		{
+			if (t == null) {
+				return 0;
+			}
+			(var dSize, _) = TextModel.DisplaySize (t, start, end);
+			if (dSize < width) {
+				return start;
+			}
+			int size = 0;
+			int tcount = end > t.Count - 1 ? t.Count - 1 : end;
+			int col = 0;
+			for (int i = tcount; i > start; i--) {
+				var rune = t [i];
+				var s = Rune.ColumnWidth (rune);
+				size += s;
+				if (size >= dSize - width) {
+					col = tcount - i + start;
+					if (start == 0 || col == start || (currentColumn == t.Count && (currentColumn - col > width))) {
+						col++;
+					}
+					break;
+				}
+			}
+			return col;
+		}
 	}
 
 	/// <summary>
@@ -356,6 +451,15 @@ namespace Terminal.Gui {
 			}
 		}
 
+		///<inheritdoc/>
+		public override Rect Frame {
+			get => base.Frame;
+			set {
+				base.Frame = value;
+				Adjust ();
+			}
+		}
+
 		/// <summary>
 		/// Loads the contents of the file into the  <see cref="TextView"/>.
 		/// </summary>
@@ -421,11 +525,22 @@ namespace Terminal.Gui {
 			}
 			var line = model.GetLine (currentRow);
 			var retreat = 0;
+			var col = 0;
 			if (line.Count > 0) {
 				retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
 				? 1 : 0), 0);
+				for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
+					if (idx == CurrentColumn)
+						break;
+					var cols = Rune.ColumnWidth (line [idx]);
+					col += cols - 1;
+				}
+			}
+			var ccol = CurrentColumn - leftColumn - retreat + col;
+			if (leftColumn <= CurrentColumn && ccol < Frame.Width
+				&& topRow <= CurrentRow && CurrentRow - topRow < Frame.Height) {
+				Move (ccol, CurrentRow - topRow);
 			}
-			Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow);
 		}
 
 		void ClearRegion (int left, int top, int right, int bottom)
@@ -569,10 +684,12 @@ namespace Terminal.Gui {
 				}
 
 				Move (bounds.Left, row);
-				for (int col = bounds.Left; col < right; col++) {
-					var lineCol = leftColumn + col;
+				var col = 0;
+				for (int idx = bounds.Left; idx < right; idx++) {
+					var lineCol = leftColumn + idx;
 					var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
-					if (selecting && PointInSelection (col, row)) {
+					var cols = Rune.ColumnWidth (rune);
+					if (selecting && PointInSelection (idx, row)) {
 						ColorSelection ();
 					} else {
 						ColorNormal ();
@@ -581,6 +698,7 @@ namespace Terminal.Gui {
 					if (!SpecialRune (rune)) {
 						AddRune (col, row, rune);
 					}
+					col = TextModel.SetCol (col, bounds.Right, cols);
 				}
 			}
 			PositionCursor ();
@@ -641,19 +759,25 @@ namespace Terminal.Gui {
 
 		void InsertText (ustring text)
 		{
+			if (ustring.IsNullOrEmpty (text)) {
+				return;
+			}
+
 			var lines = TextModel.StringToRunes (text);
 
-			if (lines.Count == 0)
+			if (lines.Count == 0) {
 				return;
+			}
 
 			var line = GetCurrentLine ();
 
-			// Optmize single line
+			// Optimize single line
 			if (lines.Count == 1) {
 				line.InsertRange (currentColumn, lines [0]);
 				currentColumn += lines [0].Count;
-				if (currentColumn - leftColumn > Frame.Width)
+				if (currentColumn - leftColumn > Frame.Width) {
 					leftColumn = currentColumn - Frame.Width + 1;
+				}
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
 				return;
 			}
@@ -666,26 +790,18 @@ namespace Terminal.Gui {
 			// First line is inserted at the current location, the rest is appended
 			line.InsertRange (currentColumn, lines [0]);
 
-			for (int i = 1; i < lines.Count; i++)
+			for (int i = 1; i < lines.Count; i++) {
 				model.AddLine (currentRow + i, lines [i]);
+			}
 
 			var last = model.GetLine (currentRow + lines.Count - 1);
 			var lastp = last.Count;
 			last.InsertRange (last.Count, rest);
 
-			// Now adjjust column and row positions
+			// Now adjust column and row positions
 			currentRow += lines.Count - 1;
 			currentColumn = lastp;
-			if (currentRow - topRow > Frame.Height) {
-				topRow = currentRow - Frame.Height + 1;
-				if (topRow < 0)
-					topRow = 0;
-			}
-			if (currentColumn < leftColumn)
-				leftColumn = currentColumn;
-			if (currentColumn - leftColumn >= Frame.Width)
-				leftColumn = currentColumn - Frame.Width + 1;
-			SetNeedsDisplay ();
+			Adjust ();
 		}
 
 		// The column we are tracking, or -1 if we are not tracking any column
@@ -707,38 +823,63 @@ namespace Terminal.Gui {
 
 		void Adjust ()
 		{
+			var offB = OffSetBackground ();
+			var line = GetCurrentLine ();
 			bool need = false;
 			if (currentColumn < leftColumn) {
-				currentColumn = leftColumn;
+				leftColumn = currentColumn;
 				need = true;
-			}
-			if (currentColumn - leftColumn > Frame.Width) {
-				leftColumn = currentColumn - Frame.Width + 1;
+			} else if (currentColumn - leftColumn > Frame.Width + offB.width ||
+				TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB.width) {
+				leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn,
+					currentColumn, Frame.Width - 1 + offB.width, currentColumn), 0);
 				need = true;
 			}
 			if (currentRow < topRow) {
 				topRow = currentRow;
 				need = true;
-			}
-			if (currentRow - topRow > Frame.Height) {
-				topRow = currentRow - Frame.Height + 1;
+			} else if (currentRow - topRow >= Frame.Height + offB.height) {
+				topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1, 0), currentRow);
 				need = true;
 			}
-			if (need)
+			if (need) {
 				SetNeedsDisplay ();
-			else
+			} else {
 				PositionCursor ();
+			}
+		}
+
+		(int width, int height) OffSetBackground ()
+		{
+			int w = 0;
+			int h = 0;
+			if (SuperView?.Frame.Right - Frame.Right < 0) {
+				w = SuperView.Frame.Right - Frame.Right - 1;
+			}
+			if (SuperView?.Frame.Bottom - Frame.Bottom < 0) {
+				h = SuperView.Frame.Bottom - Frame.Bottom - 1;
+			}
+			return (w, h);
 		}
 
 		/// <summary>
-		/// Will scroll the <see cref="TextView"/> to display the specified row at the top
+		/// Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is true or
+		/// will scroll the <see cref="TextView"/> to display the specified column at the left if <paramref name="isRow"/> is false.
 		/// </summary>
-		/// <param name="row">Row that should be displayed at the top, if the value is negative it will be reset to zero</param>
-		public void ScrollTo (int row)
+		/// <param name="idx">Row that should be displayed at the top or Column that should be displayed at the left,
+		///  if the value is negative it will be reset to zero</param>
+		/// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
+		public void ScrollTo (int idx, bool isRow = true)
 		{
-			if (row < 0)
-				row = 0;
-			topRow = row > model.Count ? model.Count - 1 : row;
+			if (idx < 0) {
+				idx = 0;
+			}
+			if (isRow) {
+				topRow = idx > model.Count - 1 ? model.Count - 1 : idx;
+			} else {
+				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
+				leftColumn = idx > maxlength - 1 ? maxlength - 1 : idx;
+			}
 			SetNeedsDisplay ();
 		}
 
@@ -786,7 +927,7 @@ namespace Terminal.Gui {
 				break;
 
 			case Key.PageUp:
-			case ((int)'v' + Key.AltMask):
+			case ((int)'V' + Key.AltMask):
 				int nPageUpShift = Frame.Height - 1;
 				if (currentRow > 0) {
 					if (columnTrack == -1)
@@ -816,53 +957,33 @@ namespace Terminal.Gui {
 				var currentLine = GetCurrentLine ();
 				if (currentColumn < currentLine.Count) {
 					currentColumn++;
-					if (currentColumn >= leftColumn + Frame.Width) {
-						leftColumn++;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 					if (currentRow + 1 < model.Count) {
 						currentRow++;
 						currentColumn = 0;
-						leftColumn = 0;
 						if (currentRow >= topRow + Frame.Height) {
 							topRow++;
 						}
-						SetNeedsDisplay ();
-						PositionCursor ();
 					}
-					break;
 				}
+				Adjust ();
 				break;
 
 			case Key.B | Key.CtrlMask:
 			case Key.CursorLeft:
 				if (currentColumn > 0) {
 					currentColumn--;
-					if (currentColumn < leftColumn) {
-						leftColumn--;
-						SetNeedsDisplay ();
-					}
-					PositionCursor ();
 				} else {
 					if (currentRow > 0) {
 						currentRow--;
 						if (currentRow < topRow) {
 							topRow--;
-							SetNeedsDisplay ();
 						}
 						currentLine = GetCurrentLine ();
 						currentColumn = currentLine.Count;
-						int prev = leftColumn;
-						leftColumn = currentColumn - Frame.Width + 1;
-						if (leftColumn < 0)
-							leftColumn = 0;
-						if (prev != leftColumn)
-							SetNeedsDisplay ();
-						PositionCursor ();
 					}
 				}
+				Adjust ();
 				break;
 
 			case Key.Delete:
@@ -890,10 +1011,7 @@ namespace Terminal.Gui {
 					model.RemoveLine (currentRow);
 					currentRow--;
 					currentColumn = prevCount;
-					leftColumn = currentColumn - Frame.Width + 1;
-					if (leftColumn < 0)
-						leftColumn = 0;
-					SetNeedsDisplay ();
+					Adjust ();
 				}
 				break;
 
@@ -901,11 +1019,7 @@ namespace Terminal.Gui {
 			case Key.Home:
 			case Key.A | Key.CtrlMask:
 				currentColumn = 0;
-				if (currentColumn < leftColumn) {
-					leftColumn = 0;
-					SetNeedsDisplay ();
-				} else
-					PositionCursor ();
+				Adjust ();
 				break;
 			case Key.DeleteChar:
 			case Key.D | Key.CtrlMask: // Delete
@@ -932,12 +1046,7 @@ namespace Terminal.Gui {
 				currentLine = GetCurrentLine ();
 				currentColumn = currentLine.Count;
 				int pcol = leftColumn;
-				leftColumn = currentColumn - Frame.Width + 1;
-				if (leftColumn < 0)
-					leftColumn = 0;
-				if (pcol != leftColumn)
-					SetNeedsDisplay ();
-				PositionCursor ();
+				Adjust ();
 				break;
 
 			case Key.K | Key.CtrlMask: // kill-to-end
@@ -978,11 +1087,7 @@ namespace Terminal.Gui {
 				selectionStartRow = currentRow;
 				break;
 
-			case ((int)'w' + Key.AltMask):
-				SetClipboard (GetRegion ());
-				selecting = false;
-				break;
-
+			case ((int)'W' + Key.AltMask):
 			case Key.W | Key.CtrlMask:
 				SetClipboard (GetRegion ());
 				if (!isReadOnly)
@@ -990,7 +1095,8 @@ namespace Terminal.Gui {
 				selecting = false;
 				break;
 
-			case (Key)((int)'b' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorLeft:
+			case (Key)((int)'B' + Key.AltMask):
 				var newPos = WordBackward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
@@ -1000,7 +1106,8 @@ namespace Terminal.Gui {
 
 				break;
 
-			case (Key)((int)'f' + Key.AltMask):
+			case Key.CtrlMask | Key.CursorRight:
+			case (Key)((int)'F' + Key.AltMask):
 				newPos = WordForward (currentColumn, currentRow);
 				if (newPos.HasValue) {
 					currentColumn = newPos.Value.col;
@@ -1012,7 +1119,6 @@ namespace Terminal.Gui {
 			case Key.Enter:
 				if (isReadOnly)
 					break;
-				var orow = currentRow;
 				currentLine = GetCurrentLine ();
 				restCount = currentLine.Count - currentColumn;
 				rest = currentLine.GetRange (currentColumn, restCount);
@@ -1063,11 +1169,12 @@ namespace Terminal.Gui {
 			return true;
 		}
 
-		private void MoveUp ()
+		void MoveUp ()
 		{
 			if (currentRow > 0) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
+				}
 				currentRow--;
 				if (currentRow < topRow) {
 					topRow--;
@@ -1078,11 +1185,12 @@ namespace Terminal.Gui {
 			}
 		}
 
-		private void MoveDown ()
+		void MoveDown ()
 		{
 			if (currentRow + 1 < model.Count) {
-				if (columnTrack == -1)
+				if (columnTrack == -1) {
 					columnTrack = currentColumn;
+				}
 				currentRow++;
 				if (currentRow >= topRow + Frame.Height) {
 					topRow++;
@@ -1090,6 +1198,8 @@ namespace Terminal.Gui {
 				}
 				TrackColumn ();
 				PositionCursor ();
+			} else if (currentRow > Frame.Height) {
+				Adjust ();
 			}
 		}
 
@@ -1186,28 +1296,31 @@ namespace Terminal.Gui {
 		{
 			var col = fromCol;
 			var row = fromRow;
-			var line = GetCurrentLine ();
-			var rune = RuneAt (col, row);
+			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;
-				}
-				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;
+				var srow = row;
+				if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
+					while (MoveNext (ref col, ref row, out rune)) {
+						if (Rune.IsLetterOrDigit (rune))
+							break;
+					}
+					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;
+					}
 				}
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
 			}
-			if (fromCol != col || fromRow != row)
-				return (col, row);
-			return null;
 		}
 
 		(int col, int row)? WordBackward (int fromCol, int fromRow)
@@ -1217,27 +1330,30 @@ namespace Terminal.Gui {
 
 			var col = fromCol;
 			var row = fromRow;
-			var line = GetCurrentLine ();
-			var rune = RuneAt (col, row);
+			try {
+				var rune = RuneAt (col, row);
 
-			if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (Rune.IsLetterOrDigit (rune))
-						break;
-				}
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
-				}
-			} else {
-				while (MovePrev (ref col, ref row, out rune)) {
-					if (!Rune.IsLetterOrDigit (rune))
-						break;
+				if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (Rune.IsLetterOrDigit (rune))
+							break;
+					}
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
+				} else {
+					while (MovePrev (ref col, ref row, out rune)) {
+						if (!Rune.IsLetterOrDigit (rune))
+							break;
+					}
 				}
+				if (fromCol != col || fromRow != row)
+					return (col, row);
+				return null;
+			} catch (Exception) {
+				return null;
 			}
-			if (fromCol != col || fromRow != row)
-				return (col, row);
-			return null;
 		}
 
 		///<inheritdoc/>
@@ -1265,18 +1381,30 @@ namespace Terminal.Gui {
 						currentRow = ev.Y + topRow;
 					}
 					var r = GetCurrentLine ();
-					if (ev.X - leftColumn >= r.Count)
+					var idx = TextModel.GetColFromX (r, leftColumn, ev.X);
+					if (idx - leftColumn >= r.Count) {
 						currentColumn = r.Count - leftColumn;
-					else
-						currentColumn = ev.X - leftColumn;
+					} else {
+						currentColumn = idx + leftColumn;
+					}
 				}
 				PositionCursor ();
 			} else if (ev.Flags == MouseFlags.WheeledDown) {
 				lastWasKill = false;
-				MoveDown ();
+				columnTrack = currentColumn;
+				ScrollTo (topRow + 1);
 			} else if (ev.Flags == MouseFlags.WheeledUp) {
 				lastWasKill = false;
-				MoveUp ();
+				columnTrack = currentColumn;
+				ScrollTo (topRow - 1);
+			} else if (ev.Flags == MouseFlags.WheeledRight) {
+				lastWasKill = false;
+				columnTrack = currentColumn;
+				ScrollTo (leftColumn + 1, false);
+			} else if (ev.Flags == MouseFlags.WheeledLeft) {
+				lastWasKill = false;
+				columnTrack = currentColumn;
+				ScrollTo (leftColumn - 1, false);
 			}
 
 			return true;