From 041686709d17ddf4fbe0cb85e9d2750a48b9495d Mon Sep 17 00:00:00 2001 From: rttv Date: Mon, 18 May 2026 22:18:41 -0400 Subject: [PATCH] squashed --- .../clientcommands/command/Flag.java | 64 +++++++--- .../command/arguments/ExcludingArgument.java | 116 ++++++++++++++++++ .../flag/ArgumentCommandNodeAccessor.java | 19 +++ .../assets/clientcommands/lang/en_us.json | 1 + src/main/resources/mixins.clientcommands.json | 1 + 5 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/arguments/ExcludingArgument.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/mixin/commands/flag/ArgumentCommandNodeAccessor.java diff --git a/src/main/java/net/earthcomputer/clientcommands/command/Flag.java b/src/main/java/net/earthcomputer/clientcommands/command/Flag.java index 1f625bec..28098453 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/Flag.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/Flag.java @@ -7,7 +7,11 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; +import net.earthcomputer.clientcommands.command.arguments.ExcludingArgument; +import net.earthcomputer.clientcommands.mixin.commands.flag.ArgumentCommandNodeAccessor; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.Minecraft; import org.jspecify.annotations.Nullable; @@ -68,31 +72,51 @@ public boolean isRepeatable() { return repeatable; } - public void addToCommand(CommandDispatcher dispatcher, LiteralCommandNode commandNode, Function, T> value) { - dispatcher.register(commandNode.createBuilder() - .then(literal(getFlag()) - .redirect(commandNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, value.apply(ctx))) - .executes(commandNode.getCommand()))); + private void addExclusion(LiteralCommandNode baseNode) { + Collection> children = baseNode.getChildren(); + for (CommandNode childDowncasted : children) { + if (childDowncasted instanceof ArgumentCommandNodeAccessor child) { + ArgumentType argument = child.getArgumentType(); + if (argument instanceof ExcludingArgument excluding) { + excluding.exclusions.add(ExcludingArgument.Exclusion.forFlag(this)); + } else { + ArgumentType excluding = ExcludingArgument.excluding(argument, ExcludingArgument.Exclusion.forFlag(this)); + child.setArgumentType(excluding); + } + } + } + } + + private void addLiteralFlagToCommand(String literal, CommandDispatcher dispatcher, LiteralCommandNode baseNode, Function, T> function) { + dispatcher.register(baseNode.createBuilder() + .then(literal(literal) + .redirect(baseNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, function.apply(ctx))) + .executes(baseNode.getCommand()))); + } + + private void addArgumentFlagToCommand(String literal, ArgumentType argument, CommandDispatcher dispatcher, LiteralCommandNode baseNode) { + dispatcher.register(baseNode.createBuilder() + .then(literal(literal) + .then(argument(this.name, argument) + .redirect(baseNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, ctx.getArgument(this.name, this.type))) + .executes(baseNode.getCommand())))); + } + + public void addToCommand(CommandDispatcher dispatcher, LiteralCommandNode baseNode, Function, T> function) { + addExclusion(baseNode); + + addLiteralFlagToCommand(getFlag(), dispatcher, baseNode, function); if (shortName != null) { - dispatcher.register(commandNode.createBuilder() - .then(literal(getShortFlag()) - .redirect(commandNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, value.apply(ctx))) - .executes(commandNode.getCommand()))); + addLiteralFlagToCommand(getShortFlag(), dispatcher, baseNode, function); } } - public void addToCommandWithArg(CommandDispatcher dispatcher, LiteralCommandNode commandNode, ArgumentType argument) { - dispatcher.register(commandNode.createBuilder() - .then(literal(getFlag()) - .then(argument(this.name, argument) - .redirect(commandNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, ctx.getArgument(this.name, this.type))) - .executes(commandNode.getCommand())))); + public void addToCommandWithArg(CommandDispatcher dispatcher, LiteralCommandNode baseNode, ArgumentType argument) { + addExclusion(baseNode); + + addArgumentFlagToCommand(getFlag(), argument, dispatcher, baseNode); if (shortName != null) { - dispatcher.register(commandNode.createBuilder() - .then(literal(getShortFlag()) - .then(argument(this.name, argument) - .redirect(commandNode, ctx -> ClientCommandHelper.withFlag(ctx.getSource(), this, ctx.getArgument(this.name, this.type))) - .executes(commandNode.getCommand())))); + addArgumentFlagToCommand(getShortFlag(), argument, dispatcher, baseNode); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/ExcludingArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/ExcludingArgument.java new file mode 100644 index 00000000..42afcfec --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/ExcludingArgument.java @@ -0,0 +1,116 @@ +package net.earthcomputer.clientcommands.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.earthcomputer.clientcommands.command.Flag; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class ExcludingArgument> implements ArgumentType { + private static final SimpleCommandExceptionType MATCHED_EXCLUSION_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.client.excluding")); + + public final U argument; + public final ArrayList exclusions; + @Nullable + private List cachedExamples = null; + + public static > ExcludingArgument excluding(U argument, ArgumentType... exclusions) { + return new ExcludingArgument<>(argument, Arrays.stream(exclusions).map(Exclusion::forArgument).toList()); + } + + public static > ExcludingArgument excluding(U argument, Flag... exclusions) { + return new ExcludingArgument<>(argument, Arrays.stream(exclusions).map(Exclusion::forFlag).toList()); + } + + public static > ExcludingArgument excluding(U argument, Exclusion... exclusions) { + return new ExcludingArgument<>(argument, List.of(exclusions)); + } + + private ExcludingArgument(U argument, List exclusions) { + this.argument = argument; + this.exclusions = new ArrayList<>(exclusions); + } + + @Override + public Collection getExamples() { + if (cachedExamples == null) { + cachedExamples = argument.getExamples() + .stream() + .filter(example -> !isExcluded(new StringReader(example))) + .toList(); + } + + return cachedExamples; + } + + private boolean isExcluded(StringReader reader) { + int cursor = reader.getCursor(); + for (Exclusion exclusion : exclusions) { + boolean result = exclusion.canParse(reader); + reader.setCursor(cursor); + + if (result) { + return true; + } + } + + return false; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return argument.listSuggestions(context, builder); + } + + @Override + public T parse(StringReader reader) throws CommandSyntaxException { + int cursorStart = reader.getCursor(); + T result = argument.parse(reader); + int cursorEnd = reader.getCursor(); + + reader.setCursor(cursorStart); + if (isExcluded(reader)) { + throw MATCHED_EXCLUSION_EXCEPTION.create(); + } + + reader.setCursor(cursorEnd); + return result; + } + + public interface Exclusion { + boolean canParse(StringReader reader); + + static Exclusion forArgument(ArgumentType argumentType) { + return reader -> { + try { + argumentType.parse(reader); + return true; + } catch (CommandSyntaxException ignored) { + return false; + } + }; + } + + static Exclusion forFlag(Flag flag) { + return reader -> { + String str = reader.readUnquotedString(); + return str.equals(flag.getShortFlag()) || str.equals(flag.getFlag()); + }; + } + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/flag/ArgumentCommandNodeAccessor.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/flag/ArgumentCommandNodeAccessor.java new file mode 100644 index 00000000..c88ce3fe --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/flag/ArgumentCommandNodeAccessor.java @@ -0,0 +1,19 @@ +package net.earthcomputer.clientcommands.mixin.commands.flag; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ArgumentCommandNode.class) +public interface ArgumentCommandNodeAccessor { + @Accessor("type") + ArgumentType getArgumentType(); + + @Accessor("type") + @Mutable + @Final + void setArgumentType(ArgumentType type); +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 910da5cf..300322c8 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -208,6 +208,7 @@ "commands.client.componentTooDeeplyNested": "Chat component too deeply nested", "commands.client.crack": "Crack", "commands.client.enable": "Enable", + "commands.client.excluding": "Invalid argument; excluded", "commands.client.expectedRegex": "Invalid regex %s", "commands.client.glow": "[Glow]", "commands.client.invalidArgumentException": "Invalid argument '%s'", diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index d1743a5d..ebdd5e54 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -8,6 +8,7 @@ "commands.fish.EntityMixin", "commands.fish.FishingHookMixin", "commands.fish.FishingRodItemMixin", + "commands.flag.ArgumentCommandNodeAccessor", "commands.generic.ChatScreenMixin", "commands.generic.ClientSuggestionsProviderMixin", "commands.glow.ArmorStandRendererMixin",