diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index ea49ef3ba..3146935de 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -2458,6 +2458,12 @@ internal class PluginInstallerWindow : Window, IDisposable } } + if (plugin is LocalDevPlugin devPlugin) + { + this.DrawDevPluginValidationIssues(devPlugin); + ImGuiHelpers.ScaledDummy(5); + } + // Controls this.DrawPluginControlButton(plugin, availablePluginUpdate); this.DrawDevPluginButtons(plugin); @@ -2961,6 +2967,70 @@ internal class PluginInstallerWindow : Window, IDisposable } } + private void DrawDevPluginValidationIssues(LocalDevPlugin devPlugin) + { + if (!devPlugin.IsLoaded) + { + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "You have to load this plugin to see validation issues."); + } + else + { + var problems = PluginValidator.CheckForProblems(devPlugin); + if (problems.Count == 0) + { + ImGui.PushFont(InterfaceManager.IconFont); + ImGui.Text(FontAwesomeIcon.Check.ToIconString()); + ImGui.PopFont(); + ImGui.SameLine(); + ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.HealerGreen, "No validation issues found in this plugin!"); + } + else + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); + using (var tree = ImRaii.TreeNode($"Found {problems.Count} validation issue{(problems.Count > 1 ? "s" : string.Empty)} in this plugin!")) + { + if (tree.Success) + { + foreach (var problem in problems) + { + ImGui.PushStyleColor(ImGuiCol.Text, problem.Severity switch + { + PluginValidator.ValidationSeverity.Fatal => ImGuiColors.DalamudRed, + PluginValidator.ValidationSeverity.Warning => ImGuiColors.DalamudOrange, + PluginValidator.ValidationSeverity.Information => ImGuiColors.TankBlue, + _ => ImGuiColors.DalamudGrey, + }); + + ImGui.PushFont(InterfaceManager.IconFont); + + switch (problem.Severity) + { + case PluginValidator.ValidationSeverity.Fatal: + ImGui.Text(FontAwesomeIcon.TimesCircle.ToIconString()); + break; + case PluginValidator.ValidationSeverity.Warning: + ImGui.Text(FontAwesomeIcon.ExclamationTriangle.ToIconString()); + break; + case PluginValidator.ValidationSeverity.Information: + ImGui.Text(FontAwesomeIcon.InfoCircle.ToIconString()); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + ImGui.PopFont(); + ImGui.SameLine(); + ImGuiHelpers.SafeTextWrapped(problem.GetLocalizedDescription()); + ImGui.PopStyleColor(); + } + } + } + + ImGui.PopStyleColor(); + } + } + } + private void DrawDevPluginButtons(LocalPlugin localPlugin) { ImGui.SameLine(); diff --git a/Dalamud/Plugin/Internal/PluginValidator.cs b/Dalamud/Plugin/Internal/PluginValidator.cs new file mode 100644 index 000000000..2757ae7f4 --- /dev/null +++ b/Dalamud/Plugin/Internal/PluginValidator.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; + +using Dalamud.Game.Command; +using Dalamud.Plugin.Internal.Types; + +namespace Dalamud.Plugin.Internal; + +/// +/// Class responsible for validating a dev plugin. +/// +internal static class PluginValidator +{ + /// + /// Represents the severity of a validation problem. + /// + public enum ValidationSeverity + { + /// + /// The problem is informational. + /// + Information, + + /// + /// The problem is a warning. + /// + Warning, + + /// + /// The problem is fatal. + /// + Fatal, + } + + /// + /// Represents a validation problem. + /// + public interface IValidationProblem + { + /// + /// Gets the severity of the validation. + /// + public ValidationSeverity Severity { get; } + + /// + /// Compute the localized description of the problem. + /// + /// Localized string to be shown to the developer. + public string GetLocalizedDescription(); + } + + /// + /// Check for problems in a plugin. + /// + /// The plugin to validate. + /// An list of problems. + /// Thrown when the plugin is not loaded. A plugin must be loaded to validate it. + public static IReadOnlyList CheckForProblems(LocalDevPlugin plugin) + { + var problems = new List(); + + if (!plugin.IsLoaded) + throw new InvalidOperationException("Plugin must be loaded to validate."); + + if (!plugin.DalamudInterface!.UiBuilder.HasConfigUi) + problems.Add(new NoConfigUiProblem()); + + if (!plugin.DalamudInterface.UiBuilder.HasMainUi) + problems.Add(new NoMainUiProblem()); + + var cmdManager = Service.Get(); + foreach (var cmd in cmdManager.Commands.Where(x => x.Value.LoaderAssemblyName == plugin.InternalName && x.Value.ShowInHelp)) + { + if (string.IsNullOrEmpty(cmd.Value.HelpMessage)) + problems.Add(new CommandWithoutHelpTextProblem(cmd.Key)); + } + + return problems; + } + + /// + /// Representing a problem where the plugin does not have a config UI callback. + /// + public class NoConfigUiProblem : IValidationProblem + { + /// + public ValidationSeverity Severity => ValidationSeverity.Warning; + + /// + public string GetLocalizedDescription() => "The plugin does register a config UI callback. If you have a settings window or section, please consider registering UiBuilder.OpenConfigUi."; + } + + /// + /// Representing a problem where the plugin does not have a main UI callback. + /// + public class NoMainUiProblem : IValidationProblem + { + /// + public ValidationSeverity Severity => ValidationSeverity.Warning; + + /// + public string GetLocalizedDescription() => "The plugin does not register a main UI callback. If your plugin draws any kind of ImGui windows, please consider registering UiBuilder.OpenMainUi to open the plugin's main window."; + } + + /// + /// Representing a problem where a command does not have a help text. + /// + /// Name of the command. + public class CommandWithoutHelpTextProblem(string commandName) : IValidationProblem + { + /// + public ValidationSeverity Severity => ValidationSeverity.Fatal; + + /// + public string GetLocalizedDescription() => $"The plugin has a command({commandName}) without a help message. Please consider adding a help message to the command when registering it."; + } +}