diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 0a228511a..bf654d3a7 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -170,12 +170,12 @@ public static partial class Application /// /// /// must be called when the application is closing (typically after - /// has returned) to ensure resources are cleaned up and terminal settings + /// has returned) to ensure resources are cleaned up and terminal settings /// restored. /// /// /// The function combines - /// and into a single + /// and into a single /// call. An application cam use without explicitly calling /// . /// @@ -334,7 +334,7 @@ public static partial class Application /// Shutdown an application initialized with . /// /// Shutdown must be called for every call to or - /// to ensure all resources are cleaned up (Disposed) + /// to ensure all resources are cleaned up (Disposed) /// and terminal settings are restored. /// public static void Shutdown () @@ -533,14 +533,14 @@ public static partial class Application } /// - /// Runs the application by calling with the value of + /// Runs the application by calling with the value of /// . /// - /// See for more details. - public static void Run (Func errorHandler = null) { Run (errorHandler);} + /// See for more details. + public static void Run (Func errorHandler = null, ConsoleDriver driver = null) { Run (errorHandler, driver);} /// - /// Runs the application by calling with a new instance of the + /// Runs the application by calling with a new instance of the /// specified -derived class. /// Calling first is not needed as this function will initialize the application. /// @@ -548,7 +548,7 @@ public static partial class Application /// ensure resources are cleaned up and terminal settings restored. /// /// - /// See for more details. + /// See for more details. /// /// /// The to use. If not specified the default driver for the platform will @@ -560,28 +560,9 @@ public static partial class Application { var top = new T () as Toplevel; - if (top is null) - { - throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel"); - } + EnsureValidInitialization (top, driver); - if (_initialized) - { - if (Driver is null) - { - // This code path should be impossible because Init(null, null) will select the platform default driver - throw new InvalidOperationException ( - "Init() completed without a driver being set (this should be impossible); Run() cannot be called." - ); - } - } - else - { - // Init() has NOT been called. - InternalInit (driver, null, true); - } - - Run (top, errorHandler); + RunApp (top, errorHandler); } /// Runs the main loop on the given container. @@ -591,11 +572,11 @@ public static partial class Application /// modal s such as boxes. /// /// - /// To make a stop execution, call + /// To make a stop execution, call /// . /// /// - /// Calling is equivalent to calling + /// Calling is equivalent to calling /// , followed by , and then calling /// . /// @@ -618,7 +599,19 @@ public static partial class Application /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, /// rethrows when null). /// - public static void Run (Toplevel view, Func errorHandler = null) + /// + /// The to use. If not specified the default driver for the platform will + /// be used ( , , or ). Must be + /// if has already been called. + /// + public static void Run (Toplevel view, Func errorHandler = null, ConsoleDriver driver = null) + { + EnsureValidInitialization (view, driver); + + RunApp (view, errorHandler); + } + + private static void RunApp (Toplevel view, Func errorHandler = null) { var resume = true; @@ -654,6 +647,30 @@ public static partial class Application } } + private static void EnsureValidInitialization (Toplevel top, ConsoleDriver driver) + { + if (top is null) + { + throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel"); + } + + if (_initialized) + { + if (Driver is null) + { + // This code path should be impossible because Init(null, null) will select the platform default driver + throw new InvalidOperationException ( + "Init() completed without a driver being set (this should be impossible); Run() cannot be called." + ); + } + } + else + { + // Init() has NOT been called. + InternalInit (driver, null, true); + } + } + /// Adds a timeout to the application. /// /// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be @@ -844,7 +861,7 @@ public static partial class Application /// Stops running the most recent or the if provided. /// The to stop. /// - /// This will cause to return. + /// This will cause to return. /// /// Calling is equivalent to setting the /// property on the currently running to false. @@ -1065,7 +1082,7 @@ public static partial class Application /// /// The current object. This is updated when - /// enters and leaves to point to the current + /// enters and leaves to point to the current /// . /// /// The current. @@ -1185,7 +1202,7 @@ public static partial class Application && top != OverlappedTop && top != Current && Current?.Running == false - && !top.Running) + && top?.Running == false) { lock (_topLevels) { diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs index de519450c..71714e5f8 100644 --- a/UnitTests/Application/ApplicationTests.cs +++ b/UnitTests/Application/ApplicationTests.cs @@ -916,9 +916,9 @@ public class ApplicationTests } [Fact] - public void Run_Creates_Top_With_Init () + public void Run_Creates_Top_Without_Init () { - Application.Init (new FakeDriver ()); + var driver = new FakeDriver (); Assert.Null (Application.Top); @@ -927,7 +927,16 @@ public class ApplicationTests Assert.NotNull (Application.Top); Application.RequestStop (); }; - Application.Run (); + Application.Run (null, driver); +#if DEBUG_IDISPOSABLE + Assert.False (Application.Top.WasDisposed); + var exception = Record.Exception (() => Application.Shutdown ()); + Assert.NotNull (exception); + Assert.False (Application.Top.WasDisposed); + // It's up to caller to dispose it + Application.Top.Dispose (); + Assert.True (Application.Top.WasDisposed); +#endif Assert.NotNull (Application.Top); Application.Shutdown (); @@ -947,6 +956,15 @@ public class ApplicationTests Application.RequestStop (); }; Application.Run (null, driver); +#if DEBUG_IDISPOSABLE + Assert.False (Application.Top.WasDisposed); + var exception = Record.Exception (() => Application.Shutdown ()); + Assert.NotNull (exception); + Assert.False (Application.Top.WasDisposed); + // It's up to caller to dispose it + Application.Top.Dispose (); + Assert.True (Application.Top.WasDisposed); +#endif Assert.NotNull (Application.Top); Application.Shutdown (); @@ -954,9 +972,9 @@ public class ApplicationTests } [Fact] - public void Run_t_Creates_Top_With_Init () + public void Run_t_Creates_Top_Without_Init () { - Application.Init (new FakeDriver ()); + var driver = new FakeDriver (); Assert.Null (Application.Top); @@ -965,7 +983,16 @@ public class ApplicationTests Assert.NotNull (Application.Top); Application.RequestStop (); }; - Application.Run (new Toplevel ()); + Application.Run (new (), null, driver); +#if DEBUG_IDISPOSABLE + Assert.False (Application.Top.WasDisposed); + var exception = Record.Exception (() => Application.Shutdown ()); + Assert.NotNull (exception); + Assert.False (Application.Top.WasDisposed); + // It's up to caller to dispose it + Application.Top.Dispose (); + Assert.True (Application.Top.WasDisposed); +#endif Assert.NotNull (Application.Top); Application.Shutdown ();