Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 44 additions & 20 deletions src/main/java/net/earthcomputer/clientcommands/command/Flag.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,31 +72,51 @@ public boolean isRepeatable() {
return repeatable;
}

public void addToCommand(CommandDispatcher<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> commandNode, Function<CommandContext<FabricClientCommandSource>, 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<FabricClientCommandSource> baseNode) {
Collection<CommandNode<FabricClientCommandSource>> children = baseNode.getChildren();
for (CommandNode<FabricClientCommandSource> 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<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> baseNode, Function<CommandContext<FabricClientCommandSource>, 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<T> argument, CommandDispatcher<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> 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<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> baseNode, Function<CommandContext<FabricClientCommandSource>, 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<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> commandNode, ArgumentType<T> 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<FabricClientCommandSource> dispatcher, LiteralCommandNode<FabricClientCommandSource> baseNode, ArgumentType<T> 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);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<T, U extends ArgumentType<T>> implements ArgumentType<T> {
private static final SimpleCommandExceptionType MATCHED_EXCLUSION_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.client.excluding"));

public final U argument;
public final ArrayList<Exclusion> exclusions;
@Nullable
private List<String> cachedExamples = null;

public static <T, U extends ArgumentType<T>> ExcludingArgument<T, U> excluding(U argument, ArgumentType<?>... exclusions) {
return new ExcludingArgument<>(argument, Arrays.stream(exclusions).map(Exclusion::forArgument).toList());
}

public static <T, U extends ArgumentType<T>> ExcludingArgument<T, U> excluding(U argument, Flag<?>... exclusions) {
return new ExcludingArgument<>(argument, Arrays.stream(exclusions).map(Exclusion::forFlag).toList());
}

public static <T, U extends ArgumentType<T>> ExcludingArgument<T, U> excluding(U argument, Exclusion... exclusions) {
return new ExcludingArgument<>(argument, List.of(exclusions));
}

private ExcludingArgument(U argument, List<Exclusion> exclusions) {
this.argument = argument;
this.exclusions = new ArrayList<>(exclusions);
}

@Override
public Collection<String> 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 <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> 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());
};
}
}
}
Original file line number Diff line number Diff line change
@@ -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<T> {
@Accessor("type")
ArgumentType<T> getArgumentType();

@Accessor("type")
@Mutable
@Final
void setArgumentType(ArgumentType<?> type);
}
1 change: 1 addition & 0 deletions src/main/resources/assets/clientcommands/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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'",
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/mixins.clientcommands.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading