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.";
+ }
+}