Browse Source

Fixes #2629 - Config Manager error logging improvements (#2630)

* Fixes #2629

* Fixed broken unit tests (which were already broken but latent)

* Removed test code
Tig 2 years ago
parent
commit
f3ab1fb1bd

+ 550 - 551
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -13,646 +13,645 @@ using System.Text.Json.Serialization;
 
 #nullable enable
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides settings and configuration management for Terminal.Gui applications. 
-	/// <para>
-	/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration files.
-	/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g. <c>C:/Users/username/.tui</c>, 
-	/// or <c>/usr/username/.tui</c>),
-	/// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
-	/// within the Terminal.Gui application's main assembly. 
-	/// </para>
-	/// <para>
-	/// Settings are defined in JSON format, according to this schema: 
-	///	https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
-	/// </para>
-	/// <para>
-	/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings 
-	/// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
-	/// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
-	/// </para>
-	/// Settings are applied using the following precedence (higher precedence settings
-	/// overwrite lower precedence settings):
-	/// <para>
-	///	1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest precedence 
-	/// </para>
-	/// <para>
-	///	2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
-	/// </para>
-	/// <para>
-	///	3. Application configuration found in the applications's resources (<c>Resources/config.json</c>). 
-	/// </para>
-	/// <para>
-	///	4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
-	/// </para>
-	/// <para>
-	///	5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
-	/// </para>
-	/// <para>
-	///     6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
-	/// </para>
-	/// </summary>
-	public static partial class ConfigurationManager {
-
-		private static readonly string _configFilename = "config.json";
-
-		private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
-			ReadCommentHandling = JsonCommentHandling.Skip,
-			PropertyNameCaseInsensitive = true,
-			DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
-			WriteIndented = true,
-			Converters = {
+namespace Terminal.Gui;
+/// <summary>
+/// Provides settings and configuration management for Terminal.Gui applications. 
+/// <para>
+/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration files.
+/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g. <c>C:/Users/username/.tui</c>, 
+/// or <c>/usr/username/.tui</c>),
+/// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
+/// within the Terminal.Gui application's main assembly. 
+/// </para>
+/// <para>
+/// Settings are defined in JSON format, according to this schema: 
+///	https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+/// </para>
+/// <para>
+/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings 
+/// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
+/// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
+/// </para>
+/// Settings are applied using the following precedence (higher precedence settings
+/// overwrite lower precedence settings):
+/// <para>
+///	1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest precedence 
+/// </para>
+/// <para>
+///	2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
+/// </para>
+/// <para>
+///	3. Application configuration found in the applications's resources (<c>Resources/config.json</c>). 
+/// </para>
+/// <para>
+///	4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
+/// </para>
+/// <para>
+///	5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
+/// </para>
+/// <para>
+///     6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
+/// </para>
+/// </summary>
+public static partial class ConfigurationManager {
+
+	private static readonly string _configFilename = "config.json";
+
+	private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
+		ReadCommentHandling = JsonCommentHandling.Skip,
+		PropertyNameCaseInsensitive = true,
+		DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+		WriteIndented = true,
+		Converters = {
 				// We override the standard Rune converter to support specifying Glyphs in
 				// a flexible way
 				new RuneJsonConverter(),
 			},
-		};
+	};
 
+	/// <summary>
+	/// An attribute that can be applied to a property to indicate that it should included in the configuration file.
+	/// </summary>
+	/// <example>
+	/// 	[SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
+	///	public static LineStyle DefaultBorderStyle {
+	///	...
+	/// </example>
+	[AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+	public class SerializableConfigurationProperty : System.Attribute {
 		/// <summary>
-		/// An attribute that can be applied to a property to indicate that it should included in the configuration file.
+		/// Specifies the scope of the property. 
 		/// </summary>
-		/// <example>
-		/// 	[SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
-		///	public static LineStyle DefaultBorderStyle {
-		///	...
-		/// </example>
-		[AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
-		public class SerializableConfigurationProperty : System.Attribute {
-			/// <summary>
-			/// Specifies the scope of the property. 
-			/// </summary>
-			public Type? Scope { get; set; }
-
-			/// <summary>
-			/// If <see langword="true"/>, the property will be serialized to the configuration file using only the property name
-			/// as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
-			/// property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
-			/// </summary>
-			public bool OmitClassName { get; set; }
+		public Type? Scope { get; set; }
+
+		/// <summary>
+		/// If <see langword="true"/>, the property will be serialized to the configuration file using only the property name
+		/// as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
+		/// property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
+		/// </summary>
+		public bool OmitClassName { get; set; }
+	}
+
+	/// <summary>
+	/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> 
+	/// to get and set the property's value.
+	/// </summary>
+	/// <remarks>
+	/// Configuration properties must be <see langword="public"/> and <see langword="static"/> 
+	/// and have the <see cref="SerializableConfigurationProperty"/>
+	/// attribute. If the type of the property requires specialized JSON serialization, 
+	/// a <see cref="JsonConverter"/> must be provided using 
+	/// the <see cref="JsonConverterAttribute"/> attribute.
+	/// </remarks>
+	public class ConfigProperty {
+		private object? propertyValue;
+
+		/// <summary>
+		/// Describes the property.
+		/// </summary>
+		public PropertyInfo? PropertyInfo { get; set; }
+
+		/// <summary>
+		/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
+		/// or the actual property name.
+		/// </summary>
+		/// <param name="pi"></param>
+		/// <returns></returns>
+		public static string GetJsonPropertyName (PropertyInfo pi)
+		{
+			var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
+			return jpna?.Name ?? pi.Name;
 		}
 
 		/// <summary>
-		/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> 
-		/// to get and set the property's value.
+		/// Holds the property's value as it was either read from the class's implementation or from a config file. 
+		/// If the property has not been set (e.g. because no configuration file specified a value), 
+		/// this will be <see langword="null"/>.
 		/// </summary>
 		/// <remarks>
-		/// Configuration properties must be <see langword="public"/> and <see langword="static"/> 
-		/// and have the <see cref="SerializableConfigurationProperty"/>
-		/// attribute. If the type of the property requires specialized JSON serialization, 
-		/// a <see cref="JsonConverter"/> must be provided using 
-		/// the <see cref="JsonConverterAttribute"/> attribute.
+		/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of 
+		/// the object that are non-null).
 		/// </remarks>
-		public class ConfigProperty {
-			private object? propertyValue;
-
-			/// <summary>
-			/// Describes the property.
-			/// </summary>
-			public PropertyInfo? PropertyInfo { get; set; }
-
-			/// <summary>
-			/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
-			/// or the actual property name.
-			/// </summary>
-			/// <param name="pi"></param>
-			/// <returns></returns>
-			public static string GetJsonPropertyName (PropertyInfo pi)
-			{
-				var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
-				return jpna?.Name ?? pi.Name;
-			}
-
-			/// <summary>
-			/// Holds the property's value as it was either read from the class's implementation or from a config file. 
-			/// If the property has not been set (e.g. because no configuration file specified a value), 
-			/// this will be <see langword="null"/>.
-			/// </summary>
-			/// <remarks>
-			/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of 
-			/// the object that are non-null).
-			/// </remarks>
-			public object? PropertyValue {
-				get => propertyValue;
-				set {
-					propertyValue = value;
-				}
+		public object? PropertyValue {
+			get => propertyValue;
+			set {
+				propertyValue = value;
 			}
+		}
 
-			internal object? UpdateValueFrom (object source)
-			{
-				if (source == null) {
-					return PropertyValue;
-				}
-
-				var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
-				if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
-					throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
-				}
-				if (PropertyValue != null && source != null) {
-					PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
-				} else {
-					PropertyValue = source;
-				}
-
+		internal object? UpdateValueFrom (object source)
+		{
+			if (source == null) {
 				return PropertyValue;
 			}
 
-			/// <summary>
-			/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
-			/// into <see cref="PropertyValue"/>.
-			/// </summary>
-			/// <returns></returns>
-			public object? RetrieveValue ()
-			{
-				return PropertyValue = PropertyInfo!.GetValue (null);
+			var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
+			if (source.GetType () != PropertyInfo!.PropertyType && (ut != null && source.GetType () != ut)) {
+				throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
 			}
-
-			/// <summary>
-			/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
-			/// </summary>
-			/// <returns></returns>
-			public bool Apply ()
-			{
-				if (PropertyValue != null) {
-					PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
-				}
-				return PropertyValue != null;
+			if (PropertyValue != null && source != null) {
+				PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
+			} else {
+				PropertyValue = source;
 			}
-		}
 
-		/// <summary>
-		/// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
-		/// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
-		/// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
-		/// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
-		/// </summary>
-		/// <remarks>
-		/// Is <see langword="null"/> until <see cref="Initialize"/> is called. 
-		/// </remarks>
-		private static Dictionary<string, ConfigProperty>? _allConfigProperties;
+			return PropertyValue;
+		}
 
 		/// <summary>
-		/// The backing property for <see cref="Settings"/>. 
+		/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
+		/// into <see cref="PropertyValue"/>.
 		/// </summary>
-		/// <remarks>
-		/// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
-		/// deserialization (see <see cref="Load"/>).
-		/// </remarks>
-		private static SettingsScope? _settings;
+		/// <returns></returns>
+		public object? RetrieveValue ()
+		{
+			return PropertyValue = PropertyInfo!.GetValue (null);
+		}
 
 		/// <summary>
-		/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the <see cref="SettingsScope"/>
-		/// attribute value.
+		/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
 		/// </summary>
-		public static SettingsScope? Settings {
-			get {
-				if (_settings == null) {
-					throw new InvalidOperationException ("ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property.");
-				}
-				return _settings;
+		/// <returns></returns>
+		public bool Apply ()
+		{
+			if (PropertyValue != null) {
+				PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
 			}
-			set {
-				_settings = value!;
+			return PropertyValue != null;
+		}
+	}
+
+	/// <summary>
+	/// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
+	/// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
+	/// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
+	/// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
+	/// </summary>
+	/// <remarks>
+	/// Is <see langword="null"/> until <see cref="Initialize"/> is called. 
+	/// </remarks>
+	private static Dictionary<string, ConfigProperty>? _allConfigProperties;
+
+	/// <summary>
+	/// The backing property for <see cref="Settings"/>. 
+	/// </summary>
+	/// <remarks>
+	/// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
+	/// deserialization (see <see cref="Load"/>).
+	/// </remarks>
+	private static SettingsScope? _settings;
+
+	/// <summary>
+	/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the <see cref="SettingsScope"/>
+	/// attribute value.
+	/// </summary>
+	public static SettingsScope? Settings {
+		get {
+			if (_settings == null) {
+				throw new InvalidOperationException ("ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property.");
 			}
+			return _settings;
 		}
+		set {
+			_settings = value!;
+		}
+	}
 
-		/// <summary>
-		/// The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
-		/// attribute value.
-		/// </summary>
-		public static ThemeManager? Themes => ThemeManager.Instance;
+	/// <summary>
+	/// The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
+	/// attribute value.
+	/// </summary>
+	public static ThemeManager? Themes => ThemeManager.Instance;
 
-		/// <summary>
-		/// Application-specific configuration settings scope.
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
-		public static AppScope? AppSettings { get; set; }
+	/// <summary>
+	/// Application-specific configuration settings scope.
+	/// </summary>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
+	public static AppScope? AppSettings { get; set; }
 
-		/// <summary>
-		/// The set of glyphs used to draw checkboxes, lines, borders, etc...See also <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true),
-			JsonPropertyName ("Glyphs")]
-		public static GlyphDefinitions Glyphs { get; set; } = new GlyphDefinitions ();
-		
-		/// <summary>
-		/// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
-		/// startup to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
-		/// </summary>
-		internal static void Initialize ()
-		{
-			_allConfigProperties = new Dictionary<string, ConfigProperty> ();
-			_settings = null;
+	/// <summary>
+	/// The set of glyphs used to draw checkboxes, lines, borders, etc...See also <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
+	/// </summary>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true),
+		JsonPropertyName ("Glyphs")]
+	public static GlyphDefinitions Glyphs { get; set; } = new GlyphDefinitions ();
 
-			Dictionary<string, Type> classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
-			// Get Terminal.Gui.dll classes
+	/// <summary>
+	/// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
+	/// startup to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
+	/// </summary>
+	internal static void Initialize ()
+	{
+		_allConfigProperties = new Dictionary<string, ConfigProperty> ();
+		_settings = null;
 
-			var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
-				    from type in assembly.GetTypes ()
-				    where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
-				    select type;
+		Dictionary<string, Type> classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
+		// Get Terminal.Gui.dll classes
 
-			foreach (var classWithConfig in types) {
-				classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
-			}
+		var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
+			    from type in assembly.GetTypes ()
+			    where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
+			    select type;
+
+		foreach (var classWithConfig in types) {
+			classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
+		}
 
-			Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
-			classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
-
-			foreach (var p in from c in classesWithConfigProps
-					  let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
-						prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
-					  let enumerable = props
-					  from p in enumerable
-					  select p) {
-				if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
-					if (p.GetGetMethod (true)!.IsStatic) {
-						// If the class name is omitted, JsonPropertyName is allowed. 
-						_allConfigProperties!.Add (scp.OmitClassName ? ConfigProperty.GetJsonPropertyName (p) : $"{p.DeclaringType?.Name}.{p.Name}", new ConfigProperty {
-							PropertyInfo = p,
-							PropertyValue = null
-						});
-					} else {
-						throw new Exception ($"Property {p.Name} in class {p.DeclaringType?.Name} is not static. All SerializableConfigurationProperty properties must be static.");
-					}
+		Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
+		classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
+
+		foreach (var p in from c in classesWithConfigProps
+				  let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
+					prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
+				  let enumerable = props
+				  from p in enumerable
+				  select p) {
+			if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
+				if (p.GetGetMethod (true)!.IsStatic) {
+					// If the class name is omitted, JsonPropertyName is allowed. 
+					_allConfigProperties!.Add (scp.OmitClassName ? ConfigProperty.GetJsonPropertyName (p) : $"{p.DeclaringType?.Name}.{p.Name}", new ConfigProperty {
+						PropertyInfo = p,
+						PropertyValue = null
+					});
+				} else {
+					throw new Exception ($"Property {p.Name} in class {p.DeclaringType?.Name} is not static. All SerializableConfigurationProperty properties must be static.");
 				}
 			}
+		}
 
-			_allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key).ToDictionary (x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);
+		_allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key).ToDictionary (x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);
 
-			Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
-			//_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($"  Property: {x.Key}"));
+		Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
+		//_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($"  Property: {x.Key}"));
 
-			AppSettings = new AppScope ();
-		}
+		AppSettings = new AppScope ();
+	}
 
-		/// <summary>
-		/// Creates a JSON document with the configuration specified. 
-		/// </summary>
-		/// <returns></returns>
-		internal static string ToJson ()
-		{
-			Debug.WriteLine ($"ConfigurationManager.ToJson()");
-			return JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
-		}
+	/// <summary>
+	/// Creates a JSON document with the configuration specified. 
+	/// </summary>
+	/// <returns></returns>
+	internal static string ToJson ()
+	{
+		Debug.WriteLine ($"ConfigurationManager.ToJson()");
+		return JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
+	}
 
-		internal static Stream ToStream ()
-		{
-			var json = JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
-			// turn it into a stream
-			var stream = new MemoryStream ();
-			var writer = new StreamWriter (stream);
-			writer.Write (json);
-			writer.Flush ();
-			stream.Position = 0;
-			return stream;
-		}
+	internal static Stream ToStream ()
+	{
+		var json = JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
+		// turn it into a stream
+		var stream = new MemoryStream ();
+		var writer = new StreamWriter (stream);
+		writer.Write (json);
+		writer.Flush ();
+		stream.Position = 0;
+		return stream;
+	}
 
-		/// <summary>
-		/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters 
-		/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the 
-		/// console when <see cref="Application.Shutdown"/> is called. 
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static bool? ThrowOnJsonErrors { get; set; } = false;
+	/// <summary>
+	/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters 
+	/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the 
+	/// console when <see cref="Application.Shutdown"/> is called. 
+	/// </summary>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static bool? ThrowOnJsonErrors { get; set; } = false;
 
-		internal static StringBuilder jsonErrors = new StringBuilder ();
+	internal static StringBuilder jsonErrors = new StringBuilder ();
 
-		private static void AddJsonError (string error)
-		{
-			Debug.WriteLine ($"ConfigurationManager: {error}");
-			jsonErrors.AppendLine (error);
-		}
+	private static void AddJsonError (string error)
+	{
+		Debug.WriteLine ($"ConfigurationManager: {error}");
+		jsonErrors.AppendLine (error);
+	}
 
-		/// <summary>
-		/// Prints any Json deserialization errors that occurred during deserialization to the console.
-		/// </summary>
-		public static void PrintJsonErrors ()
-		{
-			if (jsonErrors.Length > 0) {
-				Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
-				Console.WriteLine (jsonErrors.ToString ());
-			}
+	/// <summary>
+	/// Prints any Json deserialization errors that occurred during deserialization to the console.
+	/// </summary>
+	public static void PrintJsonErrors ()
+	{
+		if (jsonErrors.Length > 0) {
+			Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
+			Console.WriteLine (jsonErrors.ToString ());
 		}
+	}
 
-		private static void ClearJsonErrors ()
-		{
-			jsonErrors.Clear ();
-		}
+	private static void ClearJsonErrors ()
+	{
+		jsonErrors.Clear ();
+	}
 
-		/// <summary>
-		/// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
-		/// event.
-		/// </summary>
-		public static void OnUpdated ()
-		{
-			Debug.WriteLine ($"ConfigurationManager.OnApplied()");
-			Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
-		}
+	/// <summary>
+	/// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
+	/// event.
+	/// </summary>
+	public static void OnUpdated ()
+	{
+		Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+		Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
+	}
 
-		/// <summary>
-		/// Event fired when the configuration has been updated from a configuration source.  
-		/// application.
-		/// </summary>
-		public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
+	/// <summary>
+	/// Event fired when the configuration has been updated from a configuration source.  
+	/// application.
+	/// </summary>
+	public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
 
-		/// <summary>
-		/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
-		/// (e.g. in <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/> starts. Called by <see cref="Load"/>
-		/// if the <c>reset</c> parameter is <see langword="true"/>.
-		/// </summary>
-		/// <remarks>
-		/// 
-		/// </remarks>
-		public static void Reset ()
-		{
-			Debug.WriteLine ($"ConfigurationManager.Reset()");
-			if (_allConfigProperties == null) {
-				ConfigurationManager.Initialize ();
-			}
+	/// <summary>
+	/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
+	/// (e.g. in <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/> starts. Called by <see cref="Load"/>
+	/// if the <c>reset</c> parameter is <see langword="true"/>.
+	/// </summary>
+	/// <remarks>
+	/// 
+	/// </remarks>
+	public static void Reset ()
+	{
+		Debug.WriteLine ($"ConfigurationManager.Reset()");
+		if (_allConfigProperties == null) {
+			ConfigurationManager.Initialize ();
+		}
 
-			ClearJsonErrors ();
+		ClearJsonErrors ();
 
-			Settings = new SettingsScope ();
-			ThemeManager.Reset ();
-			AppSettings = new AppScope ();
+		Settings = new SettingsScope ();
+		ThemeManager.Reset ();
+		AppSettings = new AppScope ();
 
-			// To enable some unit tests, we only load from resources if the flag is set
-			if (Locations.HasFlag (ConfigLocations.DefaultOnly)) {
-				Settings.UpdateFromResource (typeof (ConfigurationManager).Assembly, $"Terminal.Gui.Resources.{_configFilename}");
-			}
-			Apply ();
-			ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
-			AppSettings?.Apply ();
+		// To enable some unit tests, we only load from resources if the flag is set
+		if (Locations.HasFlag (ConfigLocations.DefaultOnly)) {
+			Settings.UpdateFromResource (typeof (ConfigurationManager).Assembly, $"Terminal.Gui.Resources.{_configFilename}");
 		}
+		Apply ();
+		ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
+		AppSettings?.Apply ();
+	}
 
-		/// <summary>
-		/// Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
-		/// the library to generate the default configuration file. Before calling Application.Init, make sure
-		/// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// This method is only really useful when using ConfigurationManagerTests
-		/// to generate the JSON doc that is embedded into Terminal.Gui (during development). 
-		/// </para>
-		/// <para>
-		/// WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
-		/// that are NOT generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
-		/// make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
-		/// </para>		
-		/// </remarks>
-		internal static void GetHardCodedDefaults ()
-		{
-			if (_allConfigProperties == null) {
-				throw new InvalidOperationException ("Initialize must be called first.");
-			}
-			Settings = new SettingsScope ();
-			ThemeManager.GetHardCodedDefaults ();
-			AppSettings?.RetrieveValues ();
-			foreach (var p in Settings!.Where (cp => cp.Value.PropertyInfo != null)) {
-				Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
-			}
+	/// <summary>
+	/// Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
+	/// the library to generate the default configuration file. Before calling Application.Init, make sure
+	/// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// This method is only really useful when using ConfigurationManagerTests
+	/// to generate the JSON doc that is embedded into Terminal.Gui (during development). 
+	/// </para>
+	/// <para>
+	/// WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
+	/// that are NOT generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
+	/// make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
+	/// </para>		
+	/// </remarks>
+	internal static void GetHardCodedDefaults ()
+	{
+		if (_allConfigProperties == null) {
+			throw new InvalidOperationException ("Initialize must be called first.");
 		}
-
-		/// <summary>
-		/// Applies the configuration settings to the running <see cref="Application"/> instance.
-		/// </summary>
-		public static void Apply ()
-		{
-			bool settings = Settings?.Apply () ?? false;
-			bool themes = !string.IsNullOrEmpty(ThemeManager.SelectedTheme) && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
-			bool appsettings = AppSettings?.Apply () ?? false;
-			if (settings || themes || appsettings) {
-				OnApplied ();
-			}
+		Settings = new SettingsScope ();
+		ThemeManager.GetHardCodedDefaults ();
+		AppSettings?.RetrieveValues ();
+		foreach (var p in Settings!.Where (cp => cp.Value.PropertyInfo != null)) {
+			Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
 		}
+	}
 
-		/// <summary>
-		/// Called when an updated configuration has been applied to the  
-		/// application. Fires the <see cref="Applied"/> event.
-		/// </summary>
-		public static void OnApplied ()
-		{
-			Debug.WriteLine ($"ConfigurationManager.OnApplied()");
-			Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
+	/// <summary>
+	/// Applies the configuration settings to the running <see cref="Application"/> instance.
+	/// </summary>
+	public static void Apply ()
+	{
+		bool settings = Settings?.Apply () ?? false;
+		bool themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme) && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+		bool appsettings = AppSettings?.Apply () ?? false;
+		if (settings || themes || appsettings) {
+			OnApplied ();
 		}
+	}
 
-		/// <summary>
-		/// Event fired when an updated configuration has been applied to the  
-		/// application.
-		/// </summary>
-		public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
+	/// <summary>
+	/// Called when an updated configuration has been applied to the  
+	/// application. Fires the <see cref="Applied"/> event.
+	/// </summary>
+	public static void OnApplied ()
+	{
+		Debug.WriteLine ($"ConfigurationManager.OnApplied()");
+		Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
+	}
 
-		/// <summary>
-		/// Name of the running application. By default this property is set to the application's assembly name.
-		/// </summary>
-		public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
+	/// <summary>
+	/// Event fired when an updated configuration has been applied to the  
+	/// application.
+	/// </summary>
+	public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
 
+	/// <summary>
+	/// Name of the running application. By default this property is set to the application's assembly name.
+	/// </summary>
+	public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
+
+	/// <summary>
+	/// Describes the location of the configuration files. The constants can be
+	/// combined (bitwise) to specify multiple locations.
+	/// </summary>
+	[Flags]
+	public enum ConfigLocations {
 		/// <summary>
-		/// Describes the location of the configuration files. The constants can be
-		/// combined (bitwise) to specify multiple locations.
+		/// No configuration will be loaded.
 		/// </summary>
-		[Flags]
-		public enum ConfigLocations {
-			/// <summary>
-			/// No configuration will be loaded.
-			/// </summary>
-			/// <remarks>
-			///  Used for development and testing only. For Terminal,Gui to function properly, at least
-			///  <see cref="DefaultOnly"/> should be set.
-			/// </remarks>
-			None = 0,
-
-			/// <summary>
-			/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
-			/// </summary>
-			DefaultOnly,
-
-			/// <summary>
-			/// This constant is a combination of all locations
-			/// </summary>
-			All = -1
-
-		}
+		/// <remarks>
+		///  Used for development and testing only. For Terminal,Gui to function properly, at least
+		///  <see cref="DefaultOnly"/> should be set.
+		/// </remarks>
+		None = 0,
 
 		/// <summary>
-		/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
-		/// The value is <see cref="ConfigLocations.All"/>.
+		/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
 		/// </summary>
-		public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
+		DefaultOnly,
 
 		/// <summary>
-		/// Loads all settings found in the various configuration storage locations to 
-		/// the <see cref="ConfigurationManager"/>. Optionally,
-		/// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults.
+		/// This constant is a combination of all locations
 		/// </summary>
-		/// <remarks>
-		/// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
-		/// </remarks>
-		/// <param name="reset">If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
-		/// be reset to the defaults.</param>
-		public static void Load (bool reset = false)
-		{
-			Debug.WriteLine ($"ConfigurationManager.Load()");
+		All = -1
 
-			if (reset) Reset ();
+	}
 
-			// LibraryResources is always loaded by Reset
-			if (Locations == ConfigLocations.All) {
-				var embeddedStylesResourceName = Assembly.GetEntryAssembly ()?
-					.GetManifestResourceNames ().FirstOrDefault (x => x.EndsWith (_configFilename));
-				if (string.IsNullOrEmpty (embeddedStylesResourceName)) {
-					embeddedStylesResourceName = _configFilename;
-				}
+	/// <summary>
+	/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
+	/// The value is <see cref="ConfigLocations.All"/>.
+	/// </summary>
+	public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
 
-				Settings = Settings?
-					// Global current directory
-					.Update ($"./.tui/{_configFilename}")?
-					// Global home directory
-					.Update ($"~/.tui/{_configFilename}")?
-					// App resources
-					.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)?
-					// App current directory
-					.Update ($"./.tui/{AppName}.{_configFilename}")?
-					// App home directory
-					.Update ($"~/.tui/{AppName}.{_configFilename}");
+	/// <summary>
+	/// Loads all settings found in the various configuration storage locations to 
+	/// the <see cref="ConfigurationManager"/>. Optionally,
+	/// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults.
+	/// </summary>
+	/// <remarks>
+	/// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
+	/// </remarks>
+	/// <param name="reset">If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
+	/// be reset to the defaults.</param>
+	public static void Load (bool reset = false)
+	{
+		Debug.WriteLine ($"ConfigurationManager.Load()");
+
+		if (reset) Reset ();
+
+		// LibraryResources is always loaded by Reset
+		if (Locations == ConfigLocations.All) {
+			var embeddedStylesResourceName = Assembly.GetEntryAssembly ()?
+				.GetManifestResourceNames ().FirstOrDefault (x => x.EndsWith (_configFilename));
+			if (string.IsNullOrEmpty (embeddedStylesResourceName)) {
+				embeddedStylesResourceName = _configFilename;
 			}
-		}
 
-		/// <summary>
-		/// Returns an empty Json document with just the $schema tag.
-		/// </summary>
-		/// <returns></returns>
-		public static string GetEmptyJson ()
-		{
-			var emptyScope = new SettingsScope ();
-			emptyScope.Clear ();
-			return JsonSerializer.Serialize<SettingsScope> (emptyScope, _serializerOptions);
+			Settings = Settings?
+				// Global current directory
+				.Update ($"./.tui/{_configFilename}")?
+				// Global home directory
+				.Update ($"~/.tui/{_configFilename}")?
+				// App resources
+				.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)?
+				// App current directory
+				.Update ($"./.tui/{AppName}.{_configFilename}")?
+				// App home directory
+				.Update ($"~/.tui/{AppName}.{_configFilename}");
 		}
+	}
 
-		/// <summary>
-		/// System.Text.Json does not support copying a deserialized object to an existing instance.
-		/// To work around this, we implement a 'deep, memberwise copy' method. 
-		/// </summary>
-		/// <remarks>
-		/// TOOD: When System.Text.Json implements `PopulateObject` revisit
-		///	https://github.com/dotnet/corefx/issues/37627
-		/// </remarks>
-		/// <param name="source"></param>
-		/// <param name="destination"></param>
-		/// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
-		internal static object? DeepMemberwiseCopy (object? source, object? destination)
-		{
-			if (destination == null) {
-				throw new ArgumentNullException (nameof (destination));
-			}
+	/// <summary>
+	/// Returns an empty Json document with just the $schema tag.
+	/// </summary>
+	/// <returns></returns>
+	public static string GetEmptyJson ()
+	{
+		var emptyScope = new SettingsScope ();
+		emptyScope.Clear ();
+		return JsonSerializer.Serialize<SettingsScope> (emptyScope, _serializerOptions);
+	}
 
-			if (source == null) {
-				return null!;
-			}
+	/// <summary>
+	/// System.Text.Json does not support copying a deserialized object to an existing instance.
+	/// To work around this, we implement a 'deep, memberwise copy' method. 
+	/// </summary>
+	/// <remarks>
+	/// TOOD: When System.Text.Json implements `PopulateObject` revisit
+	///	https://github.com/dotnet/corefx/issues/37627
+	/// </remarks>
+	/// <param name="source"></param>
+	/// <param name="destination"></param>
+	/// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
+	internal static object? DeepMemberwiseCopy (object? source, object? destination)
+	{
+		if (destination == null) {
+			throw new ArgumentNullException (nameof (destination));
+		}
 
-			if (source.GetType () == typeof (SettingsScope)) {
-				return ((SettingsScope)destination).Update ((SettingsScope)source);
-			}
-			if (source.GetType () == typeof (ThemeScope)) {
-				return ((ThemeScope)destination).Update ((ThemeScope)source);
-			}
-			if (source.GetType () == typeof (AppScope)) {
-				return ((AppScope)destination).Update ((AppScope)source);
-			}
+		if (source == null) {
+			return null!;
+		}
 
-			// If value type, just use copy constructor.
-			if (source.GetType ().IsValueType || source.GetType () == typeof (string)) {
-				return source;
-			}
+		if (source.GetType () == typeof (SettingsScope)) {
+			return ((SettingsScope)destination).Update ((SettingsScope)source);
+		}
+		if (source.GetType () == typeof (ThemeScope)) {
+			return ((ThemeScope)destination).Update ((ThemeScope)source);
+		}
+		if (source.GetType () == typeof (AppScope)) {
+			return ((AppScope)destination).Update ((AppScope)source);
+		}
+
+		// If value type, just use copy constructor.
+		if (source.GetType ().IsValueType || source.GetType () == typeof (string)) {
+			return source;
+		}
 
-			// Dictionary
-			if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
-				foreach (var srcKey in ((IDictionary)source).Keys) {
-					if (((IDictionary)destination).Contains (srcKey))
-						((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
-					else {
-						((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
-					}
+		// Dictionary
+		if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
+			foreach (var srcKey in ((IDictionary)source).Keys) {
+				if (((IDictionary)destination).Contains (srcKey))
+					((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
+				else {
+					((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
 				}
-				return destination;
 			}
+			return destination;
+		}
 
-			// ALl other object types
-			var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
-			var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
-			foreach (var (sourceProp, destProp) in
-				from sourceProp in sourceProps
-				where destProps.Any (x => x.Name == sourceProp.Name)
-				let destProp = destProps.First (x => x.Name == sourceProp.Name)
-				where destProp.CanWrite
-				select (sourceProp, destProp)) {
-
-				var sourceVal = sourceProp.GetValue (source);
-				var destVal = destProp.GetValue (destination);
-				if (sourceVal != null) {
-					if (destVal != null) {
-						// Recurse
-						destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
-					} else {
-						destProp.SetValue (destination, sourceVal);
-					}
+		// ALl other object types
+		var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
+		var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
+		foreach (var (sourceProp, destProp) in
+			from sourceProp in sourceProps
+			where destProps.Any (x => x.Name == sourceProp.Name)
+			let destProp = destProps.First (x => x.Name == sourceProp.Name)
+			where destProp.CanWrite
+			select (sourceProp, destProp)) {
+
+			var sourceVal = sourceProp.GetValue (source);
+			var destVal = destProp.GetValue (destination);
+			if (sourceVal != null) {
+				if (destVal != null) {
+					// Recurse
+					destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
+				} else {
+					destProp.SetValue (destination, sourceVal);
 				}
 			}
-			return destination!;
 		}
-
-		//public class ConfiguraitonLocation
-		//{
-		//	public string Name { get; set; } = string.Empty;
-
-		//	public string? Path { get; set; }
-
-		//	public async Task<SettingsScope> UpdateAsync (Stream stream)
-		//	{
-		//		var scope = await JsonSerializer.DeserializeAsync<SettingsScope> (stream, serializerOptions);
-		//		if (scope != null) {
-		//			ConfigurationManager.Settings?.UpdateFrom (scope);
-		//			return scope;
-		//		}
-		//		return new SettingsScope ();
-		//	}
-
-		//}
-
-		//public class StreamConfiguration {
-		//	private bool _reset;
-
-		//	public StreamConfiguration (bool reset)
-		//	{
-		//		_reset = reset;
-		//	}
-
-		//	public StreamConfiguration UpdateAppResources ()
-		//	{
-		//		if (Locations.HasFlag (ConfigLocations.AppResources)) LoadAppResources ();
-		//		return this;
-		//	}
-
-		//	public StreamConfiguration UpdateAppDirectory ()
-		//	{
-		//		if (Locations.HasFlag (ConfigLocations.AppDirectory)) LoadAppDirectory ();
-		//		return this;
-		//	}
-
-		//	// Additional update methods for each location here
-
-		//	private void LoadAppResources ()
-		//	{
-		//		// Load AppResources logic here
-		//	}
-
-		//	private void LoadAppDirectory ()
-		//	{
-		//		// Load AppDirectory logic here
-		//	}
-		//}
+		return destination!;
 	}
+
+	//public class ConfiguraitonLocation
+	//{
+	//	public string Name { get; set; } = string.Empty;
+
+	//	public string? Path { get; set; }
+
+	//	public async Task<SettingsScope> UpdateAsync (Stream stream)
+	//	{
+	//		var scope = await JsonSerializer.DeserializeAsync<SettingsScope> (stream, serializerOptions);
+	//		if (scope != null) {
+	//			ConfigurationManager.Settings?.UpdateFrom (scope);
+	//			return scope;
+	//		}
+	//		return new SettingsScope ();
+	//	}
+
+	//}
+
+	//public class StreamConfiguration {
+	//	private bool _reset;
+
+	//	public StreamConfiguration (bool reset)
+	//	{
+	//		_reset = reset;
+	//	}
+
+	//	public StreamConfiguration UpdateAppResources ()
+	//	{
+	//		if (Locations.HasFlag (ConfigLocations.AppResources)) LoadAppResources ();
+	//		return this;
+	//	}
+
+	//	public StreamConfiguration UpdateAppDirectory ()
+	//	{
+	//		if (Locations.HasFlag (ConfigLocations.AppDirectory)) LoadAppDirectory ();
+	//		return this;
+	//	}
+
+	//	// Additional update methods for each location here
+
+	//	private void LoadAppResources ()
+	//	{
+	//		// Load AppResources logic here
+	//	}
+
+	//	private void LoadAppDirectory ()
+	//	{
+	//		// Load AppDirectory logic here
+	//	}
+	//}
 }

+ 4 - 0
Terminal.Gui/Configuration/DictionaryJsonConverter.cs

@@ -7,6 +7,10 @@ namespace Terminal.Gui {
 	class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>> {
 		public override Dictionary<string, T> Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 		{
+			if (reader.TokenType != JsonTokenType.StartArray) {
+				throw new JsonException ($"Expected a JSON array (\"[ {{ ... }} ]\"), but got \"{reader.TokenType}\".");
+			}
+
 			var dictionary = new Dictionary<string, T> ();
 			while (reader.Read ()) {
 				if (reader.TokenType == JsonTokenType.StartObject) {

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

@@ -100,7 +100,7 @@ namespace Terminal.Gui {
 			public override scopeT Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 			{
 				if (reader.TokenType != JsonTokenType.StartObject) {
-					throw new JsonException ($"Expected a JSON object, but got \"{reader.TokenType}\".");
+					throw new JsonException ($"Expected a JSON object (\"{{ \"propName\" : ... }}\"), but got \"{reader.TokenType}\".");
 				}
 
 				var scope = (scopeT)Activator.CreateInstance (typeof (scopeT))!;

+ 5 - 0
Terminal.Gui/Resources/config.json

@@ -11,6 +11,11 @@
   // null).
   //
   "$schema": "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
+
+  // Set this to true in a .config file to be loaded to cause JSON parsing errors
+  // to throw exceptions. 
+  "ConfigurationManager.ThrowOnJsonErrors": false,
+
   "Application.AlternateBackwardKey": {
     "Key": "PageUp",
     "Modifiers": [

+ 9 - 21
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -544,8 +544,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// "yellow" is not a color
 			string json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" : [
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -560,8 +559,7 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
-				}
+				]
 			}";
 
 			JsonException jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
@@ -570,8 +568,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// AbNormal is not a ColorScheme attribute
 			json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" : [ 
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -586,8 +583,7 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
-				}
+				]
 			}";
 
 			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
@@ -596,8 +592,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// Modify hotNormal background only 
 			json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" : [ 
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -611,8 +606,7 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
-				}
+				]
 			}";
 
 			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
@@ -641,8 +635,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// "yellow" is not a color
 			string json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" : [ 
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -657,7 +650,6 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
 				}
 			}";
 
@@ -666,8 +658,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// AbNormal is not a ColorScheme attribute
 			json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" : [ 
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -682,7 +673,6 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
 				}
 			}";
 
@@ -691,8 +681,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// Modify hotNormal background only 
 			json = @"
 			{
-				""Themes"" : {
-					""ThemeDefinitions"" : [ 
+				""Themes"" :  [ 
                                         {
 						""Default"" : {
 							""ColorSchemes"": [
@@ -706,7 +695,6 @@ namespace Terminal.Gui.ConfigurationTests {
 							]
 						}
 					}
-					]
 				}
 			}";