Browse Source

Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop

Tig Kindel 3 years ago
parent
commit
375cd9f298

+ 14 - 2
Terminal.Gui/Core/Application.cs

@@ -279,9 +279,18 @@ namespace Terminal.Gui {
 
 			public override void Send (SendOrPostCallback d, object state)
 			{
-				mainLoop.Invoke (() => {
+				if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
 					d (state);
-				});
+				} else {
+					var wasExecuted = false;
+					mainLoop.Invoke (() => {
+						d (state);
+						wasExecuted = true;
+					});
+					while (!wasExecuted) {
+						Thread.Sleep (15);
+					}
+				}
 			}
 		}
 
@@ -307,6 +316,7 @@ namespace Terminal.Gui {
 		public static void Init (ConsoleDriver driver = null, IMainLoopDriver mainLoopDriver = null) => Init (() => Toplevel.Create (), driver, mainLoopDriver);
 
 		internal static bool _initialized = false;
+		internal static int _mainThreadId = -1;
 
 		/// <summary>
 		/// Initializes the Terminal.Gui application
@@ -360,6 +370,7 @@ namespace Terminal.Gui {
 			Top = topLevelFactory ();
 			Current = Top;
 			supportedCultures = GetSupportedCultures ();
+			_mainThreadId = Thread.CurrentThread.ManagedThreadId;
 			_initialized = true;
 		}
 
@@ -893,6 +904,7 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			RootKeyEvent = null;
 			Resized = null;
+			_mainThreadId = -1;
 			NotifyNewRunState = null;
 			NotifyStopRunState = null;
 			_initialized = false;

+ 65 - 22
Terminal.Gui/Views/TextView.cs

@@ -1176,6 +1176,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action TextChanged;
 
+		/// <summary>
+		/// Invoked with the unwrapped <see cref="CursorPosition"/>.
+		/// </summary>
+		public event Action<Point> UnwrappedCursorPosition;
+
 		/// <summary>
 		/// Provides autocomplete context menu based on suggestions at the current cursor
 		/// position.  Populate <see cref="Autocomplete.AllSuggestions"/> to enable this feature
@@ -1468,8 +1473,7 @@ namespace Terminal.Gui {
 		void ResetPosition ()
 		{
 			topRow = leftColumn = currentRow = currentColumn = 0;
-			selecting = false;
-			shiftSelecting = false;
+			StopSelecting ();
 			ResetCursorVisibility ();
 		}
 
@@ -1609,12 +1613,7 @@ namespace Terminal.Gui {
 					return ustring.Empty;
 				}
 
-				SetWrapModel ();
-				var sel = GetRegion ();
-				UpdateWrapModel ();
-				Adjust ();
-
-				return sel;
+				return GetSelectedRegion ();
 			}
 		}
 
@@ -2023,10 +2022,18 @@ namespace Terminal.Gui {
 		}
 
 		// Returns an encoded region start..end (top 32 bits are the row, low32 the column)
-		void GetEncodedRegionBounds (out long start, out long end)
-		{
-			long selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
-			long point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
+		void GetEncodedRegionBounds (out long start, out long end,
+			int? startRow = null, int? startCol = null, int? cRow = null, int? cCol = null)
+		{
+			long selection;
+			long point;
+			if (startRow == null || startCol == null || cRow == null || cCol == null) {
+				selection = ((long)(uint)selectionStartRow << 32) | (uint)selectionStartColumn;
+				point = ((long)(uint)currentRow << 32) | (uint)currentColumn;
+			} else {
+				selection = ((long)(uint)startRow << 32) | (uint)startCol;
+				point = ((long)(uint)cRow << 32) | (uint)cCol;
+			}
 			if (selection > point) {
 				start = point;
 				end = selection;
@@ -2048,10 +2055,10 @@ namespace Terminal.Gui {
 		// Returns a ustring with the text in the selected 
 		// region.
 		//
-		ustring GetRegion ()
+		ustring GetRegion (int? sRow = null, int? sCol = null, int? cRow = null, int? cCol = null, TextModel model = null)
 		{
 			long start, end;
-			GetEncodedRegionBounds (out start, out end);
+			GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol);
 			if (start == end) {
 				return ustring.Empty;
 			}
@@ -2059,7 +2066,7 @@ namespace Terminal.Gui {
 			var maxrow = ((int)(end >> 32));
 			int startCol = (int)(start & 0xffffffff);
 			var endCol = (int)(end & 0xffffffff);
-			var line = model.GetLine (startRow);
+			var line = model == null ? this.model.GetLine (startRow) : model.GetLine (startRow);
 
 			if (startRow == maxrow)
 				return StringFromRunes (line.GetRange (startCol, endCol - startCol));
@@ -2067,9 +2074,10 @@ namespace Terminal.Gui {
 			ustring res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
 
 			for (int row = startRow + 1; row < maxrow; row++) {
-				res = res + ustring.Make (Environment.NewLine) + StringFromRunes (model.GetLine (row));
+				res = res + ustring.Make (Environment.NewLine) + StringFromRunes (model == null
+					? this.model.GetLine (row) : model.GetLine (row));
 			}
-			line = model.GetLine (maxrow);
+			line = model == null ? this.model.GetLine (maxrow) : model.GetLine (maxrow);
 			res = res + ustring.Make (Environment.NewLine) + StringFromRunes (line.GetRange (0, endCol));
 			return res;
 		}
@@ -2321,6 +2329,37 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
 		}
 
+		/// <summary>
+		/// Invoke the <see cref="UnwrappedCursorPosition"/> event with the unwrapped <see cref="CursorPosition"/>.
+		/// </summary>
+		public virtual void OnUnwrappedCursorPosition ()
+		{
+			var row = currentRow;
+			var col = currentColumn;
+			if (wordWrap) {
+				row = wrapManager.GetModelLineFromWrappedLines (currentRow);
+				col = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn);
+			}
+			UnwrappedCursorPosition?.Invoke (new Point (col, row));
+		}
+
+		ustring GetSelectedRegion ()
+		{
+			var cRow = currentRow;
+			var cCol = currentColumn;
+			var startRow = selectionStartRow;
+			var startCol = selectionStartColumn;
+			var model = this.model;
+			if (wordWrap) {
+				cRow = wrapManager.GetModelLineFromWrappedLines (currentRow);
+				cCol = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn);
+				startRow = wrapManager.GetModelLineFromWrappedLines (selectionStartRow);
+				startCol = wrapManager.GetModelColFromWrappedLines (selectionStartRow, selectionStartColumn);
+				model = wrapManager.Model;
+			}
+			return GetRegion (startRow, startCol, cRow, cCol, model);
+		}
+
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
@@ -2618,6 +2657,8 @@ namespace Terminal.Gui {
 			} else {
 				PositionCursor ();
 			}
+
+			OnUnwrappedCursorPosition ();
 		}
 
 		(int width, int height) OffSetBackground ()
@@ -3872,6 +3913,7 @@ namespace Terminal.Gui {
 		{
 			shiftSelecting = false;
 			selecting = false;
+			isButtonShift = false;
 		}
 
 		void ClearSelectedRegion ()
@@ -4151,6 +4193,8 @@ namespace Terminal.Gui {
 			}
 		}
 
+		bool isButtonShift;
+
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent ev)
 		{
@@ -4182,9 +4226,8 @@ namespace Terminal.Gui {
 			}
 
 			if (ev.Flags == MouseFlags.Button1Clicked) {
-				if (shiftSelecting) {
-					shiftSelecting = false;
-					selecting = false;
+				if (shiftSelecting && !isButtonShift) {
+					StopSelecting ();
 				}
 				ProcessMouseClick (ev, out _);
 				PositionCursor ();
@@ -4235,6 +4278,7 @@ namespace Terminal.Gui {
 				columnTrack = currentColumn;
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ButtonShift)) {
 				if (!shiftSelecting) {
+					isButtonShift = true;
 					StartSelecting ();
 				}
 				ProcessMouseClick (ev, out _);
@@ -4243,8 +4287,7 @@ namespace Terminal.Gui {
 				columnTrack = currentColumn;
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
 				if (shiftSelecting) {
-					shiftSelecting = false;
-					selecting = false;
+					StopSelecting ();
 				}
 				ProcessMouseClick (ev, out _);
 				PositionCursor ();

+ 34 - 27
UICatalog/Scenarios/Editor.cs

@@ -26,11 +26,11 @@ namespace UICatalog.Scenarios {
 		private string _textToReplace;
 		private bool _matchCase;
 		private bool _matchWholeWord;
-		private Window winDialog;
+		private Window _winDialog;
 		private TabView _tabView;
-		private MenuItem miForceMinimumPosToZero;
-		private bool forceMinimumPosToZero = true;
-		private readonly List<CultureInfo> cultureInfos = Application.SupportedCultures;
+		private MenuItem _miForceMinimumPosToZero;
+		private bool _forceMinimumPosToZero = true;
+		private readonly List<CultureInfo> _cultureInfos = Application.SupportedCultures;
 
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
@@ -60,6 +60,12 @@ namespace UICatalog.Scenarios {
 
 			CreateDemoFile (_fileName);
 
+			var siCursorPosition = new StatusItem (Key.Null, "", null);
+
+			_textView.UnwrappedCursorPosition += (e) => {
+				siCursorPosition.Title = $"Ln {e.Y + 1}, Col {e.X + 1}";
+			};
+
 			LoadFile ();
 
 			Win.Add (_textView);
@@ -103,16 +109,17 @@ namespace UICatalog.Scenarios {
 					CreateVisibleChecked ()
 				}),
 				new MenuBarItem ("Conte_xtMenu", new MenuItem [] {
-					miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => {
-						miForceMinimumPosToZero.Checked = forceMinimumPosToZero = !forceMinimumPosToZero;
-						_textView.ContextMenu.ForceMinimumPosToZero = forceMinimumPosToZero;
-					}) { CheckType = MenuItemCheckStyle.Checked, Checked = forceMinimumPosToZero },
+					_miForceMinimumPosToZero = new MenuItem ("ForceMinimumPosTo_Zero", "", () => {
+						_miForceMinimumPosToZero.Checked = _forceMinimumPosToZero = !_forceMinimumPosToZero;
+						_textView.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
+					}) { CheckType = MenuItemCheckStyle.Checked, Checked = _forceMinimumPosToZero },
 					new MenuBarItem ("_Languages", GetSupportedCultures ())
 				})
 			});
 			Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
+				siCursorPosition,
 				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
 				new StatusItem(Key.F3, "~F3~ Save", () => Save()),
 				new StatusItem(Key.F4, "~F4~ Save As", () => SaveAs()),
@@ -168,20 +175,20 @@ namespace UICatalog.Scenarios {
 
 			Win.KeyPress += (e) => {
 				var keys = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-				if (winDialog != null && (e.KeyEvent.Key == Key.Esc
+				if (_winDialog != null && (e.KeyEvent.Key == Key.Esc
 					|| e.KeyEvent.Key == (Key.Q | Key.CtrlMask))) {
 					DisposeWinDialog ();
 				} else if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
 					Quit ();
 					e.Handled = true;
-				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask)) {
+				} else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (0);
 					} else {
 						_tabView.SwitchTabBy (1);
 					}
 					e.Handled = true;
-				} else if (winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
+				} else if (_winDialog != null && keys == (Key.Tab | Key.CtrlMask | Key.ShiftMask)) {
 					if (_tabView.SelectedTab == _tabView.Tabs.ElementAt (0)) {
 						_tabView.SelectedTab = _tabView.Tabs.ElementAt (_tabView.Tabs.Count - 1);
 					} else {
@@ -196,9 +203,9 @@ namespace UICatalog.Scenarios {
 
 		private void DisposeWinDialog ()
 		{
-			winDialog.Dispose ();
-			Win.Remove (winDialog);
-			winDialog = null;
+			_winDialog.Dispose ();
+			Win.Remove (_winDialog);
+			_winDialog = null;
 		}
 
 		public override void Setup ()
@@ -276,7 +283,7 @@ namespace UICatalog.Scenarios {
 				Find ();
 				return;
 			} else if (replace && (string.IsNullOrEmpty (_textToFind)
-				|| (winDialog == null && string.IsNullOrEmpty (_textToReplace)))) {
+				|| (_winDialog == null && string.IsNullOrEmpty (_textToReplace)))) {
 				Replace ();
 				return;
 			}
@@ -323,7 +330,7 @@ namespace UICatalog.Scenarios {
 
 		private void ReplaceAll ()
 		{
-			if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && winDialog == null)) {
+			if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _winDialog == null)) {
 				Replace ();
 				return;
 			}
@@ -468,7 +475,7 @@ namespace UICatalog.Scenarios {
 			List<MenuItem> supportedCultures = new List<MenuItem> ();
 			var index = -1;
 
-			foreach (var c in cultureInfos) {
+			foreach (var c in _cultureInfos) {
 				var culture = new MenuItem {
 					CheckType = MenuItemCheckStyle.Checked
 				};
@@ -714,17 +721,17 @@ namespace UICatalog.Scenarios {
 
 		private void CreateFindReplace (bool isFind = true)
 		{
-			if (winDialog != null) {
-				winDialog.SetFocus ();
+			if (_winDialog != null) {
+				_winDialog.SetFocus ();
 				return;
 			}
 
-			winDialog = new Window (isFind ? "Find" : "Replace") {
+			_winDialog = new Window (isFind ? "Find" : "Replace") {
 				X = Win.Bounds.Width / 2 - 30,
 				Y = Win.Bounds.Height / 2 - 10,
 				ColorScheme = Colors.TopLevel
 			};
-			winDialog.Border.Effect3D = true;
+			_winDialog.Border.Effect3D = true;
 
 			_tabView = new TabView () {
 				X = 0,
@@ -737,15 +744,15 @@ namespace UICatalog.Scenarios {
 			var replace = ReplaceTab ();
 			_tabView.AddTab (new TabView.Tab ("Replace", replace), !isFind);
 			_tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusFirst ();
-			winDialog.Add (_tabView);
+			_winDialog.Add (_tabView);
 
-			Win.Add (winDialog);
+			Win.Add (_winDialog);
 
-			winDialog.Width = replace.Width + 4;
-			winDialog.Height = replace.Height + 4;
+			_winDialog.Width = replace.Width + 4;
+			_winDialog.Height = replace.Height + 4;
 
-			winDialog.SuperView.BringSubviewToFront (winDialog);
-			winDialog.SetFocus ();
+			_winDialog.SuperView.BringSubviewToFront (_winDialog);
+			_winDialog.SetFocus ();
 		}
 
 		private void SetFindText ()

+ 73 - 0
UnitTests/ApplicationTests.cs

@@ -84,6 +84,7 @@ namespace Terminal.Gui.Core {
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.MainLoop);
+			Assert.NotNull (SynchronizationContext.Current);
 		}
 
 		void Shutdown ()
@@ -1335,5 +1336,77 @@ namespace Terminal.Gui.Core {
 				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
 			}
 		}
+
+		[Fact]
+		public void SynchronizationContext_Post ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// non blocking
+				context.Post (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.False (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_Send ()
+		{
+			Init ();
+			var context = SynchronizationContext.Current;
+
+			var success = false;
+			Task.Run (() => {
+				Thread.Sleep (1_000);
+
+				// blocking
+				context.Send (
+					delegate (object o) {
+						success = true;
+
+						// then tell the application to quit
+						Application.MainLoop.Invoke (() => Application.RequestStop ());
+					}, null);
+				Assert.True (success);
+			});
+
+			// blocks here until the RequestStop is processed at the end of the test
+			Application.Run ();
+			Assert.True (success);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void SynchronizationContext_CreateCopy ()
+		{
+			Init ();
+
+			var context = SynchronizationContext.Current;
+			Assert.NotNull (context);
+
+			var contextCopy = context.CreateCopy ();
+			Assert.NotNull (contextCopy);
+
+			Assert.NotEqual (context, contextCopy);
+
+			Application.Shutdown ();
+		}
 	}
 }

+ 153 - 1
UnitTests/TextViewTests.cs

@@ -1323,20 +1323,26 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 			_textView.SelectionStartColumn = 0;
 			_textView.SelectionStartRow = 0;
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
 			Assert.True (_textView.Selecting);
 			_textView.Selecting = false;
 			_textView.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
+			Assert.Equal (new Point (28, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 			_textView.SelectionStartColumn = 24;
 			_textView.SelectionStartRow = 0;
 			_textView.ProcessKey (new KeyEvent (Key.W | Key.CtrlMask, new KeyModifiers ())); // Cut
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			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.ProcessKey (new KeyEvent (Key.Y | Key.CtrlMask, new KeyModifiers ())); // Paste
+			Assert.Equal (new Point (28, 0), _textView.CursorPosition);
+			Assert.False (_textView.Selecting);
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 		}
 
@@ -5873,5 +5879,151 @@ line.
 			tv.CursorPosition = new Point (tv.LeftColumn, tv.TopRow);
 			Assert.Equal (new Point (0, 50), tv.CursorPosition);
 		}
+
+		[Fact]
+		[InitShutdown]
+		public void Mouse_Button_Shift_Preserves_Selection ()
+		{
+			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 12, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartColumn);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (12, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump ", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 12, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (12, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump ", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 19, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (19, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 19, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (19, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Pressed | MouseFlags.ButtonShift }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between text", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Clicked }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("TAB to jump between text", _textView.SelectedText);
+
+			Assert.True (_textView.MouseEvent (new MouseEvent () { X = 24, Y = 0, Flags = MouseFlags.Button1Pressed }));
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (0, _textView.SelectionStartRow);
+			Assert.Equal (new Point (24, 0), _textView.CursorPosition);
+			Assert.True (_textView.Selecting);
+			Assert.Equal ("", _textView.SelectedText);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void UnwrappedCursorPosition_Event ()
+		{
+			var cp = Point.Empty;
+			var tv = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = "This is the first line.\nThis is the second line.\n"
+			};
+			tv.UnwrappedCursorPosition += (e) => {
+				cp = e;
+			};
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.False (tv.WordWrap);
+			Assert.Equal (Point.Empty, tv.CursorPosition);
+			Assert.Equal (Point.Empty, cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			tv.WordWrap = true;
+			tv.CursorPosition = new Point (12, 0);
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (12, 0), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This is the first line. 
+This is the second line.
+", output);
+
+			((FakeDriver)Application.Driver).SetBufferSize (6, 25);
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (4, 2), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (0, 3), tv.CursorPosition);
+			Assert.Equal (new Point (12, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			tv.Redraw (tv.Bounds);
+			Assert.Equal (new Point (1, 3), tv.CursorPosition);
+			Assert.Equal (new Point (13, 0), cp);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+This 
+is   
+the  
+first
+     
+line.
+This 
+is   
+the  
+secon
+d    
+line.
+", output);
+		}
 	}
 }

+ 1 - 1
UnitTests/UnitTests.csproj

@@ -18,7 +18,7 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.0" />
     <PackageReference Include="ReportGenerator" Version="5.1.9" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.2" />