using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Internal.ConsoleWriting { /// Writes color-coded text to the console. internal class ColorfulConsoleWriter : IConsoleWriter { /********* ** Fields *********/ /// The console text color for each log level. private readonly IDictionary? Colors; /// Whether the current console supports color formatting. [MemberNotNullWhen(true, nameof(ColorfulConsoleWriter.Colors))] private bool SupportsColor { get; } /********* ** Public methods *********/ /// Construct an instance. /// The target platform. public ColorfulConsoleWriter(Platform platform) : this(platform, ColorfulConsoleWriter.GetDefaultColorSchemeConfig(MonitorColorScheme.AutoDetect)) { } /// Construct an instance. /// The target platform. /// The colors to use for text written to the SMAPI console. public ColorfulConsoleWriter(Platform platform, ColorSchemeConfig colorConfig) { if (colorConfig.UseScheme == MonitorColorScheme.None) { this.SupportsColor = false; this.Colors = null; } else { this.SupportsColor = this.TestColorSupport(); this.Colors = this.GetConsoleColorScheme(platform, colorConfig); } } /// Write a message line to the log. /// The message to log. /// The log level. public void WriteLine(string message, ConsoleLogLevel level) { if (this.SupportsColor) { if (level == ConsoleLogLevel.Critical) { Console.BackgroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(message); Console.ResetColor(); } else { Console.ForegroundColor = this.Colors[level]; Console.WriteLine(message); Console.ResetColor(); } } else Console.WriteLine(message); } /// Get the default color scheme config for cases where it's not configurable (e.g. the installer). /// The default color scheme ID to use, or to select one automatically. /// The colors here should be kept in sync with the SMAPI config file. public static ColorSchemeConfig GetDefaultColorSchemeConfig(MonitorColorScheme useScheme) { return new ColorSchemeConfig( useScheme: useScheme, schemes: new Dictionary> { [MonitorColorScheme.DarkBackground] = new Dictionary { [ConsoleLogLevel.Trace] = ConsoleColor.DarkGray, [ConsoleLogLevel.Debug] = ConsoleColor.DarkGray, [ConsoleLogLevel.Info] = ConsoleColor.White, [ConsoleLogLevel.Warn] = ConsoleColor.Yellow, [ConsoleLogLevel.Error] = ConsoleColor.Red, [ConsoleLogLevel.Alert] = ConsoleColor.Magenta, [ConsoleLogLevel.Success] = ConsoleColor.DarkGreen }, [MonitorColorScheme.LightBackground] = new Dictionary { [ConsoleLogLevel.Trace] = ConsoleColor.DarkGray, [ConsoleLogLevel.Debug] = ConsoleColor.DarkGray, [ConsoleLogLevel.Info] = ConsoleColor.Black, [ConsoleLogLevel.Warn] = ConsoleColor.DarkYellow, [ConsoleLogLevel.Error] = ConsoleColor.Red, [ConsoleLogLevel.Alert] = ConsoleColor.DarkMagenta, [ConsoleLogLevel.Success] = ConsoleColor.DarkGreen } } ); } /********* ** Private methods *********/ /// Test whether the current console supports color formatting. private bool TestColorSupport() { try { Console.ForegroundColor = Console.ForegroundColor; return true; } catch (Exception) { return false; // Mono bug } } /// Get the color scheme to use for the current console. /// The target platform. /// The colors to use for text written to the SMAPI console. private IDictionary GetConsoleColorScheme(Platform platform, ColorSchemeConfig colorConfig) { // get color scheme ID MonitorColorScheme schemeID = colorConfig.UseScheme; if (schemeID == MonitorColorScheme.AutoDetect) { schemeID = platform == Platform.Mac ? MonitorColorScheme.LightBackground // macOS doesn't provide console background color info, but it's usually white. : ColorfulConsoleWriter.IsDark(Console.BackgroundColor) ? MonitorColorScheme.DarkBackground : MonitorColorScheme.LightBackground; } // get colors for scheme return colorConfig.Schemes.TryGetValue(schemeID, out IDictionary? scheme) ? scheme : throw new NotSupportedException($"Unknown color scheme '{schemeID}'."); } /// Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'. /// The color to check. private static bool IsDark(ConsoleColor color) { switch (color) { case ConsoleColor.Black: case ConsoleColor.Blue: case ConsoleColor.DarkBlue: case ConsoleColor.DarkMagenta: // PowerShell case ConsoleColor.DarkRed: case ConsoleColor.Red: return true; default: return false; } } } }