Fix NBT editor paste and delete shortcuts
This commit is contained in:
parent
30f4dc4933
commit
a45c574595
3 changed files with 120 additions and 11 deletions
|
|
@ -0,0 +1,52 @@
|
||||||
|
package dev.quorate.client.nbt;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.TagParser;
|
||||||
|
|
||||||
|
public final class ClipboardParser extends TagParser {
|
||||||
|
private final StringReader input;
|
||||||
|
|
||||||
|
private ClipboardParser(StringReader input) {
|
||||||
|
super(input);
|
||||||
|
this.input = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag parse(String text) throws CommandSyntaxException {
|
||||||
|
StringReader input = new StringReader(text);
|
||||||
|
CompoundTag result = new ClipboardParser(input).readStruct();
|
||||||
|
input.skipWhitespace();
|
||||||
|
if (input.canRead()) throw ERROR_TRAILING_DATA.createWithContext(input);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag readStruct() throws CommandSyntaxException {
|
||||||
|
expect('{');
|
||||||
|
CompoundTag result = new CompoundTag();
|
||||||
|
input.skipWhitespace();
|
||||||
|
while (input.canRead() && input.peek() != '}') {
|
||||||
|
int start = input.getCursor();
|
||||||
|
String key = readKey();
|
||||||
|
if (key.isEmpty() && input.getCursor() == start) {
|
||||||
|
throw ERROR_EXPECTED_KEY.createWithContext(input);
|
||||||
|
}
|
||||||
|
expect(':');
|
||||||
|
result.put(key, readValue());
|
||||||
|
input.skipWhitespace();
|
||||||
|
if (!input.canRead() || input.peek() != ',') break;
|
||||||
|
input.skip();
|
||||||
|
input.skipWhitespace();
|
||||||
|
if (!input.canRead()) throw ERROR_EXPECTED_KEY.createWithContext(input);
|
||||||
|
}
|
||||||
|
expect('}');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expect(char character) throws CommandSyntaxException {
|
||||||
|
input.skipWhitespace();
|
||||||
|
input.expect(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,11 +7,12 @@ import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBT;
|
||||||
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTCollection;
|
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTCollection;
|
||||||
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTMap;
|
import dev.ftb.mods.ftblibrary.nbtedit.NBTEditorScreen.ButtonNBTMap;
|
||||||
import dev.ftb.mods.ftblibrary.ui.input.Key;
|
import dev.ftb.mods.ftblibrary.ui.input.Key;
|
||||||
|
import dev.ftb.mods.ftblibrary.ui.misc.SimpleToast;
|
||||||
|
import dev.quorate.client.nbt.ClipboardParser;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.nbt.Tag;
|
import net.minecraft.nbt.Tag;
|
||||||
import net.minecraft.nbt.TagParser;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
@ -22,6 +23,8 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Mixin(value = NBTEditorScreen.class, remap = false)
|
@Mixin(value = NBTEditorScreen.class, remap = false)
|
||||||
public abstract class EditorMixin {
|
public abstract class EditorMixin {
|
||||||
@Shadow private ButtonNBT selected;
|
@Shadow private ButtonNBT selected;
|
||||||
|
|
@ -33,14 +36,17 @@ public abstract class EditorMixin {
|
||||||
|
|
||||||
@Inject(method = "keyPressed", at = @At("RETURN"), cancellable = true)
|
@Inject(method = "keyPressed", at = @At("RETURN"), cancellable = true)
|
||||||
private void saltatory$keys(Key key, CallbackInfoReturnable<Boolean> result) {
|
private void saltatory$keys(Key key, CallbackInfoReturnable<Boolean> result) {
|
||||||
if (result.getReturnValue() || !key.modifiers.control()) return;
|
if (result.getReturnValue() || (!key.modifiers.control() && !key.is(GLFW.GLFW_KEY_DELETE)))
|
||||||
|
return;
|
||||||
if (key.is(GLFW.GLFW_KEY_C)) {
|
if (key.is(GLFW.GLFW_KEY_C)) {
|
||||||
saltatory$copy();
|
saltatory$copy();
|
||||||
result.setReturnValue(true);
|
result.setReturnValue(true);
|
||||||
} else if (key.is(GLFW.GLFW_KEY_V)) {
|
} else if (key.is(GLFW.GLFW_KEY_V)) {
|
||||||
saltatory$paste();
|
saltatory$paste();
|
||||||
result.setReturnValue(true);
|
result.setReturnValue(true);
|
||||||
} else if (key.is(GLFW.GLFW_KEY_D) || key.is(GLFW.GLFW_KEY_X)) {
|
} else if (key.is(GLFW.GLFW_KEY_DELETE)
|
||||||
|
|| key.is(GLFW.GLFW_KEY_D)
|
||||||
|
|| key.is(GLFW.GLFW_KEY_X)) {
|
||||||
NodeAccess node = (NodeAccess) selected;
|
NodeAccess node = (NodeAccess) selected;
|
||||||
ButtonNBTCollection parent = node.saltatory$parent();
|
ButtonNBTCollection parent = node.saltatory$parent();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
|
|
@ -74,11 +80,10 @@ public abstract class EditorMixin {
|
||||||
if (!(selected instanceof ButtonNBTCollection target)) return;
|
if (!(selected instanceof ButtonNBTCollection target)) return;
|
||||||
CompoundTag copied;
|
CompoundTag copied;
|
||||||
try {
|
try {
|
||||||
copied = TagParser.parseTag(Minecraft.getInstance().keyboardHandler.getClipboard());
|
copied = ClipboardParser.parse(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||||
} catch (CommandSyntaxException e) {
|
} catch (CommandSyntaxException e) {
|
||||||
Minecraft.getInstance()
|
SimpleToast.error(
|
||||||
.player
|
Component.literal("Invalid NBT clipboard"), Component.literal(e.getMessage()));
|
||||||
.displayClientMessage(Component.literal("Invalid NBT clipboard"), true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte elementType = 0;
|
byte elementType = 0;
|
||||||
|
|
@ -88,18 +93,33 @@ public abstract class EditorMixin {
|
||||||
|| (!(target instanceof ButtonNBTMap)
|
|| (!(target instanceof ButtonNBTMap)
|
||||||
&& elementType != 0
|
&& elementType != 0
|
||||||
&& elementType != tag.getId())) {
|
&& elementType != tag.getId())) {
|
||||||
Minecraft.getInstance()
|
SimpleToast.error(
|
||||||
.player
|
Component.literal("NBT type does not fit selection"), Component.empty());
|
||||||
.displayClientMessage(
|
|
||||||
Component.literal("NBT type does not fit selection"), true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
elementType = tag.getId();
|
elementType = tag.getId();
|
||||||
}
|
}
|
||||||
|
Map<String, ButtonNBT> previous = Map.copyOf(target.children);
|
||||||
for (String name : copied.getAllKeys()) {
|
for (String name : copied.getAllKeys()) {
|
||||||
target.setTag(target instanceof ButtonNBTMap ? name : "-1", copied.get(name).copy());
|
target.setTag(target instanceof ButtonNBTMap ? name : "-1", copied.get(name).copy());
|
||||||
}
|
}
|
||||||
target.updateChildren(false);
|
target.updateChildren(false);
|
||||||
|
saltatory$restore(previous, target);
|
||||||
target.getGui().refreshWidgets();
|
target.getGui().refreshWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static void saltatory$restore(
|
||||||
|
Map<String, ButtonNBT> previous, ButtonNBTCollection target) {
|
||||||
|
for (var entry : target.children.entrySet()) {
|
||||||
|
ButtonNBT old = previous.get(entry.getKey());
|
||||||
|
ButtonNBT current = entry.getValue();
|
||||||
|
if (old instanceof ButtonNBTCollection oldTree
|
||||||
|
&& current instanceof ButtonNBTCollection newTree
|
||||||
|
&& old.getClass() == current.getClass()) {
|
||||||
|
newTree.setCollapsed(oldTree.collapsed);
|
||||||
|
saltatory$restore(oldTree.children, newTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.quorate.client.nbt;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.nbt.TagParser;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
final class ClipboardParserTest {
|
||||||
|
private static final String EMPTY_NAMES = "{outer:{args:[{name:'value'},{'':''},{'':''}]}}";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readsEmptyNames() throws CommandSyntaxException {
|
||||||
|
assertThrows(CommandSyntaxException.class, () -> TagParser.parseTag(EMPTY_NAMES));
|
||||||
|
CompoundTag parsed = ClipboardParser.parse(EMPTY_NAMES);
|
||||||
|
var args = parsed.getCompound("outer").getList("args", Tag.TAG_COMPOUND);
|
||||||
|
assertEquals(3, args.size());
|
||||||
|
assertTrue(args.getCompound(1).contains("", Tag.TAG_STRING));
|
||||||
|
assertEquals("", args.getCompound(1).getString(""));
|
||||||
|
assertEquals(parsed, ClipboardParser.parse(parsed.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keepsNormalSyntax() throws CommandSyntaxException {
|
||||||
|
String text = "{a:[1,2,3],b:{nested:'value'},c:[B;1b,2b]}";
|
||||||
|
assertEquals(TagParser.parseTag(text), ClipboardParser.parse(text));
|
||||||
|
for (String invalid : new String[] {"{:1}", "{a:1} trailing", "{a:[1,2b]}", "{a:1,"}) {
|
||||||
|
assertThrows(CommandSyntaxException.class, () -> ClipboardParser.parse(invalid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue