#nullable enable using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; using Godot; using ShrinkSDK.Installer; namespace ShrinkSDK.Godot.Editor; [Tool] public partial class ShrinkGodotInstallerPlugin : EditorPlugin { private EditorDock? _panel; private Label? _status; private RichTextLabel? _output; private readonly System.Collections.Generic.Dictionary _installActions = new(StringComparer.OrdinalIgnoreCase); private readonly System.Collections.Generic.Dictionary _removeActions = new(StringComparer.OrdinalIgnoreCase); private string ProjectDirectory => ProjectSettings.GlobalizePath("res://"); private string ProjectPath => Directory.GetFiles(ProjectDirectory, "*.csproj", SearchOption.TopDirectoryOnly).Single(); public override void _EnterTree() { _panel = new EditorDock { Name = "ShrinkSDK", Title = "ShrinkSDK", DefaultSlot = EditorDock.DockSlot.LeftBr }; var content = new VBoxContainer(); _panel.AddChild(content); content.AddChild(new Label { Text = "ShrinkSDK Packages" }); _installActions.Clear(); _removeActions.Clear(); foreach (var package in ShrinkProjectPackageEditor.RecommendedPackages) { var row = new HBoxContainer(); row.AddChild(new Label { Text = $"{package.Key} {package.Value}", SizeFlagsHorizontal = Control.SizeFlags.ExpandFill }); var install = new Button { Text = "Install / Update" }; install.Pressed += () => ChangePackage(package.Key, package.Value); row.AddChild(install); _installActions[package.Key] = install; var remove = new Button { Text = "Remove" }; remove.Pressed += () => ChangePackage(package.Key, null); row.AddChild(remove); _removeActions[package.Key] = remove; content.AddChild(row); } var restore = new Button { Text = "Restore and Build" }; restore.Pressed += BuildProject; content.AddChild(restore); _status = new Label { Text = "CodeGen: not built" }; content.AddChild(_status); _output = new RichTextLabel { FitContent = true, CustomMinimumSize = new Vector2(360, 180) }; content.AddChild(_output); AddDock(_panel); RefreshInstalledState(); } public override void _ExitTree() { if (_panel == null) return; RemoveDock(_panel); _panel.QueueFree(); _panel = null; } private async void ChangePackage(string packageId, string? version) { try { ShrinkProjectPackageEditor.EnsureShrinkFeed(Path.Combine(ProjectDirectory, "NuGet.Config")); ShrinkProjectPackageEditor.SetPackageReference(ProjectPath, packageId, version); await RunBuild("restore"); } catch (Exception exception) { ShowFailure(exception); } } private async void BuildProject() { try { await RunBuild("build"); } catch (Exception exception) { ShowFailure(exception); } } private async System.Threading.Tasks.Task RunBuild(string command) { _status!.Text = $"CodeGen: running dotnet {command}"; var result = await ShrinkProjectPackageEditor.RunDotnetAsync(ProjectDirectory, $"{command} \"{ProjectPath}\" /nr:false"); _output!.Text = result.Output; var match = Regex.Matches(result.Output, @"\[ShrinkSDK\.CodeGen\].*woven: instance=(\d+), static=(\d+), registry=(\d+)").Cast().LastOrDefault(); _status.Text = result.ExitCode == 0 ? match == null ? "CodeGen: build succeeded (no attributed registrations)" : $"CodeGen: woven; instance={match.Groups[1].Value}, static={match.Groups[2].Value}, registry={match.Groups[3].Value}" : "CodeGen: build failed"; RefreshInstalledState(); } private void RefreshInstalledState() { if (_output == null) return; var installed = File.Exists(ProjectPath) ? ShrinkProjectPackageEditor.ReadPackageReferences(ProjectPath) : new System.Collections.Generic.Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var package in ShrinkProjectPackageEditor.RecommendedPackages) { var hasInstalled = installed.TryGetValue(package.Key, out var installedVersion); var relation = hasInstalled ? ShrinkPackageVersion.Classify(installedVersion, package.Value) : ShrinkPackageVersionRelation.Unknown; if (_installActions.TryGetValue(package.Key, out var install)) { install.Text = !hasInstalled ? $"Install {package.Value}" : relation switch { ShrinkPackageVersionRelation.Equal => $"Installed {installedVersion}", ShrinkPackageVersionRelation.Newer => $"Newer {installedVersion}", ShrinkPackageVersionRelation.Older => $"Upgrade {installedVersion} → {package.Value}", _ => $"Update {installedVersion} → {package.Value}" }; install.Disabled = hasInstalled && relation is ShrinkPackageVersionRelation.Equal or ShrinkPackageVersionRelation.Newer; } if (_removeActions.TryGetValue(package.Key, out var remove)) remove.Disabled = !hasInstalled; } _output.Text = "Direct packages: " + string.Join(", ", installed.Select(item => $"{item.Key}@{item.Value}")); } private void ShowFailure(Exception exception) { _status!.Text = "CodeGen: operation failed"; _output!.Text = exception.ToString(); GD.PushError(exception.ToString()); } }