diff options
-rw-r--r-- | src/SMAPI/Framework/Logging/InterceptingTextWriter.cs | 28 | ||||
-rw-r--r-- | src/SMAPI/Framework/Logging/LogManager.cs | 31 | ||||
-rw-r--r-- | src/SMAPI/Framework/Monitor.cs | 2 |
3 files changed, 30 insertions, 31 deletions
diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index 36f81d22..bad69a2a 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -8,15 +8,11 @@ namespace StardewModdingAPI.Framework.Logging internal class InterceptingTextWriter : TextWriter { /********* - ** Fields + ** Accessors *********/ /// <summary>Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary> - private readonly char IgnoreChar; - + public const char IgnoreChar = '\u200B'; - /********* - ** Accessors - *********/ /// <summary>The underlying console output.</summary> public TextWriter Out { get; } @@ -36,30 +32,38 @@ namespace StardewModdingAPI.Framework.Logging *********/ /// <summary>Construct an instance.</summary> /// <param name="output">The underlying output writer.</param> - /// <param name="ignoreChar">Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</param> - public InterceptingTextWriter(TextWriter output, char ignoreChar) + public InterceptingTextWriter(TextWriter output) { this.Out = output; - this.IgnoreChar = ignoreChar; } /// <inheritdoc /> public override void Write(char[] buffer, int index, int count) { + // track newline skip bool ignoreIfNewline = this.IgnoreNextIfNewline; this.IgnoreNextIfNewline = false; - if (buffer.Length == 0) + // get first character if valid + if (count == 0 || index < 0 || index >= buffer.Length) + { this.Out.Write(buffer, index, count); - else if (buffer[0] == this.IgnoreChar || char.IsControl(buffer[0])) // ignore control characters like backspace + return; + } + char firstChar = buffer[index]; + + // handle output + if (firstChar == InterceptingTextWriter.IgnoreChar) this.Out.Write(buffer, index + 1, count - 1); + else if (char.IsControl(firstChar) && firstChar is not ('\r' or '\n')) + this.Out.Write(buffer, index, count); else if (this.IsEmptyOrNewline(buffer)) { if (!ignoreIfNewline) this.Out.Write(buffer, index, count); } else - this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n')); + this.OnMessageIntercepted?.Invoke(new string(buffer, index, count)); } /// <inheritdoc /> diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index acd2c617..a8a8b6ee 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -22,9 +22,6 @@ namespace StardewModdingAPI.Framework.Logging /********* ** Fields *********/ - /// <summary>Whether to show trace messages in the console.</summary> - private readonly bool ShowTraceInConsole; - /// <summary>The log file to which to write messages.</summary> private readonly LogFileManager LogFile; @@ -32,7 +29,7 @@ namespace StardewModdingAPI.Framework.Logging private readonly InterceptingTextWriter ConsoleInterceptor; /// <summary>Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary> - private readonly char IgnoreChar = '\u200B'; + private const char IgnoreChar = InterceptingTextWriter.IgnoreChar; /// <summary>Get a named monitor instance.</summary> private readonly Func<string, Monitor> GetMonitorImpl; @@ -40,18 +37,18 @@ namespace StardewModdingAPI.Framework.Logging /// <summary>Regex patterns which match console non-error messages to suppress from the console and log.</summary> private readonly Regex[] SuppressConsolePatterns = { - new Regex(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant), - new Regex(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant) + new(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant), + new(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant) }; /// <summary>Regex patterns which match console messages to show a more friendly error for.</summary> private readonly ReplaceLogPattern[] ReplaceConsolePatterns = { // Steam not loaded - new ReplaceLogPattern( + new( search: new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant), replacement: #if SMAPI_FOR_WINDOWS @@ -63,7 +60,7 @@ namespace StardewModdingAPI.Framework.Logging ), // save file not found error - new ReplaceLogPattern( + new( search: new Regex(@"^System\.IO\.FileNotFoundException: [^\n]+\n[^:]+: '[^\n]+[/\\]Saves[/\\]([^'\r\n]+)[/\\]([^'\r\n]+)'[\s\S]+LoadGameMenu\.FindSaveGames[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant), replacement: "The game can't find the '$2' file for your '$1' save. See https://stardewvalleywiki.com/Saves#Troubleshooting for help.", logLevel: LogLevel.Error @@ -97,11 +94,10 @@ namespace StardewModdingAPI.Framework.Logging public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func<int?> getScreenIdForLog) { // init construction logic - this.ShowTraceInConsole = isDeveloperMode; - this.GetMonitorImpl = name => new Monitor(name, this.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) + this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) { WriteToConsole = writeToConsole, - ShowTraceInConsole = this.ShowTraceInConsole, + ShowTraceInConsole = isDeveloperMode, ShowFullStampInConsole = isDeveloperMode }; @@ -111,7 +107,7 @@ namespace StardewModdingAPI.Framework.Logging this.MonitorForGame = this.GetMonitor("game"); // redirect direct console output - this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out, this.IgnoreChar); + this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out); if (writeToConsole) this.ConsoleInterceptor.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); Console.SetOut(this.ConsoleInterceptor); @@ -153,7 +149,7 @@ namespace StardewModdingAPI.Framework.Logging .Add(new ReloadI18nCommand(reloadTranslations), this.Monitor); // start handling command line input - Thread inputThread = new Thread(() => + Thread inputThread = new(() => { while (true) { @@ -393,8 +389,7 @@ namespace StardewModdingAPI.Framework.Logging // forward to monitor gameMonitor.Log(message, level); - if (level == LogLevel.Trace && !this.ShowTraceInConsole) - this.ConsoleInterceptor.IgnoreNextIfNewline = true; + this.ConsoleInterceptor.IgnoreNextIfNewline = true; } /// <summary>Write a summary of mod warnings to the console and log.</summary> diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 04e67d68..ab76e7c0 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast<LogLevel>() select level.ToString().Length).Max(); /// <summary>A cache of messages that should only be logged once.</summary> - private readonly HashSet<string> LogOnceCache = new HashSet<string>(); + private readonly HashSet<string> LogOnceCache = new(); /// <summary>Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</summary> private readonly Func<int?> GetScreenIdForLog; |