Remove scratchpad

So sad, a king among features.
This commit is contained in:
Raymond 2021-09-27 18:00:15 -04:00
parent ef16e346c6
commit 653363fd8f
7 changed files with 0 additions and 778 deletions

View file

@ -43,7 +43,6 @@ namespace Dalamud.Interface.Internal
private readonly ConsoleWindow consoleWindow;
private readonly PluginStatWindow pluginStatWindow;
private readonly PluginInstallerWindow pluginWindow;
private readonly ScratchpadWindow scratchpadWindow;
private readonly SettingsWindow settingsWindow;
private readonly SelfTestWindow selfTestWindow;
private readonly StyleEditorWindow styleEditorWindow;
@ -77,7 +76,6 @@ namespace Dalamud.Interface.Internal
this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup };
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
this.pluginWindow = new PluginInstallerWindow() { IsOpen = false };
this.scratchpadWindow = new ScratchpadWindow() { IsOpen = false };
this.settingsWindow = new SettingsWindow() { IsOpen = false };
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
@ -92,7 +90,6 @@ namespace Dalamud.Interface.Internal
this.WindowSystem.AddWindow(this.consoleWindow);
this.WindowSystem.AddWindow(this.pluginStatWindow);
this.WindowSystem.AddWindow(this.pluginWindow);
this.WindowSystem.AddWindow(this.scratchpadWindow);
this.WindowSystem.AddWindow(this.settingsWindow);
this.WindowSystem.AddWindow(this.selfTestWindow);
this.WindowSystem.AddWindow(this.styleEditorWindow);
@ -130,7 +127,6 @@ namespace Dalamud.Interface.Internal
this.creditsWindow.Dispose();
this.consoleWindow.Dispose();
this.scratchpadWindow.Dispose();
}
#region Open
@ -198,11 +194,6 @@ namespace Dalamud.Interface.Internal
/// </summary>
public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="ScratchpadWindow"/>.
/// </summary>
public void OpenScratchpadWindow() => this.scratchpadWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="SettingsWindow"/>.
/// </summary>
@ -294,11 +285,6 @@ namespace Dalamud.Interface.Internal
/// </summary>
public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle();
/// <summary>
/// Toggles the <see cref="ScratchpadWindow"/>.
/// </summary>
public void ToggleScratchpadWindow() => this.scratchpadWindow.Toggle();
/// <summary>
/// Toggles the <see cref="SettingsWindow"/>.
/// </summary>
@ -621,21 +607,6 @@ namespace Dalamud.Interface.Internal
ImGui.EndMenu();
}
if (ImGui.BeginMenu("Scratchpad"))
{
if (ImGui.MenuItem("Open Scratchpad"))
{
this.OpenScratchpadWindow();
}
if (ImGui.MenuItem("Dispose all scratches"))
{
this.scratchpadWindow.Execution.DisposeAllScratches();
}
ImGui.EndMenu();
}
if (ImGui.BeginMenu("Localization"))
{
var localization = Service<Localization>.Get();

View file

@ -1,134 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.IoC.Internal;
using Dalamud.Plugin;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Serilog;
namespace Dalamud.Interface.Internal.Scratchpad
{
/// <summary>
/// This class manages the execution of <see cref="ScratchpadDocument"/> classes.
/// </summary>
internal class ScratchExecutionManager
{
private Dictionary<Guid, IDalamudPlugin> loadedScratches = new();
/// <summary>
/// Initializes a new instance of the <see cref="ScratchExecutionManager"/> class.
/// </summary>
public ScratchExecutionManager()
{
}
/// <summary>
/// Gets the ScratchPad macro processor.
/// </summary>
public ScratchMacroProcessor MacroProcessor { get; private set; } = new();
/// <summary>
/// Dispose of all currently loaded ScratchPads.
/// </summary>
public void DisposeAllScratches()
{
foreach (var dalamudPlugin in this.loadedScratches)
{
dalamudPlugin.Value.Dispose();
}
this.loadedScratches.Clear();
}
/// <summary>
/// Renew a given ScratchPadDocument.
/// </summary>
/// <param name="doc">The document to renew.</param>
/// <returns>The new load status.</returns>
public ScratchLoadStatus RenewScratch(ScratchpadDocument doc)
{
var existingScratch = this.loadedScratches.FirstOrDefault(x => x.Key == doc.Id);
if (existingScratch.Value != null)
{
existingScratch.Value.Dispose();
this.loadedScratches[existingScratch.Key] = null;
}
var code = doc.IsMacro ? this.MacroProcessor.Process(doc.Content) : doc.Content;
var options = ScriptOptions.Default
// Dalamud
.AddReferences(typeof(Dalamud).Assembly)
// ImGui
.AddReferences(typeof(ImGuiNET.ImGui).Assembly)
// ImGuiScene
.AddReferences(typeof(ImGuiScene.RawDX11Scene).Assembly)
// FFXIVClientStructs
.AddReferences(typeof(FFXIVClientStructs.Resolver).Assembly)
// Lumina
.AddReferences(typeof(Lumina.GameData).Assembly)
// Lumina.Excel
.AddReferences(typeof(Lumina.Excel.GeneratedSheets.TerritoryType).Assembly)
.AddImports("System")
.AddImports("System.IO")
.AddImports("System.Reflection")
.AddImports("System.Runtime.InteropServices")
.AddImports("Dalamud")
.AddImports("Dalamud.Data")
.AddImports("Dalamud.Game")
.AddImports("Dalamud.Game.ClientState")
.AddImports("Dalamud.Game.ClientState.Buddy")
.AddImports("Dalamud.Game.ClientState.Conditions")
.AddImports("Dalamud.Game.ClientState.Fates")
.AddImports("Dalamud.Game.ClientState.JobGauge")
.AddImports("Dalamud.Game.ClientState.Keys")
.AddImports("Dalamud.Game.ClientState.Objects")
.AddImports("Dalamud.Game.ClientState.Party")
.AddImports("Dalamud.Game.Command")
.AddImports("Dalamud.Game.Gui")
.AddImports("Dalamud.Game.Gui.FlyText")
.AddImports("Dalamud.Game.Gui.PartyFinder")
.AddImports("Dalamud.Game.Gui.Toast")
.AddImports("Dalamud.Hooking")
.AddImports("Dalamud.Game.Libc")
.AddImports("Dalamud.Game.Network")
.AddImports("Dalamud.Game.Text.SeStringHandling")
.AddImports("Dalamud.Logging")
.AddImports("Dalamud.Plugin")
.AddImports("Dalamud.Utility")
.AddImports("ImGuiNET");
try
{
var script = CSharpScript.Create(code, options);
var pluginType = script.ContinueWith<Type>("return typeof(ScratchPlugin);")
.RunAsync().GetAwaiter().GetResult().ReturnValue;
var pi = new DalamudPluginInterface($"Scratch-{doc.Id}", PluginLoadReason.Unknown, false);
var ioc = Service<ServiceContainer>.Get();
var plugin = ioc.Create(pluginType, pi);
if (plugin == null)
throw new Exception("Could not initialize scratch plugin");
this.loadedScratches[doc.Id] = (IDalamudPlugin)plugin;
return ScratchLoadStatus.Success;
}
catch (CompilationErrorException ex)
{
Log.Error(ex, "Compilation error occurred!\n" + string.Join(Environment.NewLine, ex.Diagnostics));
return ScratchLoadStatus.FailureCompile;
}
catch (Exception ex)
{
Log.Error(ex, "Initialization error occured!\n");
return ScratchLoadStatus.FailureInit;
}
}
}
}

View file

@ -1,37 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace Dalamud.Interface.Internal.Scratchpad
{
/// <summary>
/// A file watcher for <see cref="ScratchpadDocument"/> classes.
/// </summary>
internal class ScratchFileWatcher
{
private FileSystemWatcher watcher = new();
/// <summary>
/// Gets or sets the list of tracked ScratchPad documents.
/// </summary>
public List<ScratchpadDocument> TrackedScratches { get; set; } = new List<ScratchpadDocument>();
/// <summary>
/// Load a new ScratchPadDocument from disk.
/// </summary>
/// <param name="path">The filepath to load.</param>
public void Load(string path)
{
this.TrackedScratches.Add(new ScratchpadDocument
{
Title = Path.GetFileName(path),
Content = File.ReadAllText(path),
});
this.watcher.Path = Path.GetDirectoryName(path);
this.watcher.Filter = Path.GetFileName(path);
this.watcher.EnableRaisingEvents = true;
this.watcher.Changed += (sender, args) => this.TrackedScratches[0].Content = File.ReadAllText(args.FullPath);
this.watcher.BeginInit();
}
}
}

View file

@ -1,28 +0,0 @@
namespace Dalamud.Interface.Internal.Scratchpad
{
/// <summary>
/// The load status of a <see cref="ScratchpadDocument"/> class.
/// </summary>
internal enum ScratchLoadStatus
{
/// <summary>
/// Unknown.
/// </summary>
Unknown,
/// <summary>
/// Failure to compile.
/// </summary>
FailureCompile,
/// <summary>
/// Failure to initialize.
/// </summary>
FailureInit,
/// <summary>
/// Success.
/// </summary>
Success,
}
}

View file

@ -1,309 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Logging;
namespace Dalamud.Interface.Internal.Scratchpad
{
/// <summary>
/// This class converts ScratchPad macros into runnable scripts.
/// </summary>
internal class ScratchMacroProcessor
{
private const string Template = @"
public class ScratchPlugin : IDalamudPlugin {
public string Name => ""ScratchPlugin"";
private readonly DalamudPluginInterface pi;
private readonly BuddyList buddies;
private readonly ChatGui chat;
private readonly ChatHandlers chatHandlers;
private readonly ClientState clientState;
private readonly CommandManager commands;
private readonly Condition condition;
private readonly DataManager data;
private readonly FateTable fates;
private readonly FlyTextGui flyText;
private readonly Framework framework;
private readonly GameGui gameGui;
private readonly GameNetwork gameNetwork;
private readonly JobGauges gauges;
private readonly KeyState keyState;
private readonly LibcFunction libc;
private readonly ObjectTable objects;
private readonly PartyFinderGui pfGui;
private readonly PartyList party;
private readonly SeStringManager seStringManager;
private readonly SigScanner sigScanner;
private readonly TargetManager targets;
private readonly ToastGui toasts;
{SETUPBODY}
public ScratchPlugin(
DalamudPluginInterface pluginInterface,
BuddyList buddies,
ChatGui chat,
ChatHandlers chatHandlers,
ClientState clientState,
CommandManager commands,
Condition condition,
DataManager data,
FateTable fates,
FlyTextGui flyText,
Framework framework,
GameGui gameGui,
GameNetwork gameNetwork,
JobGauges gauges,
KeyState keyState,
LibcFunction libcFunction,
ObjectTable objects,
PartyFinderGui pfGui,
PartyList party,
SeStringManager seStringManager,
SigScanner sigScanner,
TargetManager targets,
ToastGui toasts)
{
this.pi = pluginInterface;
this.buddies = buddies;
this.chat = chat;
this.chatHandlers = chatHandlers;
this.clientState = clientState;
this.commands = commands;
this.condition = condition;
this.data = data;
this.fates = fates;
this.flyText = flyText;
this.framework = framework;
this.gameGui = gameGui;
this.gameNetwork = gameNetwork;
this.gauges = gauges;
this.keyState = keyState;
this.libc = libcFunction;
this.objects = objects;
this.pfGui = pfGui;
this.party = party;
this.seStringManager = seStringManager;
this.sigScanner = sigScanner;
this.targets = targets;
this.toasts = toasts;
this.pi.UiBuilder.Draw += DrawUI;
{INITBODY}
}
private void DrawUI()
{
{DRAWBODY}
}
{NONEBODY}
public void Dispose()
{
this.pi.UiBuilder.Draw -= DrawUI;
{DISPOSEBODY}
}
}
";
private enum ParseContext
{
None,
Init,
Draw,
Hook,
Dispose,
}
/// <summary>
/// Process the given macro input and return a script.
/// </summary>
/// <param name="input">Input to process.</param>
/// <returns>A runnable script.</returns>
public string Process(string input)
{
var lines = input.Split(new[] { '\r', '\n' });
var ctx = ParseContext.None;
var setupBody = string.Empty;
var noneBody = string.Empty;
var initBody = string.Empty;
var disposeBody = string.Empty;
var drawBody = string.Empty;
var tHook = new HookInfo();
var hooks = new List<HookInfo>();
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (line.StartsWith("INITIALIZE:"))
{
ctx = ParseContext.Init;
continue;
}
if (line.StartsWith("DRAW:"))
{
ctx = ParseContext.Draw;
continue;
}
if (line.StartsWith("DISPOSE:"))
{
ctx = ParseContext.Dispose;
continue;
}
if (line.StartsWith("HOOK("))
{
ctx = ParseContext.Hook;
var args = Regex.Match(line, "HOOK\\((.+)+\\):").Groups[0].Captures[0].Value
.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(x => x[0] == ' ' ? x[1..] : x).ToArray();
tHook.Sig = args[0].Replace("\"", string.Empty); // Split quotation marks if any
tHook.Sig = tHook.Sig.Replace("HOOK(", string.Empty);
tHook.RetType = args[1];
tHook.Arguments = string.Join(", ", args.Skip(2).ToArray()).Replace("):", string.Empty);
var invocationGroups = Regex.Matches(tHook.Arguments, "\\S+ ([a-zA-Z0-9]+),*").Cast<Match>()
.Select(x => x.Groups[1].Value);
tHook.Invocation = string.Join(", ", invocationGroups);
continue;
}
if (line.StartsWith("END;"))
{
switch (ctx)
{
case ParseContext.None:
throw new Exception("Not in a macro!!!");
case ParseContext.Init:
break;
case ParseContext.Draw:
break;
case ParseContext.Hook:
hooks.Add(tHook);
tHook = new HookInfo();
break;
case ParseContext.Dispose:
break;
default:
throw new ArgumentOutOfRangeException(paramName: nameof(input));
}
ctx = ParseContext.None;
continue;
}
switch (ctx)
{
case ParseContext.None:
noneBody += line + "\n";
break;
case ParseContext.Init:
initBody += line + "\n";
break;
case ParseContext.Draw:
drawBody += line + "\n";
break;
case ParseContext.Hook:
tHook.Body += line + "\n";
break;
case ParseContext.Dispose:
disposeBody += line + "\n";
break;
default:
throw new ArgumentOutOfRangeException(paramName: nameof(input));
}
}
var hookSetup = string.Empty;
var hookInit = string.Empty;
var hookDetour = string.Empty;
var hookDispose = string.Empty;
for (var i = 0; i < hooks.Count; i++)
{
var hook = hooks[i];
hookSetup += $"private delegate {hook.RetType} Hook{i}Delegate({hook.Arguments});\n";
hookSetup += $"private Hook<Hook{i}Delegate> hook{i}Inst;\n";
hookInit += $"var addrH{i} = pi.TargetModuleScanner.ScanText(\"{hook.Sig}\");\n";
hookInit += $"this.hook{i}Inst = new Hook<Hook{i}Delegate>(addrH{i}, new Hook{i}Delegate(Hook{i}Detour), this);\n";
hookInit += $"this.hook{i}Inst.Enable();\n";
var originalCall = $"this.hook{i}Inst.Original({hook.Invocation});\n";
if (hook.RetType != "void")
originalCall = "return " + originalCall;
if (hook.Body.Contains($"hook{i}Inst.Original(") || hook.Body.Contains("ORIG("))
{
PluginLog.Warning($"Attention! A manual call to Original() in Hook #{i} was detected. Original calls will not be managed for you.");
originalCall = string.Empty;
}
if (hook.Body.Contains("ORIG("))
{
PluginLog.Warning($"Normalizing Original() call in Hook #{i}.");
hook.Body = hook.Body.Replace("ORIG(", $"this.hook{i}Inst.Original(");
}
hookDetour +=
$"private {hook.RetType} Hook{i}Detour({hook.Arguments}) {{\n" +
(!string.IsNullOrEmpty(originalCall) ? "try {\n" : string.Empty) +
$" {hook.Body}\n";
if (!string.IsNullOrEmpty(originalCall))
{
hookDetour += "} catch(Exception ex) {\n" +
$" PluginLog.Error(ex, \"Exception in Hook{i}Detour!!\");\n" +
"}\n" +
$"{originalCall}";
}
hookDetour += $"\n}}\n";
hookDispose += $"this.hook{i}Inst.Dispose();\n";
}
setupBody += "\n" + hookSetup;
initBody = hookInit + "\n" + initBody;
noneBody += "\n" + hookDetour;
disposeBody += "\n" + hookDispose;
var output = Template;
output = output.Replace("{SETUPBODY}", setupBody);
output = output.Replace("{INITBODY}", initBody);
output = output.Replace("{DRAWBODY}", drawBody);
output = output.Replace("{NONEBODY}", noneBody);
output = output.Replace("{DISPOSEBODY}", disposeBody);
return output;
}
private class HookInfo
{
public string Body { get; set; }
public string Arguments { get; set; }
public string Invocation { get; set; }
public string RetType { get; set; }
public string Sig { get; set; }
}
}
}

View file

@ -1,45 +0,0 @@
using System;
namespace Dalamud.Interface.Internal.Scratchpad
{
/// <summary>
/// This class represents a single document in the ScratchPad.
/// </summary>
internal class ScratchpadDocument
{
/// <summary>
/// Gets or sets the guid ID of the document.
/// </summary>
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// Gets or sets the document content.
/// </summary>
public string Content { get; set; } = "INITIALIZE:\n\tPluginLog.Information(\"Loaded!\");\nEND;\n\nDISPOSE:\n\tPluginLog.Information(\"Disposed!\");\nEND;\n";
/// <summary>
/// Gets or sets the document title.
/// </summary>
public string Title { get; set; } = "New Document";
/// <summary>
/// Gets or sets a value indicating whether the document has unsaved content.
/// </summary>
public bool HasUnsaved { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the document is open.
/// </summary>
public bool IsOpen { get; set; }
/// <summary>
/// Gets or sets the load status of the document.
/// </summary>
public ScratchLoadStatus Status { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this document is a macro.
/// </summary>
public bool IsMacro { get; set; } = true;
}
}

View file

@ -1,196 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Scratchpad;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using Serilog;
namespace Dalamud.Interface.Internal.Windows
{
/// <summary>
/// This class facilitates interacting with the ScratchPad window.
/// </summary>
internal class ScratchpadWindow : Window, IDisposable
{
private readonly List<ScratchpadDocument> documents = new();
private readonly ScratchFileWatcher watcher = new();
private string pathInput = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="ScratchpadWindow"/> class.
/// </summary>
public ScratchpadWindow()
: base("Plugin Scratchpad", ImGuiWindowFlags.MenuBar)
{
this.documents.Add(new ScratchpadDocument());
this.SizeConstraints = new WindowSizeConstraints
{
MinimumSize = new Vector2(400, 400),
MaximumSize = new Vector2(1000, 1000),
};
this.Execution = new ScratchExecutionManager();
this.RespectCloseHotkey = false;
}
/// <summary>
/// Gets the ScratchPad execution manager.
/// </summary>
public ScratchExecutionManager Execution { get; private set; }
/// <inheritdoc/>
public override void Draw()
{
if (ImGui.BeginPopupModal("Choose Path"))
{
ImGui.Text("Enter path:\n\n");
ImGui.InputText("###ScratchPathInput", ref this.pathInput, 1000);
if (ImGui.Button("OK", new Vector2(120, 0)))
{
ImGui.CloseCurrentPopup();
this.watcher.Load(this.pathInput);
this.pathInput = string.Empty;
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Cancel", new Vector2(120, 0)))
{
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
}
if (ImGui.BeginMenuBar())
{
if (ImGui.BeginMenu("File"))
{
if (ImGui.MenuItem("Load & Watch"))
{
ImGui.OpenPopup("Choose Path");
}
ImGui.EndMenu();
}
ImGui.EndMenuBar();
}
var flags = ImGuiTabBarFlags.Reorderable | ImGuiTabBarFlags.TabListPopupButton |
ImGuiTabBarFlags.FittingPolicyScroll;
if (ImGui.BeginTabBar("ScratchDocTabBar", flags))
{
if (ImGui.TabItemButton("+", ImGuiTabItemFlags.Trailing | ImGuiTabItemFlags.NoTooltip))
this.documents.Add(new ScratchpadDocument());
var docs = this.documents.Concat(this.watcher.TrackedScratches).ToArray();
for (var i = 0; i < docs.Length; i++)
{
var isOpen = true;
if (ImGui.BeginTabItem(docs[i].Title + (docs[i].HasUnsaved ? "*" : string.Empty) + "###ScratchItem" + i, ref isOpen))
{
var content = docs[i].Content;
if (ImGui.InputTextMultiline("###ScratchInput" + i, ref content, 20000, new Vector2(-1, -34), ImGuiInputTextFlags.AllowTabInput))
{
docs[i].Content = content;
docs[i].HasUnsaved = true;
}
ImGuiHelpers.ScaledDummy(3);
if (ImGui.Button("Compile & Reload"))
{
docs[i].Status = this.Execution.RenewScratch(docs[i]);
}
ImGui.SameLine();
if (ImGui.Button("Dispose all"))
{
this.Execution.DisposeAllScratches();
}
ImGui.SameLine();
if (ImGui.Button("Dump processed code"))
{
try
{
var code = this.Execution.MacroProcessor.Process(docs[i].Content);
Log.Information(code);
ImGui.SetClipboardText(code);
}
catch (Exception ex)
{
Log.Error(ex, "Could not process macros");
}
}
ImGui.SameLine();
if (ImGui.Button("Toggle Log"))
{
Service<DalamudInterface>.Get().ToggleLogWindow();
}
ImGui.SameLine();
var isMacro = docs[i].IsMacro;
if (ImGui.Checkbox("Use Macros", ref isMacro))
{
docs[i].IsMacro = isMacro;
}
ImGui.SameLine();
switch (docs[i].Status)
{
case ScratchLoadStatus.Unknown:
ImGui.TextColored(ImGuiColors.DalamudGrey, "Compile scratch to see status");
break;
case ScratchLoadStatus.FailureCompile:
ImGui.TextColored(ImGuiColors.DalamudRed, "Error during compilation");
break;
case ScratchLoadStatus.FailureInit:
ImGui.TextColored(ImGuiColors.DalamudRed, "Error during init");
break;
case ScratchLoadStatus.Success:
ImGui.TextColored(ImGuiColors.HealerGreen, "OK!");
break;
default:
throw new ArgumentOutOfRangeException();
}
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey, docs[i].Id.ToString());
ImGui.EndTabItem();
}
}
ImGui.EndTabBar();
}
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Execution.DisposeAllScratches();
}
}
}