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
This commit is contained in:
Tig
2025-02-28 15:06:01 -07:00
committed by GitHub
parent c88c772462
commit 79cd4e92b7
20 changed files with 396 additions and 194 deletions

2
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -89,11 +89,11 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
try try
{ {
scope! [propertyName].PropertyValue = scope! [propertyName].PropertyValue =
JsonSerializer.Deserialize (ref reader, propertyType!, _serializerContext); JsonSerializer.Deserialize (ref reader, propertyType!, SerializerContext);
} }
catch (Exception ex) 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 { }) if (property is { })
{ {
PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!; 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 else
{ {
@@ -165,7 +165,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
{ {
writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p)); writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
object? prop = scope.GetType ().GetProperty (p.Name)?.GetValue (scope); 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 foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
@@ -211,7 +211,7 @@ internal class ScopeJsonConverter<[DynamicallyAccessedMembers (DynamicallyAccess
else else
{ {
object? prop = p.Value.PropertyValue; object? prop = p.Value.PropertyValue;
JsonSerializer.Serialize (writer, prop, prop!.GetType (), _serializerContext); JsonSerializer.Serialize (writer, prop, prop!.GetType (), SerializerContext);
} }
} }

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ namespace Terminal.Gui;
/// <remarks> /// <remarks>
/// Also contains the /// Also contains the
/// <see cref="Meter"/> instance that should be used for internal metrics /// <see cref="Meter"/> instance that should be used for internal metrics
/// (iteration timing etc). /// (iteration timing etc.).
/// </remarks> /// </remarks>
public static class Logging public static class Logging
{ {
@@ -51,7 +51,71 @@ public static class Logging
public static readonly Histogram<int> DrainInputStream = Meter.CreateHistogram<int> ("Drain Input (ms)"); public static readonly Histogram<int> DrainInputStream = Meter.CreateHistogram<int> ("Drain Input (ms)");
/// <summary> /// <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> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <param name="caller"></param> /// <param name="caller"></param>
@@ -65,4 +129,20 @@ public static class Logging
string className = Path.GetFileNameWithoutExtension (filePath); string className = Path.GetFileNameWithoutExtension (filePath);
Logger.LogTrace ($"[{className}] [{caller}] {message}"); 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}");
}
} }

View File

@@ -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/=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/=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/=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: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: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> <s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileA92AB40A0394A342A0BE0D83FEEA5DBF/@KeyIndexDefined">True</s:Boolean>

View File

@@ -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 (); Application.RequestStop ();

View File

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

View File

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

View File

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

View File

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

View File

@@ -475,19 +475,19 @@ public class MessageBoxTests
string expectedText = """ string expectedText = """
UI Catalog: A comprehensive sample library for UI Catalog: A comprehensive sample library and test app for
_______ _ _ _____ _ _______ _ _ _____ _
|__ __| (_) | | / ____| (_) |__ __| (_) | | / ____| (_)
| | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ | | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
| | __/ | | | | | | | | | | | (_| | || |__| | |_| | | | | __/ | | | | | | | | | | | (_| | || |__| | |_| | |
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| |_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
v2 - Pre-Alpha v2 - Pre-Alpha
Ok Ok
"""; """;

View File

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