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 ());
}
}