diff --git a/src/StudioCore/Editor/EditorDecorations.cs b/src/StudioCore/Editor/EditorDecorations.cs index 8c7550731..79bcc4056 100644 --- a/src/StudioCore/Editor/EditorDecorations.cs +++ b/src/StudioCore/Editor/EditorDecorations.cs @@ -4,6 +4,7 @@ using SoulsFormats; using StudioCore.ParamEditor; using StudioCore.TextEditor; +using StudioCore.Editor.MassEdit; using System; using System.Collections.Generic; using System.Diagnostics; @@ -555,9 +556,9 @@ public static bool PropertyRowRefsContextItems(ParamBank bank, List re ParamMetaData meta = ParamMetaData.Get(bank.Params[rt].AppliedParamdef); var maxResultsPerRefType = 15 / reftypes.Count; - List rows = RowSearchEngine.rse.Search((bank, bank.Params[rt]), + List<(string, Param.Row)> rows = SearchEngine.row.Search((bank, bank.Params[rt]), _refContextCurrentAutoComplete, true, true); - foreach (Param.Row r in rows) + foreach ((string param, Param.Row r) in rows) { if (maxResultsPerRefType <= 0) { @@ -790,7 +791,7 @@ public static void DrawCalcCorrectGraph(EditorScreen screen, ParamMetaData meta, var searchTerm = pref.conditionField != null ? $@"prop {fieldName} ^{currentID}$ && prop {pref.conditionField} ^{pref.conditionValue}$" : $@"prop {fieldName} ^{currentID}$"; - return RowSearchEngine.rse.Search((bank, bank.Params[paramName]), searchTerm, false, false); + return SearchEngine.row.Search((bank, bank.Params[paramName]), searchTerm, false, false).Select((x, i) => x.Item2).ToList(); } public static bool ImguiTableSeparator() diff --git a/src/StudioCore/ParamEditor/AutoFill.cs b/src/StudioCore/Editor/MassEdit/AutoFill.cs similarity index 50% rename from src/StudioCore/ParamEditor/AutoFill.cs rename to src/StudioCore/Editor/MassEdit/AutoFill.cs index 35bcfde2e..f2fac74e7 100644 --- a/src/StudioCore/ParamEditor/AutoFill.cs +++ b/src/StudioCore/Editor/MassEdit/AutoFill.cs @@ -1,41 +1,52 @@ using Andre.Formats; -using static Andre.Native.ImGuiBindings; using StudioCore.Editor; +using static Andre.Native.ImGuiBindings; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using Octokit; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; +using DotNext.Collections.Generic; -namespace StudioCore.ParamEditor; +namespace StudioCore.Editor.MassEdit; -internal class AutoFillSearchEngine +internal class AutoFillSearchEngine { private readonly string[] _autoFillArgs; - private readonly SearchEngine engine; + private readonly SearchEngine engine; private readonly string id; - private AutoFillSearchEngine _additionalCondition; + internal string displayedCategory; + private AutoFillSearchEngine _additionalCondition; private bool _autoFillNotToggle; private bool _useAdditionalCondition; - internal AutoFillSearchEngine(string id, SearchEngine searchEngine) + internal AutoFillSearchEngine(SearchEngine searchEngine, string dispCat) { - this.id = id; + AutoFill._imguiID++; + id = "e" + AutoFill._imguiID; engine = searchEngine; _autoFillArgs = Enumerable.Repeat("", engine.AllCommands().Sum(x => x.Item2.Length)).ToArray(); _autoFillNotToggle = false; _useAdditionalCondition = false; _additionalCondition = null; + displayedCategory = dispCat; + AutoFill.autoFillersSE[searchEngine] = this; } - - internal string Menu(bool enableComplexToggles, bool enableDefault, string suffix, string inheritedCommand, - Func subMenu) + private AutoFillSearchEngine(AutoFillSearchEngine parentEngine) { - return Menu(enableComplexToggles, null, enableDefault, suffix, inheritedCommand, subMenu); + id = parentEngine.id + "+"; + engine = parentEngine.engine; + _autoFillArgs = Enumerable.Repeat("", engine.AllCommands().Sum(x => x.Item2.Length)).ToArray(); + _autoFillNotToggle = false; + _useAdditionalCondition = false; + _additionalCondition = null; + displayedCategory = $@"where {parentEngine.displayedCategory} also"; } - - internal string Menu(bool enableComplexToggles, AutoFillSearchEngine multiStageSE, - bool enableDefault, string suffix, string inheritedCommand, Func subMenu) + internal string Menu(bool enableComplexToggles, bool enableDefault, string suffix, string inheritedCommand, bool terminal) { + ImGui.Separator(); + ImGui.TextColored(AutoFill.HINTCOLOUR, @$"Select {displayedCategory}..."); var currentArgIndex = 0; if (enableComplexToggles) { @@ -44,25 +55,17 @@ internal string Menu(bool enableComplexToggles, AutoFillSearchEngine ImGui.Checkbox("Add another condition?##meautoinputadditionalcondition" + id, ref _useAdditionalCondition); } - else if (multiStageSE != null) - { - ImGui.Checkbox("Add another condition?##meautoinputadditionalcondition" + id, - ref _useAdditionalCondition); - } if (_useAdditionalCondition && _additionalCondition == null) { - _additionalCondition = new AutoFillSearchEngine(id + "0", engine); + _additionalCondition = new AutoFillSearchEngine(this); } else if (!_useAdditionalCondition) { _additionalCondition = null; } - foreach ((string, string[], string) cmd in enableDefault - ? engine.VisibleCommands().Append((null, engine.defaultFilter.args, engine.defaultFilter.wiki)) - .ToList() - : engine.VisibleCommands()) + foreach ((string, string[], string) cmd in engine.VisibleCommands(enableDefault)) { var argIndices = new int[cmd.Item2.Length]; var valid = true; @@ -78,27 +81,19 @@ internal string Menu(bool enableComplexToggles, AutoFillSearchEngine string subResult = null; var wiki = cmd.Item3; - UIHints.AddImGuiHintButton(cmd.Item1 == null ? "hintdefault" : "hint" + cmd.Item1, ref wiki, false, - true); - if (subMenu != null || _additionalCondition != null) + UIHints.AddImGuiHintButton(cmd.Item1 == null ? "hintdefault" : "hint" + cmd.Item1, ref wiki, false, true); + if (!terminal || _additionalCondition != null) { if (ImGui.BeginMenu(cmd.Item1 == null ? "Default filter..." : cmd.Item1, valid)) { - var curResult = inheritedCommand + getCurrentStepText(valid, cmd.Item1, argIndices, - _additionalCondition != null ? " && " : suffix); - if (_useAdditionalCondition && multiStageSE != null) + var curResult = inheritedCommand + getCurrentStepText(valid, cmd.Item1, argIndices, _additionalCondition != null ? " && " : suffix); + if (_additionalCondition != null) { - subResult = multiStageSE.Menu(enableComplexToggles, enableDefault, suffix, curResult, - subMenu); - } - else if (_additionalCondition != null) - { - subResult = _additionalCondition.Menu(enableComplexToggles, enableDefault, suffix, - curResult, subMenu); + subResult = _additionalCondition.Menu(enableComplexToggles, enableDefault, suffix, curResult, terminal); } else { - subResult = subMenu(curResult); + subResult = SubMenu(curResult, suffix); } ImGui.EndMenu(); @@ -106,10 +101,7 @@ internal string Menu(bool enableComplexToggles, AutoFillSearchEngine } else { - subResult = ImGui.Selectable(cmd.Item1 == null ? "Default filter..." : cmd.Item1, false, - valid ? ImGuiSelectableFlags.None : ImGuiSelectableFlags.Disabled) - ? suffix - : null; + subResult = ImGui.Selectable(cmd.Item1 == null ? "Default filter..." : cmd.Item1, false, valid ? ImGuiSelectableFlags.None : ImGuiSelectableFlags.Disabled) ? suffix : null; } //ImGui.Indent(); @@ -141,6 +133,46 @@ internal string Menu(bool enableComplexToggles, AutoFillSearchEngine return null; } + private string SubMenu(string inheritedCommand, string suffix) + { + ImGui.TextColored(AutoFill.PREVIEWCOLOUR, inheritedCommand); + + IEnumerable afses = getChildAutoFillSearchEngines(); + string res1 = null; + foreach (AutoFillSearchEngine afse in afses) + { + res1 = afse.Menu(true, true, ": ", inheritedCommand, false); + if (res1 != null) + { + return res1; + } + } + AutoFillOperation afo = getChildAutoFillOperation(); + if (afo != null) + { + var res2 = afo.Menu(";"); + if (res2 != null) + { + return res2; + } + } + + return res1; + } + + private AutoFillOperation getChildAutoFillOperation() + { + var nextOp = engine.NextOperation(); + if (nextOp == null) + return null; + return AutoFill.autoFillersO.GetValueOrDefault(nextOp); + } + + private IEnumerable getChildAutoFillSearchEngines() + { + return engine.NextSearchEngines().Select(x => AutoFill.autoFillersSE.GetValueOrDefault(x.Item1)).Where(x => x != null); + } + internal string getCurrentStepText(bool valid, string command, int[] argIndices, string suffixToUse) { if (!valid) @@ -174,30 +206,131 @@ internal string getCurrentStepText(bool valid, string command, int[] argIndices, } } -internal class AutoFill +internal class AutoFillOperation { - // Type hell. Can't omit the type. - private static readonly AutoFillSearchEngine - autoFillParse = new("parse", ParamAndRowSearchEngine.parse); + private readonly string[] _autoFillArgs; + private readonly OperationCategory op; + private readonly string id; + internal string displayedCategory; - private static readonly AutoFillSearchEngine autoFillVse = new("vse", VarSearchEngine.vse); + internal AutoFillOperation(OperationCategory operation, string dispCat) + { + AutoFill._imguiID++; + id = "e" + AutoFill._imguiID; + op = operation; + _autoFillArgs = Enumerable.Repeat("", op.AllCommands().Sum(x => x.Value.argNames.Length)).ToArray(); + displayedCategory = dispCat; + AutoFill.autoFillersO[operation] = this; + } - private static readonly AutoFillSearchEngine autoFillPse = - new("pse", ParamSearchEngine.pse); + internal string Menu(string suffix) + { + ImGui.Separator(); + ImGui.TextColored(AutoFill.HINTCOLOUR, @$"Select {displayedCategory} operation..."); + var currentArgIndex = 0; + string result = null; + foreach (KeyValuePair cmd in op.AllCommands()) + { + string cmdName = cmd.Key; + METypelessOperationDef cmdData = cmd.Value; + var argIndices = new int[cmdData.argNames.Length]; + var valid = true; + for (var i = 0; i < argIndices.Length; i++) + { + argIndices[i] = currentArgIndex; + currentArgIndex++; + if (string.IsNullOrEmpty(_autoFillArgs[argIndices[i]])) + { + valid = false; + } + } + if (cmdData.shouldShow != null && !cmdData.shouldShow()) + continue; + + var wiki = cmdData.wiki; + UIHints.AddImGuiHintButton(cmdName, ref wiki, false, true); + result = ImGui.Selectable(cmdName, false, + valid ? ImGuiSelectableFlags.None : ImGuiSelectableFlags.Disabled) + ? suffix + : null; + + ImGui.Indent(); + for (var i = 0; i < argIndices.Length; i++) + { + if (i != 0) + { + ImGui.SameLine(); + } + + ImGui.InputTextWithHint("##meautoinputop" + argIndices[i], cmdData.argNames[i], + ref _autoFillArgs[argIndices[i]], 256); + ImGui.SameLine(); + ImGui.Button($@"{ForkAwesome.CaretDown}"); + if (ImGui.BeginPopupContextItem("##meautoinputoapopup" + argIndices[i], + ImGuiPopupFlags.MouseButtonLeft)) + { + var opargResult = AutoFill.MassEditAutoFillForArguments(); + if (opargResult != null) + { + _autoFillArgs[argIndices[i]] = opargResult; + } + + ImGui.EndPopup(); + } + } - private static readonly AutoFillSearchEngine<(ParamBank, Param), Param.Row> autoFillRse = - new("rse", RowSearchEngine.rse); + ImGui.Unindent(); + if (result != null && valid) + { + var argText = argIndices.Length > 0 ? _autoFillArgs[argIndices[0]] : null; + for (var i = 1; i < argIndices.Length; i++) + { + argText += ":" + _autoFillArgs[argIndices[i]]; + } - private static readonly AutoFillSearchEngine<(string, Param.Row), (PseudoColumn, Param.Column)> autoFillCse = - new("cse", CellSearchEngine.cse); + result = cmdName + (argText != null ? " " + argText + result : result); + return result; + } + } - private static string[] _autoFillArgsGop = Enumerable.Repeat("", MEGlobalOperation.globalOps.AvailableCommands(true).Sum((x) => x.Item2.Length)).ToArray(); - private static string[] _autoFillArgsRop = Enumerable.Repeat("", MERowOperation.rowOps.AvailableCommands(true).Sum((x) => x.Item2.Length)).ToArray(); - private static string[] _autoFillArgsCop = Enumerable.Repeat("", MEValueOperation.valueOps.AvailableCommands(true).Sum((x) => x.Item2.Length)).ToArray(); - private static string[] _autoFillArgsOa = - Enumerable.Repeat("", MEOperationArgument.arg.AllArguments().Sum(x => x.Item2.Length)).ToArray(); + return result; + } +} + +internal class AutoFill +{ + internal static int _imguiID = 0; + + static AutoFill() + { + autoFillPrsse = new(SearchEngine.paramRowSelection, "param and rows"); + autoFillPrcse = new(SearchEngine.paramRowClipboard, "param and rows"); + autoFillPse = new(SearchEngine.param, "params"); + autoFillRse = new(SearchEngine.row, "rows"); + autoFillCse = new(SearchEngine.cell, "cells"); + autoFillVse = new(SearchEngine.var, "variables"); + + autoFillGo = new(OperationCategory.global, "global"); + autoFillRo = new(OperationCategory.row, "row"); + autoFillCo = new(OperationCategory.cell, "cell"); + autoFillVo = new(OperationCategory.var, "variable"); + } + private static readonly AutoFillSearchEngine autoFillPrsse; + private static readonly AutoFillSearchEngine autoFillPrcse; + private static readonly AutoFillSearchEngine autoFillPse; + private static readonly AutoFillSearchEngine autoFillRse; + private static readonly AutoFillSearchEngine autoFillCse; + private static readonly AutoFillSearchEngine autoFillVse; + + private static readonly AutoFillOperation autoFillGo; + private static readonly AutoFillOperation autoFillRo; + private static readonly AutoFillOperation autoFillCo; + private static readonly AutoFillOperation autoFillVo; - private static string _literalArg = ""; + private static string[] _autoFillArgsOa = Enumerable.Repeat("", OperationArguments.arg.AllArguments().Sum(x => x.Item2.Length) + 1).ToArray(); + + internal static Dictionary autoFillersSE = new(); + internal static Dictionary autoFillersO = new(); internal static Vector4 HINTCOLOUR = new(0.3f, 0.5f, 1.0f, 1.0f); internal static Vector4 PREVIEWCOLOUR = new(0.65f, 0.75f, 0.65f, 1.0f); @@ -208,8 +341,7 @@ public static string ParamSearchBarAutoFill() ImGui.Button($@"{ForkAwesome.CaretDown}"); if (ImGui.BeginPopupContextItem("##psbautoinputoapopup", ImGuiPopupFlags.MouseButtonLeft)) { - ImGui.TextColored(HINTCOLOUR, "Select params..."); - var result = autoFillPse.Menu(true, false, "", null, null); + var result = autoFillPse.Menu(true, false, "", null, true); ImGui.EndPopup(); return result; } @@ -223,8 +355,7 @@ public static string RowSearchBarAutoFill() ImGui.Button($@"{ForkAwesome.CaretDown}"); if (ImGui.BeginPopupContextItem("##rsbautoinputoapopup", ImGuiPopupFlags.MouseButtonLeft)) { - ImGui.TextColored(HINTCOLOUR, "Select rows..."); - var result = autoFillRse.Menu(true, false, "", null, null); + var result = autoFillRse.Menu(true, false, "", null, true); ImGui.EndPopup(); return result; } @@ -238,8 +369,7 @@ public static string ColumnSearchBarAutoFill() ImGui.Button($@"{ForkAwesome.CaretDown}"); if (ImGui.BeginPopupContextItem("##csbautoinputoapopup", ImGuiPopupFlags.MouseButtonLeft)) { - ImGui.TextColored(HINTCOLOUR, "Select fields..."); - var result = autoFillCse.Menu(true, false, "", null, null); + var result = autoFillCse.Menu(true, false, "", null, true); ImGui.EndPopup(); return result; } @@ -254,130 +384,32 @@ public static string MassEditCompleteAutoFill() ImGui.Button($@"{ForkAwesome.CaretDown}"); if (ImGui.BeginPopupContextItem("##meautoinputoapopup", ImGuiPopupFlags.MouseButtonLeft)) { - ImGui.PushID("paramrow"); - ImGui.TextColored(HINTCOLOUR, "Select param and rows..."); - var result1 = autoFillParse.Menu(false, autoFillRse, false, ": ", null, inheritedCommand => - { - if (inheritedCommand != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand); - } - - ImGui.TextColored(HINTCOLOUR, "Select fields..."); - var res1 = autoFillCse.Menu(true, true, ": ", inheritedCommand, inheritedCommand2 => - { - if (inheritedCommand2 != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand2); - } - - ImGui.TextColored(HINTCOLOUR, "Select field operation..."); - return MassEditAutoFillForOperation(MEValueOperation.valueOps, ref _autoFillArgsCop, ";", null); - }); - ImGui.Separator(); - ImGui.TextColored(HINTCOLOUR, "Select row operation..."); - var res2 = MassEditAutoFillForOperation(MERowOperation.rowOps, ref _autoFillArgsRop, ";", null); - if (res1 != null) - { - return res1; - } - - return res2; - }); + /*special case*/ + ImGui.PushID("paramrowselection"); + var result = autoFillPrsse.Menu(false, false, ": ", null, false); ImGui.PopID(); - ImGui.Separator(); + ImGui.PushID("paramrowclipboard"); + result ??= autoFillPrcse.Menu(false, false, ": ", null, false); + ImGui.PopID(); + /*end special case*/ ImGui.PushID("param"); - ImGui.TextColored(HINTCOLOUR, "Select params..."); - var result2 = autoFillPse.Menu(true, false, ": ", null, inheritedCommand => - { - if (inheritedCommand != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand); - } - - ImGui.TextColored(HINTCOLOUR, "Select rows..."); - return autoFillRse.Menu(true, false, ": ", inheritedCommand, inheritedCommand2 => - { - if (inheritedCommand2 != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand2); - } - - ImGui.TextColored(HINTCOLOUR, "Select fields..."); - var res1 = autoFillCse.Menu(true, true, ": ", inheritedCommand2, inheritedCommand3 => - { - if (inheritedCommand3 != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand3); - } - - ImGui.TextColored(HINTCOLOUR, "Select field operation..."); - return MassEditAutoFillForOperation(MEValueOperation.valueOps, ref _autoFillArgsCop, ";", - null); - }); - string res2 = null; - if (CFG.Current.Param_AdvancedMassedit) - { - ImGui.Separator(); - ImGui.TextColored(HINTCOLOUR, "Select row operation..."); - res2 = MassEditAutoFillForOperation(MERowOperation.rowOps, ref _autoFillArgsRop, ";", null); - } - - if (res1 != null) - { - return res1; - } - - return res2; - }); - }); + result ??= autoFillPse.Menu(true, false, ": ", null, false); ImGui.PopID(); - string result3 = null; - string result4 = null; if (CFG.Current.Param_AdvancedMassedit) { - ImGui.Separator(); ImGui.PushID("globalop"); - ImGui.TextColored(HINTCOLOUR, "Select global operation..."); - result3 = MassEditAutoFillForOperation(MEGlobalOperation.globalOps, ref _autoFillArgsGop, ";", - null); + result ??= autoFillGo.Menu(";"); ImGui.PopID(); - if (MassParamEdit.massEditVars.Count != 0) - { - ImGui.Separator(); - ImGui.PushID("var"); - ImGui.TextColored(HINTCOLOUR, "Select variables..."); - result4 = autoFillVse.Menu(false, false, ": ", null, inheritedCommand => - { - if (inheritedCommand != null) - { - ImGui.TextColored(PREVIEWCOLOUR, inheritedCommand); - } - - ImGui.TextColored(HINTCOLOUR, "Select value operation..."); - return MassEditAutoFillForOperation(MEValueOperation.valueOps, ref _autoFillArgsCop, ";", - null); - }); - } } - - ImGui.EndPopup(); - if (result1 != null) + if (CFG.Current.Param_AdvancedMassedit && MassParamEdit.massEditVars.Count != 0) { - return result1; - } - - if (result2 != null) - { - return result2; - } - - if (result3 != null) - { - return result3; + ImGui.PushID("var"); + result ??= autoFillVse.Menu(false, false, ": ", null, false); + ImGui.PopID(); } - return result4; + ImGui.EndPopup(); + return result; } return null; @@ -385,93 +417,14 @@ public static string MassEditCompleteAutoFill() public static string MassEditOpAutoFill() { - return MassEditAutoFillForOperation(MEValueOperation.valueOps, ref _autoFillArgsCop, ";", null); - } - - private static string MassEditAutoFillForOperation(MEOperation ops, ref string[] staticArgs, - string suffix, Func subMenu) - { - var currentArgIndex = 0; - string result = null; - foreach ((string, string[], string) cmd in ops.AvailableCommands()) - { - var argIndices = new int[cmd.Item2.Length]; - var valid = true; - for (var i = 0; i < argIndices.Length; i++) - { - argIndices[i] = currentArgIndex; - currentArgIndex++; - if (string.IsNullOrEmpty(staticArgs[argIndices[i]])) - { - valid = false; - } - } - - var wiki = cmd.Item3; - UIHints.AddImGuiHintButton(cmd.Item1, ref wiki, false, true); - if (subMenu != null) - { - if (ImGui.BeginMenu(cmd.Item1, valid)) - { - result = subMenu(); - ImGui.EndMenu(); - } - } - else - { - result = ImGui.Selectable(cmd.Item1, false, - valid ? ImGuiSelectableFlags.None : ImGuiSelectableFlags.Disabled) - ? suffix - : null; - } - - ImGui.Indent(); - for (var i = 0; i < argIndices.Length; i++) - { - if (i != 0) - { - ImGui.SameLine(); - } - - ImGui.InputTextWithHint("##meautoinputop" + argIndices[i], cmd.Item2[i], - ref staticArgs[argIndices[i]], 256); - ImGui.SameLine(); - ImGui.Button($@"{ForkAwesome.CaretDown}"); - if (ImGui.BeginPopupContextItem("##meautoinputoapopup" + argIndices[i], - ImGuiPopupFlags.MouseButtonLeft)) - { - var opargResult = MassEditAutoFillForArguments(MEOperationArgument.arg, ref _autoFillArgsOa); - if (opargResult != null) - { - staticArgs[argIndices[i]] = opargResult; - } - - ImGui.EndPopup(); - } - } - - ImGui.Unindent(); - if (result != null && valid) - { - var argText = argIndices.Length > 0 ? staticArgs[argIndices[0]] : null; - for (var i = 1; i < argIndices.Length; i++) - { - argText += ":" + staticArgs[argIndices[i]]; - } - - result = cmd.Item1 + (argText != null ? " " + argText + result : result); - return result; - } - } - - return result; + return autoFillCo.Menu(";"); } - private static string MassEditAutoFillForArguments(MEOperationArgument oa, ref string[] staticArgs) + internal static string MassEditAutoFillForArguments() { var currentArgIndex = 0; string result = null; - foreach ((string, string, string[]) arg in oa.VisibleArguments()) + foreach ((string, string, string[]) arg in OperationArguments.arg.VisibleArguments()) { var argIndices = new int[arg.Item3.Length]; var valid = true; @@ -479,7 +432,7 @@ private static string MassEditAutoFillForArguments(MEOperationArgument oa, ref s { argIndices[i] = currentArgIndex; currentArgIndex++; - if (string.IsNullOrEmpty(staticArgs[argIndices[i]])) + if (string.IsNullOrEmpty(_autoFillArgsOa[argIndices[i]])) { valid = false; } @@ -495,7 +448,7 @@ private static string MassEditAutoFillForArguments(MEOperationArgument oa, ref s var argText = ""; for (var i = 0; i < argIndices.Length; i++) { - argText += " " + staticArgs[argIndices[i]]; + argText += " " + _autoFillArgsOa[argIndices[i]]; } return arg.Item1 + argText; @@ -510,11 +463,11 @@ private static string MassEditAutoFillForArguments(MEOperationArgument oa, ref s } ImGui.InputTextWithHint("##meautoinputoa" + argIndices[i], arg.Item3[i], - ref staticArgs[argIndices[i]], 256); + ref _autoFillArgsOa[argIndices[i]], 256); var var = MassEditAutoFillForVars(argIndices[i]); if (var != null) { - staticArgs[argIndices[i]] = var; + _autoFillArgsOa[argIndices[i]] = var; } } @@ -537,10 +490,10 @@ private static string MassEditAutoFillForArguments(MEOperationArgument oa, ref s ImGui.Separator(); if (ImGui.Selectable("Exactly...")) { - result = '"' + _literalArg + '"'; + result = '"' + _autoFillArgsOa[_autoFillArgsOa.Length-1] + '"'; } - ImGui.InputTextWithHint("##meautoinputoaExact", "literal value...", ref _literalArg, 256); + ImGui.InputTextWithHint("##meautoinputoaExact", "literal value...", ref _autoFillArgsOa[_autoFillArgsOa.Length-1], 256); return result; } diff --git a/src/StudioCore/Editor/MassEdit/EditOperation.cs b/src/StudioCore/Editor/MassEdit/EditOperation.cs new file mode 100644 index 000000000..891be77c4 --- /dev/null +++ b/src/StudioCore/Editor/MassEdit/EditOperation.cs @@ -0,0 +1,865 @@ +#nullable enable +using Andre.Formats; +using Microsoft.AspNetCore.Razor.TagHelpers; +using StudioCore.Editor; +using StudioCore.ParamEditor; +using System; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Linq; +using System.Reflection.Metadata.Ecma335; +using System.Text.RegularExpressions; + +namespace StudioCore.Editor.MassEdit; + +internal abstract class METypelessOperationDef +{ + internal string[] argNames; + internal string wiki; + internal Func function; + internal Func shouldShow; +} +internal class MEOperationDef : METypelessOperationDef +{ + internal MEOperationDef(string[] args, string tooltip, Func func, Func show = null) + { + argNames = args; + wiki = tooltip; + function = (dummy, v, str) => func((TInputValue)v, str); //Shitty wrapping perf loss. + shouldShow = show; + } + internal MEOperationDef(string[] args, string tooltip, Func func, Func show = null) + { + argNames = args; + wiki = tooltip; + function = (o, v, str) => func((TInputObject) o, (TInputValue)v, str); //Shitty wrapping perf loss. + shouldShow = show; + } +} +internal abstract class OperationCategory +{ + private static Dictionary editOperations; + + internal static MEGlobalOperation global; + internal static MERowOperation row; + internal static MECellOperation cell; + internal static MEVarOperation var; + static OperationCategory() + { + editOperations = new(); + global = new(); + row = new(); + cell = new(); + var = new(); + } + + internal static void AddEditOperation(TypedOperationCategory engine) + { + editOperations[typeof(TMECategory)] = engine; + } + internal static OperationCategory GetEditOperation(Type t) + { + return editOperations.GetValueOrDefault(t); + } + + internal abstract Dictionary AllCommands(); + internal abstract string NameForHelpTexts(); + internal abstract object GetElementValue((object, object) currentObject, Dictionary contextObjects); + internal abstract ResultValidity ValidateResult(object res); + internal abstract void UseResult(List actionList, (object, object) currentObject, Dictionary contextObjects, object res); + internal abstract bool HandlesCommand(string command); +} +internal abstract class TypedOperationCategory : OperationCategory +{ + internal Dictionary operations = new(); + internal string name = "[Unnamed operation type]"; + + internal TypedOperationCategory() + { + Setup(); + AddEditOperation(this); + } + + internal virtual void Setup() + { + } + + internal override bool HandlesCommand(string command) + { + return operations.ContainsKey(command); + } + internal override Dictionary AllCommands() + { + return operations; + } + internal void NewCmd(string command, string[] args, string wiki, Func func, Func show = null) + { + operations.Add(command, new MEOperationDef(args, wiki, func, show)); + } + internal void NewCmd(string command, string[] args, string wiki, Func func, Func show = null) + { + operations.Add(command, new MEOperationDef(args, wiki, func, show)); + } + + internal override string NameForHelpTexts() + { + return name; + } +} + +internal class MEGlobalOperation : TypedOperationCategory<(bool, bool), bool, bool, bool> +{ + internal override void Setup() + { + name = "global"; + NewCmd("clear", [], "Clears clipboard param and rows", (dummy, args) => + { + ParamBank.ClipboardParam = null; + ParamBank.ClipboardRows.Clear(); + return true; + }); + NewCmd("newvar", ["variable name", "value"], + "Creates a variable with the given value, and the type of that value", (dummy, args) => + { + int asInt; + double asDouble; + if (int.TryParse(args[1], out asInt)) + { + MassParamEdit.massEditVars[args[0]] = asInt; + } + else if (double.TryParse(args[1], out asDouble)) + { + MassParamEdit.massEditVars[args[0]] = asDouble; + } + else + { + MassParamEdit.massEditVars[args[0]] = args[1]; + } + + return true; + }, () => CFG.Current.Param_AdvancedMassedit); + NewCmd("clearvars", [], "Deletes all variables", (dummy, args) => + { + MassParamEdit.massEditVars.Clear(); + return true; + }, () => CFG.Current.Param_AdvancedMassedit); + } + internal override object GetElementValue((object, object) currentObject, Dictionary contextObjects) + { + return true; //Global op technically has no context / uses the dummy context of boolean + } + + internal override ResultValidity ValidateResult(object res) + { + return ResultValidity.SKIP; //Glocal ops don't do anything with the resulting obj + } + + internal override void UseResult(List actionList, (object, object) currentObject, Dictionary contextObjects, object res) + { + return; //Global ops, for now, don't use actions and simply execute effects themselves + } +} + +internal class MERowOperation : TypedOperationCategory<(string, Param.Row), string, Param.Row, (Param, Param.Row)> //technically we're still using string as the containing object in place of Param +{ + internal override void Setup() + { + name = "row"; + NewCmd("copy", [], + "Adds the selected rows into clipboard. If the clipboard param is different, the clipboard is emptied first", + (paramKey, row, args) => + { + if (paramKey == null) + { + throw new MEOperationException(@"Could not locate param"); + } + + if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) + { + throw new MEOperationException($@"Could not locate param {paramKey}"); + } + + Param p = ParamBank.PrimaryBank.Params[paramKey]; + // Only supporting single param in clipboard + if (ParamBank.ClipboardParam != paramKey) + { + ParamBank.ClipboardParam = paramKey; + ParamBank.ClipboardRows.Clear(); + } + + ParamBank.ClipboardRows.Add(new Param.Row(row, p)); + return (p, null); + } + ); + NewCmd("copyN", ["count"], + "Adds the selected rows into clipboard the given number of times. If the clipboard param is different, the clipboard is emptied first", + (paramKey, row, args) => + { + if (paramKey == null) + { + throw new MEOperationException(@"Could not locate param"); + } + + if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) + { + throw new MEOperationException($@"Could not locate param {paramKey}"); + } + + var count = uint.Parse(args[0]); + Param p = ParamBank.PrimaryBank.Params[paramKey]; + // Only supporting single param in clipboard + if (ParamBank.ClipboardParam != paramKey) + { + ParamBank.ClipboardParam = paramKey; + ParamBank.ClipboardRows.Clear(); + } + + for (var i = 0; i < count; i++) + { + ParamBank.ClipboardRows.Add(new Param.Row(row, p)); + } + + return (p, null); + }, () => CFG.Current.Param_AdvancedMassedit); + NewCmd("paste", [], + "Adds the selected rows to the primary regulation or parambnd in the selected param", + (paramKey, row, args) => + { + if (paramKey == null) + { + throw new MEOperationException(@"Could not locate param"); + } + + if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) + { + throw new MEOperationException($@"Could not locate param {paramKey}"); + } + + Param p = ParamBank.PrimaryBank.Params[paramKey]; + return (p, new Param.Row(row, p)); + } + ); + } + internal override object GetElementValue((object, object) currentObject, Dictionary contextObjects) + { + return currentObject.Item2; + } + + internal override ResultValidity ValidateResult(object res) + { + if (res.GetType() != typeof((Param, Param.Row))) + return ResultValidity.ERROR; + (Param, Param.Row) r2 = ((Param, Param.Row))res; + if (r2.Item1 == null) + return ResultValidity.ERROR; + if (r2.Item2 == null) + return ResultValidity.SKIP; + return ResultValidity.OK; + } + + internal override void UseResult(List actionList, (object, object) currentObject, Dictionary contextObjects, object res) + { + //use Param from result as this may be different to original Param obj + (Param p2, Param.Row rs) = ((Param, Param.Row))res; + actionList.Add(new AddParamsAction(p2, "FromMassEdit", new List { rs }, false, true)); + } +} + +internal abstract class MEValueOperation : TypedOperationCategory +{ + internal override void Setup() + { + name = "value"; + NewCmd("=", + ["number or text"], + "Assigns the given value to the selected values. Will attempt conversion to the value's data type", + (value, args) => MassParamEdit.WithDynamicOf(value, v => args[0])); + NewCmd("+", ["number or text"], + "Adds the number to the selected values, or appends text if that is the data type of the values", + (value, args) => MassParamEdit.WithDynamicOf(value, v => + { + if (double.TryParse(args[0], out double val)) + { + return v + val; + } + + return v + args[0]; + })); + NewCmd("-", + ["number"], "Subtracts the number from the selected values", + (value, args) => MassParamEdit.WithDynamicOf(value, v => v - double.Parse(args[0]))); + NewCmd("*", + ["number"], "Multiplies selected values by the number", + (value, args) => MassParamEdit.WithDynamicOf(value, v => v * double.Parse(args[0]))); + NewCmd("/", + ["number"], "Divides the selected values by the number", + (value, args) => MassParamEdit.WithDynamicOf(value, v => v / double.Parse(args[0]))); + NewCmd("%", + ["number"], "Gives the remainder when the selected values are divided by the number", + (value, args) => MassParamEdit.WithDynamicOf(value, v => v % double.Parse(args[0])), () => CFG.Current.Param_AdvancedMassedit); + NewCmd("scale", ["factor number", "center number"], + "Multiplies the difference between the selected values and the center number by the factor number", + (value, args) => + { + var opp1 = double.Parse(args[0]); + var opp2 = double.Parse(args[1]); + return MassParamEdit.WithDynamicOf(value, v => + { + return ((v - opp2) * opp1) + opp2; + }); + } + ); + NewCmd("replace", + ["text to replace", "new text"], + "Interprets the selected values as text and replaces all occurances of the text to replace with the new text", + (value, args) => MassParamEdit.WithDynamicOf(value, v => v.Replace(args[0], args[1]))); + NewCmd("replacex", ["text to replace (regex)", "new text (w/ groups)"], + "Interprets the selected values as text and replaces all occurances of the given regex with the replacement, supporting regex groups", + (value, args) => + { + Regex rx = new(args[0]); + return MassParamEdit.WithDynamicOf(value, v => rx.Replace(v, args[1])); + }, () => CFG.Current.Param_AdvancedMassedit); + NewCmd("max", + ["number"], "Returns the larger of the current value and number", + (value, args) => MassParamEdit.WithDynamicOf(value, v => Math.Max(v, double.Parse(args[0]))), () => CFG.Current.Param_AdvancedMassedit); + NewCmd("min", + ["number"], "Returns the smaller of the current value and number", + (value, args) => MassParamEdit.WithDynamicOf(value, v => Math.Min(v, double.Parse(args[0]))), () => CFG.Current.Param_AdvancedMassedit); + } + + internal override ResultValidity ValidateResult(object res) + { + if (res == null) + return ResultValidity.ERROR; + return ResultValidity.OK; + } +} +internal class MECellOperation : MEValueOperation<(PseudoColumn, Param.Column)> +{ + internal override object GetElementValue((object, object) currentObject, Dictionary contextObjects) + { + (string param, Param.Row row) = ((string, Param.Row))contextObjects[typeof((string, Param.Row))]; + (PseudoColumn, Param.Column) col = ((PseudoColumn, Param.Column))currentObject; + return row.Get(col); + } + internal override void UseResult(List actionList, (object, object) currentObject, Dictionary contextObjects, object res) + { + (string param, Param.Row row) = ((string, Param.Row))contextObjects[typeof((string, Param.Row))]; + (PseudoColumn, Param.Column) col = ((PseudoColumn, Param.Column))currentObject; + actionList.AppendParamEditAction(row, col, res); + } +} +internal class MEVarOperation : MEValueOperation<(bool, string)> +{ + internal override object GetElementValue((object, object) currentObject, Dictionary contextObjects) + { + return MassParamEdit.massEditVars[(string)currentObject.Item2]; + } + + internal override void UseResult(List actionList, (object, object) currentObject, Dictionary contextObjects, object res) + { + MassParamEdit.massEditVars[(string)currentObject.Item2] = res; + } +} + +internal enum ResultValidity +{ + OK, + SKIP, + ERROR +} + +internal class OperationArguments +{ + internal static OperationArguments arg = new(); + private readonly Dictionary argumentGetters = new(); + private OperationArgumentGetter defaultGetter; + + private OperationArguments() + { + Setup(); + } + private OperationArgumentGetter newGetter(string[] args, string wiki, + Func> + func, Func shouldShow = null) + { + return new OperationArgumentGetter(args, wiki, func, shouldShow); + } + private OperationArgumentGetter newGetter(string[] args, string wiki, + Func>> + func, Func shouldShow = null) + { + return new OperationArgumentGetter(args, wiki, func, shouldShow); + } + private OperationArgumentGetter newGetter(string[] args, string wiki, + Func>>> + func, Func shouldShow = null) + { + return new OperationArgumentGetter(args, wiki, func, shouldShow); + } + + private void Setup() + { + defaultGetter = newGetter([], "Gives the specified value", + value => (i, c, c2) => value[0]); + argumentGetters.Add("self", newGetter([], "Gives the value of the currently selected value", + empty => (j, rowP, rowR) => (k, colE, colC) => + { + return rowR.Get((colE, colC)).ToParamEditorString(); + })); + argumentGetters.Add("field", newGetter(["field internalName"], + "Gives the value of the given cell/field for the currently selected row and param", field => + (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + return (j, rowP, rowR) => + { + var v = rowR.Get(col).ToParamEditorString(); + return v; + }; + })); + argumentGetters.Add("vanilla", newGetter([], + "Gives the value of the equivalent cell/field in the vanilla regulation or parambnd for the currently selected cell/field, row and param.\nWill fail if a row does not have a vanilla equivilent. Consider using && !added", + empty => + { + ParamBank bank = ParamBank.VanillaBank; + return (i, paramB, paramP) => + { + var paramName = ParamBank.PrimaryBank.GetKeyForParam(paramP); + if (!bank.Params.ContainsKey(paramName)) + { + throw new MEOperationException($@"Could not locate vanilla param for {paramP.ParamType}"); + } + + Param vParam = bank.Params[paramName]; + return (j, rowP, rowR) => + { + Param.Row vRow = vParam?[rowR.ID]; + if (vRow == null) + { + throw new MEOperationException($@"Could not locate vanilla row {rowR.ID}"); + } + + return (k, colE, colC) => + { + if (colE == PseudoColumn.None && colC == null) + { + throw new MEOperationException(@"Could not locate given field or property"); + } + + return vRow.Get((colE, colC)).ToParamEditorString(); + }; + }; + }; + })); + argumentGetters.Add("aux", newGetter(["parambank name"], + "Gives the value of the equivalent cell/field in the specified regulation or parambnd for the currently selected cell/field, row and param.\nWill fail if a row does not have an aux equivilent. Consider using && auxprop ID .*", + bankName => + { + if (!ParamBank.AuxBanks.ContainsKey(bankName[0])) + { + throw new MEOperationException($@"Could not locate paramBank {bankName[0]}"); + } + + ParamBank bank = ParamBank.AuxBanks[bankName[0]]; + return (i, paramB, paramP) => + { + var paramName = ParamBank.PrimaryBank.GetKeyForParam(paramP); + if (!bank.Params.ContainsKey(paramName)) + { + throw new MEOperationException($@"Could not locate aux param for {paramP.ParamType}"); + } + + Param vParam = bank.Params[paramName]; + return (j, rowP, rowR) => + { + Param.Row vRow = vParam?[rowR.ID]; + if (vRow == null) + { + throw new MEOperationException($@"Could not locate aux row {rowR.ID}"); + } + + return (k, colE, colC) => + { + if (!(colE, colC).IsColumnValid()) + { + throw new MEOperationException(@"Could not locate given field or property"); + } + + return vRow.Get((colE, colC)).ToParamEditorString(); + }; + }; + }; + }, () => ParamBank.AuxBanks.Count > 0)); + argumentGetters.Add("vanillafield", newGetter(["field internalName"], + "Gives the value of the specified cell/field in the vanilla regulation or parambnd for the currently selected row and param.\nWill fail if a row does not have a vanilla equivilent. Consider using && !added", + field => (i, paramB, paramP) => + { + var paramName = ParamBank.PrimaryBank.GetKeyForParam(paramP); + Param? vParam = ParamBank.VanillaBank.GetParamFromName(paramName); + if (vParam == null) + { + throw new MEOperationException($@"Could not locate vanilla param for {paramP.ParamType}"); + } + + (PseudoColumn, Param.Column) col = vParam.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + return (j, rowP, rowR) => + { + Param.Row vRow = vParam?[rowR.ID]; + if (vRow == null) + { + throw new MEOperationException($@"Could not locate vanilla row {rowR.ID}"); + } + + var v = vRow.Get(col).ToParamEditorString(); + return v; + }; + })); + argumentGetters.Add("auxfield", newGetter(["parambank name", "field internalName"], + "Gives the value of the specified cell/field in the specified regulation or parambnd for the currently selected row and param.\nWill fail if a row does not have an aux equivilent. Consider using && auxprop ID .*", + bankAndField => + { + if (!ParamBank.AuxBanks.ContainsKey(bankAndField[0])) + { + throw new MEOperationException($@"Could not locate paramBank {bankAndField[0]}"); + } + + ParamBank bank = ParamBank.AuxBanks[bankAndField[0]]; + return (i, paramB, paramP) => + { + var paramName = ParamBank.PrimaryBank.GetKeyForParam(paramP); + if (!bank.Params.ContainsKey(paramName)) + { + throw new MEOperationException($@"Could not locate aux param for {paramP.ParamType}"); + } + + Param vParam = bank.Params[paramName]; + (PseudoColumn, Param.Column) col = vParam.GetCol(bankAndField[1]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {bankAndField[1]}"); + } + + return (j, rowP, rowR) => + { + Param.Row vRow = vParam?[rowR.ID]; + if (vRow == null) + { + throw new MEOperationException($@"Could not locate aux row {rowR.ID}"); + } + + var v = vRow.Get(col).ToParamEditorString(); + return v; + }; + }; + }, () => ParamBank.AuxBanks.Count > 0)); + argumentGetters.Add("paramlookup", newGetter(["param name", "row id", "field name"], + "Returns the specific value specified by the exact param, row and field.", address => + { + Param param = ParamBank.PrimaryBank.Params[address[0]]; + if (param == null) + throw new MEOperationException($@"Could not find param {address[0]}"); + var id = int.Parse(address[1]); + (PseudoColumn, Param.Column) field = param.GetCol(address[2]); + if (!field.IsColumnValid()) + throw new MEOperationException($@"Could not find field {address[2]} in param {address[0]}"); + var row = param[id]; + if (row == null) + throw new MEOperationException($@"Could not find row {id} in param {address[0]}"); + var value = row.Get(field).ToParamEditorString(); + return (i, c, c2) => value; + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("average", newGetter(["field internalName", "row selector"], + "Gives the mean value of the cells/fields found using the given selector, for the currently selected param", + field => (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + Type colType = col.GetColumnType(); + if (colType == typeof(string) || colType == typeof(byte[])) + { + throw new MEOperationException($@"Cannot average field {field[0]}"); + } + + List<(string, Param.Row)>? rows = + SearchEngine.row.Search((paramB, paramP), field[1], false, false); + IEnumerable vals = rows.Select((row, i) => row.Item2.Get(col)); + var avg = vals.Average(val => Convert.ToDouble(val)); + return avg.ToString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("median", newGetter(["field internalName", "row selector"], + "Gives the median value of the cells/fields found using the given selector, for the currently selected param", + field => (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + List<(string, Param.Row)>? rows = + SearchEngine.row.Search((paramB, paramP), field[1], false, false); + IEnumerable vals = rows.Select((row, i) => row.Item2.Get(col)); + var avg = vals.OrderBy(val => Convert.ToDouble(val)).ElementAt(vals.Count() / 2); + return avg.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("mode", newGetter(["field internalName", "row selector"], + "Gives the most common value of the cells/fields found using the given selector, for the currently selected param", + field => (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + List<(string, Param.Row)>? rows = + SearchEngine.row.Search((paramB, paramP), field[1], false, false); + var avg = ParamUtils.GetParamValueDistribution(rows.Select((x, i) => x.Item2), col).OrderByDescending(g => g.Item2) + .First().Item1; + return avg.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("min", newGetter(["field internalName", "row selector"], + "Gives the smallest value from the cells/fields found using the given param, row selector and field", + field => (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + List<(string, Param.Row)>? rows = + SearchEngine.row.Search((paramB, paramP), field[1], false, false); + var min = rows.Min(r => r.Item2[field[0]].Value.Value); + return min.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("max", newGetter(["field internalName", "row selector"], + "Gives the largest value from the cells/fields found using the given param, row selector and field", + field => (i, paramB, paramP) => + { + (PseudoColumn, Param.Column) col = paramP.GetCol(field[0]); + if (!col.IsColumnValid()) + { + throw new MEOperationException($@"Could not locate field {field[0]}"); + } + + List<(string, Param.Row)>? rows = + SearchEngine.row.Search((paramB, paramP), field[1], false, false); + var max = rows.Max(r => r.Item2[field[0]].Value.Value); + return max.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("random", newGetter( + ["minimum number (inclusive)", "maximum number (exclusive)"], + "Gives a random decimal number between the given values for each selected value", minAndMax => + { + double min; + double max; + if (!double.TryParse(minAndMax[0], out min) || !double.TryParse(minAndMax[1], out max)) + { + throw new MEOperationException(@"Could not parse min and max random values"); + } + + if (max <= min) + { + throw new MEOperationException(@"Random max must be greater than min"); + } + + var range = max - min; + return (i, c, c2) => ((Random.Shared.NextDouble() * range) + min).ToString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("randint", newGetter( + ["minimum integer (inclusive)", "maximum integer (inclusive)"], + "Gives a random integer between the given values for each selected value", minAndMax => + { + int min; + int max; + if (!int.TryParse(minAndMax[0], out min) || !int.TryParse(minAndMax[1], out max)) + { + throw new MEOperationException(@"Could not parse min and max randint values"); + } + + if (max <= min) + { + throw new MEOperationException(@"Random max must be greater than min"); + } + + return (i, c, c2) => Random.Shared.NextInt64(min, max + 1).ToString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("randFrom", newGetter(["param name", "field internalName", "row selector"], + "Gives a random value from the cells/fields found using the given param, row selector and field, for each selected value", + paramFieldRowSelector => + { + Param srcParam = ParamBank.PrimaryBank.Params[paramFieldRowSelector[0]]; + List<(string, Param.Row)> srcRows = SearchEngine.row.Search((ParamBank.PrimaryBank, srcParam), + paramFieldRowSelector[2], false, false); + var values = srcRows.Select((r, i) => r.Item2[paramFieldRowSelector[1]].Value.Value).ToArray(); + return (i, c, c2) => values[Random.Shared.NextInt64(values.Length)].ToString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("paramIndex", newGetter([], + "Gives an integer for the current selected param, beginning at 0 and increasing by 1 for each param selected", + empty => (i, paramB, paramP) => + { + return i.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("rowIndex", newGetter([], + "Gives an integer for the current selected row, beginning at 0 and increasing by 1 for each row selected", + empty => (j, rowP, rowR) => + { + return j.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + argumentGetters.Add("fieldIndex", newGetter([], + "Gives an integer for the current selected cell/field, beginning at 0 and increasing by 1 for each cell/field selected", + empty => (k, colE, colC) => + { + return k.ToParamEditorString(); + }, () => CFG.Current.Param_AdvancedMassedit)); + } + + internal List<(string, string[])> AllArguments() + { + List<(string, string[])> options = new(); + foreach (var op in argumentGetters.Keys) + { + options.Add((op, argumentGetters[op].args)); + } + + return options; + } + + internal List<(string, string, string[])> VisibleArguments() + { + List<(string, string, string[])> options = new(); + foreach (var op in argumentGetters.Keys) + { + OperationArgumentGetter oag = argumentGetters[op]; + if (oag.shouldShow == null || oag.shouldShow()) + { + options.Add((op, oag.wiki, oag.args)); + } + } + + return options; + } + + internal object[] getContextualArguments(int argumentCount, string opData) + { + var opArgs = opData == null ? [] : opData.Split(':', argumentCount); + var contextualArgs = new object[opArgs.Length]; + for (var i = 0; i < opArgs.Length; i++) + { + contextualArgs[i] = getContextualArgumentFromArgs(opArgs[i]); + } + + return contextualArgs; + } + + internal object getContextualArgumentFromArgs(string opArg) + { + if (opArg.StartsWith('"') && opArg.EndsWith('"')) + { + return opArg.Substring(1, opArg.Length - 2); + } + + if (opArg.StartsWith('$')) + { + opArg = MassParamEdit.massEditVars[opArg.Substring(1)].ToString(); + } + + var arg = opArg.Split(" ", 2); + if (argumentGetters.ContainsKey(arg[0].Trim())) + { + OperationArgumentGetter getter = argumentGetters[arg[0]]; + var opArgArgs = arg.Length > 1 ? arg[1].Split(" ", getter.args.Length) : []; + if (opArgArgs.Length != getter.args.Length) + { + throw new MEOperationException( + @$"Contextual value {arg[0]} has wrong number of arguments. Expected {opArgArgs.Length}"); + } + + for (var i = 0; i < opArgArgs.Length; i++) + { + if (opArgArgs[i].StartsWith('$')) + { + opArgArgs[i] = MassParamEdit.massEditVars[opArgArgs[i].Substring(1)].ToString(); + } + } + + return getter.func(opArgArgs); + } + + return defaultGetter.func([opArg]); + } +} + +internal class OperationArgumentGetter +{ + internal string[] args; + + internal Func func; + + internal Func shouldShow; + internal string wiki; + + internal OperationArgumentGetter(string[] args, string wiki, + Func + func, Func shouldShow) + { + this.args = args; + this.wiki = wiki; + this.func = func; + this.shouldShow = shouldShow; + } +} + +internal static class OAGFuncExtension +{ + /*internal static object tryFold(this Func func, object newContextInput) + { + Type t = newContextInput.GetType(); + if (func.Method.GetParameters()[0].ParameterType == t) + return func(newContextInput); + return func; + }*/ + internal static object tryFoldAsFunc(this object maybeFunc, int editIndex, (object, object) newContextInput) + { + if (maybeFunc is not Delegate) + return maybeFunc; + Delegate func = (Delegate)maybeFunc; + var parameters = func.Method.GetParameters(); + var a = newContextInput.Item1.GetType(); + var b = newContextInput.Item2.GetType(); + if (parameters.Length == 3 && parameters[0].ParameterType == typeof(int) && parameters[1].ParameterType == newContextInput.Item1.GetType() && parameters[2].ParameterType == newContextInput.Item2.GetType()) + return func.DynamicInvoke(editIndex, newContextInput.Item1, newContextInput.Item2); + return func; + } + internal static object assertCompleteContextOrThrow(this object maybeFunc, int editIndex) + { + if (maybeFunc is Delegate) + { + Delegate func = (Delegate)maybeFunc; + var parameters = func.Method.GetParameters(); + /* bool is provided as a special context argument for no context */ + if (parameters.Length == 3 && parameters[0].ParameterType == typeof(int) && parameters[1].ParameterType == typeof(bool) && parameters[2].ParameterType == typeof(bool)) + return assertCompleteContextOrThrow(func.DynamicInvoke(editIndex, false, false), editIndex); + else + throw new MEOperationException("Argument getter did not have enough context to determine the value to use."); + } + return maybeFunc; + } +} diff --git a/src/StudioCore/Editor/MassEdit/MassEdit.cs b/src/StudioCore/Editor/MassEdit/MassEdit.cs new file mode 100644 index 000000000..c2dec0783 --- /dev/null +++ b/src/StudioCore/Editor/MassEdit/MassEdit.cs @@ -0,0 +1,367 @@ +#nullable enable +using Andre.Formats; +using DotNext.Collections.Generic; +using Org.BouncyCastle.Crypto.Engines; +using StudioCore.Editor; +using StudioCore.ParamEditor; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace StudioCore.Editor.MassEdit; + +public enum MassEditResultType +{ + SUCCESS, + PARSEERROR, + OPERATIONERROR +} + +public class MassEditResult +{ + public string Information; + public MassEditResultType Type; + + public MassEditResult(MassEditResultType result, string info) + { + Type = result; + Information = info; + } +} + +internal class MEParseException : Exception +{ + internal MEParseException(string? message, int line) : base($@"{message} (line {line})") + { + } +} +internal class MEOperationException : Exception +{ + internal MEOperationException(string? message) : base(message) + { + } +} + +internal struct MEFilterStage +{ + internal string command; + internal SearchEngine engine; + // No arguments because this is handled separately in SearchEngine + internal MEFilterStage(string toParse, int line, string stageName, SearchEngine stageEngine) + { + command = toParse.Trim(); + if (command.Equals("")) + { + throw new MEParseException($@"Could not find {stageName} filter. Add : and one of {string.Join(", ", stageEngine.AvailableCommandsForHelpText())}", line); + } + engine = stageEngine; + } +} +internal struct MEOperationStage +{ + internal string command; + internal string arguments; + internal OperationCategory operation; + internal MEOperationStage(string toParse, int line, string stageName, OperationCategory operationType) + { + var stage = toParse.TrimStart().Split(' ', 2); + command = stage[0].Trim(); + if (stage.Length > 1) + arguments = stage[1]; + if (command.Equals("")) + { + throw new MEParseException($@"Could not find operation to perform. Add : and one of {string.Join(' ', operationType.AllCommands().Keys)}", line); + } + if (!operationType.AllCommands().ContainsKey(command)) + { + throw new MEParseException($@"Unknown {stageName} operation {command}", line); + } + operation = operationType; + } + internal string[] getArguments(int count) + { + return arguments.Split(':', count); + } +} + +public static class MassParamEdit +{ + internal static Dictionary massEditVars = new(); + + internal static object WithDynamicOf(object instance, Func dynamicFunc) + { + try + { + return Convert.ChangeType(dynamicFunc(instance), instance.GetType()); + } + catch + { + // Second try, handle byte[], and casts from numerical values to string which need parsing. + var ret = dynamicFunc(instance.ToParamEditorString()); + if (instance.GetType() == typeof(byte[])) + { + ret = ParamUtils.Dummy8Read((string)ret, ((byte[])instance).Length); + } + + return Convert.ChangeType(ret, instance.GetType()); + } + } + + internal static void AppendParamEditAction(this List actions, Param.Row row, + (PseudoColumn, Param.Column) col, object newval) + { + if (col.Item1 == PseudoColumn.ID) + { + if (!row.ID.Equals(newval)) + { + actions.Add(new PropertiesChangedAction(row.GetType().GetProperty("ID"), -1, row, newval)); + } + } + else if (col.Item1 == PseudoColumn.Name) + { + if (row.Name == null || !row.Name.Equals(newval)) + { + actions.Add(new PropertiesChangedAction(row.GetType().GetProperty("Name"), -1, row, newval)); + } + } + else + { + Param.Cell handle = row[col.Item2]; + if (!(handle.Value.Equals(newval) + || (handle.Value.GetType() == typeof(byte[]) + && ParamUtils.ByteArrayEquals((byte[])handle.Value, (byte[])newval)))) + { + actions.Add(new PropertiesChangedAction(handle.GetType().GetProperty("Value"), -1, handle, newval)); + } + } + } +} + +public class MassParamEditRegex +{ + /* Line number associated with this edit command, for error reporting. */ + private int _currentLine; + /* Current actions from execution of this edit command. */ + private List _partialActions = new(); + + private ParamBank bank; + private ParamEditorSelectionState context; + private object[] argFuncs; + METypelessOperationDef parsedOp; + + List filters = new(); + MEOperationStage operation; + + internal static ParamEditorSelectionState totalHackPleaseKillme = null; + public static (MassEditResult, ActionManager child) PerformMassEdit(ParamBank bank, string commandsString, + ParamEditorSelectionState context) + { + int currentLine = 0; + try + { + var commands = commandsString.Split('\n'); + var changeCount = 0; + ActionManager childManager = new(); + foreach (var cmd in commands) + { + currentLine++; + var command = cmd; + if (command.StartsWith("##") || string.IsNullOrWhiteSpace(command)) + { + continue; + } + + if (command.EndsWith(';')) + { + command = command.Substring(0, command.Length - 1); + } + + MassParamEditRegex currentEditData = new(); + currentEditData._currentLine = currentLine; + currentEditData.bank = bank; + currentEditData.context = context; + totalHackPleaseKillme = context; + + MassEditResult result = currentEditData.ParseCommand(command); + + if (result.Type != MassEditResultType.SUCCESS) + { + return (result, null); + } + + List actions = currentEditData._partialActions; + + changeCount += actions.Count; + childManager.ExecuteAction(new CompoundAction(actions)); + } + + return (new MassEditResult(MassEditResultType.SUCCESS, $@"{changeCount} cells affected"), childManager); + } + catch (Exception e) + { + return (new MassEditResult(MassEditResultType.PARSEERROR, e.ToString()), null); + } + } + private MassEditResult ParseCommand(string command) + { + var stage = command.Split(":", 2); + + Type currentType = typeof((bool, bool)); // Always start at boolbool type as basis + string firstStage = stage[0]; + string firstStageKeyword = firstStage.Trim().Split(" ", 2)[0]; + + var op = OperationCategory.GetEditOperation(currentType); + // Try run an operation + if (op != null && op.HandlesCommand(firstStageKeyword)) + return ParseOpStep(command, op.NameForHelpTexts(), op); + + var nextStage = SearchEngine.GetSearchEngines(currentType); + // Try out each defined search engine for the current type + foreach ((SearchEngine engine, Type t) in nextStage) + { + if (engine.HandlesCommand(firstStageKeyword)) + { + return ParseFilterStep(command, engine); + } + } + //Assume it's default search of last search option + return ParseFilterStep(command, nextStage.Last().Item1); + } + + private MassEditResult ParseFilterStep(string stageText, SearchEngine expectedSearchEngine) + { + var stage = stageText.Split(":", 2); + string stageName = expectedSearchEngine.NameForHelpTexts(); + filters.Add(new MEFilterStage(stage[0], _currentLine, stageName, expectedSearchEngine)); + + if (stage.Length < 2) + { + var esList = expectedSearchEngine.NextSearchEngines(); + var eo = expectedSearchEngine.NextOperation(); + if (esList.Any() && eo != null) + return new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find {esList.Last().Item1.NameForHelpTexts()} filter or {eo.NameForHelpTexts()} operation to perform. Check your colon placement. (line {_currentLine})"); + if (esList.Any()) + return new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find {esList.Last().Item1.NameForHelpTexts()} filter. Check your colon placement. (line {_currentLine})"); + if (eo != null) + return new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find {eo.NameForHelpTexts()} operation to perform. Check your colon placement. (line {_currentLine})"); + return new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find next stage to perform (no suggestions found). Check your colon placement. (line {_currentLine})"); + } + + Type currentType = expectedSearchEngine.getElementType(); + string restOfStages = stage[1]; + string nextStageKeyword = restOfStages.Trim().Split(" ", 2)[0]; + + var op = OperationCategory.GetEditOperation(currentType); + // Try run an operation + if (op != null && op.HandlesCommand(nextStageKeyword)) + return ParseOpStep(stage[1], op.NameForHelpTexts(), op); + + var nextStage = SearchEngine.GetSearchEngines(currentType); + // Try out each defined search engine for the current type + foreach ((SearchEngine engine, Type t) in nextStage) + { + if (engine.HandlesCommand(nextStageKeyword)) + return ParseFilterStep(restOfStages, engine); + } + //Assume it's default search of last search option + return ParseFilterStep(restOfStages, nextStage.Last().Item1); + } + private MassEditResult ParseOpStep(string stageText, string stageName, OperationCategory operation) + { + this.operation = new MEOperationStage(stageText, _currentLine, stageName, operation); + + parsedOp = operation.AllCommands()[this.operation.command]; + argFuncs = OperationArguments.arg.getContextualArguments(parsedOp.argNames.Length, this.operation.arguments); + if (parsedOp.argNames.Length != argFuncs.Length) + { + return new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {this.operation.command} (line {_currentLine})"); + } + var currentObj = (true, true); + var contextObjects = new Dictionary() { { typeof(bool), currentObj} }; + + if (filters.Count == 0) + return SandboxMassEditExecution(() => ExecOp(this.operation, this.operation.command, argFuncs, currentObj, contextObjects, operation)); + else + { + int filterDepth = 0; + MEFilterStage baseFilter = filters[filterDepth]; + return SandboxMassEditExecution(() => ExecStage(baseFilter, baseFilter.engine, filterDepth, currentObj, contextObjects, argFuncs)); + } + throw new MEParseException("No initial stage or op was parsed", _currentLine); + } + + private MassEditResult SandboxMassEditExecution(Func innerFunc) + { + try + { + return innerFunc(); + } + catch (Exception e) + { + return new MassEditResult(MassEditResultType.OPERATIONERROR, @$"Error on line {_currentLine}" + '\n' + e.GetBaseException().ToString()); + } + } + + private MassEditResult ExecStage(MEFilterStage info, SearchEngine engine, int filterDepth, (object, object) contextObject, Dictionary contextObjects, IEnumerable argFuncs) + { + var editCount = -1; + filterDepth++; + var contexts = engine.GetStaticContextItems(); + foreach (var context in contexts) + { + argFuncs = argFuncs.Select((func, i) => func.tryFoldAsFunc(editCount, context.Item2)); + } + foreach ((object, object) currentObject in engine.SearchNoType(contextObject, info.command, false, false)) + { + editCount++; + //add context + contextObjects[engine.getElementType()] = currentObject; + //update argGetters + IEnumerable newArgFuncs = argFuncs.Select((func, i) => func.tryFoldAsFunc(editCount, currentObject)); + //exec it + MassEditResult res; + + if (filterDepth == filters.Count) + res = ExecOp(operation, operation.command, argFuncs, currentObject, contextObjects, operation.operation); + else + { + MEFilterStage nextFilter = filters[filterDepth]; + res = ExecStage(nextFilter, nextFilter.engine, filterDepth, currentObject, contextObjects, newArgFuncs); + } + if (res.Type != MassEditResultType.SUCCESS) + { + return res; + } + } + + return new MassEditResult(MassEditResultType.SUCCESS, ""); + } + private MassEditResult ExecOp(MEOperationStage opInfo, string opName, IEnumerable argFuncs, (object, object) currentObject, Dictionary contextObjects, OperationCategory opType) + { + var argValues = argFuncs.Select(f => f.assertCompleteContextOrThrow(_currentLine).ToParamEditorString()).ToArray(); + var opResult = parsedOp.function(currentObject.Item1, opType.GetElementValue(currentObject, contextObjects), argValues); + ResultValidity res = opType.ValidateResult(opResult); + if (res == ResultValidity.ERROR) + { + return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Error performing {opName} operation {opInfo.command} (line {_currentLine})"); + } + if (res == ResultValidity.OK) + { + opType.UseResult(_partialActions, currentObject, contextObjects, opResult); + } + return new MassEditResult(MassEditResultType.SUCCESS, ""); + } +} + +public class MassParamEditOther +{ + public static AddParamsAction SortRows(ParamBank bank, string paramName) + { + Param param = bank.Params[paramName]; + List newRows = new(param.Rows); + newRows.Sort((a, b) => { return a.ID - b.ID; }); + return new AddParamsAction(param, paramName, newRows, true, + true); //appending same params and allowing overwrite + } +} diff --git a/src/StudioCore/ParamEditor/SearchEngine.cs b/src/StudioCore/Editor/MassEdit/SearchEngine.cs similarity index 68% rename from src/StudioCore/ParamEditor/SearchEngine.cs rename to src/StudioCore/Editor/MassEdit/SearchEngine.cs index 594d5844e..b7507abcb 100644 --- a/src/StudioCore/ParamEditor/SearchEngine.cs +++ b/src/StudioCore/Editor/MassEdit/SearchEngine.cs @@ -1,40 +1,91 @@ using Andre.Formats; +using Microsoft.Toolkit.HighPerformance; +using Org.BouncyCastle.Tls; using SoulsFormats; using StudioCore.ParamEditor; using StudioCore.TextEditor; using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; -namespace StudioCore.Editor; +namespace StudioCore.Editor.MassEdit; /* Restricted characters: colon, space, forward slash, ampersand, exclamation mark * */ -internal class SearchEngine +internal abstract class SearchEngine { - internal SearchEngineCommand defaultFilter; - internal Dictionary> filterList = new(); - internal Func> unpacker; + private static Dictionary> searchEngines; + // Listing engines here so they are initialised always + public static ParamRowSelectionSearchEngine paramRowSelection; + public static ParamRowClipBoardSearchEngine paramRowClipboard; + public static ParamSearchEngine param; + public static RowSearchEngine row; + public static CellSearchEngine cell; + public static VarSearchEngine var; + static SearchEngine(){ + searchEngines = new(); + paramRowSelection = new(); + paramRowClipboard = new(); + param = new(ParamBank.PrimaryBank); + row = new(ParamBank.PrimaryBank); + cell = new(); + var = new(); + } - public SearchEngine() + internal static void AddSearchEngine(TypedSearchEngine engine) + { + if (!searchEngines.ContainsKey(typeof((TContextObject, TContextField)))) + searchEngines.Add(typeof((TContextObject, TContextField)), new()); + searchEngines[typeof((TContextObject, TContextField))].Add((engine, typeof((TElementObject, TElementField)))); + } + internal static List<(SearchEngine, Type)> GetSearchEngines(Type t) //Type t is expected to be (TContextObject, TContextField) + { + return searchEngines.GetValueOrDefault(t) ?? ([]); + } + internal abstract List<(string, string[], string)> VisibleCommands(bool includeDefault); + internal abstract List<(string, string[])> AllCommands(); + internal abstract List AvailableCommandsForHelpText(); + internal abstract List<(SearchEngine, Type)> NextSearchEngines(); //Type t is expected to be (TContextObject, TContextField) + internal abstract OperationCategory NextOperation(); + internal abstract string NameForHelpTexts(); + internal abstract Type getContainerType(); + internal abstract Type getElementType(); + public abstract IEnumerable<(object, object)> SearchNoType((object, object) container, string command, bool lenient, bool failureAllOrNone); + internal abstract bool HandlesCommand(string command); + internal virtual IEnumerable<(Type, (object, object))> GetStaticContextItems() + { + return []; + } +} +internal class TypedSearchEngine : SearchEngine +{ + internal SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)> defaultFilter; + + internal Dictionary> filterList = new(); + internal Func<(TContextObject, TContextField), List<(TElementObject, TElementField)>> unpacker; + internal string name = "[unnamed search engine]"; + + internal TypedSearchEngine() { Setup(); + AddSearchEngine(this); } protected void addExistsFilter() { - filterList.Add("exists", newCmd(new string[0], "Selects all elements", noArgs(noContext(B => true)))); + filterList.Add("exists", newCmd([], "Selects all elements", noArgs(noContext(b => true)))); } - protected Func>> noArgs(Func> func) + protected Func>> noArgs(Func<(TContextObject, TContextField), Func<(TElementObject, TElementField), bool>> func) { return (args, lenient) => func; } - protected Func> noContext(Func func) + protected Func<(TContextObject, TContextField), Func<(TElementObject, TElementField), bool>> noContext(Func<(TElementObject, TElementField), bool> func) { return context => func; } @@ -43,13 +94,13 @@ internal virtual void Setup() { } - internal SearchEngineCommand newCmd(string[] args, string wiki, - Func>> func, Func shouldShow = null) + internal SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)> newCmd(string[] args, string wiki, + Func>> func, Func shouldShow = null) { - return new SearchEngineCommand(args, wiki, func, shouldShow); + return new SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)>(args, wiki, func, shouldShow); } - public bool HandlesCommand(string command) + internal override bool HandlesCommand(string command) { if (command.Length > 0 && command.StartsWith('!')) { @@ -59,12 +110,12 @@ public bool HandlesCommand(string command) return filterList.ContainsKey(command.Split(" ")[0]); } - public List AvailableCommandsForHelpText() + internal override List AvailableCommandsForHelpText() { List options = new(); foreach (var op in filterList.Keys) { - SearchEngineCommand cmd = filterList[op]; + SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)> cmd = filterList[op]; if (cmd.shouldShow == null || cmd.shouldShow()) { options.Add(op + "(" + filterList[op].args.Length + " args)"); @@ -79,22 +130,24 @@ public List AvailableCommandsForHelpText() return options; } - public List<(string, string[], string)> VisibleCommands() + internal override List<(string, string[], string)> VisibleCommands(bool includeDefault) { List<(string, string[], string)> options = new(); foreach (var op in filterList.Keys) { - SearchEngineCommand cmd = filterList[op]; + SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)> cmd = filterList[op]; if (cmd.shouldShow == null || cmd.shouldShow()) { options.Add((op, cmd.args, cmd.wiki)); } } + if (includeDefault) + options.Add((null, defaultFilter.args, defaultFilter.wiki)); return options; } - public List<(string, string[])> AllCommands() + internal override List<(string, string[])> AllCommands() { List<(string, string[])> options = new(); foreach (var op in filterList.Keys) @@ -109,17 +162,21 @@ public List AvailableCommandsForHelpText() return options; } - - public List Search(A param, string command, bool lenient, bool failureAllOrNone) + public override IEnumerable<(object, object)> SearchNoType((object, object) container, string command, bool lenient, bool failureAllOrNone) + { + List<(TElementObject, TElementField)> res = Search(((TContextObject, TContextField))container, command, lenient, failureAllOrNone); + return res.Select((x) => ((object)x.Item1, (object)x.Item2)); + } + public List<(TElementObject, TElementField)> Search((TContextObject, TContextField) param, string command, bool lenient, bool failureAllOrNone) { return Search(param, unpacker(param), command, lenient, failureAllOrNone); } - public virtual List Search(A context, List sourceSet, string command, bool lenient, bool failureAllOrNone) + public virtual List<(TElementObject, TElementField)> Search((TContextObject, TContextField) context, List<(TElementObject, TElementField)> sourceSet, string command, bool lenient, bool failureAllOrNone) { //assumes unpacking doesn't fail var conditions = command.Split("&&", StringSplitOptions.TrimEntries); - List liveSet = sourceSet; + List<(TElementObject, TElementField)> liveSet = sourceSet; try { @@ -133,7 +190,7 @@ public virtual List Search(A context, List sourceSet, string command, bool var cmd = condition.Split(' ', 2); - SearchEngineCommand selectedCommand; + SearchEngineCommand<(TContextObject, TContextField), (TElementObject, TElementField)> selectedCommand; int argC; string[] args; var not = false; @@ -148,7 +205,7 @@ public virtual List Search(A context, List sourceSet, string command, bool selectedCommand = filterList[cmd[0]]; argC = selectedCommand.args.Length; args = cmd.Length == 1 - ? new string[0] + ? [] : cmd[1].Split(' ', argC, StringSplitOptions.TrimEntries); } else @@ -166,10 +223,10 @@ public virtual List Search(A context, List sourceSet, string command, bool } } - Func> filter = selectedCommand.func(args, lenient); - Func criteria = filter(context); - List newRows = new(); - foreach (B row in liveSet) + Func<(TContextObject, TContextField), Func<(TElementObject, TElementField), bool>> filter = selectedCommand.func(args, lenient); + Func<(TElementObject, TElementField), bool> criteria = filter(context); + List<(TElementObject, TElementField)> newRows = new(); + foreach ((TElementObject, TElementField) row in liveSet) { if (not ^ criteria(row)) { @@ -182,19 +239,43 @@ public virtual List Search(A context, List sourceSet, string command, bool } catch (Exception e) { - liveSet = failureAllOrNone ? sourceSet : new List(); + liveSet = failureAllOrNone ? sourceSet : []; } return liveSet; } + + internal override List<(SearchEngine, Type)> NextSearchEngines() + { + return GetSearchEngines(typeof((TElementObject, TElementField))); + } + internal override OperationCategory NextOperation() + { + return OperationCategory.GetEditOperation(typeof((TElementObject, TElementField))); + } + + internal override string NameForHelpTexts() + { + return name; + } + + internal override Type getContainerType() + { + return typeof((TContextObject, TContextField)); + } + + internal override Type getElementType() + { + return typeof((TElementObject, TElementField)); + } } internal class SearchEngineCommand { - public string[] args; + internal string[] args; internal Func>> func; internal Func shouldShow; - public string wiki; + internal string wiki; internal SearchEngineCommand(string[] args, string wiki, Func>> func, Func shouldShow) @@ -206,89 +287,58 @@ internal SearchEngineCommand(string[] args, string wiki, Func : SearchEngine +internal class ParamRowSelectionSearchEngine : TypedSearchEngine { - internal Func contextGetterForMultiStage; - internal Func resultRetrieverForMultiStage; - internal SearchEngine searchEngineForMultiStage; - internal Func sourceListGetterForMultiStage; - - public override List Search(A context, List sourceSet, string command, bool lenient, - bool failureAllOrNone) + internal override void Setup() { - var conditions = command.Split("&&", 2, StringSplitOptions.TrimEntries); - List stage1list = base.Search(context, sourceSet, conditions[0], lenient, failureAllOrNone); - if (conditions.Length == 1) - { - return stage1list; - } - - B exampleItem = stage1list.FirstOrDefault(); - List stage2list = searchEngineForMultiStage.Search(contextGetterForMultiStage(context, exampleItem), - stage1list.Select(x => sourceListGetterForMultiStage(x)).ToList(), conditions[1], lenient, - failureAllOrNone); - return stage2list.Select(x => resultRetrieverForMultiStage(x, exampleItem)).ToList(); + name = "selection"; + unpacker = dummy => { + string param = MassParamEditRegex.totalHackPleaseKillme.GetActiveParam(); + return MassParamEditRegex.totalHackPleaseKillme.GetSelectedRows().Select((x) => (param, x)).ToList(); + }; + filterList.Add("selection", newCmd([], + "Selects param rows selected in the current param window", + noArgs(noContext(param => true)))); + } + internal override IEnumerable<(Type, (object, object))> GetStaticContextItems() + { + return [(typeof((ParamBank, Param)), (ParamBank.PrimaryBank, ParamBank.PrimaryBank.Params[MassParamEditRegex.totalHackPleaseKillme.GetActiveParam()]))]; } } - -internal class ParamAndRowSearchEngine : MultiStageSearchEngine +internal class ParamRowClipBoardSearchEngine : TypedSearchEngine { - public static SearchEngine parse = - new ParamAndRowSearchEngine(); - internal override void Setup() { - unpacker = selection => - { - List<(MassEditRowSource, Param.Row)> list = new(); - list.AddRange(selection.GetSelectedRows().Select((x, i) => (MassEditRowSource.Selection, x))); - list.AddRange(ParamBank.ClipboardRows.Select((x, i) => (MassEditRowSource.Clipboard, x))); - return list; + name = "clipboard"; + unpacker = dummy => { + string param = ParamBank.ClipboardParam; + return ParamBank.ClipboardRows.Select((x) => (param, x)).ToList(); }; - filterList.Add("selection", - newCmd(new string[0], "Selects the current param selection and selected rows in that param", - noArgs(noContext(row => row.Item1 == MassEditRowSource.Selection)))); - filterList.Add("clipboard", - newCmd(new string[0], "Selects the param of the clipboard and the rows in the clipboard", - noArgs(noContext(row => row.Item1 == MassEditRowSource.Clipboard)), - () => ParamBank.ClipboardRows?.Count > 0)); - contextGetterForMultiStage = (state, exampleItem) => (ParamBank.PrimaryBank, - ParamBank.PrimaryBank.Params[ - exampleItem.Item1 == MassEditRowSource.Selection - ? state.GetActiveParam() - : ParamBank.ClipboardParam]); - sourceListGetterForMultiStage = row => row.Item2; - searchEngineForMultiStage = RowSearchEngine.rse; - resultRetrieverForMultiStage = (row, exampleItem) => (exampleItem.Item1, row); + filterList.Add("clipboard", newCmd([], + "Selects param rows copied in the clipboard", + noArgs(noContext(param => true)))); + } + internal override IEnumerable<(Type, (object, object))> GetStaticContextItems() + { + return [(typeof((ParamBank, Param)), (ParamBank.PrimaryBank, ParamBank.PrimaryBank.Params[ParamBank.ClipboardParam]))]; } } -internal enum MassEditRowSource -{ - Selection, - Clipboard -} - -internal class ParamSearchEngine : SearchEngine +internal class ParamSearchEngine : TypedSearchEngine { - public static ParamSearchEngine pse = new(ParamBank.PrimaryBank); private readonly ParamBank bank; - - private ParamSearchEngine(ParamBank bank) + internal ParamSearchEngine(ParamBank bank) { this.bank = bank; } internal override void Setup() { + name = "param"; unpacker = dummy => ParamBank.AuxBanks.Select((aux, i) => aux.Value.Params.Select((x, i) => (aux.Value, x.Value))) - .Aggregate(bank.Params.Values.Select((x, i) => (bank, x)), (o, n) => o.Concat(n)).ToList(); - filterList.Add("modified", newCmd(new string[0], + .Aggregate(bank.Params.Values.Select((x, i) => (bank, x)), (o, n) => o.Concat(n)).Select((x, i) => (x.bank, x.x)).ToList(); + filterList.Add("modified", newCmd([], "Selects params where any rows do not match the vanilla version, or where any are added. Ignores row names", noArgs(noContext(param => { @@ -300,7 +350,7 @@ internal override void Setup() HashSet cache = bank.GetVanillaDiffRows(bank.GetKeyForParam(param.Item2)); return cache.Count > 0; })))); - filterList.Add("param", newCmd(new[] { "param name (regex)" }, + filterList.Add("param", newCmd(["param name (regex)"], "Selects all params whose name matches the given regex", (args, lenient) => { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); @@ -311,7 +361,7 @@ internal override void Setup() ? "" : bank.GetKeyForParam(param.Item2))); })); - filterList.Add("auxparam", newCmd(new[] { "parambank name", "param name (regex)" }, + filterList.Add("auxparam", newCmd(["parambank name", "param name (regex)"], "Selects params from the specified regulation or parambnd where the param name matches the given regex", (args, lenient) => { @@ -324,7 +374,7 @@ internal override void Setup() ? "" : auxBank.GetKeyForParam(param.Item2))); }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); - defaultFilter = newCmd(new[] { "param name (regex)" }, + defaultFilter = newCmd(["param name (regex)"], "Selects all params whose name matches the given regex", (args, lenient) => { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); @@ -338,28 +388,32 @@ internal override void Setup() } } -internal class RowSearchEngine : SearchEngine<(ParamBank, Param), Param.Row> +internal class RowSearchEngine : TypedSearchEngine { - public static RowSearchEngine rse = new(ParamBank.PrimaryBank); private readonly ParamBank bank; - private RowSearchEngine(ParamBank bank) + internal RowSearchEngine(ParamBank bank) { this.bank = bank; } internal override void Setup() { - unpacker = param => new List(param.Item2.Rows); - filterList.Add("modified", newCmd(new string[0], + name = "row"; + unpacker = param => + { + string name = param.Item1.GetKeyForParam(param.Item2); + return param.Item2.Rows.Select((x, i) => (name, x)).ToList(); + }; + filterList.Add("modified", newCmd([], "Selects rows which do not match the vanilla version, or are added. Ignores row name", noArgs(context => { var paramName = context.Item1.GetKeyForParam(context.Item2); HashSet cache = context.Item1.GetVanillaDiffRows(paramName); - return row => cache.Contains(row.ID); + return row => cache.Contains(row.Item2.ID); } ))); - filterList.Add("added", newCmd(new string[0], "Selects rows where the ID is not found in the vanilla param", + filterList.Add("added", newCmd([], "Selects rows where the ID is not found in the vanilla param", noArgs(context => { var paramName = context.Item1.GetKeyForParam(context.Item2); @@ -369,10 +423,10 @@ internal override void Setup() } Param vanilParam = ParamBank.VanillaBank.Params[paramName]; - return row => vanilParam[row.ID] == null; + return row => vanilParam[row.Item2.ID] == null; } ))); - filterList.Add("mergeable", newCmd(new string[0], + filterList.Add("mergeable", newCmd([], "Selects rows which are not modified in the primary regulation or parambnd and there is exactly one equivalent row in another regulation or parambnd that is modified", noArgs(context => { @@ -386,11 +440,11 @@ internal override void Setup() List<(HashSet, HashSet)> auxCaches = ParamBank.AuxBanks.Select(x => (x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); return row => - !pCache.Contains(row.ID) && - auxCaches.Where(x => x.Item2.Contains(row.ID) && x.Item1.Contains(row.ID)).Count() == 1; + !pCache.Contains(row.Item2.ID) && + auxCaches.Where(x => x.Item2.Contains(row.Item2.ID) && x.Item1.Contains(row.Item2.ID)).Count() == 1; } ), () => ParamBank.AuxBanks.Count > 0)); - filterList.Add("conflicts", newCmd(new string[0], + filterList.Add("conflicts", newCmd([], "Selects rows which, among all equivalents in the primary and additional regulations or parambnds, there is more than row 1 which is modified", noArgs(context => { @@ -399,37 +453,37 @@ internal override void Setup() List<(HashSet, HashSet)> auxCaches = ParamBank.AuxBanks.Select(x => (x.Value.GetPrimaryDiffRows(paramName), x.Value.GetVanillaDiffRows(paramName))).ToList(); return row => - (pCache.Contains(row.ID) ? 1 : 0) + auxCaches - .Where(x => x.Item2.Contains(row.ID) && x.Item1.Contains(row.ID)).Count() > 1; + (pCache.Contains(row.Item2.ID) ? 1 : 0) + auxCaches + .Where(x => x.Item2.Contains(row.Item2.ID) && x.Item1.Contains(row.Item2.ID)).Count() > 1; } ), () => ParamBank.AuxBanks.Count > 0)); - filterList.Add("id", newCmd(new[] { "row id (regex)" }, "Selects rows whose ID matches the given regex", + filterList.Add("id", newCmd(["row id (regex)"], "Selects rows whose ID matches the given regex", (args, lenient) => { Regex rx = lenient ? new Regex(args[0].ToLower()) : new Regex($@"^{args[0]}$"); - return noContext(row => rx.IsMatch(row.ID.ToString())); + return noContext(row => rx.IsMatch(row.Item2.ID.ToString())); })); - filterList.Add("idrange", newCmd(new[] { "row id minimum (inclusive)", "row id maximum (inclusive)" }, + filterList.Add("idrange", newCmd(["row id minimum (inclusive)", "row id maximum (inclusive)"], "Selects rows whose ID falls in the given numerical range", (args, lenient) => { var floor = double.Parse(args[0]); var ceil = double.Parse(args[1]); - return noContext(row => row.ID >= floor && row.ID <= ceil); + return noContext(row => row.Item2.ID >= floor && row.Item2.ID <= ceil); })); - filterList.Add("name", newCmd(new[] { "row name (regex)" }, + filterList.Add("name", newCmd(["row name (regex)"], "Selects rows whose Name matches the given regex", (args, lenient) => { Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext(row => rx.IsMatch(row.Name == null ? "" : row.Name)); + return noContext(row => rx.IsMatch(row.Item2.Name == null ? "" : row.Item2.Name)); })); - filterList.Add("prop", newCmd(new[] { "field internalName", "field value (regex)" }, + filterList.Add("prop", newCmd(["field internalName", "field value (regex)"], "Selects rows where the specified field has a value that matches the given regex", (args, lenient) => { Regex rx = lenient ? new Regex(args[1], RegexOptions.IgnoreCase) : new Regex($@"^{args[1]}$"); var field = args[0]; return noContext(row => { - Param.Cell? cq = row[field]; + Param.Cell? cq = row.Item2[field]; if (cq == null) { throw new Exception(); @@ -441,7 +495,7 @@ internal override void Setup() }); })); filterList.Add("proprange", newCmd( - new[] { "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" }, + ["field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)"], "Selects rows where the specified field has a value that falls in the given numerical range", (args, lenient) => { @@ -450,7 +504,7 @@ internal override void Setup() var ceil = double.Parse(args[2]); return noContext(row => { - Param.Cell? c = row[field]; + Param.Cell? c = row.Item2[field]; if (c == null) { throw new Exception(); @@ -459,7 +513,7 @@ internal override void Setup() return Convert.ToDouble(c.Value.Value) >= floor && Convert.ToDouble(c.Value.Value) <= ceil; }); })); - filterList.Add("propref", newCmd(new[] { "field internalName", "referenced row name (regex)" }, + filterList.Add("propref", newCmd(["field internalName", "referenced row name (regex)"], "Selects rows where the specified field that references another param has a value referencing a row whose name matches the given regex", (args, lenient) => { @@ -472,7 +526,7 @@ internal override void Setup() .FindAll(p => bank.Params.ContainsKey(p.param)); return row => { - Param.Cell? c = row[field]; + Param.Cell? c = row.Item2[field]; if (c == null) { throw new Exception(); @@ -492,7 +546,7 @@ internal override void Setup() }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - filterList.Add("propwhere", newCmd(new[] { "field internalName", "cell/field selector" }, + filterList.Add("propwhere", newCmd(["field internalName", "cell/field selector"], "Selects rows where the specified field appears when the given cell/field search is given", (args, lenient) => { @@ -501,17 +555,18 @@ internal override void Setup() { var paramName = context.Item1.GetKeyForParam(context.Item2); IReadOnlyList cols = context.Item2.Columns; - (PseudoColumn, Param.Column) testCol = context.Item2.GetCol(field); + var vtup = context.Item2.GetCol(field); + (PseudoColumn, Param.Column) testCol = (vtup.Item1, vtup.Item2); return row => { - (string paramName, Param.Row row) cseSearchContext = (paramName, row); - List<(PseudoColumn, Param.Column)> res = CellSearchEngine.cse.Search(cseSearchContext, + (string paramName, Param.Row row) cseSearchContext = (paramName, row.Item2); + List<(PseudoColumn, Param.Column)> res = cell.Search(cseSearchContext, new List<(PseudoColumn, Param.Column)> { testCol }, args[1], lenient, false); return res.Contains(testCol); }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - filterList.Add("fmg", newCmd(new[] { "fmg title (regex)" }, + filterList.Add("fmg", newCmd(["fmg title (regex)"], "Selects rows which have an attached FMG and that FMG's text matches the given regex", (args, lenient) => { @@ -544,17 +599,17 @@ internal override void Setup() return row => { - if (!_cache.ContainsKey(row.ID)) + if (!_cache.ContainsKey(row.Item2.ID)) { return false; } - FMG.Entry e = _cache[row.ID]; + FMG.Entry e = _cache[row.Item2.ID]; return e != null && rx.IsMatch(e.Text ?? ""); }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - filterList.Add("vanillaprop", newCmd(new[] { "field internalName", "field value (regex)" }, + filterList.Add("vanillaprop", newCmd(["field internalName", "field value (regex)"], "Selects rows where the vanilla equivilent of that row has a value for the given field that matches the given regex", (args, lenient) => { @@ -565,7 +620,7 @@ internal override void Setup() Param vparam = ParamBank.VanillaBank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); return row => { - Param.Row vrow = vparam[row.ID]; + Param.Row vrow = vparam[row.Item2.ID]; if (vrow == null) { return false; @@ -584,7 +639,7 @@ internal override void Setup() }; }, () => CFG.Current.Param_AdvancedMassedit)); filterList.Add("vanillaproprange", newCmd( - new[] { "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" }, + ["field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)"], "Selects rows where the vanilla equivilent of that row has a value for the given field that falls in the given numerical range", (args, lenient) => { @@ -596,7 +651,7 @@ internal override void Setup() Param vparam = ParamBank.VanillaBank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); return row => { - Param.Row vrow = vparam[row.ID]; + Param.Row vrow = vparam[row.Item2.ID]; if (vrow == null) { return false; @@ -612,7 +667,7 @@ internal override void Setup() }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - filterList.Add("auxprop", newCmd(new[] { "parambank name", "field internalName", "field value (regex)" }, + filterList.Add("auxprop", newCmd(["parambank name", "field internalName", "field value (regex)"], "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that matches the given regex.\nCan be used to determine if an aux row exists.", (args, lenient) => { @@ -629,7 +684,7 @@ internal override void Setup() Param vparam = bank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); return row => { - Param.Row vrow = vparam[row.ID]; + Param.Row vrow = vparam[row.Item2.ID]; if (vrow == null) { return false; @@ -648,11 +703,10 @@ internal override void Setup() }; }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); filterList.Add("auxproprange", newCmd( - new[] - { + [ "parambank name", "field internalName", "field value minimum (inclusive)", "field value maximum (inclusive)" - }, + ], "Selects rows where the equivilent of that row in the given regulation or parambnd has a value for the given field that falls in the given range", (args, lenient) => { @@ -670,7 +724,7 @@ internal override void Setup() Param vparam = bank.GetParamFromName(param.Item1.GetKeyForParam(param.Item2)); return row => { - Param.Row vrow = vparam[row.ID]; + Param.Row vrow = vparam[row.Item2.ID]; Param.Cell? c = vrow[field]; if (c == null) { @@ -683,11 +737,10 @@ internal override void Setup() }, () => ParamBank.AuxBanks.Count > 0 && CFG.Current.Param_AdvancedMassedit)); filterList.Add("semijoin", newCmd( - new[] - { + [ "this field internalName", "other param", "other param field internalName", "other param row search" - }, + ], "Selects all rows where the value of a given field is any of the values in the second given field found in the given param using the given row selector", (args, lenient) => { @@ -701,7 +754,7 @@ internal override void Setup() throw new Exception("Could not find param " + otherParam); } - List rows = rse.Search((ParamBank.PrimaryBank, otherParamReal), otherSearchTerm, + List<(string, Param.Row)> rows = row.Search((ParamBank.PrimaryBank, otherParamReal), otherSearchTerm, lenient, false); (PseudoColumn, Param.Column) otherFieldReal = otherParamReal.GetCol(otherField); if (!otherFieldReal.IsColumnValid()) @@ -709,7 +762,7 @@ internal override void Setup() throw new Exception("Could not find field " + otherField); } - HashSet possibleValues = rows.Select(x => x.Get(otherFieldReal).ToParamEditorString()) + HashSet possibleValues = rows.Select(x => x.Item2.Get(otherFieldReal).ToParamEditorString()) .Distinct().ToHashSet(); return param => { @@ -721,12 +774,12 @@ internal override void Setup() return row => { - var toFind = row.Get(thisFieldReal).ToParamEditorString(); + var toFind = row.Item2.Get(thisFieldReal).ToParamEditorString(); return possibleValues.Contains(toFind); }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - filterList.Add("unique", newCmd(new string[] { "field" }, "Selects all rows where the value in the given field is unique", (args, lenient) => + filterList.Add("unique", newCmd(["field"], "Selects all rows where the value in the given field is unique", (args, lenient) => { string field = args[0].Replace(@"\s", " "); return (param) => @@ -738,11 +791,11 @@ internal override void Setup() var setOfDuped = distribution.Where((entry, linqi) => entry.Item2 > 1).Select((entry, linqi) => entry.Item1).ToHashSet(); return (row) => { - return !setOfDuped.Contains(row.Get(col)); + return !setOfDuped.Contains(row.Item2.Get(col)); }; }; }, () => CFG.Current.Param_AdvancedMassedit)); - defaultFilter = newCmd(new[] { "row ID or Name (regex)" }, + defaultFilter = newCmd(["row ID or Name (regex)"], "Selects rows where either the ID or Name matches the given regex, except in strict/massedit mode", (args, lenient) => { @@ -768,7 +821,7 @@ internal override void Setup() if (category == FmgEntryCategory.None || !FMGBank.IsLoaded) { - return row => rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString()); + return row => rx.IsMatch(row.Item2.Name ?? "") || rx.IsMatch(row.Item2.ID.ToString()); } List fmgEntries = FMGBank.GetFmgEntriesByCategory(category, false); @@ -780,17 +833,17 @@ internal override void Setup() return row => { - if (rx.IsMatch(row.Name ?? "") || rx.IsMatch(row.ID.ToString())) + if (rx.IsMatch(row.Item2.Name ?? "") || rx.IsMatch(row.Item2.ID.ToString())) { return true; } - if (!_cache.ContainsKey(row.ID)) + if (!_cache.ContainsKey(row.Item2.ID)) { return false; } - FMG.Entry e = _cache[row.ID]; + FMG.Entry e = _cache[row.Item2.ID]; return e != null && rx.IsMatch(e.Text ?? ""); }; }; @@ -798,12 +851,12 @@ internal override void Setup() } } -internal class CellSearchEngine : SearchEngine<(string, Param.Row), (PseudoColumn, Param.Column)> +internal class CellSearchEngine : TypedSearchEngine { - public static CellSearchEngine cse = new(); internal override void Setup() { + name = "cell/property"; unpacker = row => { List<(PseudoColumn, Param.Column)> list = new(); @@ -812,7 +865,7 @@ internal override void Setup() list.AddRange(row.Item2.Columns.Select((cell, i) => (PseudoColumn.None, cell))); return list; }; - defaultFilter = newCmd(new[] { "field internalName (regex)" }, + defaultFilter = newCmd(["field internalName (regex)"], "Selects cells/fields where the internal name of that field matches the given regex", (args, lenient) => { var matchID = args[0] == "ID"; @@ -847,7 +900,7 @@ internal override void Setup() return false; }); }); - filterList.Add("modified", newCmd(new string[0], + filterList.Add("modified", newCmd([], "Selects cells/fields where the equivalent cell in the vanilla regulation or parambnd has a different value", (args, lenient) => row => { @@ -870,13 +923,13 @@ internal override void Setup() return col => { - (PseudoColumn, Param.Column) vcol = col.GetAs(vParam); - var valA = row.Item2.Get(col); + (PseudoColumn, Param.Column) vcol = (col.Item1, col.Item2).GetAs(vParam); + var valA = row.Item2.Get((col.Item1, col.Item2)); var valB = r.Get(vcol); - return ParamUtils.IsValueDiff(ref valA, ref valB, col.GetColumnType()); + return ParamUtils.IsValueDiff(ref valA, ref valB, (col.Item1, col.Item2).GetColumnType()); }; })); - filterList.Add("auxmodified", newCmd(new[] { "parambank name" }, + filterList.Add("auxmodified", newCmd(["parambank name"], "Selects cells/fields where the equivalent cell in the specified regulation or parambnd has a different value", (args, lenient) => { @@ -919,36 +972,35 @@ internal override void Setup() return col => { - (PseudoColumn, Param.Column) auxcol = col.GetAs(auxParam); - (PseudoColumn, Param.Column) vcol = col.GetAs(vParam); + (PseudoColumn, Param.Column) auxcol = (col.Item1, col.Item2).GetAs(auxParam); + (PseudoColumn, Param.Column) vcol = (col.Item1, col.Item2).GetAs(vParam); var valA = r.Get(auxcol); var valB = r2.Get(vcol); - return ParamUtils.IsValueDiff(ref valA, ref valB, col.GetColumnType()); + return ParamUtils.IsValueDiff(ref valA, ref valB, (col.Item1, col.Item2).GetColumnType()); }; }; }, () => ParamBank.AuxBanks.Count > 0)); - filterList.Add("sftype", newCmd(new[] { "paramdef type" }, + filterList.Add("sftype", newCmd(["paramdef type"], "Selects cells/fields where the field's data type, as enumerated by soulsformats, matches the given regex", (args, lenient) => { Regex r = new('^' + args[0] + '$', lenient ? RegexOptions.IgnoreCase : RegexOptions.None); //Leniency rules break from the norm - return row => col => r.IsMatch(col.GetColumnSfType()); + return row => col => r.IsMatch((col.Item1, col.Item2).GetColumnSfType()); }, () => CFG.Current.Param_AdvancedMassedit)); } } -internal class VarSearchEngine : SearchEngine +internal class VarSearchEngine : TypedSearchEngine { - public static VarSearchEngine vse = new(); - internal override void Setup() { + name = "variable"; unpacker = dummy => { - return MassParamEdit.massEditVars.Keys.ToList(); + return MassParamEdit.massEditVars.Keys.Select(x => (true, x)).ToList(); }; - filterList.Add("vars", newCmd(new[] { "variable names (regex)" }, + filterList.Add("vars", newCmd(["variable names (regex)"], "Selects variables whose name matches the given regex", (args, lenient) => { if (args[0].StartsWith('$')) @@ -957,7 +1009,7 @@ internal override void Setup() } Regex rx = lenient ? new Regex(args[0], RegexOptions.IgnoreCase) : new Regex($@"^{args[0]}$"); - return noContext(name => rx.IsMatch(name)); + return noContext(bname => rx.IsMatch(bname.Item2)); })); } } diff --git a/src/StudioCore/ParamEditor/MassParamEdit.cs b/src/StudioCore/ParamEditor/MassParamEdit.cs deleted file mode 100644 index e323b3ce2..000000000 --- a/src/StudioCore/ParamEditor/MassParamEdit.cs +++ /dev/null @@ -1,1267 +0,0 @@ -#nullable enable -using Andre.Formats; -using StudioCore.Editor; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace StudioCore.ParamEditor; - -public enum MassEditResultType -{ - SUCCESS, - PARSEERROR, - OPERATIONERROR -} - -public class MassEditResult -{ - public string Information; - public MassEditResultType Type; - - public MassEditResult(MassEditResultType result, string info) - { - Type = result; - Information = info; - } -} - -public static class MassParamEdit -{ - public static Dictionary massEditVars = new(); - - internal static object WithDynamicOf(object instance, Func dynamicFunc) - { - try - { - return Convert.ChangeType(dynamicFunc(instance), instance.GetType()); - } - catch - { - // Second try, handle byte[], and casts from numerical values to string which need parsing. - var ret = dynamicFunc(instance.ToParamEditorString()); - if (instance.GetType() == typeof(byte[])) - { - ret = ParamUtils.Dummy8Read((string)ret, ((byte[])instance).Length); - } - - return Convert.ChangeType(ret, instance.GetType()); - } - } - - public static void AppendParamEditAction(this List actions, Param.Row row, - (PseudoColumn, Param.Column) col, object newval) - { - if (col.Item1 == PseudoColumn.ID) - { - if (!row.ID.Equals(newval)) - { - actions.Add(new PropertiesChangedAction(row.GetType().GetProperty("ID"), -1, row, newval)); - } - } - else if (col.Item1 == PseudoColumn.Name) - { - if (row.Name == null || !row.Name.Equals(newval)) - { - actions.Add(new PropertiesChangedAction(row.GetType().GetProperty("Name"), -1, row, newval)); - } - } - else - { - Param.Cell handle = row[col.Item2]; - if (!(handle.Value.Equals(newval) - || (handle.Value.GetType() == typeof(byte[]) - && ParamUtils.ByteArrayEquals((byte[])handle.Value, (byte[])newval)))) - { - actions.Add(new PropertiesChangedAction(handle.GetType().GetProperty("Value"), -1, handle, newval)); - } - } - } -} - -public class MassParamEditRegex -{ - private int argc; - - // Run data - private string[] argNames; - - // Do we want these variables? shouldn't they be contained? - private ParamBank bank; - private string cellOperation; - private string cellSelector; - private ParamEditorSelectionState context; - private Func genericFunc; - - private Func globalFunc; - - private string globalOperation; - private Func>>[] paramArgFuncs; - private string paramRowSelector; - - private string paramSelector; - private Func<(string, Param.Row), string[], (Param, Param.Row)> rowFunc; - private string rowOperation; - - - private Func[], string, Param.Row, List, - MassEditResult> rowOpOrCellStageFunc; - - private string rowSelector; - private string varOperation; - - // Parsing data - private string varSelector; - - public static (MassEditResult, ActionManager child) PerformMassEdit(ParamBank bank, string commandsString, - ParamEditorSelectionState context) - { - int currentLine = 0; - try - { - var commands = commandsString.Split('\n'); - var changeCount = 0; - ActionManager childManager = new(); - foreach (var cmd in commands) - { - currentLine++; - var command = cmd; - if (command.StartsWith("##") || string.IsNullOrWhiteSpace(command)) - { - continue; - } - - if (command.EndsWith(';')) - { - command = command.Substring(0, command.Length - 1); - } - - (MassEditResult result, List actions) = (null, null); - - MassParamEditRegex currentEditData = new(); - currentEditData.bank = bank; - currentEditData.context = context; - - var primaryFilter = command.Split(':', 2)[0].Trim(); - - if (MEGlobalOperation.globalOps.HandlesCommand(primaryFilter.Split(" ", 2)[0])) - { - (result, actions) = currentEditData.ParseGlobalOpStep(currentLine, command); - } - else if (VarSearchEngine.vse.HandlesCommand(primaryFilter.Split(" ", 2)[0])) - { - (result, actions) = currentEditData.ParseVarStep(currentLine, command); - } - else if (ParamAndRowSearchEngine.parse.HandlesCommand(primaryFilter.Split(" ", 2)[0])) - { - (result, actions) = currentEditData.ParseParamRowStep(currentLine, command); - } - else - { - (result, actions) = currentEditData.ParseParamStep(currentLine, command); - } - - if (result.Type != MassEditResultType.SUCCESS) - { - return (result, null); - } - - changeCount += actions.Count; - childManager.ExecuteAction(new CompoundAction(actions)); - } - - return (new MassEditResult(MassEditResultType.SUCCESS, $@"{changeCount} cells affected"), childManager); - } - catch (Exception e) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Unknown parsing error on line {currentLine}: " + e.ToString()), null); - } - } - - private (MassEditResult, List) ParseGlobalOpStep(int currentLine, string restOfStages) - { - var opStage = restOfStages.Split(" ", 2); - globalOperation = opStage[0].Trim(); - if (!MEGlobalOperation.globalOps.operations.ContainsKey(globalOperation)) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Unknown global operation {globalOperation} (line {currentLine})"), null); - } - - string wiki; - (argNames, wiki, globalFunc, _) = MEGlobalOperation.globalOps.operations[globalOperation]; - ExecParamOperationArguments(currentLine, opStage.Length > 1 ? opStage[1] : null); - if (argc != paramArgFuncs.Length) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {globalOperation} (line {currentLine})"), null); - } - - return SandboxMassEditExecution(currentLine, partials => ExecGlobalOp(currentLine)); - } - - private (MassEditResult, List) ParseVarStep(int currentLine, string restOfStages) - { - var varstage = restOfStages.Split(":", 2); - varSelector = varstage[0].Trim(); - if (varSelector.Equals("")) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find variable filter. Add : and one of {String.Join(", ", VarSearchEngine.vse.AvailableCommandsForHelpText())} (line {currentLine})"), null); - } - - if (varstage.Length < 2) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find var operation. Check your colon placement. (line {currentLine})"), null); - } - - return ParseVarOpStep(currentLine, varstage[1]); - } - - private (MassEditResult, List) ParseVarOpStep(int currentLine, string restOfStages) - { - var operationstage = restOfStages.TrimStart().Split(" ", 2); - varOperation = operationstage[0].Trim(); - if (varOperation.Equals("") || !MEValueOperation.valueOps.operations.ContainsKey(varOperation)) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find operation to perform. Add : and one of + - * / replace (line {currentLine})"), null); - } - - string wiki; - (argNames, wiki, genericFunc, _) = MEValueOperation.valueOps.operations[varOperation]; - ExecParamOperationArguments(currentLine, operationstage.Length > 1 ? operationstage[1] : null); - if (argc != paramArgFuncs.Length) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {varOperation} (line {currentLine})"), null); - } - - return SandboxMassEditExecution(currentLine, partials => ExecVarStage(currentLine)); - } - - private (MassEditResult, List) ParseParamRowStep(int currentLine, string restOfStages) - { - var paramrowstage = restOfStages.Split(":", 2); - paramRowSelector = paramrowstage[0].Trim(); - if (paramRowSelector.Equals("")) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find paramrow filter. Add : and one of {String.Join(", ", ParamAndRowSearchEngine.parse.AvailableCommandsForHelpText())} (line {currentLine})"), null); - } - - if (paramrowstage.Length < 2) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find cell filter or row operation. Check your colon placement. (line {currentLine})"), null); - } - - if (MERowOperation.rowOps.HandlesCommand(paramrowstage[1].Trim().Split(" ", 2)[0])) - { - return ParseRowOpStep(currentLine, paramrowstage[1]); - } - - return ParseCellStep(currentLine, paramrowstage[1]); - } - - private (MassEditResult, List) ParseParamStep(int currentLine, string restOfStages) - { - var paramstage = restOfStages.Split(":", 2); - paramSelector = paramstage[0].Trim(); - if (paramSelector.Equals("")) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find param filter. Add : and one of {String.Join(", ", ParamSearchEngine.pse.AvailableCommandsForHelpText())} (line {currentLine})"), null); - } - - if (paramstage.Length < 2) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find row filter. Check your colon placement. (line {currentLine})"), null); - } - - return ParseRowStep(currentLine, paramstage[1]); - } - - private (MassEditResult, List) ParseRowStep(int currentLine, string restOfStages) - { - var rowstage = restOfStages.Split(":", 2); - rowSelector = rowstage[0].Trim(); - if (rowSelector.Equals("")) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find row filter. Add : and one of {String.Join(", ", RowSearchEngine.rse.AvailableCommandsForHelpText())} (line {currentLine})"), null); - } - - if (rowstage.Length < 2) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find cell filter or row operation to perform. Check your colon placement. (line {currentLine})"), null); - } - - if (MERowOperation.rowOps.HandlesCommand(rowstage[1].Trim().Split(" ", 2)[0])) - { - return ParseRowOpStep(currentLine, rowstage[1]); - } - - return ParseCellStep(currentLine, rowstage[1]); - } - - private (MassEditResult, List) ParseCellStep(int currentLine, string restOfStages) - { - var cellstage = restOfStages.Split(":", 2); - cellSelector = cellstage[0].Trim(); - if (cellSelector.Equals("")) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find cell/property filter. Add : and one of {String.Join(", ", CellSearchEngine.cse.AvailableCommandsForHelpText())} or Name (0 args) (line {currentLine})"), null); - } - - if (cellstage.Length < 2) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find operation to perform. Check your colon placement. (line {currentLine})"), null); - } - - rowOpOrCellStageFunc = ExecCellStage; - return ParseCellOpStep(currentLine, cellstage[1]); - } - - private (MassEditResult, List) ParseRowOpStep(int currentLine, string restOfStages) - { - var operationstage = restOfStages.TrimStart().Split(" ", 2); - rowOperation = operationstage[0].Trim(); - if (!MERowOperation.rowOps.operations.ContainsKey(rowOperation)) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Unknown row operation {rowOperation} (line {currentLine})"), null); - } - - string wiki; - (argNames, wiki, rowFunc, _) = MERowOperation.rowOps.operations[rowOperation]; - ExecParamOperationArguments(currentLine, operationstage.Length > 1 ? operationstage[1] : null); - if (argc != paramArgFuncs.Length) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {rowOperation} (line {currentLine})"), null); - } - - rowOpOrCellStageFunc = ExecRowOp; - return SandboxMassEditExecution(currentLine, partials => - paramRowSelector != null ? ExecParamRowStage(currentLine, partials) : ExecParamStage(currentLine, partials)); - } - - private (MassEditResult, List) ParseCellOpStep(int currentLine, string restOfStages) - { - var operationstage = restOfStages.TrimStart().Split(" ", 2); - cellOperation = operationstage[0].Trim(); - - if (cellOperation.Equals("") || !MEValueOperation.valueOps.operations.ContainsKey(cellOperation)) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Could not find operation to perform. Add : and one of + - * / replace (line {currentLine})"), null); - } - - if (!MEValueOperation.valueOps.operations.ContainsKey(cellOperation)) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Unknown cell operation {cellOperation} (line {currentLine})"), null); - } - - string wiki; - (argNames, wiki, genericFunc, _) = MEValueOperation.valueOps.operations[cellOperation]; - ExecParamOperationArguments(currentLine, operationstage.Length > 1 ? operationstage[1] : null); - if (argc != paramArgFuncs.Length) - { - return (new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {cellOperation} (line {currentLine})"), null); - } - - return SandboxMassEditExecution(currentLine, partials => - paramRowSelector != null ? ExecParamRowStage(currentLine, partials) : ExecParamStage(currentLine, partials)); - } - - private void ExecParamOperationArguments(int currentLine, string opargs) - { - argc = argNames.Length; - paramArgFuncs = MEOperationArgument.arg.getContextualArguments(argc, opargs); - } - - private (MassEditResult, List) SandboxMassEditExecution(int currentLine, - Func, MassEditResult> innerFunc) - { - List partialActions = new(); - try - { - return (innerFunc(partialActions), partialActions); - } - catch (Exception e) - { - return (new MassEditResult(MassEditResultType.OPERATIONERROR, @$"Error on line {currentLine}" + '\n' + e.ToString()), null); - } - - return (new MassEditResult(MassEditResultType.SUCCESS, $@"{partialActions.Count} cells affected"), - partialActions); - } - - private MassEditResult ExecGlobalOp(int currentLine) - { - var globalArgValues = paramArgFuncs.Select(f => f(-1, null)(-1, null)(-1, (PseudoColumn.None, null))) - .ToArray(); - var result = globalFunc(context, globalArgValues); - if (!result) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Error performing global operation {globalOperation} (line {currentLine})"); - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecVarStage(int currentLine) - { - ; - var varArgs = paramArgFuncs - .Select((func, i) => func(-1, null)(-1, null)(-1, (PseudoColumn.None, null))).ToArray(); - foreach (var varName in VarSearchEngine.vse.Search(false, varSelector, false, false)) - { - MassEditResult res = ExecVarOpStage(currentLine, varName, varArgs); - if (res.Type != MassEditResultType.SUCCESS) - { - return res; - } - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecVarOpStage(int currentLine, string var, string[] args) - { - MassParamEdit.massEditVars[var] = genericFunc(MassParamEdit.massEditVars[var], args); - var result = true; // Anything that practicably can go wrong - if (!result) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Error performing var operation {varOperation} (line {currentLine})"); - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecParamRowStage(int currentLine, List partialActions) - { - Param activeParam = bank.Params[context.GetActiveParam()]; - IEnumerable>> paramArgFunc = - paramArgFuncs.Select((func, i) => func(0, activeParam)); // technically invalid for clipboard - var rowEditCount = -1; - foreach ((MassEditRowSource source, Param.Row row) in ParamAndRowSearchEngine.parse.Search(context, - paramRowSelector, false, false)) - { - rowEditCount++; - Func[] rowArgFunc = - paramArgFunc.Select((rowFunc, i) => rowFunc(rowEditCount, row)).ToArray(); - var paramname = source == MassEditRowSource.Selection - ? context.GetActiveParam() - : ParamBank.ClipboardParam; - MassEditResult res = rowOpOrCellStageFunc(currentLine, rowArgFunc, paramname, row, partialActions); - if (res.Type != MassEditResultType.SUCCESS) - { - return res; - } - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecParamStage(int currentLine, List partialActions) - { - var paramEditCount = -1; - var operationForPrint = rowOperation != null ? rowOperation : cellOperation; - foreach ((ParamBank b, Param p) in ParamSearchEngine.pse.Search(false, paramSelector, false, false)) - { - paramEditCount++; - IEnumerable>> paramArgFunc = - paramArgFuncs.Select((func, i) => func(paramEditCount, p)); - if (argc != paramArgFuncs.Length) - { - return new MassEditResult(MassEditResultType.PARSEERROR, $@"Invalid number of arguments for operation {operationForPrint} (line {currentLine})"); - } - - var paramname = b.GetKeyForParam(p); - MassEditResult res = ExecRowStage(currentLine, paramArgFunc, paramname, b, p, partialActions); - if (res.Type != MassEditResultType.SUCCESS) - { - return res; - } - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecRowStage(int currentLine, - IEnumerable>> paramArgFunc, - string paramname, ParamBank b, Param p, List partialActions) - { - var rowEditCount = -1; - foreach (Param.Row row in RowSearchEngine.rse.Search((b, p), rowSelector, false, false)) - { - rowEditCount++; - Func[] rowArgFunc = - paramArgFunc.Select((rowFunc, i) => rowFunc(rowEditCount, row)).ToArray(); - MassEditResult res = rowOpOrCellStageFunc(currentLine, rowArgFunc, paramname, row, partialActions); - if (res.Type != MassEditResultType.SUCCESS) - { - return res; - } - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecRowOp(int currentLine, Func[] rowArgFunc, string paramname, - Param.Row row, List partialActions) - { - var rowArgValues = rowArgFunc.Select((argV, i) => argV(-1, (PseudoColumn.None, null))).ToArray(); - (Param? p2, Param.Row? rs) = rowFunc((paramname, row), rowArgValues); - if (p2 == null) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Could not perform operation {rowOperation} {String.Join(' ', rowArgValues)} on row (line {currentLine})"); - } - - if (rs != null) - { - partialActions.Add(new AddParamsAction(p2, "FromMassEdit", new List { rs }, false, true)); - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecCellStage(int currentLine, Func[] rowArgFunc, - string paramname, Param.Row row, List partialActions) - { - var cellEditCount = -1; - foreach ((PseudoColumn, Param.Column) col in CellSearchEngine.cse.Search((paramname, row), cellSelector, - false, false)) - { - cellEditCount++; - var cellArgValues = rowArgFunc.Select((argV, i) => argV(cellEditCount, col)).ToArray(); - MassEditResult res = ExecCellOp(currentLine, cellArgValues, paramname, row, col, partialActions); - if (res.Type != MassEditResultType.SUCCESS) - { - return res; - } - } - - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } - - private MassEditResult ExecCellOp(int currentLine, string[] cellArgValues, string paramname, Param.Row row, - (PseudoColumn, Param.Column) col, List partialActions) - { - object res = null; - string errHelper = null; - try - { - res = genericFunc(row.Get(col), cellArgValues); - } - catch (FormatException e) - { - errHelper = "Type is not correct"; - } - catch (InvalidCastException e) - { - errHelper = "Cannot cast to correct type"; - } - catch (Exception e) - { - errHelper = "Unknown error"; - } - - if (res == null && col.Item1 == PseudoColumn.ID) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Could not perform operation {cellOperation} {String.Join(' ', cellArgValues)} on ID ({errHelper}) (line {currentLine})"); - } - - if (res == null && col.Item1 == PseudoColumn.Name) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Could not perform operation {cellOperation} {String.Join(' ', cellArgValues)} on Name ({errHelper}) (line {currentLine})"); - } - - if (res == null) - { - return new MassEditResult(MassEditResultType.OPERATIONERROR, $@"Could not perform operation {cellOperation} {String.Join(' ', cellArgValues)} on field {col.Item2.Def.InternalName} ({errHelper}) (line {currentLine})"); - } - - partialActions.AppendParamEditAction(row, col, res); - return new MassEditResult(MassEditResultType.SUCCESS, ""); - } -} - -public class MassParamEditOther -{ - public static AddParamsAction SortRows(ParamBank bank, string paramName) - { - Param param = bank.Params[paramName]; - List newRows = new(param.Rows); - newRows.Sort((a, b) => { return a.ID - b.ID; }); - return new AddParamsAction(param, paramName, newRows, true, - true); //appending same params and allowing overwrite - } -} - -public class MEOperation -{ - internal Dictionary, Func?)> operations = new(); - - internal MEOperation() - { - Setup(); - } - - internal virtual void Setup() - { - } - - internal bool HandlesCommand(string command) - { - return operations.ContainsKey(command); - } - - public List<(string, string[], string)> AvailableCommands(bool omitNone = false) - { - List<(string, string[], string)> options = new(); - foreach (var op in operations.Keys) - { - var toTest = operations[op].Item4; - if (omitNone || toTest == null || toTest()) - options.Add((op, operations[op].Item1, operations[op].Item2)); - } - - return options; - } -} - -public class MEGlobalOperation : MEOperation -{ - public static MEGlobalOperation globalOps = new(); - - internal override void Setup() - { - operations.Add("clear", (new string[0], "Clears clipboard param and rows", (selectionState, args) => - { - ParamBank.ClipboardParam = null; - ParamBank.ClipboardRows.Clear(); - return true; - }, null)); - operations.Add("newvar", (new[] { "variable name", "value" }, - "Creates a variable with the given value, and the type of that value", (selectionState, args) => - { - int asInt; - double asDouble; - if (int.TryParse(args[1], out asInt)) - { - MassParamEdit.massEditVars[args[0]] = asInt; - } - else if (double.TryParse(args[1], out asDouble)) - { - MassParamEdit.massEditVars[args[0]] = asDouble; - } - else - { - MassParamEdit.massEditVars[args[0]] = args[1]; - } - - return true; - }, () => CFG.Current.Param_AdvancedMassedit)); - operations.Add("clearvars", (new string[0], "Deletes all variables", (selectionState, args) => - { - MassParamEdit.massEditVars.Clear(); - return true; - }, () => CFG.Current.Param_AdvancedMassedit)); - } -} - -public class MERowOperation : MEOperation<(string, Param.Row), (Param, Param.Row)> -{ - public static MERowOperation rowOps = new(); - - internal override void Setup() - { - operations.Add("copy", (new string[0], - "Adds the selected rows into clipboard. If the clipboard param is different, the clipboard is emptied first", - (paramAndRow, args) => - { - var paramKey = paramAndRow.Item1; - Param.Row row = paramAndRow.Item2; - if (paramKey == null) - { - throw new Exception(@"Could not locate param"); - } - - if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) - { - throw new Exception($@"Could not locate param {paramKey}"); - } - - Param p = ParamBank.PrimaryBank.Params[paramKey]; - // Only supporting single param in clipboard - if (ParamBank.ClipboardParam != paramKey) - { - ParamBank.ClipboardParam = paramKey; - ParamBank.ClipboardRows.Clear(); - } - - ParamBank.ClipboardRows.Add(new Param.Row(row, p)); - return (p, null); - }, null)); - operations.Add("copyN", (new[] { "count" }, - "Adds the selected rows into clipboard the given number of times. If the clipboard param is different, the clipboard is emptied first", - (paramAndRow, args) => - { - var paramKey = paramAndRow.Item1; - Param.Row row = paramAndRow.Item2; - if (paramKey == null) - { - throw new Exception(@"Could not locate param"); - } - - if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) - { - throw new Exception($@"Could not locate param {paramKey}"); - } - - var count = uint.Parse(args[0]); - Param p = ParamBank.PrimaryBank.Params[paramKey]; - // Only supporting single param in clipboard - if (ParamBank.ClipboardParam != paramKey) - { - ParamBank.ClipboardParam = paramKey; - ParamBank.ClipboardRows.Clear(); - } - - for (var i = 0; i < count; i++) - { - ParamBank.ClipboardRows.Add(new Param.Row(row, p)); - } - - return (p, null); - }, () => CFG.Current.Param_AdvancedMassedit)); - operations.Add("paste", (new string[0], - "Adds the selected rows to the primary regulation or parambnd in the selected param", - (paramAndRow, args) => - { - var paramKey = paramAndRow.Item1; - Param.Row row = paramAndRow.Item2; - if (paramKey == null) - { - throw new Exception(@"Could not locate param"); - } - - if (!ParamBank.PrimaryBank.Params.ContainsKey(paramKey)) - { - throw new Exception($@"Could not locate param {paramKey}"); - } - - Param p = ParamBank.PrimaryBank.Params[paramKey]; - return (p, new Param.Row(row, p)); - }, null)); - } -} - -public class MEValueOperation : MEOperation -{ - public static MEValueOperation valueOps = new(); - - internal override void Setup() - { - operations.Add("=", - (new[] { "number or text" }, - "Assigns the given value to the selected values. Will attempt conversion to the value's data type", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => args[0]), null)); - operations.Add("+", (new[] { "number or text" }, - "Adds the number to the selected values, or appends text if that is the data type of the values", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => - { - double val; - if (double.TryParse(args[0], out val)) - { - return v + val; - } - - return v + args[0]; - }), null)); - operations.Add("-", - (new[] { "number" }, "Subtracts the number from the selected values", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => v - double.Parse(args[0])), null)); - operations.Add("*", - (new[] { "number" }, "Multiplies selected values by the number", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => v * double.Parse(args[0])), null)); - operations.Add("/", - (new[] { "number" }, "Divides the selected values by the number", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => v / double.Parse(args[0])), null)); - operations.Add("%", - (new[] { "number" }, "Gives the remainder when the selected values are divided by the number", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => v % double.Parse(args[0])), () => CFG.Current.Param_AdvancedMassedit)); - operations.Add("scale", (new[] { "factor number", "center number" }, - "Multiplies the difference between the selected values and the center number by the factor number", - (ctx, args) => - { - var opp1 = double.Parse(args[0]); - var opp2 = double.Parse(args[1]); - return MassParamEdit.WithDynamicOf(ctx, v => - { - return ((v - opp2) * opp1) + opp2; - }); - }, null)); - operations.Add("replace", - (new[] { "text to replace", "new text" }, - "Interprets the selected values as text and replaces all occurances of the text to replace with the new text", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => v.Replace(args[0], args[1])), null)); - operations.Add("replacex", (new[] { "text to replace (regex)", "new text (w/ groups)" }, - "Interprets the selected values as text and replaces all occurances of the given regex with the replacement, supporting regex groups", - (ctx, args) => - { - Regex rx = new(args[0]); - return MassParamEdit.WithDynamicOf(ctx, v => rx.Replace(v, args[1])); - }, () => CFG.Current.Param_AdvancedMassedit)); - operations.Add("max", - (new[] { "number" }, "Returns the larger of the current value and number", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => Math.Max(v, double.Parse(args[0]))), () => CFG.Current.Param_AdvancedMassedit)); - operations.Add("min", - (new[] { "number" }, "Returns the smaller of the current value and number", - (ctx, args) => MassParamEdit.WithDynamicOf(ctx, v => Math.Min(v, double.Parse(args[0]))), () => CFG.Current.Param_AdvancedMassedit)); - } -} - -public class MEOperationArgument -{ - public static MEOperationArgument arg = new(); - private readonly Dictionary argumentGetters = new(); - private OperationArgumentGetter defaultGetter; - - private MEOperationArgument() - { - Setup(); - } - - private OperationArgumentGetter newGetter(string[] args, string wiki, - Func>>> - func, Func shouldShow = null) - { - return new OperationArgumentGetter(args, wiki, func, shouldShow); - } - - private void Setup() - { - defaultGetter = newGetter(new string[0], "Gives the specified value", - value => (i, param) => (j, row) => (k, col) => value[0]); - argumentGetters.Add("self", newGetter(new string[0], "Gives the value of the currently selected value", - empty => (i, param) => (j, row) => (k, col) => - { - return row.Get(col).ToParamEditorString(); - })); - argumentGetters.Add("field", newGetter(new[] { "field internalName" }, - "Gives the value of the given cell/field for the currently selected row and param", field => - (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - return (j, row) => - { - var v = row.Get(col).ToParamEditorString(); - return (k, c) => v; - }; - })); - argumentGetters.Add("vanilla", newGetter(new string[0], - "Gives the value of the equivalent cell/field in the vanilla regulation or parambnd for the currently selected cell/field, row and param.\nWill fail if a row does not have a vanilla equivilent. Consider using && !added", - empty => - { - ParamBank bank = ParamBank.VanillaBank; - return (i, param) => - { - var paramName = ParamBank.PrimaryBank.GetKeyForParam(param); - if (!bank.Params.ContainsKey(paramName)) - { - throw new Exception($@"Could not locate vanilla param for {param.ParamType}"); - } - - Param vParam = bank.Params[paramName]; - return (j, row) => - { - Param.Row vRow = vParam?[row.ID]; - if (vRow == null) - { - throw new Exception($@"Could not locate vanilla row {row.ID}"); - } - - return (k, col) => - { - if (col.Item1 == PseudoColumn.None && col.Item2 == null) - { - throw new Exception(@"Could not locate given field or property"); - } - - return vRow.Get(col).ToParamEditorString(); - }; - }; - }; - })); - argumentGetters.Add("aux", newGetter(new[] { "parambank name" }, - "Gives the value of the equivalent cell/field in the specified regulation or parambnd for the currently selected cell/field, row and param.\nWill fail if a row does not have an aux equivilent. Consider using && auxprop ID .*", - bankName => - { - if (!ParamBank.AuxBanks.ContainsKey(bankName[0])) - { - throw new Exception($@"Could not locate paramBank {bankName[0]}"); - } - - ParamBank bank = ParamBank.AuxBanks[bankName[0]]; - return (i, param) => - { - var paramName = ParamBank.PrimaryBank.GetKeyForParam(param); - if (!bank.Params.ContainsKey(paramName)) - { - throw new Exception($@"Could not locate aux param for {param.ParamType}"); - } - - Param vParam = bank.Params[paramName]; - return (j, row) => - { - Param.Row vRow = vParam?[row.ID]; - if (vRow == null) - { - throw new Exception($@"Could not locate aux row {row.ID}"); - } - - return (k, col) => - { - if (!col.IsColumnValid()) - { - throw new Exception(@"Could not locate given field or property"); - } - - return vRow.Get(col).ToParamEditorString(); - }; - }; - }; - }, () => ParamBank.AuxBanks.Count > 0)); - argumentGetters.Add("vanillafield", newGetter(new[] { "field internalName" }, - "Gives the value of the specified cell/field in the vanilla regulation or parambnd for the currently selected row and param.\nWill fail if a row does not have a vanilla equivilent. Consider using && !added", - field => (i, param) => - { - var paramName = ParamBank.PrimaryBank.GetKeyForParam(param); - Param? vParam = ParamBank.VanillaBank.GetParamFromName(paramName); - if (vParam == null) - { - throw new Exception($@"Could not locate vanilla param for {param.ParamType}"); - } - - (PseudoColumn, Param.Column) col = vParam.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - return (j, row) => - { - Param.Row vRow = vParam?[row.ID]; - if (vRow == null) - { - throw new Exception($@"Could not locate vanilla row {row.ID}"); - } - - var v = vRow.Get(col).ToParamEditorString(); - return (k, c) => v; - }; - })); - argumentGetters.Add("auxfield", newGetter(new[] { "parambank name", "field internalName" }, - "Gives the value of the specified cell/field in the specified regulation or parambnd for the currently selected row and param.\nWill fail if a row does not have an aux equivilent. Consider using && auxprop ID .*", - bankAndField => - { - if (!ParamBank.AuxBanks.ContainsKey(bankAndField[0])) - { - throw new Exception($@"Could not locate paramBank {bankAndField[0]}"); - } - - ParamBank bank = ParamBank.AuxBanks[bankAndField[0]]; - return (i, param) => - { - var paramName = ParamBank.PrimaryBank.GetKeyForParam(param); - if (!bank.Params.ContainsKey(paramName)) - { - throw new Exception($@"Could not locate aux param for {param.ParamType}"); - } - - Param vParam = bank.Params[paramName]; - (PseudoColumn, Param.Column) col = vParam.GetCol(bankAndField[1]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {bankAndField[1]}"); - } - - return (j, row) => - { - Param.Row vRow = vParam?[row.ID]; - if (vRow == null) - { - throw new Exception($@"Could not locate aux row {row.ID}"); - } - - var v = vRow.Get(col).ToParamEditorString(); - return (k, c) => v; - }; - }; - }, () => ParamBank.AuxBanks.Count > 0)); - argumentGetters.Add("paramlookup", newGetter(new[] { "param name", "row id", "field name" }, - "Returns the specific value specified by the exact param, row and field.", address => - { - Param param = ParamBank.PrimaryBank.Params[address[0]]; - if (param == null) - throw new Exception($@"Could not find param {address[0]}"); - var id = int.Parse(address[1]); - (PseudoColumn, Param.Column) field = param.GetCol(address[2]); - if (!field.IsColumnValid()) - throw new Exception($@"Could not find field {address[2]} in param {address[0]}"); - var row = param[id]; - if (row == null) - throw new Exception($@"Could not find row {id} in param {address[0]}"); - var value = row.Get(field).ToParamEditorString(); - return (i, param) => (j, row) => (k, col) => value; - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("average", newGetter(new[] { "field internalName", "row selector" }, - "Gives the mean value of the cells/fields found using the given selector, for the currently selected param", - field => (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - Type colType = col.GetColumnType(); - if (colType == typeof(string) || colType == typeof(byte[])) - { - throw new Exception($@"Cannot average field {field[0]}"); - } - - List? rows = - RowSearchEngine.rse.Search((ParamBank.PrimaryBank, param), field[1], false, false); - IEnumerable vals = rows.Select((row, i) => row.Get(col)); - var avg = vals.Average(val => Convert.ToDouble(val)); - return (j, row) => (k, c) => avg.ToString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("median", newGetter(new[] { "field internalName", "row selector" }, - "Gives the median value of the cells/fields found using the given selector, for the currently selected param", - field => (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - List? rows = - RowSearchEngine.rse.Search((ParamBank.PrimaryBank, param), field[1], false, false); - IEnumerable vals = rows.Select((row, i) => row.Get(col)); - var avg = vals.OrderBy(val => Convert.ToDouble(val)).ElementAt(vals.Count() / 2); - return (j, row) => (k, c) => avg.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("mode", newGetter(new[] { "field internalName", "row selector" }, - "Gives the most common value of the cells/fields found using the given selector, for the currently selected param", - field => (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - List? rows = - RowSearchEngine.rse.Search((ParamBank.PrimaryBank, param), field[1], false, false); - var avg = ParamUtils.GetParamValueDistribution(rows, col).OrderByDescending(g => g.Item2) - .First().Item1; - return (j, row) => (k, c) => avg.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("min", newGetter(new[] { "field internalName", "row selector" }, - "Gives the smallest value from the cells/fields found using the given param, row selector and field", - field => (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - List? rows = - RowSearchEngine.rse.Search((ParamBank.PrimaryBank, param), field[1], false, false); - var min = rows.Min(r => r[field[0]].Value.Value); - return (j, row) => (k, c) => min.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("max", newGetter(new[] { "field internalName", "row selector" }, - "Gives the largest value from the cells/fields found using the given param, row selector and field", - field => (i, param) => - { - (PseudoColumn, Param.Column) col = param.GetCol(field[0]); - if (!col.IsColumnValid()) - { - throw new Exception($@"Could not locate field {field[0]}"); - } - - List? rows = - RowSearchEngine.rse.Search((ParamBank.PrimaryBank, param), field[1], false, false); - var max = rows.Max(r => r[field[0]].Value.Value); - return (j, row) => (k, c) => max.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("random", newGetter( - new[] { "minimum number (inclusive)", "maximum number (exclusive)" }, - "Gives a random decimal number between the given values for each selected value", minAndMax => - { - double min; - double max; - if (!double.TryParse(minAndMax[0], out min) || !double.TryParse(minAndMax[1], out max)) - { - throw new Exception(@"Could not parse min and max random values"); - } - - if (max <= min) - { - throw new Exception(@"Random max must be greater than min"); - } - - var range = max - min; - return (i, param) => (j, row) => (k, c) => ((Random.Shared.NextDouble() * range) + min).ToString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("randint", newGetter( - new[] { "minimum integer (inclusive)", "maximum integer (inclusive)" }, - "Gives a random integer between the given values for each selected value", minAndMax => - { - int min; - int max; - if (!int.TryParse(minAndMax[0], out min) || !int.TryParse(minAndMax[1], out max)) - { - throw new Exception(@"Could not parse min and max randint values"); - } - - if (max <= min) - { - throw new Exception(@"Random max must be greater than min"); - } - - return (i, param) => (j, row) => (k, c) => Random.Shared.NextInt64(min, max + 1).ToString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("randFrom", newGetter(new[] { "param name", "field internalName", "row selector" }, - "Gives a random value from the cells/fields found using the given param, row selector and field, for each selected value", - paramFieldRowSelector => - { - Param srcParam = ParamBank.PrimaryBank.Params[paramFieldRowSelector[0]]; - List srcRows = RowSearchEngine.rse.Search((ParamBank.PrimaryBank, srcParam), - paramFieldRowSelector[2], false, false); - var values = srcRows.Select((r, i) => r[paramFieldRowSelector[1]].Value.Value).ToArray(); - return (i, param) => - (j, row) => (k, c) => values[Random.Shared.NextInt64(values.Length)].ToString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("paramIndex", newGetter(new string[0], - "Gives an integer for the current selected param, beginning at 0 and increasing by 1 for each param selected", - empty => (i, param) => (j, row) => (k, col) => - { - return i.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("rowIndex", newGetter(new string[0], - "Gives an integer for the current selected row, beginning at 0 and increasing by 1 for each row selected", - empty => (i, param) => (j, row) => (k, col) => - { - return j.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - argumentGetters.Add("fieldIndex", newGetter(new string[0], - "Gives an integer for the current selected cell/field, beginning at 0 and increasing by 1 for each cell/field selected", - empty => (i, param) => (j, row) => (k, col) => - { - return k.ToParamEditorString(); - }, () => CFG.Current.Param_AdvancedMassedit)); - } - - public List<(string, string[])> AllArguments() - { - List<(string, string[])> options = new(); - foreach (var op in argumentGetters.Keys) - { - options.Add((op, argumentGetters[op].args)); - } - - return options; - } - - public List<(string, string, string[])> VisibleArguments() - { - List<(string, string, string[])> options = new(); - foreach (var op in argumentGetters.Keys) - { - OperationArgumentGetter oag = argumentGetters[op]; - if (oag.shouldShow == null || oag.shouldShow()) - { - options.Add((op, oag.wiki, oag.args)); - } - } - - return options; - } - - internal Func>>[] - getContextualArguments(int argumentCount, string opData) - { - var opArgs = opData == null ? new string[0] : opData.Split(':', argumentCount); - var contextualArgs = - new Func>>[opArgs - .Length]; - for (var i = 0; i < opArgs.Length; i++) - { - contextualArgs[i] = getContextualArgument(opArgs[i]); - } - - return contextualArgs; - } - - internal Func>> - getContextualArgument(string opArg) - { - if (opArg.StartsWith('"') && opArg.EndsWith('"')) - { - return (i, p) => (j, r) => (k, c) => opArg.Substring(1, opArg.Length - 2); - } - - if (opArg.StartsWith('$')) - { - opArg = MassParamEdit.massEditVars[opArg.Substring(1)].ToString(); - } - - var arg = opArg.Split(" ", 2); - if (argumentGetters.ContainsKey(arg[0].Trim())) - { - OperationArgumentGetter getter = argumentGetters[arg[0]]; - var opArgArgs = arg.Length > 1 ? arg[1].Split(" ", getter.args.Length) : new string[0]; - if (opArgArgs.Length != getter.args.Length) - { - throw new Exception( - @$"Contextual value {arg[0]} has wrong number of arguments. Expected {opArgArgs.Length}"); - } - - for (var i = 0; i < opArgArgs.Length; i++) - { - if (opArgArgs[i].StartsWith('$')) - { - opArgArgs[i] = MassParamEdit.massEditVars[opArgArgs[i].Substring(1)].ToString(); - } - } - - return getter.func(opArgArgs); - } - - return defaultGetter.func(new[] { opArg }); - } -} - -internal class OperationArgumentGetter -{ - public string[] args; - - internal Func>>> - func; - - internal Func shouldShow; - public string wiki; - - internal OperationArgumentGetter(string[] args, string wiki, - Func>>> - func, Func shouldShow) - { - this.args = args; - this.wiki = wiki; - this.func = func; - this.shouldShow = shouldShow; - } -} diff --git a/src/StudioCore/ParamEditor/ParamBank.cs b/src/StudioCore/ParamEditor/ParamBank.cs index 0065ad7ea..d33fe5d67 100644 --- a/src/StudioCore/ParamEditor/ParamBank.cs +++ b/src/StudioCore/ParamEditor/ParamBank.cs @@ -3,6 +3,7 @@ using Octokit; using SoulsFormats; using StudioCore.Editor; +using StudioCore.Editor.MassEdit; using StudioCore.Platform; using StudioCore.TextEditor; using System; diff --git a/src/StudioCore/ParamEditor/ParamEditorScreen.cs b/src/StudioCore/ParamEditor/ParamEditorScreen.cs index 3b5bb6775..1f424e77c 100644 --- a/src/StudioCore/ParamEditor/ParamEditorScreen.cs +++ b/src/StudioCore/ParamEditor/ParamEditorScreen.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using SoulsFormats; using StudioCore.Editor; +using StudioCore.Editor.MassEdit; using StudioCore.MsbEditor; using StudioCore.Platform; using StudioCore.TextEditor; @@ -839,10 +840,10 @@ public unsafe void OnGUI(string[] initcmd) ParamBank.ClipboardParam = _activeView._selection.GetActiveParam(); foreach (Param.Row row in UICache.GetCached(this, (_activeView._viewIndex, _activeView._selection.GetActiveParam()), - () => RowSearchEngine.rse.Search( + () => SearchEngine.row.Search( (ParamBank.PrimaryBank, ParamBank.PrimaryBank.Params[_activeView._selection.GetActiveParam()]), - _activeView._selection.GetCurrentRowSearchString(), true, true))) + _activeView._selection.GetCurrentRowSearchString(), true, true).Select((x, i) => x.Item2))) { _activeView._selection.AddRowToSelection(row); diff --git a/src/StudioCore/ParamEditor/ParamEditorView.cs b/src/StudioCore/ParamEditor/ParamEditorView.cs index 492b4c6fa..77bf20d61 100644 --- a/src/StudioCore/ParamEditor/ParamEditorView.cs +++ b/src/StudioCore/ParamEditor/ParamEditorView.cs @@ -1,6 +1,7 @@ using Andre.Formats; using static Andre.Native.ImGuiBindings; using StudioCore.Editor; +using StudioCore.Editor.MassEdit; using StudioCore.Platform; using System; using System.Collections.Generic; @@ -178,7 +179,7 @@ private void ParamView_ParamList_Main(bool doFocus, float scale, float scrollTo) List paramKeyList = UICache.GetCached(_paramEditor, _viewIndex, () => { List<(ParamBank, Param)> list = - ParamSearchEngine.pse.Search(true, _selection.currentParamSearchString, true, true); + SearchEngine.param.Search((true, true), _selection.currentParamSearchString, true, true); List keyList = list.Where(param => param.Item1 == ParamBank.PrimaryBank) .Select(param => ParamBank.PrimaryBank.GetKeyForParam(param.Item2)).ToList(); @@ -476,8 +477,8 @@ private void ParamView_RowList(bool doFocus, bool isActiveView, float scrollTo, } List rows = UICache.GetCached(_paramEditor, (_viewIndex, activeParam), - () => RowSearchEngine.rse.Search((ParamBank.PrimaryBank, para), - _selection.GetCurrentRowSearchString(), true, true)); + () => SearchEngine.row.Search((ParamBank.PrimaryBank, para), + _selection.GetCurrentRowSearchString(), true, true).Select((x, i) => x.Item2).ToList()); var enableGrouping = !CFG.Current.Param_DisableRowGrouping && ParamMetaData .Get(ParamBank.PrimaryBank.Params[activeParam].AppliedParamdef).ConsecutiveIDs; diff --git a/src/StudioCore/ParamEditor/ParamIO.cs b/src/StudioCore/ParamEditor/ParamIO.cs index d69e352fd..ac9a73ed1 100644 --- a/src/StudioCore/ParamEditor/ParamIO.cs +++ b/src/StudioCore/ParamEditor/ParamIO.cs @@ -2,6 +2,7 @@ using Andre.Formats; using SoulsFormats; using StudioCore.Editor; +using StudioCore.Editor.MassEdit; using System; using System.Collections.Generic; diff --git a/src/StudioCore/ParamEditor/ParamRowEditor.cs b/src/StudioCore/ParamEditor/ParamRowEditor.cs index 816a1cc0f..596c0752a 100644 --- a/src/StudioCore/ParamEditor/ParamRowEditor.cs +++ b/src/StudioCore/ParamEditor/ParamRowEditor.cs @@ -2,6 +2,7 @@ using static Andre.Native.ImGuiBindings; using SoulsFormats; using StudioCore.Editor; +using StudioCore.Editor.MassEdit; using System; using System.Collections.Generic; using System.Linq; @@ -200,13 +201,12 @@ public void PropEditorParamRow(ParamBank bank, Param.Row row, Param.Row vrow, Li } } } - PropEditorParamRow_RowFields(bank, row, vrow, auxRows, crow, ref imguiId, selection); EditorDecorations.ImguiTableSeparator(); var search = propSearchString; List<(PseudoColumn, Param.Column)> cols = UICache.GetCached(_paramEditor, row, "fieldFilter", - () => CellSearchEngine.cse.Search((activeParam, row), search, true, true)); + () => SearchEngine.cell.Search((activeParam, row), search, true, true).Select(x => (x.Item1, x.Item2)).ToList()); List<(PseudoColumn, Param.Column)> vcols = UICache.GetCached(_paramEditor, vrow, "vFieldFilter", () => cols.Select((x, i) => x.GetAs(ParamBank.VanillaBank.GetParamFromName(activeParam))).ToList()); List> auxCols = UICache.GetCached(_paramEditor, auxRows,