浏览代码

Adds Logging level control to UICatalog (#3938)

* Tons of API doc updates

* Added logging control to UICatalog

* Added logging control to UICatalog - more

* fixed minor issues

* removed logs from .gitignore

* Fixed log file path

* Fixed app desc
Tig 4 月之前
父节点
当前提交
79cd4e92b7

+ 2 - 0
.gitignore

@@ -58,3 +58,5 @@ demo.*
 *.tui/
 
 *.dotCover
+
+logs/

+ 1 - 0
Terminal.Gui/Application/ApplicationImpl.cs

@@ -215,6 +215,7 @@ public class ApplicationImpl : IApplication
 
         bool wasInitialized = Application.Initialized;
         Application.ResetState ();
+        LogJsonErrors ();
         PrintJsonErrors ();
 
         if (wasInitialized)

+ 2 - 2
Terminal.Gui/Configuration/AttributeJsonConverter.cs

@@ -57,11 +57,11 @@ internal class AttributeJsonConverter : JsonConverter<Attribute>
             switch (propertyName?.ToLower ())
             {
                 case "foreground":
-                    foreground = JsonSerializer.Deserialize (color, _serializerContext.Color);
+                    foreground = JsonSerializer.Deserialize (color, SerializerContext.Color);
 
                     break;
                 case "background":
-                    background = JsonSerializer.Deserialize (color, _serializerContext.Color);
+                    background = JsonSerializer.Deserialize (color, SerializerContext.Color);
 
                     break;
 

+ 1 - 1
Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs

@@ -59,7 +59,7 @@ internal class ColorSchemeJsonConverter : JsonConverter<ColorScheme>
 
             string propertyName = reader.GetString ();
             reader.Read ();
-            var attribute = JsonSerializer.Deserialize (ref reader, _serializerContext.Attribute);
+            var attribute = JsonSerializer.Deserialize (ref reader, SerializerContext.Attribute);
 
             switch (propertyName.ToLower ())
             {

+ 28 - 15
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -8,6 +8,7 @@ using System.Runtime.Versioning;
 using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using Microsoft.Extensions.Logging;
 
 #nullable enable
 
@@ -65,7 +66,7 @@ public static class ConfigurationManager
     internal static Dictionary<string, ConfigProperty>? _allConfigProperties;
 
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
-    internal static readonly JsonSerializerOptions _serializerOptions = new ()
+    internal static readonly JsonSerializerOptions SerializerOptions = new ()
     {
         ReadCommentHandling = JsonCommentHandling.Skip,
         PropertyNameCaseInsensitive = true,
@@ -87,7 +88,7 @@ public static class ConfigurationManager
     };
 
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
-    internal static readonly SourceGenerationContext _serializerContext = new (_serializerOptions);
+    internal static readonly SourceGenerationContext SerializerContext = new (SerializerOptions);
 
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     internal static StringBuilder _jsonErrors = new ();
@@ -209,7 +210,7 @@ public static class ConfigurationManager
         var emptyScope = new SettingsScope ();
         emptyScope.Clear ();
 
-        return JsonSerializer.Serialize (emptyScope, typeof (SettingsScope), _serializerContext);
+        return JsonSerializer.Serialize (emptyScope, typeof (SettingsScope), SerializerContext);
     }
 
     /// <summary>
@@ -235,7 +236,7 @@ public static class ConfigurationManager
     [RequiresDynamicCode ("AOT")]
     public static void Load (bool reset = false)
     {
-        Debug.WriteLine ("ConfigurationManager.Load()");
+        Logging.Trace ($"reset = {reset}");
 
         if (reset)
         {
@@ -292,7 +293,7 @@ public static class ConfigurationManager
     /// </summary>
     public static void OnApplied ()
     {
-        Debug.WriteLine ("ConfigurationManager.OnApplied()");
+        //Logging.Trace ("");
 
         Applied?.Invoke (null, new ());
 
@@ -308,7 +309,7 @@ public static class ConfigurationManager
     /// </summary>
     public static void OnUpdated ()
     {
-        Debug.WriteLine (@"ConfigurationManager.OnUpdated()");
+        //Logging.Trace (@"");
         Updated?.Invoke (null, new ());
     }
 
@@ -324,6 +325,18 @@ public static class ConfigurationManager
         }
     }
 
+
+    public static void LogJsonErrors ()
+    {
+        if (_jsonErrors.Length > 0)
+        {
+            Logging.Warning (
+                               @"Encountered the following errors while deserializing configuration files:"
+                              );
+            Logging.Warning (_jsonErrors.ToString ());
+        }
+    }
+
     /// <summary>
     ///     Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session (e.g. in
     ///     <see cref="Application.Init"/> starts. Called by <see cref="Load"/> if the <c>reset</c> parameter is
@@ -334,7 +347,7 @@ public static class ConfigurationManager
     [RequiresDynamicCode ("AOT")]
     public static void Reset ()
     {
-        Debug.WriteLine (@"ConfigurationManager.Reset()");
+        Logging.Trace ($"_allConfigProperties = {_allConfigProperties}");
 
         if (_allConfigProperties is null)
         {
@@ -369,7 +382,7 @@ public static class ConfigurationManager
 
     internal static void AddJsonError (string error)
     {
-        Debug.WriteLine ($"ConfigurationManager: {error}");
+        Logging.Trace ($"error = {error}");
         _jsonErrors.AppendLine (error);
     }
 
@@ -541,8 +554,8 @@ public static class ConfigurationManager
             classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
         }
 
-        //Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
-        classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
+        //Logging.Trace ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
+        classesWithConfigProps.ToList ().ForEach (x => Logging.Trace ($"  Class: {x.Key}"));
 
         foreach (PropertyInfo? p in from c in classesWithConfigProps
                                     let props = c.Value
@@ -595,9 +608,9 @@ public static class ConfigurationManager
                                                                    StringComparer.InvariantCultureIgnoreCase
                                                                   );
 
-        //Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
+        //Logging.Trace ($"Found {_allConfigProperties.Count} properties:");
 
-        //_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($"  Property: {x.Key}"));
+        //_allConfigProperties.ToList ().ForEach (x => Logging.Trace ($"  Property: {x.Key}"));
 
         AppSettings = new ();
     }
@@ -608,16 +621,16 @@ public static class ConfigurationManager
     [RequiresDynamicCode ("AOT")]
     internal static string ToJson ()
     {
-        //Debug.WriteLine ("ConfigurationManager.ToJson()");
+        //Logging.Trace ("ConfigurationManager.ToJson()");
 
-        return JsonSerializer.Serialize (Settings!, typeof (SettingsScope), _serializerContext);
+        return JsonSerializer.Serialize (Settings!, typeof (SettingsScope), SerializerContext);
     }
 
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     internal static Stream ToStream ()
     {
-        string json = JsonSerializer.Serialize (Settings!, typeof (SettingsScope), _serializerContext);
+        string json = JsonSerializer.Serialize (Settings!, typeof (SettingsScope), SerializerContext);
 
         // turn it into a stream
         var stream = new MemoryStream ();

+ 2 - 2
Terminal.Gui/Configuration/DictionaryJsonConverter.cs

@@ -28,7 +28,7 @@ internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
                 {
                     string key = reader.GetString ();
                     reader.Read ();
-                    var value = JsonSerializer.Deserialize (ref reader, typeof (T), _serializerContext);
+                    var value = JsonSerializer.Deserialize (ref reader, typeof (T), SerializerContext);
                     dictionary.Add (key, (T)value);
                 }
             }
@@ -51,7 +51,7 @@ internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
 
             //writer.WriteString (item.Key, item.Key);
             writer.WritePropertyName (item.Key);
-            JsonSerializer.Serialize (writer, item.Value, typeof (T), _serializerContext);
+            JsonSerializer.Serialize (writer, item.Value, typeof (T), SerializerContext);
             writer.WriteEndObject ();
         }
 

+ 5 - 5
Terminal.Gui/Configuration/ScopeJsonConverter.cs

@@ -89,11 +89,11 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
                     try
                     {
                         scope! [propertyName].PropertyValue =
-                            JsonSerializer.Deserialize (ref reader, propertyType!, _serializerContext);
+                            JsonSerializer.Deserialize (ref reader, propertyType!, SerializerContext);
                     }
                     catch (Exception ex)
                     {
-                        Debug.WriteLine ($"scopeT Read: {ex}");
+                       // Logging.Trace ($"scopeT Read: {ex}");
                     }
                 }
             }
@@ -137,7 +137,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
                 if (property is { })
                 {
                     PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!;
-                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, _serializerContext));
+                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, SerializerContext));
                 }
                 else
                 {
@@ -165,7 +165,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
         {
             writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
             object? prop = scope.GetType ().GetProperty (p.Name)?.GetValue (scope);
-            JsonSerializer.Serialize (writer, prop, prop!.GetType (), _serializerContext);
+            JsonSerializer.Serialize (writer, prop, prop!.GetType (), SerializerContext);
         }
 
         foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
@@ -211,7 +211,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
             else
             {
                 object? prop = p.Value.PropertyValue;
-                JsonSerializer.Serialize (writer, prop, prop!.GetType (), _serializerContext);
+                JsonSerializer.Serialize (writer, prop, prop!.GetType (), SerializerContext);
             }
         }
 

+ 4 - 4
Terminal.Gui/Configuration/SettingsScope.cs

@@ -45,9 +45,9 @@ public class SettingsScope : Scope<SettingsScope>
         // Update the existing settings with the new settings.
         try
         {
-            Update ((SettingsScope)JsonSerializer.Deserialize (stream, typeof (SettingsScope), _serializerOptions)!);
+            Update ((SettingsScope)JsonSerializer.Deserialize (stream, typeof (SettingsScope), SerializerOptions)!);
             OnUpdated ();
-            Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
+            Logging.Trace ($"Read from \"{source}\"");
             if (!Sources.ContainsValue (source))
             {
                 Sources.Add (location, source);
@@ -79,7 +79,7 @@ public class SettingsScope : Scope<SettingsScope>
 
         if (!File.Exists (realPath))
         {
-            Debug.WriteLine ($"ConfigurationManager: Configuration file \"{realPath}\" does not exist.");
+            Logging.Warning ($"\"{realPath}\" does not exist.");
             if (!Sources.ContainsValue (filePath))
             {
                 Sources.Add (location, filePath);
@@ -105,7 +105,7 @@ public class SettingsScope : Scope<SettingsScope>
             }
             catch (IOException ioe)
             {
-                Debug.WriteLine ($"Couldn't open {filePath}. Retrying...: {ioe}");
+                Logging.Warning ($"Couldn't open {filePath}. Retrying...: {ioe}");
                 Task.Delay (100);
                 retryCount++;
             }

+ 3 - 3
Terminal.Gui/Configuration/ThemeManager.cs

@@ -127,7 +127,7 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     internal static void GetHardCodedDefaults ()
     {
-        //Debug.WriteLine ("Themes.GetHardCodedDefaults()");
+        //Logging.Trace ("Themes.GetHardCodedDefaults()");
         var theme = new ThemeScope ();
         theme.RetrieveValues ();
 
@@ -141,7 +141,7 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     /// <summary>Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.</summary>
     internal void OnThemeChanged (string theme)
     {
-        //Debug.WriteLine ($"Themes.OnThemeChanged({theme}) -> {Theme}");
+        //Logging.Trace ($"Themes.OnThemeChanged({theme}) -> {Theme}");
         ThemeChanged?.Invoke (this, new ThemeManagerEventArgs (theme));
     }
 
@@ -149,7 +149,7 @@ public class ThemeManager : IDictionary<string, ThemeScope>
     [RequiresDynamicCode ("Calls Terminal.Gui.ThemeManager.Themes")]
     internal static void Reset ()
     {
-        Debug.WriteLine ("Themes.Reset()");
+        //Logging.Trace ("Themes.Reset()");
         Colors.Reset ();
         Themes?.Clear ();
         SelectedTheme = string.Empty;

+ 82 - 2
Terminal.Gui/ConsoleDrivers/V2/Logging.cs

@@ -13,7 +13,7 @@ namespace Terminal.Gui;
 /// <remarks>
 ///     Also contains the
 ///     <see cref="Meter"/> instance that should be used for internal metrics
-///     (iteration timing etc).
+///     (iteration timing etc.).
 /// </remarks>
 public static class Logging
 {
@@ -51,7 +51,71 @@ public static class Logging
     public static readonly Histogram<int> DrainInputStream = Meter.CreateHistogram<int> ("Drain Input (ms)");
 
     /// <summary>
-    ///     Logs a trace message including the
+    ///     Logs an error message including the class and method name.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Error (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogError ($"[{className}] [{caller}] {message}");
+    }
+
+    /// <summary>
+    ///     Logs a critical message including the class and method name.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Critical (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogCritical ($"[{className}] [{caller}] {message}");
+    }
+
+    /// <summary>
+    ///     Logs a debug message including the class and method name.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Debug (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogDebug ($"[{className}] [{caller}] {message}");
+    }
+
+    /// <summary>
+    ///     Logs an informational message including the class and method name.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Information (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogInformation ($"[{className}] [{caller}] {message}");
+    }
+
+    /// <summary>
+    ///     Logs a trace message including the class and method name.
     /// </summary>
     /// <param name="message"></param>
     /// <param name="caller"></param>
@@ -65,4 +129,20 @@ public static class Logging
         string className = Path.GetFileNameWithoutExtension (filePath);
         Logger.LogTrace ($"[{className}] [{caller}] {message}");
     }
+
+    /// <summary>
+    ///     Logs a warning message including the class and method name.
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Warning (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogWarning ($"[{className}] [{caller}] {message}");
+    }
 }

+ 1 - 1
Terminal.sln.DotSettings

@@ -396,7 +396,7 @@
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
-	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=c873eafb_002Dd57f_002D481d_002D8c93_002D77f6863c2f88/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
+	
 	<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=A92AB40A0394A342A0BE0D83FEEA5DBF/@KeyIndexDefined">True</s:Boolean>
 	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=A92AB40A0394A342A0BE0D83FEEA5DBF/RelativePath/@EntryValue">..\Terminal.sln.ToDo.DotSettings</s:String>
 	<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileA92AB40A0394A342A0BE0D83FEEA5DBF/@KeyIndexDefined">True</s:Boolean>

+ 1 - 1
UICatalog/Scenario.cs

@@ -276,7 +276,7 @@ public class Scenario : IDisposable
             }
         }
 
-        Debug.WriteLine ($@"  Failed to Quit with {Application.QuitKey} after {BenchmarkTimeout}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
+        Logging.Trace ($@"  Failed to Quit with {Application.QuitKey} after {BenchmarkTimeout}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
 
         Application.RequestStop ();
 

+ 222 - 117
UICatalog/UICatalog.cs

@@ -16,10 +16,10 @@ using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
 using Microsoft.Extensions.Logging;
 using Serilog;
+using Serilog.Core;
+using Serilog.Events;
 using Terminal.Gui;
 using static Terminal.Gui.ConfigurationManager;
 using Command = Terminal.Gui.Command;
@@ -31,7 +31,7 @@ using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironm
 namespace UICatalog;
 
 /// <summary>
-///     UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the
+///     UI Catalog is a comprehensive sample library and test app for Terminal.Gui. It provides a simple UI for adding to the
 ///     catalog of scenarios.
 /// </summary>
 /// <remarks>
@@ -60,16 +60,24 @@ public class UICatalogApp
     private static int _cachedScenarioIndex;
     private static string? _cachedTheme = string.Empty;
     private static ObservableCollection<string>? _categories;
+
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     private static readonly FileSystemWatcher _currentDirWatcher = new ();
+
     private static ViewDiagnosticFlags _diagnosticFlags;
     private static string _forceDriver = string.Empty;
+
     [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
     private static readonly FileSystemWatcher _homeDirWatcher = new ();
+
     private static bool _isFirstRunning = true;
     private static Options _options;
     private static ObservableCollection<Scenario>? _scenarios;
 
+    private const string LOGFILE_LOCATION = "./logs";
+    private static string _logFilePath = string.Empty;
+    private static readonly LoggingLevelSwitch _logLevelSwitch = new ();
+
     // If set, holds the scenario the user selected
     private static Scenario? _selectedScenario;
     private static MenuBarItem? _themeMenuBarItem;
@@ -81,7 +89,7 @@ public class UICatalogApp
     public static bool ShowStatusBar { get; set; } = true;
 
     /// <summary>
-    /// Gets the message displayed in the About Box. `public` so it can be used from Unit tests.
+    ///     Gets the message displayed in the About Box. `public` so it can be used from Unit tests.
     /// </summary>
     /// <returns></returns>
     public static string GetAboutBoxMessage ()
@@ -89,10 +97,11 @@ public class UICatalogApp
         // NOTE: Do not use multiline verbatim strings here.
         // WSL gets all confused.
         StringBuilder msg = new ();
-        msg.AppendLine ("UI Catalog: A comprehensive sample library for");
+        msg.AppendLine ("UI Catalog: A comprehensive sample library and test app for");
         msg.AppendLine ();
 
-        msg.AppendLine ("""
+        msg.AppendLine (
+                        """
                          _______                  _             _   _____       _ 
                         |__   __|                (_)           | | / ____|     (_)
                            | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _ 
@@ -123,8 +132,6 @@ public class UICatalogApp
 
     private static int Main (string [] args)
     {
-        Logging.Logger = CreateLogger ();
-
         Console.OutputEncoding = Encoding.Default;
 
         if (Debugger.IsAttached)
@@ -136,7 +143,7 @@ public class UICatalogApp
         _categories = Scenario.GetAllCategories ();
 
         // Process command line args
-        // "UICatalog [--driver <driver>] [--benchmark] [scenario name]"
+
         // If no driver is provided, the default driver is used.
         Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
              Application.GetDriverTypes ()
@@ -146,21 +153,34 @@ public class UICatalogApp
         driverOption.AddAlias ("-d");
         driverOption.AddAlias ("--d");
 
-        Option<bool> benchmarkFlag = new Option<bool> ("--benchmark", "Enables benchmarking. If a Scenario is specified, just that Scenario will be benchmarked.");
+        Option<bool> benchmarkFlag = new ("--benchmark", "Enables benchmarking. If a Scenario is specified, just that Scenario will be benchmarked.");
         benchmarkFlag.AddAlias ("-b");
         benchmarkFlag.AddAlias ("--b");
 
-        Option<uint> benchmarkTimeout = new Option<uint> ("--timeout", getDefaultValue: () => Scenario.BenchmarkTimeout, $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms.");
+        Option<uint> benchmarkTimeout = new (
+                                             "--timeout",
+                                             () => Scenario.BenchmarkTimeout,
+                                             $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms.");
         benchmarkTimeout.AddAlias ("-t");
         benchmarkTimeout.AddAlias ("--t");
 
-        Option<string> resultsFile = new Option<string> ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView.");
+        Option<string> resultsFile = new ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView.");
         resultsFile.AddAlias ("-f");
         resultsFile.AddAlias ("--f");
 
+        // what's the app name?
+        _logFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}.log";
+        Option<string> debugLogLevel = new Option<string> ("--debug-log-level", $"The level to use for logging (debug console and {_logFilePath})").FromAmong (
+             Enum.GetNames<LogEventLevel> ()
+            );
+        debugLogLevel.SetDefaultValue("Warning");
+        debugLogLevel.AddAlias ("-dl");
+        debugLogLevel.AddAlias ("--dl");
+
         Argument<string> scenarioArgument = new Argument<string> (
-                                                                  name: "scenario",
-                                                                  description: "The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.",
+                                                                  "scenario",
+                                                                  description:
+                                                                  "The name of the Scenario to run. If not provided, the UI Catalog UI will be shown.",
                                                                   getDefaultValue: () => "none"
                                                                  ).FromAmong (
                                                                               _scenarios.Select (s => s.GetName ())
@@ -168,10 +188,9 @@ public class UICatalogApp
                                                                                         .ToArray ()
                                                                              );
 
-
-        var rootCommand = new RootCommand ("A comprehensive sample library for Terminal.Gui")
+        var rootCommand = new RootCommand ("A comprehensive sample library and test app for Terminal.Gui")
         {
-            scenarioArgument, benchmarkFlag, benchmarkTimeout, resultsFile, driverOption,
+            scenarioArgument, debugLogLevel, benchmarkFlag, benchmarkTimeout, resultsFile, driverOption
         };
 
         rootCommand.SetHandler (
@@ -184,6 +203,7 @@ public class UICatalogApp
                                         Benchmark = context.ParseResult.GetValueForOption (benchmarkFlag),
                                         BenchmarkTimeout = context.ParseResult.GetValueForOption (benchmarkTimeout),
                                         ResultsFile = context.ParseResult.GetValueForOption (resultsFile) ?? string.Empty,
+                                        DebugLogLevel = context.ParseResult.GetValueForOption (debugLogLevel) ?? "Warning"
                                         /* etc. */
                                     };
 
@@ -192,10 +212,11 @@ public class UICatalogApp
                                 }
                                );
 
-        bool helpShown = false;
-        var parser = new CommandLineBuilder (rootCommand)
-                     .UseHelp (ctx => helpShown = true)
-                     .Build ();
+        var helpShown = false;
+
+        Parser parser = new CommandLineBuilder (rootCommand)
+                        .UseHelp (ctx => helpShown = true)
+                        .Build ();
 
         parser.Invoke (args);
 
@@ -206,6 +227,8 @@ public class UICatalogApp
 
         Scenario.BenchmarkTimeout = _options.BenchmarkTimeout;
 
+        Logging.Logger = CreateLogger ();
+
         UICatalogMain (_options);
 
         return 0;
@@ -215,19 +238,23 @@ public class UICatalogApp
     {
         // Configure Serilog to write logs to a file
         Log.Logger = new LoggerConfiguration ()
-                     .MinimumLevel.Verbose () // Verbose includes Trace and Debug
+                     .MinimumLevel.ControlledBy (_logLevelSwitch)
                      .Enrich.FromLogContext () // Enables dynamic enrichment
-                     .WriteTo.File ("logs/logfile.txt", rollingInterval: RollingInterval.Day,
+                     .WriteTo.Debug ()
+                     .WriteTo.File (
+                                    _logFilePath,
+                                    rollingInterval: RollingInterval.Day,
                                     outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
                      .CreateLogger ();
 
         // Create a logger factory compatible with Microsoft.Extensions.Logging
-        using var loggerFactory = LoggerFactory.Create (builder =>
-                                                        {
-                                                            builder
-                                                                .AddSerilog (dispose: true) // Integrate Serilog with ILogger
-                                                                .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
-                                                        });
+        using ILoggerFactory loggerFactory = LoggerFactory.Create (
+                                                                   builder =>
+                                                                   {
+                                                                       builder
+                                                                           .AddSerilog (dispose: true) // Integrate Serilog with ILogger
+                                                                           .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
+                                                                   });
 
         // Get an ILogger instance
         return loggerFactory.CreateLogger ("Global Logger");
@@ -354,7 +381,6 @@ public class UICatalogApp
         _homeDirWatcher.Created -= ConfigFileChanged;
     }
 
-
     private static void UICatalogMain (Options options)
     {
         StartConfigFileWatcher ();
@@ -363,7 +389,6 @@ public class UICatalogApp
         // regardless of what's in a config file.
         Application.ForceDriver = _forceDriver = options.Driver;
 
-
         // If a Scenario name has been provided on the commandline
         // run it and exit when done.
         if (options.Scenario != "none")
@@ -378,13 +403,17 @@ public class UICatalogApp
                                                                        )!);
             _selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
 
-            var results = RunScenario (_selectedScenario, options.Benchmark);
+            BenchmarkResults? results = RunScenario (_selectedScenario, options.Benchmark);
+
             if (results is { })
             {
-                Console.WriteLine (JsonSerializer.Serialize (results, new JsonSerializerOptions ()
-                {
-                    WriteIndented = true
-                }));
+                Console.WriteLine (
+                                   JsonSerializer.Serialize (
+                                                             results,
+                                                             new JsonSerializerOptions
+                                                             {
+                                                                 WriteIndented = true
+                                                             }));
             }
 
             VerifyObjectsWereDisposed ();
@@ -408,6 +437,7 @@ public class UICatalogApp
             scenario.TopLevelColorScheme = _topLevelColorScheme;
 
 #if DEBUG_IDISPOSABLE
+
             // Measure how long it takes for the app to shut down
             var sw = new Stopwatch ();
             string scenarioName = scenario.GetName ();
@@ -435,7 +465,7 @@ public class UICatalogApp
                 else
                 {
                     sw.Stop ();
-                    Debug.WriteLine ($"Shutdown of {scenarioName} Scenario took {sw.ElapsedMilliseconds}ms");
+                    Logging.Trace ($"Shutdown of {scenarioName} Scenario took {sw.ElapsedMilliseconds}ms");
                 }
             }
 #endif
@@ -443,8 +473,6 @@ public class UICatalogApp
 
         StopConfigFileWatcher ();
         VerifyObjectsWereDisposed ();
-
-        return;
     }
 
     private static BenchmarkResults? RunScenario (Scenario scenario, bool benchmark)
@@ -473,20 +501,19 @@ public class UICatalogApp
 
         scenario.Dispose ();
 
-
         // TODO: Throw if shutdown was not called already
         Application.Shutdown ();
 
         return results;
     }
 
-
     private static void BenchmarkAllScenarios ()
     {
-        List<BenchmarkResults> resultsList = new List<BenchmarkResults> ();
+        List<BenchmarkResults> resultsList = new ();
+
+        var maxScenarios = 5;
 
-        int maxScenarios = 5;
-        foreach (var s in _scenarios!)
+        foreach (Scenario s in _scenarios!)
         {
             resultsList.Add (RunScenario (s, true)!);
             maxScenarios--;
@@ -501,24 +528,25 @@ public class UICatalogApp
         {
             if (!string.IsNullOrEmpty (_options.ResultsFile))
             {
-                var output = JsonSerializer.Serialize (
-                                                       resultsList,
-                                                       new JsonSerializerOptions ()
-                                                       {
-                                                           WriteIndented = true
-                                                       });
-
-                using var file = File.CreateText (_options.ResultsFile);
+                string output = JsonSerializer.Serialize (
+                                                          resultsList,
+                                                          new JsonSerializerOptions
+                                                          {
+                                                              WriteIndented = true
+                                                          });
+
+                using StreamWriter file = File.CreateText (_options.ResultsFile);
                 file.Write (output);
                 file.Close ();
+
                 return;
             }
 
             Application.Init ();
 
-            var benchmarkWindow = new Window ()
+            var benchmarkWindow = new Window
             {
-                Title = "Benchmark Results",
+                Title = "Benchmark Results"
             };
 
             if (benchmarkWindow.Border is { })
@@ -529,7 +557,7 @@ public class UICatalogApp
             TableView resultsTableView = new ()
             {
                 Width = Dim.Fill (),
-                Height = Dim.Fill (),
+                Height = Dim.Fill ()
             };
 
             // TableView provides many options for table headers. For simplicity we turn all 
@@ -544,17 +572,17 @@ public class UICatalogApp
             resultsTableView.Style.ShowVerticalHeaderLines = true;
 
             /* By default TableView lays out columns at render time and only
-                 * measures y rows of data at a time.  Where y is the height of the
-                 * console. This is for the following reasons:
-                 *
-                 * - Performance, when tables have a large amount of data
-                 * - Defensive, prevents a single wide cell value pushing other
-                 *   columns off screen (requiring horizontal scrolling
-                 *
-                 * In the case of UICatalog here, such an approach is overkill so
-                 * we just measure all the data ourselves and set the appropriate
-                 * max widths as ColumnStyles
-                 */
+             * measures y rows of data at a time.  Where y is the height of the
+             * console. This is for the following reasons:
+             *
+             * - Performance, when tables have a large amount of data
+             * - Defensive, prevents a single wide cell value pushing other
+             *   columns off screen (requiring horizontal scrolling
+             *
+             * In the case of UICatalog here, such an approach is overkill so
+             * we just measure all the data ourselves and set the appropriate
+             * max widths as ColumnStyles
+             */
             //int longestName = _scenarios!.Max (s => s.GetName ().Length);
 
             //resultsTableView.Style.ColumnStyles.Add (
@@ -586,7 +614,7 @@ public class UICatalogApp
             dt.Columns.Add (new DataColumn ("Updated", typeof (int)));
             dt.Columns.Add (new DataColumn ("Iterations", typeof (int)));
 
-            foreach (var r in resultsList)
+            foreach (BenchmarkResults r in resultsList)
             {
                 dt.Rows.Add (
                              r.Scenario,
@@ -603,24 +631,25 @@ public class UICatalogApp
             BenchmarkResults totalRow = new ()
             {
                 Scenario = "TOTAL",
-                Duration = new TimeSpan (resultsList.Sum (r => r.Duration.Ticks)),
+                Duration = new (resultsList.Sum (r => r.Duration.Ticks)),
                 RefreshedCount = resultsList.Sum (r => r.RefreshedCount),
                 LaidOutCount = resultsList.Sum (r => r.LaidOutCount),
                 ClearedContentCount = resultsList.Sum (r => r.ClearedContentCount),
                 DrawCompleteCount = resultsList.Sum (r => r.DrawCompleteCount),
                 UpdatedCount = resultsList.Sum (r => r.UpdatedCount),
-                IterationCount = resultsList.Sum (r => r.IterationCount),
+                IterationCount = resultsList.Sum (r => r.IterationCount)
             };
+
             dt.Rows.Add (
-                        totalRow.Scenario,
-                        totalRow.Duration,
-                        totalRow.RefreshedCount,
-                        totalRow.LaidOutCount,
-                        totalRow.ClearedContentCount,
-                        totalRow.DrawCompleteCount,
-                        totalRow.UpdatedCount,
-                        totalRow.IterationCount
-                       );
+                         totalRow.Scenario,
+                         totalRow.Duration,
+                         totalRow.RefreshedCount,
+                         totalRow.LaidOutCount,
+                         totalRow.ClearedContentCount,
+                         totalRow.DrawCompleteCount,
+                         totalRow.UpdatedCount,
+                         totalRow.IterationCount
+                        );
 
             dt.DefaultView.Sort = "Duration";
             DataTable sortedCopy = dt.DefaultView.ToTable ();
@@ -711,6 +740,7 @@ public class UICatalogApp
                         ),
                     _themeMenuBarItem,
                     new ("Diag_nostics", CreateDiagnosticMenuItems ()),
+                    new ("_Logging", CreateLoggingMenuItems ()),
                     new (
                          "_Help",
                          new MenuItem []
@@ -735,8 +765,8 @@ public class UICatalogApp
                                   "_About...",
                                   "About UI Catalog",
                                   () => MessageBox.Query (
-                                                          title: "",
-                                                          message: GetAboutBoxMessage (),
+                                                          "",
+                                                          GetAboutBoxMessage (),
                                                           wrapMessage: false,
                                                           buttons: "_Ok"
                                                          ),
@@ -760,20 +790,21 @@ public class UICatalogApp
             ShVersion = new ()
             {
                 Title = "Version Info",
-                CanFocus = false,
+                CanFocus = false
             };
 
             var statusBarShortcut = new Shortcut
             {
                 Key = Key.F10,
                 Title = "Show/Hide Status Bar",
-                CanFocus = false,
+                CanFocus = false
             };
+
             statusBarShortcut.Accepting += (sender, args) =>
-                                        {
-                                            _statusBar.Visible = !_statusBar.Visible;
-                                            args.Cancel = true;
-                                        };
+                                           {
+                                               _statusBar.Visible = !_statusBar.Visible;
+                                               args.Cancel = true;
+                                           };
 
             ShForce16Colors = new ()
             {
@@ -790,25 +821,25 @@ public class UICatalogApp
             };
 
             ((CheckBox)ShForce16Colors.CommandView).CheckedStateChanging += (sender, args) =>
-                                                              {
-                                                                  Application.Force16Colors = args.NewValue == CheckState.Checked;
-                                                                  MiForce16Colors!.Checked = Application.Force16Colors;
-                                                                  Application.LayoutAndDraw ();
-                                                              };
+                                                                            {
+                                                                                Application.Force16Colors = args.NewValue == CheckState.Checked;
+                                                                                MiForce16Colors!.Checked = Application.Force16Colors;
+                                                                                Application.LayoutAndDraw ();
+                                                                            };
 
             _statusBar.Add (
-                           new Shortcut
-                           {
-                               CanFocus = false,
-                               Title = "Quit",
-                               Key = Application.QuitKey
-                           },
-                           statusBarShortcut,
-                           ShForce16Colors,
-
-                           //ShDiagnostics,
-                           ShVersion
-                          );
+                            new Shortcut
+                            {
+                                CanFocus = false,
+                                Title = "Quit",
+                                Key = Application.QuitKey
+                            },
+                            statusBarShortcut,
+                            ShForce16Colors,
+
+                            //ShDiagnostics,
+                            ShVersion
+                           );
 
             // Create the Category list view. This list never changes.
             CategoryList = new ()
@@ -816,13 +847,17 @@ public class UICatalogApp
                 X = 0,
                 Y = Pos.Bottom (menuBar),
                 Width = Dim.Auto (),
-                Height = Dim.Fill (Dim.Func (() =>
+                Height = Dim.Fill (
+                                   Dim.Func (
+                                             () =>
                                              {
                                                  if (_statusBar.NeedsLayout)
                                                  {
-                                                       throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+                                                     throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+
                                                      //_statusBar.Layout ();
                                                  }
+
                                                  return _statusBar.Frame.Height;
                                              })),
                 AllowsMarking = false,
@@ -846,21 +881,27 @@ public class UICatalogApp
                 X = Pos.Right (CategoryList) - 1,
                 Y = Pos.Bottom (menuBar),
                 Width = Dim.Fill (),
-                Height = Dim.Fill (Dim.Func (() =>
+                Height = Dim.Fill (
+                                   Dim.Func (
+                                             () =>
                                              {
                                                  if (_statusBar.NeedsLayout)
                                                  {
                                                      throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+
                                                      //_statusBar.Layout ();
                                                  }
+
                                                  return _statusBar.Frame.Height;
                                              })),
+
                 //AllowsMarking = false,
                 CanFocus = true,
                 Title = "_Scenarios",
                 BorderStyle = CategoryList.BorderStyle,
                 SuperViewRendersLineCanvas = true
             };
+
             //ScenarioList.VerticalScrollBar.AutoHide = false;
             //ScenarioList.HorizontalScrollBar.AutoHide = false;
 
@@ -1095,12 +1136,18 @@ public class UICatalogApp
 
                                    if (item.Title == t && item.Checked == false)
                                    {
-                                       _diagnosticFlags &= ~(ViewDiagnosticFlags.Thickness | ViewDiagnosticFlags.Ruler | ViewDiagnosticFlags.Hover | ViewDiagnosticFlags.DrawIndicator);
+                                       _diagnosticFlags &= ~(ViewDiagnosticFlags.Thickness
+                                                             | ViewDiagnosticFlags.Ruler
+                                                             | ViewDiagnosticFlags.Hover
+                                                             | ViewDiagnosticFlags.DrawIndicator);
                                        item.Checked = true;
                                    }
                                    else if (item.Title == t && item.Checked == true)
                                    {
-                                       _diagnosticFlags |= ViewDiagnosticFlags.Thickness | ViewDiagnosticFlags.Ruler | ViewDiagnosticFlags.Hover | ViewDiagnosticFlags.DrawIndicator;
+                                       _diagnosticFlags |= ViewDiagnosticFlags.Thickness
+                                                           | ViewDiagnosticFlags.Ruler
+                                                           | ViewDiagnosticFlags.Hover
+                                                           | ViewDiagnosticFlags.DrawIndicator;
                                        item.Checked = false;
                                    }
                                    else
@@ -1235,6 +1282,65 @@ public class UICatalogApp
             return menuItems;
         }
 
+        private List<MenuItem []> CreateLoggingMenuItems ()
+        {
+            List<MenuItem []> menuItems = new ()
+            {
+                CreateLoggingFlagsMenuItems ()
+            };
+
+            return menuItems;
+        }
+
+        [SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
+        private MenuItem [] CreateLoggingFlagsMenuItems ()
+        {
+            string [] logLevelMenuStrings = Enum.GetNames<LogEventLevel> ().Select (n => n = "_" + n).ToArray ();
+            LogEventLevel [] logLevels = Enum.GetValues<LogEventLevel> ();
+
+            List<MenuItem?> menuItems = new ();
+
+            foreach (LogEventLevel logLevel in logLevels)
+            {
+                var item = new MenuItem
+                {
+                    Title = logLevelMenuStrings [(int)logLevel]
+                };
+                item.CheckType |= MenuItemCheckStyle.Checked;
+                item.Checked = Enum.Parse<LogEventLevel> (_options.DebugLogLevel) == logLevel;
+
+                item.Action += () =>
+                               {
+                                   foreach (MenuItem? menuItem in menuItems.Where (mi => mi is { } && logLevelMenuStrings.Contains (mi.Title)))
+                                   {
+                                       menuItem!.Checked = false;
+                                   }
+
+                                   if (item.Title == logLevelMenuStrings [(int)logLevel] && item.Checked == false)
+                                   {
+                                       _options.DebugLogLevel = Enum.GetName (logLevel)!;
+                                       _logLevelSwitch.MinimumLevel = Enum.Parse<LogEventLevel> (_options.DebugLogLevel);
+                                       item.Checked = true;
+                                   }
+
+                                   Diagnostics = _diagnosticFlags;
+                               };
+                menuItems.Add (item);
+            }
+
+            // add a separator
+            menuItems.Add (null!);
+
+            menuItems.Add (
+                           new ()
+                           {
+                               Title = $"Log file: {_logFilePath}"
+                               //CanExecute = () => false
+                           });
+
+            return menuItems.ToArray ();
+        }
+
         // TODO: This should be an ConfigurationManager setting
         private MenuItem [] CreateDisabledEnabledMenuBorder ()
         {
@@ -1250,8 +1356,8 @@ public class UICatalogApp
                                                  MiIsMenuBorderDisabled.Checked = (bool)!MiIsMenuBorderDisabled.Checked!;
 
                                                  MenuBar!.MenusBorderStyle = !(bool)MiIsMenuBorderDisabled.Checked
-                                                                                ? LineStyle.Single
-                                                                                : LineStyle.None;
+                                                                                 ? LineStyle.Single
+                                                                                 : LineStyle.None;
                                              };
             menuItems.Add (MiIsMenuBorderDisabled);
 
@@ -1284,9 +1390,9 @@ public class UICatalogApp
             MiUseSubMenusSingleFrame = new () { Title = "Enable _Sub-Menus Single Frame" };
 
             MiUseSubMenusSingleFrame.ShortcutKey = KeyCode.CtrlMask
-                                                | KeyCode.AltMask
-                                                | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [
-                                                 0];
+                                                   | KeyCode.AltMask
+                                                   | (KeyCode)MiUseSubMenusSingleFrame!.Title!.Substring (8, 1) [
+                                                    0];
             MiUseSubMenusSingleFrame.CheckType |= MenuItemCheckStyle.Checked;
 
             MiUseSubMenusSingleFrame.Action += () =>
@@ -1367,10 +1473,7 @@ public class UICatalogApp
 
             if (_statusBar is { })
             {
-                _statusBar.VisibleChanged += (s, e) =>
-                                            {
-                                                ShowStatusBar = _statusBar.Visible;
-                                            };
+                _statusBar.VisibleChanged += (s, e) => { ShowStatusBar = _statusBar.Visible; };
             }
 
             Loaded -= LoadedHandler;
@@ -1423,6 +1526,8 @@ public class UICatalogApp
         public bool Benchmark;
 
         public string ResultsFile;
+
+        public string DebugLogLevel;
         /* etc. */
     }
 }

+ 1 - 0
UICatalog/UICatalog.csproj

@@ -33,6 +33,7 @@
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21,2)" />
     <PackageReference Include="Serilog" Version="4.2.0" />
     <PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
+    <PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
     <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
     <PackageReference Include="SixLabors.ImageSharp" Version="[3.1.5,4)" />
     <PackageReference Include="CsvHelper" Version="[33.0.1,34)" />

+ 6 - 6
UnitTests/Configuration/KeyJsonConverterTests.cs

@@ -45,8 +45,8 @@ public class KeyJsonConverterTests
         // Arrange
 
         // Act
-        string json = JsonSerializer.Serialize ((Key)key, ConfigurationManager._serializerOptions);
-        var deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize ((Key)key, ConfigurationManager.SerializerOptions);
+        var deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal (expectedStringTo, deserializedKey.ToString ());
@@ -60,7 +60,7 @@ public class KeyJsonConverterTests
 
         // Act
         string json = "\"Ctrl+Q\"";
-        Key deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager._serializerOptions);
+        Key deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal (key, deserializedKey);
@@ -70,7 +70,7 @@ public class KeyJsonConverterTests
     public void Separator_Property_Serializes_As_Glyph ()
     {
         // Act
-        string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ($"\"{Key.Separator}\"", json);
@@ -85,7 +85,7 @@ public class KeyJsonConverterTests
         {
             // Act
             Key.Separator = (Rune)'*';
-            string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager._serializerOptions);
+            string json = JsonSerializer.Serialize (Key.Separator, ConfigurationManager.SerializerOptions);
 
             // Assert
             Assert.Equal ("\"*\"", json);
@@ -124,7 +124,7 @@ public class KeyJsonConverterTests
             // Act
             Key.Separator = (Rune)separator;
 
-            Key deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager._serializerOptions);
+            Key deserializedKey = JsonSerializer.Deserialize<Key> (json, ConfigurationManager.SerializerOptions);
 
             Key expectedKey = new Key ((KeyCode)keyChar).WithCtrl.WithAlt;
             // Assert

+ 10 - 10
UnitTests/Configuration/RuneJsonConverterTests.cs

@@ -31,13 +31,13 @@ public class RuneJsonConverterTests
     public void RoundTripConversion_Negative (string rune)
     {
         // Act
-        string json = JsonSerializer.Serialize (rune, ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize (rune, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Throws<JsonException> (
                                       () => JsonSerializer.Deserialize<Rune> (
                                                                               json,
-                                                                              ConfigurationManager._serializerOptions
+                                                                              ConfigurationManager.SerializerOptions
                                                                              )
                                      );
     }
@@ -61,8 +61,8 @@ public class RuneJsonConverterTests
         // Arrange
 
         // Act
-        string json = JsonSerializer.Serialize (rune, ConfigurationManager._serializerOptions);
-        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize (rune, ConfigurationManager.SerializerOptions);
+        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal (expected, deserialized.ToString ());
@@ -74,7 +74,7 @@ public class RuneJsonConverterTests
         // Arrange
 
         // Act
-        string json = JsonSerializer.Serialize ((Rune)'a', ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize ((Rune)'a', ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("\"a\"", json);
@@ -86,7 +86,7 @@ public class RuneJsonConverterTests
         // Arrange
 
         // Act
-        string json = JsonSerializer.Serialize ((Rune)0x01, ConfigurationManager._serializerOptions);
+        string json = JsonSerializer.Serialize ((Rune)0x01, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("\"\\u0001\"", json);
@@ -99,7 +99,7 @@ public class RuneJsonConverterTests
         var json = "\"a\"";
 
         // Act
-        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager._serializerOptions);
+        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("a", deserialized.ToString ());
@@ -112,7 +112,7 @@ public class RuneJsonConverterTests
         var json = "\"\\u0061\"";
 
         // Act
-        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager._serializerOptions);
+        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("a", deserialized.ToString ());
@@ -125,7 +125,7 @@ public class RuneJsonConverterTests
         var json = "\"U+0061\"";
 
         // Act
-        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager._serializerOptions);
+        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("a", deserialized.ToString ());
@@ -138,7 +138,7 @@ public class RuneJsonConverterTests
         var json = "97";
 
         // Act
-        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager._serializerOptions);
+        var deserialized = JsonSerializer.Deserialize<Rune> (json, ConfigurationManager.SerializerOptions);
 
         // Assert
         Assert.Equal ("a", deserialized.ToString ());

+ 13 - 13
UnitTests/Dialogs/MessageBoxTests.cs

@@ -475,19 +475,19 @@ public class MessageBoxTests
 
                                          string expectedText = """
                                                                ┌────────────────────────────────────────────────────────────────────┐
-                                                               │    ╔══════════════════════════════════════════════════════════╗    │
-                                                               │          UI Catalog: A comprehensive sample library for      ║    │
-                                                               │    ║                                                          ║    │
-                                                               │    ║ _______                  _             _   _____       _ ║    │
-                                                               │    ║|__   __|                (_)           | | / ____|     (_)║    │
-                                                               │    ║   | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _ ║    │
-                                                               │    ║   | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |║    │
-                                                               │    ║   | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | |║    │
-                                                               │    ║   |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|║    │
-                                                               │    ║                                                          ║    │
-                                                               │    ║                      v2 - Pre-Alpha                      ║    │
-                                                               │    ║                                                  ⟦► Ok ◄⟧║    │
-                                                               │    ╚══════════════════════════════════════════════════════════╝    │
+                                                               │   ╔══════════════════════════════════════════════════════════╗    │
+                                                               │   ║UI Catalog: A comprehensive sample library and test app for║    │
+                                                               │   ║                                                           ║    │
+                                                               │   ║ _______                  _             _   _____       _  ║    │
+                                                               │   ║|__   __|                (_)           | | / ____|     (_) ║    │
+                                                               │   ║   | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ║    │
+                                                               │   ║   | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ║    │
+                                                               │   ║   | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ║    │
+                                                               │   ║   |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ║    │
+                                                               │   ║                                                           ║    │
+                                                               │   ║                      v2 - Pre-Alpha                       ║    │
+                                                               │   ║                                                   ⟦► Ok ◄⟧║    │
+                                                               │   ╚══════════════════════════════════════════════════════════╝    │
                                                                └────────────────────────────────────────────────────────────────────┘
                                                                """;
 

+ 14 - 14
UnitTests/Text/TextFormatterTests.cs

@@ -6188,7 +6188,7 @@ ek")]
         };
 
         Size tfSize = tf.FormatAndGetSize ();
-        Assert.Equal (new (58, 13), tfSize);
+        Assert.Equal (new (59, 13), tfSize);
 
         ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height);
 
@@ -6196,19 +6196,19 @@ ek")]
         tf.Draw (Application.Screen, Attribute.Default, Attribute.Default);
 
         var expectedText = """
-                           ******UI Catalog: A comprehensive sample library for******
-                           **********************************************************
-                            _______                  _             _   _____       _ 
-                           |__   __|                (_)           | | / ____|     (_)
-                              | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _ 
-                              | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
-                              | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | |
-                              |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
-                           **********************************************************
-                           **********************v2 - Pre-Alpha**********************
-                           **********************************************************
-                           **********https://github.com/gui-cs/Terminal.Gui**********
-                           **********************************************************
+                           UI Catalog: A comprehensive sample library and test app for
+                           ***********************************************************
+                            _______                  _             _   _____       _ *
+                           |__   __|                (_)           | | / ____|     (_)*
+                              | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _ *
+                              | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |*
+                              | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | |*
+                              |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|*
+                           ***********************************************************
+                           **********************v2 - Pre-Alpha***********************
+                           ***********************************************************
+                           **********https://github.com/gui-cs/Terminal.Gui***********
+                           ***********************************************************
                            """;
 
         TestHelpers.AssertDriverContentsAre (expectedText.ReplaceLineEndings (), _output);

二进制
local_packages/Terminal.Gui.2.0.0.nupkg


二进制
local_packages/Terminal.Gui.2.0.0.snupkg