using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace Terminal.Gui.Configuration; /// /// Holds the s that define the s that are used by views to /// render /// themselves. A Scheme is a mapping from s (such as /// ) to s. /// A Scheme defines how a `View` should look based on its purpose (e.g. Menu or Dialog). /// public sealed class SchemeManager // : INotifyCollectionChanged, IDictionary { #pragma warning disable IDE1006 // Naming Styles private static readonly object _schemesLock = new (); #pragma warning restore IDE1006 // Naming Styles /// /// INTERNAL: Gets the hard-coded schemes defined by . These are not loaded from the configuration /// files, /// but are hard-coded in the source code. Used for unit testing when ConfigurationManager is not initialized. /// /// internal static ImmutableSortedDictionary? GetHardCodedSchemes () { return Scheme.GetHardCodedSchemes ()!; } /// /// Use , , , /// , etc... instead. /// [ConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)] [JsonConverter (typeof (DictionaryJsonConverter))] [UsedImplicitly] public static Dictionary? Schemes { get => GetSchemes (); [RequiresUnreferencedCode ("Calls Terminal.Gui.SchemeManager.SetSchemes(Dictionary)")] [RequiresDynamicCode ("Calls Terminal.Gui.SchemeManager.SetSchemes(Dictionary)")] private set => SetSchemes (value); } /// INTERNAL: Gets the dictionary of defined s. The get method for . internal static Dictionary GetSchemes () { if (!ConfigurationManager.IsInitialized ()) { // We're being called from the module initializer. // Hard coded default value return GetHardCodedSchemes ()!.ToDictionary (StringComparer.InvariantCultureIgnoreCase); } return GetSchemesForCurrentTheme (); } /// INTERNAL: The set method for . [RequiresUnreferencedCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")] [RequiresDynamicCode ("Calls Terminal.Gui.ConfigProperty.UpdateFrom(Object)")] internal static void SetSchemes (Dictionary? value) { lock (_schemesLock) { if (!ConfigurationManager.IsInitialized ()) { throw new InvalidOperationException ("Schemes cannot be set before ConfigurationManager is initialized."); } Debug.Assert (value is { }); // Update the backing store ThemeManager.GetCurrentTheme () ["Schemes"].UpdateFrom (value); } //Instance.OnThemeChanged (prevousValue); } /// /// Adds a new to . If the Scheme has already been added, /// it will be updated to . /// /// The name of the Scheme. This must be unique. /// /// public static void AddScheme (string schemeName, Scheme scheme) { if (!GetSchemes ()!.TryAdd (schemeName, scheme)) { GetSchemes () [schemeName] = scheme; } } /// /// Removes a Scheme from . /// /// /// If the scheme is a built-in Scheme or was not previously added. public static void RemoveScheme (string schemeName) { if (SchemeNameToSchemes (schemeName) is { }) { throw new InvalidOperationException ($@"{schemeName}: Cannot remove a built-in Scheme."); } if (!GetSchemes ().TryGetValue (schemeName, out _)) { throw new InvalidOperationException ($@"{schemeName}: Does not exist in Schemes."); } GetSchemes ().Remove (schemeName); } /// /// Gets the for the specified . /// /// /// /// public static Scheme GetScheme (Schemes schemeName) { // Convert schemeName to string via Enum api string? schemeNameString = SchemesToSchemeName (schemeName); if (schemeNameString is null) { throw new ArgumentException ($"Invalid scheme name: {schemeName}"); } return GetSchemesForCurrentTheme ()! [schemeNameString]!; } /// /// Gets the for the specified string. /// /// /// /// public static Scheme GetScheme (string schemeName) { return GetSchemesForCurrentTheme ()! [schemeName]!; } /// /// Gets the name of the specified . Will throw an exception if /// is not a built-in Scheme. /// /// /// The name of scheme. public static string? SchemesToSchemeName (Schemes schemeName) { return Enum.GetName (typeof (Schemes), schemeName); } /// /// Converts a string to a enum value. /// /// if the schemeName is not a built-in Scheme name. /// if is not the name of a built-in Scheme. public static string? SchemeNameToSchemes (string schemeName) { if (Enum.TryParse (typeof (Schemes), schemeName, out object? value)) { return value?.ToString (); } return null; } /// /// Get the dictionary of schemes from the current theme. Current means active. /// /// public static Dictionary GetSchemesForCurrentTheme () { lock (_schemesLock) { if (!ConfigurationManager.IsInitialized ()) { throw new InvalidOperationException ("CM Must be Initialized"); } if (ThemeManager.GetCurrentTheme () ["Schemes"].PropertyValue is not Dictionary schemes) { // Most likely because "Schemes": was left out of the config throw new InvalidOperationException ("Current Theme does not have a Scheme."); } return schemes!; } } /// /// Convenience method to get the names of the schemes. /// /// public static ImmutableList GetSchemeNames () { lock (_schemesLock) { return GetSchemes ()!.Keys.ToImmutableList (); } } [RequiresUnreferencedCode ("Calls SetSchemes")] [RequiresDynamicCode ("Calls SetSchemes")] internal static void LoadToHardCodedDefaults () { // BUGBUG: SchemeManager is broken and needs to be fixed to not have the hard coded schemes get overwritten. // BUGBUG: This is a partial workaround // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/4288 SetSchemes (GetHardCodedSchemes ()!.ToDictionary ()); } }