Skip to content

Commit b7adc7c

Browse files
committed
Fix & update Previewable arguments
- Removed `CommandAPIHandler#previewableArguments` and related methods - Added `PreviewableCommandNode` for storing `Previewable` information directly in Brigadier's tree - Tweak `NMS_1_19_Common_ChatPreviewHandler` to build previews from the node tree rather than by the node path TODO: Should probably test these changes on a real server to verify. Also, another example of Mojang/brigadier#144 being annoying.
1 parent 5d6e054 commit b7adc7c

File tree

6 files changed

+228
-108
lines changed

6 files changed

+228
-108
lines changed

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import dev.jorel.commandapi.executors.CommandArguments;
5050
import dev.jorel.commandapi.executors.ExecutionInfo;
5151
import dev.jorel.commandapi.preprocessor.RequireField;
52-
import dev.jorel.commandapi.wrappers.PreviewableFunction;
5352

5453
import java.util.concurrent.CompletableFuture;
5554

@@ -93,7 +92,6 @@ public class CommandAPIHandler<Argument
9392

9493
final CommandAPIPlatform<Argument, CommandSender, Source> platform;
9594
final Map<String, RegisteredCommand> registeredCommands; // Keep track of what has been registered for type checking
96-
final Map<List<String>, Previewable<?, ?>> previewableArguments; // Arguments with previewable chat
9795
static final Pattern NAMESPACE_PATTERN = Pattern.compile("[0-9a-z_.-]+");
9896

9997
private static CommandAPIHandler<?, ?, ?> instance;
@@ -105,7 +103,6 @@ public class CommandAPIHandler<Argument
105103
protected CommandAPIHandler(CommandAPIPlatform<Argument, CommandSender, Source> platform) {
106104
this.platform = platform;
107105
this.registeredCommands = new LinkedHashMap<>(); // This should be a LinkedHashMap to preserve insertion order
108-
this.previewableArguments = new HashMap<>();
109106

110107
CommandAPIHandler.instance = this;
111108
}
@@ -629,78 +626,6 @@ public Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument
629626
}
630627
}
631628

632-
////////////////////////////////////
633-
// SECTION: Previewable Arguments //
634-
////////////////////////////////////
635-
636-
/**
637-
* Handles a previewable argument. This stores the path to the previewable argument
638-
* in {@link CommandAPIHandler#previewableArguments} for runtime resolving
639-
*
640-
* @param previousArguments The list of arguments that came before this argument
641-
* @param previewableArgument The {@link Previewable} argument
642-
*/
643-
public void addPreviewableArgument(List<Argument> previousArguments, Argument previewableArgument) {
644-
if (!(previewableArgument instanceof Previewable<?, ?> previewable)) {
645-
throw new IllegalArgumentException("An argument must implement Previewable to be added as previewable argument");
646-
}
647-
648-
// Generate all paths to the argument
649-
List<List<String>> paths = new ArrayList<>();
650-
paths.add(new ArrayList<>());
651-
652-
// TODO: Fix this, the `appendToCommandPaths` method was removed
653-
// A smarter way to get this information should exist
654-
// It probably makes sense to make a custom CommandNode for PreviewableArgument
655-
if(true) throw new IllegalStateException("TODO: Fix this method");
656-
657-
// for (Argument argument : previousArguments) {
658-
// argument.appendToCommandPaths(paths);
659-
// }
660-
// previewableArgument.appendToCommandPaths(paths);
661-
662-
// Insert paths to our map
663-
for (List<String> path : paths) {
664-
previewableArguments.put(path, previewable);
665-
}
666-
}
667-
668-
/**
669-
* Looks up the function to generate a chat preview for a path of nodes in the
670-
* command tree. This is a method internal to the CommandAPI and isn't expected
671-
* to be used by plugin developers (but you're more than welcome to use it as
672-
* you see fit).
673-
*
674-
* @param path a list of Strings representing the path (names of command nodes)
675-
* to (and including) the previewable argument
676-
* @return a {@link PreviewableFunction} that takes in a {@link PreviewInfo} and returns a
677-
* text Component. If such a function is not available, this will
678-
* return a function that always returns null.
679-
*/
680-
@SuppressWarnings("unchecked")
681-
public Optional<PreviewableFunction<?>> lookupPreviewable(List<String> path) {
682-
final Previewable<?, ?> previewable = previewableArguments.get(path);
683-
if (previewable != null) {
684-
return (Optional<PreviewableFunction<?>>) (Optional<?>) previewable.getPreview();
685-
} else {
686-
return Optional.empty();
687-
}
688-
}
689-
690-
/**
691-
* @param path a list of Strings representing the path (names of command nodes)
692-
* to (and including) the previewable argument
693-
* @return Whether a previewable is legacy (non-Adventure) or not
694-
*/
695-
public boolean lookupPreviewableLegacyStatus(List<String> path) {
696-
final Previewable<?, ?> previewable = previewableArguments.get(path);
697-
if (previewable != null && previewable.getPreview().isPresent()) {
698-
return previewable.isLegacy();
699-
} else {
700-
return true;
701-
}
702-
}
703-
704629
/////////////////////////
705630
// SECTION: Reflection //
706631
/////////////////////////

commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
2727
import com.mojang.brigadier.context.CommandContext;
2828
import com.mojang.brigadier.exceptions.CommandSyntaxException;
29+
import com.mojang.brigadier.suggestion.SuggestionProvider;
2930
import com.mojang.brigadier.tree.CommandNode;
3031
import dev.jorel.commandapi.AbstractArgumentTree;
3132
import dev.jorel.commandapi.CommandAPIHandler;
3233
import dev.jorel.commandapi.CommandPermission;
3334
import dev.jorel.commandapi.RegisteredCommand;
35+
import dev.jorel.commandapi.commandnodes.PreviewableArgumentBuilder;
3436
import dev.jorel.commandapi.commandnodes.UnnamedRequiredArgumentBuilder;
3537
import dev.jorel.commandapi.exceptions.DuplicateNodeNameException;
3638
import dev.jorel.commandapi.exceptions.GreedyArgumentException;
@@ -371,16 +373,9 @@ public <Source> NodeInformation<Source> addArgumentNodes(
371373
List<Argument> previousArguments, List<String> previousArgumentNames,
372374
Function<List<Argument>, Command<Source>> terminalExecutorCreator
373375
) {
374-
CommandAPIHandler<Argument, CommandSender, Source> handler = CommandAPIHandler.getInstance();
375-
376376
// Check preconditions
377377
checkPreconditions(previousNodeInformation, previousArguments, previousArgumentNames);
378378

379-
// Handle previewable argument
380-
if (this instanceof Previewable<?, ?>) {
381-
handler.addPreviewableArgument(previousArguments, (Argument) this);
382-
}
383-
384379
// Create node
385380
ArgumentBuilder<Source, ?> rootBuilder = createArgumentBuilder(previousArguments, previousArgumentNames);
386381

@@ -426,19 +421,29 @@ public <Source> void checkPreconditions(
426421
CommandAPIHandler<Argument, CommandSender, Source> handler = CommandAPIHandler.getInstance();
427422

428423
// Create node and add suggestions
429-
// Note: I would like to combine these two `build.suggests(...)` calls, but they are actually two unrelated
430-
// methods since UnnamedRequiredArgumentBuilder does not extend RequiredArgumentBuilder (see
431-
// UnnamedRequiredArgumentBuilder for why). If UnnamedRequiredArgumentBuilder *does* extend
432-
// RequiredArgumentBuilder, please simplify this if statement, like what Literal#createArgumentBuilder does.
424+
// Note: I would like to combine these `builder.suggests(...)` calls, but they are actually unrelated
425+
// methods since UnnamedRequiredArgumentBuilder and PreviewableArgumentBuilder do not extend RequiredArgumentBuilder
426+
// (see those classes for why). If this has been fixed and they do extend RequiredArgumentBuilder, please simplify
427+
// this if statement, like what Literal#createArgumentBuilder does.
428+
SuggestionProvider<Source> suggestions = handler.generateBrigadierSuggestions(previousArguments, (Argument) this);
433429
ArgumentBuilder<Source, ?> rootBuilder;
434-
if(isListed) {
430+
if (this instanceof Previewable<?, ?> previewable) {
431+
// Handle previewable argument
432+
PreviewableArgumentBuilder<Source, ?> builder = PreviewableArgumentBuilder.previewableArgument(
433+
nodeName, rawType,
434+
previewable.getPreview().orElse(null), previewable.isLegacy(), isListed
435+
);
436+
builder.suggests(suggestions);
437+
438+
rootBuilder = builder;
439+
} else if (isListed) {
435440
RequiredArgumentBuilder<Source, ?> builder = RequiredArgumentBuilder.argument(nodeName, rawType);
436-
builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this));
441+
builder.suggests(suggestions);
437442

438443
rootBuilder = builder;
439444
} else {
440445
UnnamedRequiredArgumentBuilder<Source, ?> builder = UnnamedRequiredArgumentBuilder.unnamedArgument(nodeName, rawType);
441-
builder.suggests(handler.generateBrigadierSuggestions(previousArguments, (Argument) this));
446+
builder.suggests(suggestions);
442447

443448
rootBuilder = builder;
444449
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package dev.jorel.commandapi.commandnodes;
2+
3+
import com.mojang.brigadier.arguments.ArgumentType;
4+
import com.mojang.brigadier.builder.ArgumentBuilder;
5+
import com.mojang.brigadier.suggestion.SuggestionProvider;
6+
import com.mojang.brigadier.tree.CommandNode;
7+
8+
import dev.jorel.commandapi.arguments.Previewable;
9+
import dev.jorel.commandapi.wrappers.PreviewableFunction;
10+
11+
/**
12+
* A special type of {@link RequiredArgumentBuilder} for {@link Previewable} Arguments. Compared to the
13+
* {@link RequiredArgumentBuilder}, this class builds a {@link PreviewableCommandNode}
14+
*
15+
* @param <Source> The Brigadier Source object for running commands.
16+
* @param <T> The type returned when this argument is parsed.
17+
*/
18+
// We can't actually extend RequiredArgumentBuilder since its only constructor is private :(
19+
// See https://github.com/Mojang/brigadier/pull/144
20+
public class PreviewableArgumentBuilder<Source, T> extends ArgumentBuilder<Source, PreviewableArgumentBuilder<Source, T>> {
21+
// Everything here is copied from RequiredArgumentBuilder, which is why it would be nice to extend that directly
22+
private final String name;
23+
private final ArgumentType<T> type;
24+
private SuggestionProvider<Source> suggestionsProvider = null;
25+
26+
// `Previewable` information
27+
private final PreviewableFunction<?> previewableFunction;
28+
private final boolean legacy;
29+
private final boolean isListed;
30+
31+
private PreviewableArgumentBuilder(String name, ArgumentType<T> type, PreviewableFunction<?> previewableFunction, boolean legacy, boolean isListed) {
32+
this.name = name;
33+
this.type = type;
34+
35+
this.previewableFunction = previewableFunction;
36+
this.legacy = legacy;
37+
this.isListed = isListed;
38+
}
39+
40+
public static <Source, T> PreviewableArgumentBuilder<Source, T> previewableArgument(String name, ArgumentType<T> type, PreviewableFunction<?> previewableFunction, boolean legacy, boolean isListed) {
41+
return new PreviewableArgumentBuilder<>(name, type, previewableFunction, legacy, isListed);
42+
}
43+
44+
public PreviewableArgumentBuilder<Source, T> suggests(final SuggestionProvider<Source> provider) {
45+
this.suggestionsProvider = provider;
46+
return getThis();
47+
}
48+
49+
public SuggestionProvider<Source> getSuggestionsProvider() {
50+
return suggestionsProvider;
51+
}
52+
53+
@Override
54+
protected PreviewableArgumentBuilder<Source, T> getThis() {
55+
return this;
56+
}
57+
58+
public ArgumentType<T> getType() {
59+
return type;
60+
}
61+
62+
public String getName() {
63+
return name;
64+
}
65+
66+
public PreviewableCommandNode<Source, T> build() {
67+
final PreviewableCommandNode<Source, T> result = new PreviewableCommandNode<Source, T>(
68+
previewableFunction, legacy, isListed,
69+
getName(), getType(),
70+
getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider()
71+
);
72+
73+
for (final CommandNode<Source> argument : getArguments()) {
74+
result.addChild(argument);
75+
}
76+
77+
return result;
78+
}
79+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package dev.jorel.commandapi.commandnodes;
2+
3+
import java.util.Objects;
4+
import java.util.Optional;
5+
import java.util.function.Predicate;
6+
7+
import com.mojang.brigadier.Command;
8+
import com.mojang.brigadier.RedirectModifier;
9+
import com.mojang.brigadier.StringReader;
10+
import com.mojang.brigadier.arguments.ArgumentType;
11+
import com.mojang.brigadier.context.CommandContextBuilder;
12+
import com.mojang.brigadier.context.ParsedArgument;
13+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
14+
import com.mojang.brigadier.suggestion.SuggestionProvider;
15+
import com.mojang.brigadier.tree.ArgumentCommandNode;
16+
import com.mojang.brigadier.tree.CommandNode;
17+
18+
import dev.jorel.commandapi.arguments.Previewable;
19+
import dev.jorel.commandapi.wrappers.PreviewableFunction;
20+
21+
/**
22+
* A special type of {@link ArgumentCommandNode} for {@link Previewable} arguments. Compared to the
23+
* {@link ArgumentCommandNode}, this class also has the methods {@link #getPreview()} and {@link #isLegacy()},
24+
* which are used when players try to use the chat preview feature.
25+
*
26+
* @param <Source> The Brigadier Source object for running commands.
27+
* @param <T> The type returned when this argument is parsed.
28+
*/
29+
public class PreviewableCommandNode<Source, T> extends ArgumentCommandNode<Source, T> {
30+
private final PreviewableFunction<?> preview;
31+
private final boolean legacy;
32+
33+
// Instead of having a listed and unlisted copy of this class, we can just handle this with this boolean
34+
private final boolean isListed;
35+
36+
public PreviewableCommandNode(
37+
PreviewableFunction<?> preview, boolean legacy, boolean isListed,
38+
String name, ArgumentType<T> type,
39+
Command<Source> command, Predicate<Source> requirement, CommandNode<Source> redirect, RedirectModifier<Source> modifier, boolean forks, SuggestionProvider<Source> customSuggestions
40+
) {
41+
super(name, type, command, requirement, redirect, modifier, forks, customSuggestions);
42+
this.preview = preview;
43+
this.legacy = legacy;
44+
this.isListed = isListed;
45+
}
46+
47+
// Methods needed to generate a preview
48+
public Optional<PreviewableFunction<?>> getPreview() {
49+
return Optional.ofNullable(preview);
50+
}
51+
52+
public boolean isLegacy() {
53+
return legacy;
54+
}
55+
56+
// If we are unlisted, then when parsed, don't add the argument result to the CommandContext
57+
public boolean isListed() {
58+
return isListed;
59+
}
60+
61+
@Override
62+
public void parse(StringReader reader, CommandContextBuilder<Source> contextBuilder) throws CommandSyntaxException {
63+
// Copied from `super#parse`, but with listability added
64+
int start = reader.getCursor();
65+
66+
T result = this.getType().parse(reader);
67+
ParsedArgument<Source, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
68+
69+
if(isListed) contextBuilder.withArgument(this.getName(), parsed);
70+
71+
contextBuilder.withNode(this, parsed.getRange());
72+
}
73+
74+
// Typical ArgumentCommandNode methods, but make it our classes
75+
// Mostly copied and inspired by the implementations for these methods in ArgumentCommandNode
76+
@Override
77+
public boolean equals(Object obj) {
78+
if (this == obj) return true;
79+
if (!(obj instanceof PreviewableCommandNode<?, ?> other)) return false;
80+
81+
if (!Objects.equals(this.preview, other.preview)) return false;
82+
if (this.legacy != other.legacy) return false;
83+
if (this.isListed != other.isListed) return false;
84+
return super.equals(other);
85+
}
86+
87+
@Override
88+
public int hashCode() {
89+
int result = Objects.hash(this.preview, this.legacy, this.isListed);
90+
result = 31*result + super.hashCode();
91+
return result;
92+
}
93+
94+
// TODO: Um, this currently doesn't work since PreviewableArgumentBuilder does not extend RequiredArgumentBuilder
95+
// See PreviewableArgumentBuilder for why
96+
// I hope no one tries to use this method!
97+
// @Override
98+
// public PreviewableArgumentBuilder<Source, T> createBuilder() {
99+
// PreviewableArgumentBuilder<Source, T> builder = PreviewableArgumentBuilder.previewableArgument(getName(), getType(), preview, legacy, isListed);
100+
101+
// builder.requires(getRequirement());
102+
// builder.forward(getRedirect(), getRedirectModifier(), isFork());
103+
// if (getCommand() != null) {
104+
// builder.executes(getCommand());
105+
// }
106+
// return builder;
107+
// }
108+
}

commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/UnnamedArgumentCommandNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public UnnamedArgumentCommandNode(String name, ArgumentType<T> type, Command<Sou
2626
}
2727

2828
// A UnnamedArgumentCommandNode is mostly identical to a ArgumentCommandNode
29-
// The only difference is that when a UnnamedArgument is parsed, it does not add its result to the CommandContext
29+
// The only difference is that when an UnnamedArgument is parsed, it does not add its argument result to the CommandContext
3030
@Override
3131
public void parse(StringReader reader, CommandContextBuilder<Source> contextBuilder) throws CommandSyntaxException {
3232
final int start = reader.getCursor();

0 commit comments

Comments
 (0)