From f614b394913dacb78fb07cf696342bb85e43b648 Mon Sep 17 00:00:00 2001 From: Casey Warrington Date: Thu, 1 Aug 2024 20:22:09 -0500 Subject: [PATCH 1/4] Rebirth of Auto-complete Support New function for registering commands that has an auto-complete handler argument. Also added a config value for disabling the new console (in case it causes problems on some terminals). --- build/deploy-local-smapi.targets | 1 + src/SMAPI/Framework/Command.cs | 7 ++- src/SMAPI/Framework/CommandManager.cs | 48 ++++++++++++++++++- src/SMAPI/Framework/Commands/HelpCommand.cs | 21 ++++++++ .../Framework/Commands/IInternalCommand.cs | 11 +++++ src/SMAPI/Framework/Logging/LogManager.cs | 21 +++++++- .../Framework/ModHelpers/CommandHelper.cs | 6 +++ src/SMAPI/Framework/Models/SConfig.cs | 8 +++- src/SMAPI/Framework/Monitor.cs | 5 +- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/ICommandHelper.cs | 10 ++++ src/SMAPI/SMAPI.config.json | 6 +++ src/SMAPI/SMAPI.csproj | 1 + 13 files changed, 138 insertions(+), 9 deletions(-) diff --git a/build/deploy-local-smapi.targets b/build/deploy-local-smapi.targets index bd84ee11b..7ca967f84 100644 --- a/build/deploy-local-smapi.targets +++ b/build/deploy-local-smapi.targets @@ -24,6 +24,7 @@ This assumes `find-game-folder.targets` has already been imported and validated. + diff --git a/src/SMAPI/Framework/Command.cs b/src/SMAPI/Framework/Command.cs index dca1dd09b..7ae28c6d6 100644 --- a/src/SMAPI/Framework/Command.cs +++ b/src/SMAPI/Framework/Command.cs @@ -20,6 +20,9 @@ internal class Command /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. public Action Callback { get; } + /// The method to invoke for auto-complete handling. This method is passed the command name and current input, and should return the potential matches. + public Func? AutoCompleteHandler { get; } + /********* ** Public methods @@ -29,12 +32,14 @@ internal class Command /// The command name, which the user must type to trigger it. /// The human-readable documentation shown when the player runs the built-in 'help' command. /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. - public Command(IModMetadata? mod, string name, string documentation, Action callback) + /// The method to invoke for auto-complete handling. This method is passed the command name and current input, and should return the potential matches. + public Command(IModMetadata? mod, string name, string documentation, Action callback, Func? autoCompleteHandler) { this.Mod = mod; this.Name = name; this.Documentation = documentation; this.Callback = callback; + this.AutoCompleteHandler = autoCompleteHandler; } } } diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index b20e5ceb8..c7a93bfc2 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -39,6 +39,20 @@ public CommandManager(IMonitor monitor) /// The is not a valid format. /// There's already a command with that name. public CommandManager Add(IModMetadata? mod, string name, string documentation, Action callback) + { + return this.Add(mod, name, documentation, callback, null); + } + + /// Add a console command. + /// The mod adding the command (or null for a SMAPI command). + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + /// The method to invoke for auto-complete handling. This method is passed the command name and current input, and should return the potential matches. The matches should all start with the last space-separated string of input. + /// The or is null or empty. + /// The is not a valid format. + /// There's already a command with that name. + public CommandManager Add(IModMetadata? mod, string name, string documentation, Action callback, Func? autoCompleteHandler) { name = this.GetNormalizedName(name)!; // null-checked below @@ -55,7 +69,7 @@ public CommandManager Add(IModMetadata? mod, string name, string documentation, throw new ArgumentException(nameof(callback), $"Can't register the '{name}' command because there's already a command with that name."); // add command - this.Commands.Add(name, new Command(mod, name, documentation, callback)); + this.Commands.Add(name, new Command(mod, name, documentation, callback, autoCompleteHandler)); return this; } @@ -65,7 +79,7 @@ public CommandManager Add(IModMetadata? mod, string name, string documentation, /// There's already a command with that name. public CommandManager Add(IInternalCommand command, IMonitor monitor) { - return this.Add(null, command.Name, command.Description, (_, args) => command.HandleCommand(args, monitor)); + return this.Add(null, command.Name, command.Description, (_, args) => command.HandleCommand(args, monitor), (_, input) => command.HandleAutocomplete(input, monitor)); } /// Get a command by its unique name. @@ -138,6 +152,36 @@ public bool TryParse(string? input, [NotNullWhen(true)] out string? name, [NotNu return this.Commands.TryGetValue(name, out command); } + /// + /// Handle autocompletion results. + /// + /// The input string to autocomplete for. + /// An array of matches for the input. + public string[] HandleAutocomplete(string input) + { + int space = input.IndexOf(' '); + if (space == -1) + { + List matches = new(); + foreach (string cmd in this.Commands.Keys) + { + if (cmd.StartsWith(input)) + matches.Add(cmd); + } + return matches.ToArray(); + } + else + { + string currCmd = input.Substring(0, space); + if (!this.Commands.TryGetValue(currCmd, out Command? cmd) || cmd.AutoCompleteHandler == null) + { + return Array.Empty(); + } + + return cmd.AutoCompleteHandler(currCmd, input.Substring(space + 1)); + } + } + /********* ** Private methods diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs index 65dc3bce3..547a1ecd9 100644 --- a/src/SMAPI/Framework/Commands/HelpCommand.cs +++ b/src/SMAPI/Framework/Commands/HelpCommand.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; namespace StardewModdingAPI.Framework.Commands @@ -72,5 +74,24 @@ public void HandleCommand(string[] args, IMonitor monitor) monitor.Log(message, LogLevel.Info); } } + + /// Handle the console command auto-complete when requested by the user.. + /// The current input. + /// Writes messages to the console. + public string[] HandleAutocomplete(string input, IMonitor monitor) + { + if (input.Contains(' ')) + return Array.Empty(); + + var allCommandNames = this.CommandManager.GetAll().Select(cmd => cmd.Name); + + List ret = new(); + foreach (string name in allCommandNames) + { + if (name.StartsWith(input)) + ret.Add(name); + } + return ret.ToArray(); + } } } diff --git a/src/SMAPI/Framework/Commands/IInternalCommand.cs b/src/SMAPI/Framework/Commands/IInternalCommand.cs index abf105b69..db1f88fbd 100644 --- a/src/SMAPI/Framework/Commands/IInternalCommand.cs +++ b/src/SMAPI/Framework/Commands/IInternalCommand.cs @@ -1,3 +1,5 @@ +using System; + namespace StardewModdingAPI.Framework.Commands { /// A core SMAPI console command. @@ -20,5 +22,14 @@ interface IInternalCommand /// The command arguments. /// Writes messages to the console. void HandleCommand(string[] args, IMonitor monitor); + + /// Handle the console command auto-complete when requested by the user.. + /// The current input. + /// Writes messages to the console. + string[] HandleAutocomplete(string input, IMonitor monitor) + { + // Default implementation for if a command doesn't support it. + return Array.Empty(); + } } } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 978254651..2c854ceaa 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading; +using ConsoleWrapperLib; using StardewModdingAPI.Framework.Commands; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.ModLoading; @@ -25,6 +26,12 @@ internal class LogManager : IDisposable /// The log file to which to write messages. private readonly LogFileManager LogFile; + /// If we're in legacy mode or not. + private readonly bool LegacyMode; + + /// The console wrapper object. + private ConsoleWrapper? ConsoleWrapper; + /// Create a monitor instance given the ID and name. private readonly Func GetMonitorImpl; @@ -51,12 +58,16 @@ internal class LogManager : IDisposable /// Whether to output log messages to the console. /// The log contexts for which to enable verbose logging, which may show a lot more information to simplify troubleshooting. /// Whether to enable full console output for developers. + /// Whether to use legacy mode or not, which enables auto completion and moves user input to always be at the bottom of the console. /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. - public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet verboseLogging, bool isDeveloperMode, Func getScreenIdForLog) + public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, HashSet verboseLogging, bool isDeveloperMode, bool legacyMode, Func getScreenIdForLog) { // init log file this.LogFile = new LogFileManager(logPath); + // save legacy mode value + this.LegacyMode = legacyMode; + // init monitor this.GetMonitorImpl = (id, name) => new Monitor(name, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog) { @@ -104,13 +115,19 @@ public void RunConsoleInputLoop(CommandManager commandManager, Action reloadTran .Add(new HarmonySummaryCommand(), this.Monitor) .Add(new ReloadI18nCommand(reloadTranslations), this.Monitor); + if (!this.LegacyMode) + { + this.ConsoleWrapper = new ConsoleWrapper(); + this.ConsoleWrapper.AutoCompleteHandler = commandManager.HandleAutocomplete; + } + // start handling command line input Thread inputThread = new(() => { while (true) { // get input - string? input = Console.ReadLine(); + string? input = (this.ConsoleWrapper != null) ? this.ConsoleWrapper.ReadLine() : Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index d3c5a1f93..fa45711c4 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -30,5 +30,11 @@ public ICommandHelper Add(string name, string documentation, Action + public ICommandHelper Add(string name, string documentation, Action callback, Func autoCompleteHandler) + { + this.CommandManager.Add(this.Mod, name, documentation, callback, autoCompleteHandler); + return this; + } } } diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 40bdb1304..5d4c8d0b2 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -17,6 +17,7 @@ internal class SConfig { [nameof(CheckForUpdates)] = true, [nameof(ListenForConsoleInput)] = true, + [nameof(LegacyConsoleMode)] = false, [nameof(ParanoidWarnings)] = Constants.IsDebugBuild, [nameof(UseBetaChannel)] = Constants.ApiVersion.IsPrerelease(), [nameof(GitHubProjectName)] = "Pathoschild/SMAPI", @@ -53,6 +54,9 @@ internal class SConfig /// Whether SMAPI should listen for console input to support console commands. public bool ListenForConsoleInput { get; set; } + /// Whether SMAPI should use legacy console mode. This will prevent tab auto completion from working, and will not keep input at the bottom of the console. + public bool LegacyConsoleMode { get; set; } + /// Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access. public bool ParanoidWarnings { get; set; } @@ -107,6 +111,7 @@ internal class SConfig /// /// /// + /// /// /// /// @@ -122,11 +127,12 @@ internal class SConfig /// /// /// - public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate) + public SConfig(bool developerMode, bool? checkForUpdates, bool? listenForConsoleInput, bool? legacyConsoleMode, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, string[]? verboseLogging, bool? rewriteMods, bool? fixHarmony, bool? useCaseInsensitivePaths, bool? logNetworkTraffic, bool? logTechnicalDetailsForBrokenMods, ColorSchemeConfig consoleColors, bool? suppressHarmonyDebugMode, string[]? suppressUpdateChecks, string[]? modsToLoadEarly, string[]? modsToLoadLate) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates ?? (bool)SConfig.DefaultValues[nameof(this.CheckForUpdates)]; this.ListenForConsoleInput = listenForConsoleInput ?? (bool)SConfig.DefaultValues[nameof(this.ListenForConsoleInput)]; + this.LegacyConsoleMode = legacyConsoleMode ?? (bool)SConfig.DefaultValues[nameof(this.LegacyConsoleMode)]; this.ParanoidWarnings = paranoidWarnings ?? (bool)SConfig.DefaultValues[nameof(this.ParanoidWarnings)]; this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(this.UseBetaChannel)]; this.GitHubProjectName = gitHubProjectName; diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index cecb0040c..6a67a62fd 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -126,9 +126,10 @@ internal void LogFatal(string message) /// The user input to log. internal void LogUserInput(string input) { - // user input already appears in the console, so just need to write to file string prefix = this.GenerateMessagePrefix(this.Source, (ConsoleLogLevel)LogLevel.Info); - this.LogFile.WriteLine($"{prefix} $>{input}"); + string output = $"{prefix} $>{input}"; + Console.WriteLine(output); + this.LogFile.WriteLine(output); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2b10d4259..40fafab59 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -202,7 +202,7 @@ public SCore(string modsPath, bool writeToConsole, bool? developerMode) } // init basics - this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); + this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, verboseLogging: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, legacyMode: this.Settings.LegacyConsoleMode, getScreenIdForLog: this.GetScreenIdForLog); this.CommandManager = new CommandManager(this.Monitor); this.EventManager = new EventManager(this.ModRegistry); SCore.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); diff --git a/src/SMAPI/ICommandHelper.cs b/src/SMAPI/ICommandHelper.cs index afbcf2b02..ca915d321 100644 --- a/src/SMAPI/ICommandHelper.cs +++ b/src/SMAPI/ICommandHelper.cs @@ -16,5 +16,15 @@ public interface ICommandHelper : IModLinked /// The is not a valid format. /// There's already a command with that name. ICommandHelper Add(string name, string documentation, Action callback); + + /// Add a console command. + /// The command name, which the user must type to trigger it. + /// The human-readable documentation shown when the player runs the built-in 'help' command. + /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. + /// The method to invoke for auto-complete handling. This method is passed the command name and current input, and should return the potential matches. + /// The or is null or empty. + /// The is not a valid format. + /// There's already a command with that name. + ICommandHelper Add(string name, string documentation, Action callback, Func autoCompleteHandler); } } diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 55f9869b6..3ff4d1bcd 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -47,6 +47,12 @@ in future SMAPI versions. */ "ListenForConsoleInput": true, + /** + * Whether SMAPI should use legacy console mode. This will prevent tab auto completion from + * working, and will not keep input at the bottom of the console. + */ + "LegacyConsoleMode": false, + /** * Whether SMAPI should rewrite mods for compatibility. This may prevent older mods from * loading, but bypasses a Visual Studio crash when debugging. diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 426ab347d..4f52ef85e 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -27,6 +27,7 @@ + From 9eab426a53f27cd471b3995e1162f59953705f5d Mon Sep 17 00:00:00 2001 From: Casey Warrington Date: Fri, 2 Aug 2024 10:37:04 -0500 Subject: [PATCH 2/4] Fix for wrong colors for many things in terminal --- .../ConsoleWriting/ColorfulConsoleWriter.cs | 25 +++++++++++-------- src/SMAPI/SMAPI.csproj | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs index 78db0d659..f040342c1 100644 --- a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs +++ b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs @@ -51,18 +51,21 @@ public void WriteLine(string message, ConsoleLogLevel level) { if (this.SupportsColor) { - if (level == ConsoleLogLevel.Critical) + lock (Console.Out) { - Console.BackgroundColor = ConsoleColor.Red; - Console.ForegroundColor = ConsoleColor.White; - Console.WriteLine(message); - Console.ResetColor(); - } - else - { - Console.ForegroundColor = this.Colors[level]; - Console.WriteLine(message); - Console.ResetColor(); + 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 diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 4f52ef85e..290b452dd 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -27,7 +27,7 @@ - + From 5e09a093338eab6ae3f63993397f1dd6767f0e6c Mon Sep 17 00:00:00 2001 From: Casey Warrington Date: Mon, 12 Aug 2024 20:31:59 -0500 Subject: [PATCH 3/4] Update console logging to use new ConsoleWrapper function. Should fix the slowdown from logging spam while still supporting colors properly. --- .../ConsoleWriting/ColorfulConsoleWriter.cs | 41 ++++++++++++------- .../Framework/ConsoleWrapperConsoleLogger.cs | 31 ++++++++++++++ src/SMAPI/Framework/Logging/LogManager.cs | 20 +++++++-- src/SMAPI/Framework/Monitor.cs | 6 +-- src/SMAPI/SMAPI.csproj | 2 +- 5 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs index f040342c1..ccba7d545 100644 --- a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs +++ b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs @@ -51,25 +51,17 @@ public void WriteLine(string message, ConsoleLogLevel level) { if (this.SupportsColor) { - lock (Console.Out) + if (level == ConsoleLogLevel.Critical) { - 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(); - } + this.WriteLineImpl(message, ConsoleColor.White, ConsoleColor.Red); + } + else + { + this.WriteLineImpl(message, this.Colors[level], null); } } else - Console.WriteLine(message); + this.WriteLineImpl(message, null, null); } /// Get the default color scheme config for cases where it's not configurable (e.g. the installer). @@ -106,6 +98,25 @@ public static ColorSchemeConfig GetDefaultColorSchemeConfig(MonitorColorScheme u } + /********* + ** Private methods + *********/ + /// + /// Implementation of writing a line to the console, virtual to allow for other console implementations. + /// + /// The message to log. + /// The foreground color to override the default with, if any. + /// The background color to override the default with, if any. + protected virtual void WriteLineImpl(string message, ConsoleColor? foregroundColor, ConsoleColor? backgroundColor) + { + if (backgroundColor.HasValue) + Console.BackgroundColor = backgroundColor.Value; + if (foregroundColor.HasValue) + Console.ForegroundColor = foregroundColor.Value; + Console.WriteLine(message); + Console.ResetColor(); + } + /********* ** Private methods *********/ diff --git a/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs b/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs new file mode 100644 index 000000000..202850f24 --- /dev/null +++ b/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using ConsoleWrapperLib; +using StardewModdingAPI.Toolkit.Utilities; + +namespace StardewModdingAPI.Internal.ConsoleWriting +{ + /// Writes color-coded text to a ConsoleWrapper object. + internal class ConsoleWrapperConsoleWriter : ColorfulConsoleWriter + { + /// The console wrapper object to use. + private ConsoleWrapper ConsoleWrapper; + + /// Construct an instance. + /// The target platform. + /// The console wrapper object. + /// The colors to use for text written to the SMAPI console. + public ConsoleWrapperConsoleWriter(Platform platform, ConsoleWrapper consoleWrapper, ColorSchemeConfig colorConfig) + : base(platform, colorConfig) + { + this.ConsoleWrapper = consoleWrapper; + } + + /// + protected override void WriteLineImpl(string message, ConsoleColor? foregroundColor, ConsoleColor? backgroundColor) + { + this.ConsoleWrapper.WriteLine(message, foregroundColor ?? this.ConsoleWrapper.DefaultForeground, backgroundColor ?? this.ConsoleWrapper.DefaultBackground); + } + } +} diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 2c854ceaa..6fa477332 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -27,11 +27,15 @@ internal class LogManager : IDisposable private readonly LogFileManager LogFile; /// If we're in legacy mode or not. - private readonly bool LegacyMode; + [MemberNotNullWhen(false, nameof(ConsoleWrapper))] + private bool LegacyMode { get; } /// The console wrapper object. private ConsoleWrapper? ConsoleWrapper; + /// The console writer object. + private IConsoleWriter ConsoleWriter; + /// Create a monitor instance given the ID and name. private readonly Func GetMonitorImpl; @@ -68,8 +72,19 @@ public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToCon // save legacy mode value this.LegacyMode = legacyMode; + // init console + if (!this.LegacyMode) + { + this.ConsoleWrapper = new ConsoleWrapper(); + this.ConsoleWriter = new ConsoleWrapperConsoleWriter(Constants.Platform, this.ConsoleWrapper, colorConfig); + } + else + { + this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorConfig); + } + // init monitor - this.GetMonitorImpl = (id, name) => new Monitor(name, this.LogFile, colorConfig, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog) + this.GetMonitorImpl = (id, name) => new Monitor(name, this.LogFile, this.ConsoleWriter, verboseLogging.Contains("*") || verboseLogging.Contains(id), getScreenIdForLog) { WriteToConsole = writeToConsole, ShowTraceInConsole = isDeveloperMode, @@ -117,7 +132,6 @@ public void RunConsoleInputLoop(CommandManager commandManager, Action reloadTran if (!this.LegacyMode) { - this.ConsoleWrapper = new ConsoleWrapper(); this.ConsoleWrapper.AutoCompleteHandler = commandManager.HandleAutocomplete; } diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 6a67a62fd..93bab32af 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -63,10 +63,10 @@ internal class Monitor : IMonitor /// Construct an instance. /// The name of the module which logs messages using this instance. /// The log file to which to write messages. - /// The colors to use for text written to the SMAPI console. + /// The console writer to use for console output. /// Whether verbose logging is enabled. This enables more detailed diagnostic messages than are normally needed. /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. - public Monitor(string source, LogFileManager logFile, ColorSchemeConfig colorConfig, bool isVerbose, Func getScreenIdForLog) + public Monitor(string source, LogFileManager logFile, IConsoleWriter consoleWriter, bool isVerbose, Func getScreenIdForLog) { // validate if (string.IsNullOrWhiteSpace(source)) @@ -75,7 +75,7 @@ public Monitor(string source, LogFileManager logFile, ColorSchemeConfig colorCon // initialize this.Source = source; this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null."); - this.ConsoleWriter = new ColorfulConsoleWriter(Constants.Platform, colorConfig); + this.ConsoleWriter = consoleWriter; this.IsVerbose = isVerbose; this.GetScreenIdForLog = getScreenIdForLog; } diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index 290b452dd..728a3b86a 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -27,7 +27,7 @@ - + From 7501a5854d575ba7d31c83ea09c4e8ed06936333 Mon Sep 17 00:00:00 2001 From: Casey Warrington Date: Mon, 12 Aug 2024 21:56:22 -0500 Subject: [PATCH 4/4] Fix console output not showing until user input is ready --- src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs | 13 +++++++------ src/SMAPI/Framework/Logging/LogManager.cs | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs b/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs index 202850f24..d5cfaff1e 100644 --- a/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs +++ b/src/SMAPI/Framework/ConsoleWrapperConsoleLogger.cs @@ -9,23 +9,24 @@ namespace StardewModdingAPI.Internal.ConsoleWriting /// Writes color-coded text to a ConsoleWrapper object. internal class ConsoleWrapperConsoleWriter : ColorfulConsoleWriter { - /// The console wrapper object to use. - private ConsoleWrapper ConsoleWrapper; + /// The console wrapper object to use, if avaiable. + public ConsoleWrapper? ConsoleWrapper { get; set; } /// Construct an instance. /// The target platform. - /// The console wrapper object. /// The colors to use for text written to the SMAPI console. - public ConsoleWrapperConsoleWriter(Platform platform, ConsoleWrapper consoleWrapper, ColorSchemeConfig colorConfig) + public ConsoleWrapperConsoleWriter(Platform platform, ColorSchemeConfig colorConfig) : base(platform, colorConfig) { - this.ConsoleWrapper = consoleWrapper; } /// protected override void WriteLineImpl(string message, ConsoleColor? foregroundColor, ConsoleColor? backgroundColor) { - this.ConsoleWrapper.WriteLine(message, foregroundColor ?? this.ConsoleWrapper.DefaultForeground, backgroundColor ?? this.ConsoleWrapper.DefaultBackground); + if (this.ConsoleWrapper != null) + this.ConsoleWrapper.WriteLine(message, foregroundColor ?? this.ConsoleWrapper.DefaultForeground, backgroundColor ?? this.ConsoleWrapper.DefaultBackground); + else + base.WriteLineImpl(message, foregroundColor, backgroundColor); } } } diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index 6fa477332..3c3fc51bf 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -75,8 +75,7 @@ public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToCon // init console if (!this.LegacyMode) { - this.ConsoleWrapper = new ConsoleWrapper(); - this.ConsoleWriter = new ConsoleWrapperConsoleWriter(Constants.Platform, this.ConsoleWrapper, colorConfig); + this.ConsoleWriter = new ConsoleWrapperConsoleWriter(Constants.Platform, colorConfig); } else { @@ -132,7 +131,10 @@ public void RunConsoleInputLoop(CommandManager commandManager, Action reloadTran if (!this.LegacyMode) { + this.ConsoleWrapper = new ConsoleWrapper(); this.ConsoleWrapper.AutoCompleteHandler = commandManager.HandleAutocomplete; + if (this.ConsoleWriter is ConsoleWrapperConsoleWriter consoleWrapperWriter) + consoleWrapperWriter.ConsoleWrapper = this.ConsoleWrapper; } // start handling command line input