Jelajahi Sumber

merge develop

Charlie Kindel 2 tahun lalu
induk
melakukan
04af7d2106

+ 0 - 3
Example/Example.csproj

@@ -10,9 +10,6 @@
     <Version>1.0</Version>
     <InformationalVersion>1.0</InformationalVersion>
   </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="NStack.Core" Version="1.0.5" />
-  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
   </ItemGroup>

+ 1 - 1
Terminal.Gui/Core/View.cs

@@ -1496,7 +1496,7 @@ namespace Terminal.Gui {
 			if (Border != null) {
 				Border.DrawContent (this);
 			} else if (ustring.IsNullOrEmpty (TextFormatter.Text) &&
-				(GetType ().IsPublic || GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
+				(GetType ().IsNestedPublic) && !IsOverridden (this, "Redraw") &&
 				(!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded)) {
 
 				Clear ();

+ 31 - 30
Terminal.Gui/README.md

@@ -1,24 +1,24 @@
 # Terminal.Gui Project
 
-Contains all files required to build the **Terminal.Gui** library (and NuGet package).
+All files required to build the **Terminal.Gui** library (and NuGet package).
 
 ## Project Folder Structure
 
 - `Terminal.Gui.sln` - The Visual Studio solution
 - `Core/` - Source files for all types that comprise the core building blocks of **Terminal-Gui** 
     - `Application` - A `static` class that provides the base 'application driver'. Given it defines a **Terminal.Gui** application it is both logically and literally (because `static`) a singleton. It has direct dependencies on `MainLoop`, `Events.cs` `NetDriver`, `CursesDriver`, `WindowsDriver`, `Responder`, `View`, and `TopLevel` (and nothing else).
-    - `MainLoop` - Defines `IMainLoopDriver` and implements the and `MainLoop` class.
+    - `MainLoop` - Defines `IMainLoopDriver` and implements the `MainLoop` class.
     - `ConsoleDriver` - Definition for the Console Driver API.
-    - `Events.cs` - Defines keyboard and mouse related structs & classes. 
+    - `Events.cs` - Defines keyboard and mouse-related structs & classes. 
     - `PosDim.cs` - Implements *Computed Layout* system. These classes have deep dependencies on `View`.
     - `Responder` - Base class for the windowing class hierarchy. Implements support for keyboard & mouse input.
     - `View` - Derived from `Responder`, the base class for non-modal visual elements such as controls.
     - `Toplevel` - Derived from `View`, the base class for modal visual elements such as top-level windows and dialogs. Supports the concept of `MenuBar` and `StatusBar`.
-    - `Window` - Derived from `TopLevel`; implements top level views with a visible frame and Title.
+    - `Window` - Derived from `TopLevel`; implements toplevel views with a visible frame and Title.
 - `Types/` - A folder (not namespace) containing implementations of `Point`, `Rect`, and `Size` which are ancient versions of the modern `System.Drawing.Point`, `System.Drawing.Size`, and `System.Drawning.Rectangle`.
 - `ConsoleDrivers/` - Source files for the three `ConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`.
 - `Views/` - A folder (not namespace) containing the source for all built-in classes that drive from `View` (non-modals). 
-- `Windows/` - A folder (not namespace) containing the source all built-in classes that derive from `Window`.
+- `Windows/` - A folder (not namespace) containing the source of all built-in classes that derive from `Window`.
 
 ## Version numbers
 
@@ -55,43 +55,37 @@ The `tag` must be of the form `v<major>.<minor>.<patch>`, e.g. `v2.3.4`.
 
 `patch` can indicate pre-release or not (e.g. `pre`, `beta`, `rc`, etc...). 
 
-### 1) Generate release notes with the list of PRs since the last release 
+### 1) Verify the `develop` branch is ready for release
 
-Use `gh` to get a list with just titles to make it easy to paste into release notes: 
-
-```powershell
-gh pr list --limit 500 --search "is:pr is:closed is:merged closed:>=2021-05-18"
-```
+* Ensure everything is committed and pushed to the `develop` branch
+* Ensure your local `develop` branch is up-to-date with `upstream/develop`
 
-Use the output to update `./Terminal.Gui/Terminal.Gui.csproj` with latest release notes
+### 2) Create a pull request for the release in the `develop` branch
 
-### 2) Update the API documentation
-
-See `./docfx/README.md`.
-
-### 3) Create a PR for the release in the `develop` branch
-
-The PR title should be "Release v2.3.4"
+The PR title should be of the form "Release v2.3.4"
 
 ```powershell
 git checkout develop
-git pull -all
+git pull upstream develop
 git checkout -b v_2_3_4
+git merge develop
 git add .
 git commit -m "Release v2.3.4"
 git push
 ```
 
-### 4) On github.com, verify the build action worked on your fork, then merge the PR
+Go to the link printed by `git push` and fill out the Pull Request.
+
+### 3) On github.com, verify the build action worked on your fork, then merge the PR
 
-### 5) Pull the merged `develop` from `upstream`
+### 4) Pull the merged `develop` from `upstream`
 
 ```powershell
 git checkout develop
 git pull upstream develop
 ```
 
-### 6) Merge `develop` into `main`
+### 5) Merge `develop` into `main`
 
 ```powershell
 git checkout main
@@ -101,13 +95,13 @@ git merge develop
 
 Fix any merge errors.
 
-### 7) Create a new annotated tag for the release
+### 6) Create a new annotated tag for the release on `main`
 
 ```powershell
 git tag v2.3.4 -a -m "Release v2.3.4"
 ```       
 
-### 8) Push the new tag to `main` on `origin`
+### 7) Push the new tag to `main` on `upstream`
 
 ```powershell
 git push --atomic upstream main v2.3.4
@@ -115,16 +109,23 @@ git push --atomic upstream main v2.3.4
 
 *See https://stackoverflow.com/a/3745250/297526*
 
-### 9) Monitor Github actions to ensure the Nuget publishing worked.
+### 8) Monitor Github Actions to ensure the Nuget publishing worked.
 
-### 10) Check Nuget to see the new package version (wait a few minutes): 
+https://github.com/gui-cs/Terminal.Gui/actions
+
+### 9) Check Nuget to see the new package version (wait a few minutes) 
 https://www.nuget.org/packages/Terminal.Gui
 
-### 11) Add a new Release in Github: https://github.com/gui-cs/Terminal.Gui/releases
+### 10) Add a new Release in Github: https://github.com/gui-cs/Terminal.Gui/releases
+
+Generate release notes with the list of PRs since the last release 
 
-### 12) Tweet about it
+Use `gh` to get a list with just titles to make it easy to paste into release notes: 
 
-### 13) Update the `develop` branch
+```powershell
+gh pr list --limit 500 --search "is:pr is:closed is:merged closed:>=2021-05-18"
+```
+### 11) Update the `develop` branch with the new version
 
 ```powershell
 git checkout develop

+ 3 - 84
Terminal.Gui/Terminal.Gui.csproj

@@ -74,93 +74,12 @@
     <PackageIcon>logo.png</PackageIcon>
     <PackageReadmeFile>README.md</PackageReadmeFile>
     <PackageTags>csharp, terminal, c#, f#, gui, toolkit, console, tui</PackageTags>
-    <Description>Cross Platform Terminal UI toolkit for .NET</Description>
+    <Description>Cross platform Terminal UI toolkit for .NET</Description>
     <Owners>Miguel de Icaza, Charlie Kindel</Owners>
     <Summary>A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix.</Summary>
-    <Title>Terminal.Gui - Cross Platform Terminal user interface toolkit for .NET</Title>
+    <Title>Terminal.Gui - Cross platform Terminal User Interface (TUI) toolkit for .NET</Title>
     <PackageReleaseNotes>
-      Release v1.8.1
-      * Fixes #2053. MessageBox.Query not wrapping correctly 
-      
-      Release v1.8.0
-      * Fixes #2043. Update to NStack v1.0.3
-      * Fixes #2045. TrySetClipboardData test must be enclosed with a lock.
-      * Fixes #2025. API Docs are now generated via Github Action - View Source Works
-      * Fixes #1991. Broken link in README
-      * Fixes #2026. Added ClearOnVisibleFalse to flag if the view must be cleared or not.
-      * Fixes #2017 and #2013. MainLoopTests.InvokeLeakTest failures
-      * Fixes #2014. Application mouseGrabView is run twice if return true.
-      * Fixes #2011. Wizard no longer needs to set specific colors, because #1971 has been fixed.
-      * Fixes #2006. ProgressBarStyles isn't disposing the _fractionTimer on quitting if running.
-      * Fixes #2004. TextFormatter.Justified not adding the extra spaces.
-      * Fixes #2002. Added feature to fill the remaining width with spaces.
-      * Fixes #1999. Prevents the mouseGrabView being executed with a null view.
-      * Fixes #1994. BREAKING CHANGE. Ensure only a single IdleHandlers list can exist.
-      * Fixes #1979. MessageBox.Query not wrapping since 1.7.1
-      * Fixes #1989. ListView: Ensures SelectedItem visibility on MoveDown and MoveUp.
-      * Fixes #1987. Textview insert text newline fix
-      * Fixes #1984. Setting Label.Visible to false does not hide the Label
-      * Fixes #820. Added HideDropdownListOnClick property.
-      * Fixes #1981. Added SplitNewLine method to the TextFormatter.
-      * Fixes #1973. Avoid positioning Submenus off screen.
-      * Added abstract MakeColor and CreateColors to create the colors at once.
-      * Fixes #1800. TextView now uses the same colors as TextField.
-      * Fixes #1969. ESC on CursesDriver take to long to being processed.
-      * Fixes #1967. New keys for DeleteAll on TextField and TextView.
-      * Fixes #1962 - Change KeyBindings to allow chaining commands
-      * Fixes #1961 Null reference in Keybindings Scenario and hotkey collision
-      * Fixes #1963. Only remove one character on backspace when wordwrap is on
-      * Fixes #1959. GoToEnd should not fail on an empty TreeView
-      * Fixes #1953. TextView cursor position is not updating by mouse.
-      * Fixes #1951. TextView with selected text doesn't scroll beyond the cursor position.
-      * Fixes #1948. Get unwrapped cursor position when word wrap is enabled on TextView.
-      * Ensures that the isButtonShift flag is disabled in all situations.
-      * Fixes #1943. Mouse ButtonShift is not preserving the text selected.
-
-      Release v1.7.2
-      * Fixes #1773. Base color scheme for ListView hard to read
-      * Fixes #1934. WindowsDriver crash when the height is less than 1 with the VS Debugger
-
-      Release v1.7.1
-      * Fixes #1930. Trailing whitespace makes MessageBox.Query buttons disappear.
-      * Fixes #1921. Mouse continuous button pressed is not working on ScrollView.
-      * Fixes #1924. Wizard: Selected help text is unreadable
-
-      Release v1.7.0
-      * Moved Terminal.Gui (and NStack) to the github.com/gui-cs organization.
-      * Adds multi-step Wizard View for setup experiences (#124)
-      * The synchronization context method Send is now blocking (#1854).
-      * Fixes #1917. Sometimes Clipboard.IsSupported doesn't return the correct
-      * Fixes #1893: Fix URLs to match gui-cs Org
-      * Fixes #1883. Child TopLevels now get Loaded/Ready events.
-      * Fixes #1867, #1866, #1796. TextView enhancements for ReadOnly and WordWrap.
-      * Fixes #1861. Border: Title property is preferable to Text.
-      * Fixes #1855. Window and Frame content view without the margin frame.
-      * Fixes #1848. Mouse clicks in Windows Terminal.
-      * Fixes #1846. TabView now clips to the draw bounds.
-      * Fix TableView multi selections extending to -1 indexes
-      * Fixes #1837. Setting Unix clipboard freezes.
-      * Fixes #1839. Process WindowsDriver click event if location is the same after pressed and released.
-      * Fixes #1830. If "libcoreclr.so" is not present then "libncursesw.so" will be used.
-      * Fixes #1816. MessageBox: Hides underlying dialog when visible
-      * Fixes #1815. Now returns false if WSL clipboard isn't supported.
-      * Fixes #1825. Parent MenuItem stay focused if child MenuItem is empty.
-      * Fixes #1812, #1797, #1791. AutoSize fixes.
-      * Fixes #1818. Adds Title change events to Window.
-      * Fixes #1810. Dialog: Closing event is not fired when ESC is pressed to close dialog.
-      * Fixes #1793. ScrollBarView is hiding if the host fit the available space.
-      * Added Pos/Dim Function feature to automate layout.
-      * Fixes #1786. Windows Terminal is reporting well on mouse button pressed + mouse movement.
-      * Fixes #1777 - Dialog button justification. Adds unit tests.
-      * Fixes #1739. Setting menu UseKeysUpDownAsKeysLeftRight as false by default.
-      * Fixes #1772. Avoids WindowsDriver flickering when resizing.
-      * Fixed TableView always showing selected cell(s) even when not focused
-      * Fixes #1769. Supports a minimum view size for non-automatic size views.
-      * Exposes APIs to support upcoming Web console feature
-      * Fixes some scrolling performance issues
-      * Fixes #1763. Allowing read console inputs before idle handlers.
-      * TableView unicode scenario usability
-      * Added unicode testing code to TableEditor
+      See: https://github.com/gui-cs/Terminal.Gui/releases
     </PackageReleaseNotes>
   </PropertyGroup>
 </Project>

+ 1 - 1
Terminal.Gui/Windows/Wizard.cs

@@ -159,7 +159,7 @@ namespace Terminal.Gui {
 			public event Action<TitleEventArgs> TitleChanged;
 
 			// The contentView works like the ContentView in FrameView.
-			private View contentView = new View ();
+			private View contentView = new View () { Data = "WizardContentView" };
 
 			/// <summary>
 			/// Sets or gets help text for the <see cref="WizardStep"/>.If <see cref="WizardStep.HelpText"/> is empty

+ 15 - 50
UICatalog/README.md

@@ -2,8 +2,9 @@
 
 UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals:
 
-1. Be an easy to use showcase for Terminal.Gui concepts and features.
-2. Provide sample code that illustrates how to properly implement said concepts & features.
+1. Be an easy-to-use showcase for Terminal.Gui concepts and features.
+2. Provide sample code that illustrates how to properly implement 
+said concepts & features.
 3. Make it easy for contributors to add additional samples in a structured way.
 
 ![screenshot](screenshot.png)
@@ -51,7 +52,7 @@ To add a new **Scenario** simply:
 4. Implement the `Setup` override which will be called when a user selects the scenario to run.
 5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation.
 
-The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
+The sample below is provided in the `.\UICatalog\Scenarios` directory as a generic sample that can be copied and re-named:
 
 ```csharp
 using Terminal.Gui;
@@ -73,59 +74,23 @@ namespace UICatalog {
 }
 ```
 
-`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. 
+`Scenario` provides `Win`, a `Window` object that provides a canvas for the Scenario to operate. 
 
-![screenshot](generic_screenshot.png)
-
-To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario:
+The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. 
 
-```csharp
-using Terminal.Gui;
+![screenshot](generic_screenshot.png)
 
-namespace UICatalog {
-	[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
-	[ScenarioCategory ("Text")]
-	[ScenarioCategory ("Controls")]
-	class UnicodeInMenu : Scenario {
-		public override void Setup ()
-		{
-			Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_Файл", new MenuItem [] {
-					new MenuItem ("_Создать", "Creates new file", null),
-					new MenuItem ("_Открыть", "", null),
-					new MenuItem ("Со_хранить", "", null),
-					new MenuItem ("_Выход", "", () => Application.RequestStop() )
-				}),
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_Copy", "", null),
-					new MenuItem ("C_ut", "", null),
-					new MenuItem ("_Paste", "", null)
-				})
-			});
-			Top.Add (menu);
-
-			Win = new Window ($"Scenario: {GetName ()}") {
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			Top.Add (Win);
-		}
-	}
-}
-```
+To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply use `Application.Top` per normal Terminal.Gui programming, as seen in the `Notepad` scenario.
 
-For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`.
+For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` creates `Win`. The `base.Run` simply calls `Application.Run(Application.Top)`.
 
 ## Contribution Guidelines
 
-- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use.
-- Provide a clear description.
+- Provide a terse, descriptive `Name` for `Scenarios`. Keep them short.
+- Provide a clear `Description`.
 - Comment `Scenario` code to describe to others why it's a useful `Scenario`.
-- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created.
-- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs. 
+- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Minimize the number of new categories created.
+- Use the `Bug Repo` Category for `Scenarios` that reproduce bugs. 
 	- Include the Github Issue # in the Description.
-	- Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test).
-- Tag bugs or suggestions for `UI Catalog` as [`Terminal.Gui` Github Issues](https://github.com/gui-cs/Terminal.Gui/issues) with "UICatalog: ".
+	- Once the bug has been fixed in `develop` submit another PR to remove the `Scenario` (or modify it to provide a good regression test/sample).
+- Tag bugs or suggestions for `UI Catalog` as [`Terminal.Gui` Github Issues](https://github.com/gui-cs/Terminal.Gui/issues) with "UICatalog: ".

+ 3 - 0
UICatalog/Scenarios/WizardAsView.cs

@@ -12,6 +12,9 @@ namespace UICatalog.Scenarios {
 
 		public override void Init (ColorScheme colorScheme)
 		{
+			Application.Init ();
+			Top = Application.Top;
+
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_Restart Configuration...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reset the Wizard and start over?", "Ok", "Cancel")),

+ 154 - 35
UnitTests/MainLoopTests.cs

@@ -592,56 +592,175 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ((numIncrements * numPasses), tbCounter);
 		}
 
+		private static int total;
+		private static Button btn;
+		private static string clickMe;
+		private static string cancel;
+		private static string pewPew;
+		private static int zero;
+		private static int one;
+		private static int two;
+		private static int three;
+		private static int four;
+		private static bool taskCompleted;
+
+		[Theory, AutoInitShutdown]
+		[MemberData (nameof (TestAddIdle))]
+		public void Mainloop_Invoke_Or_AddIdle_Can_Be_Used_For_Events_Or_Actions (Action action, string pclickMe, string pcancel, string ppewPew, int pzero, int pone, int ptwo, int pthree, int pfour)
+		{
+			total = 0;
+			btn = null;
+			clickMe = pclickMe;
+			cancel = pcancel;
+			pewPew = ppewPew;
+			zero = pzero;
+			one = pone;
+			two = ptwo;
+			three = pthree;
+			four = pfour;
+			taskCompleted = false;
+
+			var btnLaunch = new Button ("Open Window");
+
+			btnLaunch.Clicked += () => action ();
+
+			Application.Top.Add (btnLaunch);
+
+			var iterations = -1;
+
+			Application.Iteration += () => {
+				iterations++;
+				if (iterations == 0) {
+					Assert.Null (btn);
+					Assert.Equal (zero, total);
+					Assert.True (btnLaunch.ProcessKey (new KeyEvent (Key.Enter, null)));
+					if (btn == null) {
+						Assert.Null (btn);
+						Assert.Equal (zero, total);
+					} else {
+						Assert.Equal (clickMe, btn.Text);
+						Assert.Equal (four, total);
+					}
+				} else if (iterations == 1) {
+					Assert.Equal (clickMe, btn.Text);
+					Assert.Equal (zero, total);
+					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
+					Assert.Equal (cancel, btn.Text);
+					Assert.Equal (one, total);
+				} else if (taskCompleted) {
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+
+			Assert.True (taskCompleted);
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (four, total);
+		}
 
-		[Fact, AutoInitShutdown]
-		public void TestAddManyTimeouts ()
+		public static IEnumerable<object []> TestAddIdle {
+			get {
+				// Goes fine
+				Action a1 = StartWindow;
+				yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
+
+				// Also goes fine
+				Action a2 = () => Application.MainLoop.Invoke (StartWindow);
+				yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
+			}
+		}
+
+		private static void StartWindow ()
 		{
-			int delegatesRun = 0;
-			int numberOfThreads = 100;
-			int numberOfTimeoutsPerThread = 100;
+			var startWindow = new Window {
+				Modal = true
+			};
+
+			btn = new Button {
+				Text = "Click Me"
+			};
+
+			btn.Clicked += RunAsyncTest;
 
+			var totalbtn = new Button () {
+				X = Pos.Right (btn),
+				Text = "total"
+			};
 
-			lock (Application.Top) {
-				// start lots of threads
-				for (int i = 0; i < numberOfThreads; i++) {
+			totalbtn.Clicked += () => {
+				MessageBox.Query ("Count", $"Count is {total}", "Ok");
+			};
 
-					var myi = i;
+			startWindow.Add (btn);
+			startWindow.Add (totalbtn);
 
-					Task.Run (() => {
-						Thread.Sleep (100);
+			Application.Run (startWindow);
 
-						// each thread registers lots of 1s timeouts
-						for (int j = 0; j < numberOfTimeoutsPerThread; j++) {
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (four, total);
 
-							Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (1), (s) => {
+			Application.RequestStop ();
+		}
 
-								// each timeout delegate increments delegatesRun count by 1 every second
-								Interlocked.Increment (ref delegatesRun);
-								return true;
-							});
-						}
+		private static async void RunAsyncTest ()
+		{
+			Assert.Equal (clickMe, btn.Text);
+			Assert.Equal (zero, total);
 
-						// if this is the first Thread created
-						if (myi == 0) {
+			btn.Text = "Cancel";
+			Interlocked.Increment (ref total);
+			btn.SetNeedsDisplay ();
 
-							// let the timeouts run for a bit
-							Thread.Sleep (10000);
+			await Task.Run (() => {
+				try {
+					Assert.Equal (cancel, btn.Text);
+					Assert.Equal (one, total);
 
-							// then tell the application to quit
-							Application.MainLoop.Invoke (() => Application.RequestStop ());
-						}
-					});
+					RunSql ();
+				} finally {
+					SetReadyToRun ();
 				}
+			}).ContinueWith (async (s, e) => {
 
-				// blocks here until the RequestStop is processed at the end of the test
-				Application.Run ();
+				await Task.Delay (1000);
+				Assert.Equal (clickMe, btn.Text);
+				Assert.Equal (three, total);
 
-				// undershoot a bit to be on the safe side.  The 5000 ms wait allows the timeouts to run
-				// a lot but all those timeout delegates could end up going slowly on a slow machine perhaps
-				// so the final number of delegatesRun might vary by computer.  So for this assert we say
-				// that it should have run at least 2 seconds worth of delegates
-				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
-			}
+				Interlocked.Increment (ref total);
+
+				Assert.Equal (clickMe, btn.Text);
+				Assert.Equal (four, total);
+
+				taskCompleted = true;
+
+			}, TaskScheduler.FromCurrentSynchronizationContext ());
+		}
+
+		private static void RunSql ()
+		{
+			Thread.Sleep (100);
+			Assert.Equal (cancel, btn.Text);
+			Assert.Equal (one, total);
+
+			Application.MainLoop.Invoke (() => {
+				btn.Text = "Pew Pew";
+				Interlocked.Increment (ref total);
+				btn.SetNeedsDisplay ();
+			});
+		}
+
+		private static void SetReadyToRun ()
+		{
+			Thread.Sleep (100);
+			Assert.Equal (pewPew, btn.Text);
+			Assert.Equal (two, total);
+
+			Application.MainLoop.Invoke (() => {
+				btn.Text = "Click Me";
+				Interlocked.Increment (ref total);
+				btn.SetNeedsDisplay ();
+			});
 		}
 	}
 }

+ 2 - 2
UnitTests/TextFormatterTests.cs

@@ -2841,12 +2841,12 @@ namespace Terminal.Gui.Core {
 
 			c = new System.Rune (31);
 			Assert.Equal (-1, Rune.ColumnWidth (c));        // non printable character
-			Assert.Equal (-1, ustring.Make (c).ConsoleWidth);
+			Assert.Equal (0, ustring.Make (c).ConsoleWidth);// ConsoleWidth only returns zero or greater than zero
 			Assert.Equal (1, ustring.Make (c).Length);
 
 			c = new System.Rune (127);
 			Assert.Equal (-1, Rune.ColumnWidth (c));       // non printable character
-			Assert.Equal (-1, ustring.Make (c).ConsoleWidth);
+			Assert.Equal (0, ustring.Make (c).ConsoleWidth);
 			Assert.Equal (1, ustring.Make (c).Length);
 		}