This must be kept in sync with the wrapping implementation in {@code cloud-brigadier}
+ */ +@Mixin(value = CloudStringReader.class, remap = false) +public class CloudStringReaderMixin implements StringReaderAsQueue { + + private int cloud$nextSpaceIdx; /* the character before the start of a new word */ + private @Nullable String cloud$nextWord; + + /* Next whitespace index starting at startIdx, or -1 if none is found */ + static int cloud$nextWhitespace(final String input, final int startIdx) { + for (int i = startIdx, length = input.length(); i < length; ++i) { + if (Character.isWhitespace(input.charAt(i))) { + return i; + } + } + return -1; + } + + @Override + public StringReader getOriginal() { + return (StringReader) (Object) this; + } + + /* Brigadier doesn't automatically consume whitespace... in order to get the matched behaviour, we consume whitespace + * after every popped string. + */ + private void cloud$advance() { + final int startOfNextWord = this.cloud$nextSpaceIdx + 1; + this.cloud$nextSpaceIdx = cloud$nextWhitespace(((StringReader) (Object) this).getString(), startOfNextWord); + if (this.cloud$nextSpaceIdx != -1) { + this.cloud$nextWord = ((StringReader) (Object) this).getString().substring(startOfNextWord, this.cloud$nextSpaceIdx); + } else if (startOfNextWord < ((StringReader) (Object) this).getTotalLength()) { + this.cloud$nextWord = ((StringReader) (Object) this).getString().substring(startOfNextWord); + this.cloud$nextSpaceIdx = ((StringReader) (Object) this).getTotalLength() + 1; + } else { + this.cloud$nextWord = null; + } + ((StringReader) (Object) this).setCursor(startOfNextWord); + } + + @Override + public String poll() { + /* peek and then advance */ + final String next = this.peek(); + if (next != null) { + this.cloud$advance(); + } + return next; + } + + @Override + public String peek() { + return this.cloud$nextWord; + } + + @Override + public int size() { + if (this.cloud$nextWord == null) { + return 0; + } + int counter = 1; + for (int i = this.cloud$nextSpaceIdx; + i != -1 && i < ((StringReader) (Object) this).getTotalLength(); + i = cloud$nextWhitespace(((StringReader) (Object) this).getString(), i + 1)) { + counter++; + } + return counter; + } + + @Override + public boolean remove(final Object o) { + if (Objects.equals(o, this.cloud$nextWord)) { + this.cloud$advance(); + return true; + } + return false; + } + + @Override + public void clear() { + StringReaderAsQueue.super.clear(); + this.cloud$nextWord = null; + this.cloud$nextSpaceIdx = -1; + } +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java new file mode 100644 index 00000000..49b37976 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java @@ -0,0 +1,43 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +package cloud.commandframework.fabric.mixin; + +import cloud.commandframework.fabric.internal.CloudStringReader; +import com.mojang.brigadier.StringReader; +import net.minecraft.server.command.CommandManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(CommandManager.class) +public class CommandManagerMixin { + + /* Use our StringReader. This is technically optional, but it's nicer to avoid re-wrapping the object every time */ + @Redirect(method = "execute", at = @At(value = "NEW", target = "com/mojang/brigadier/StringReader", remap = false), require = 0) + private StringReader cloud$newStringReader(final String arguments) { + return new CloudStringReader(arguments); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json b/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json new file mode 100644 index 00000000..e7d1ae12 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json @@ -0,0 +1,12 @@ +{ + "package": "cloud.commandframework.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "required": true, + "mixins": [ + "CloudStringReaderMixin", + "CommandManagerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json index b332db0a..de9d621f 100644 --- a/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json +++ b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json @@ -5,7 +5,7 @@ "name": "Cloud", "description": "Command framework and dispatcher for the JVM", - "authors": [ "Alexander Söderberg" ], + "authors": [ "Citomonstret", "zml" ], "contact": { "homepage": "https://commandframework.cloud/", "sources": "https://github.com/Incendo/cloud" diff --git a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java index f255161f..a4403bc8 100644 --- a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java @@ -24,16 +24,34 @@ package cloud.commandframework.fabric.testmod; +import cloud.commandframework.Command; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.fabric.FabricCommandManager; +import cloud.commandframework.meta.CommandMeta; +import com.google.gson.JsonObject; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import com.mojang.brigadier.CommandDispatcher; import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.ArgumentTypes; +import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.ClickEvent; import net.minecraft.text.LiteralText; import net.minecraft.text.TextColor; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; + public final class FabricExample implements ModInitializer { private static final CommandArgument