diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java index e2de05d0..240def97 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandCompletions.java @@ -23,6 +23,7 @@ package co.aikar.commands; +import co.aikar.commands.bukkit.contexts.OnlinePlayer; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -65,7 +66,7 @@ public class BukkitCommandCompletions extends CommandCompletions ACFUtil.enumNames(DyeColor.values())); registerCompletion("worlds", c -> ( - Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()) + Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList()) )); registerCompletion("players", c -> { @@ -86,6 +87,9 @@ public class BukkitCommandCompletions extends CommandCompletions completeCommand(CommandIssuer issuer, RegisteredCommand cmd, String[] args, String commandLabel, boolean isAsync) { - if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0 || cmd.complete == null) { + if (!cmd.hasPermission(issuer) || args.length > cmd.consumeInputResolvers || args.length == 0) { return Collections.emptyList(); } diff --git a/core/src/main/java/co/aikar/commands/CommandCompletions.java b/core/src/main/java/co/aikar/commands/CommandCompletions.java index 18fb87ee..0325abcc 100644 --- a/core/src/main/java/co/aikar/commands/CommandCompletions.java +++ b/core/src/main/java/co/aikar/commands/CommandCompletions.java @@ -39,7 +39,9 @@ import java.util.stream.IntStream; @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"}) public class CommandCompletions { + private static final String DEFAULT_ENUM_ID = "@__defaultenum__"; private final CommandManager manager; + // TODO: use a CompletionProvider that can return a delegated Id or provide values such as enum support private Map completionMap = new HashMap<>(); private Map defaultCompletions = new HashMap<>(); @@ -75,7 +77,7 @@ public class CommandCompletions { * @return */ public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler handler) { - return this.completionMap.put("@" + id.toLowerCase(), handler); + return this.completionMap.put(prepareCompletionId(id), handler); } /** @@ -94,7 +96,7 @@ public class CommandCompletions { * @return */ public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler handler) { - return this.completionMap.put("@" + id.toLowerCase(), handler); + return this.completionMap.put(prepareCompletionId(id), handler); } /** @@ -146,14 +148,16 @@ public class CommandCompletions { } /** + * Registers a completion handler such as @players to default apply to all command parameters of the specified types + *

+ * This enables automatic completion support for parameters without manually defining it for custom objects + * * @param id * @param classes - * @return - * @deprecated Feature Not done yet */ - CommandCompletionHandler setDefaultCompletion(String id, Class... classes) { + public void setDefaultCompletion(String id, Class... classes) { // get completion with specified id - id = id.toLowerCase(); + id = prepareCompletionId(id); CommandCompletionHandler completion = completionMap.get(id); if (completion == null) { @@ -164,8 +168,11 @@ public class CommandCompletions { for (Class clazz : classes) { defaultCompletions.put(clazz, id); } + } - return completion; + @NotNull + private static String prepareCompletionId(String id) { + return (id.startsWith("@") ? "" : "@") + id.toLowerCase(); } @NotNull @@ -176,6 +183,10 @@ public class CommandCompletions { String input = args[argIndex]; String completion = argIndex < completions.length ? completions[argIndex] : null; + if (completion == null || "*".equals(completion)) { + completion = findDefaultCompletion(cmd, args); + } + if (completion == null && completions.length > 0) { String last = completions[completions.length - 1]; if (last.startsWith("repeat@")) { @@ -190,7 +201,35 @@ public class CommandCompletions { return getCompletionValues(cmd, sender, completion, args, isAsync); } + String findDefaultCompletion(RegisteredCommand cmd, String[] args) { + int i = 0; + for (CommandParameter param : cmd.parameters) { + if (param.canConsumeInput() && ++i == args.length) { + Class type = param.getType(); + while (type != null) { + String completion = this.defaultCompletions.get(type); + if (completion != null) { + return completion; + } + type = type.getSuperclass(); + } + if (param.getType().isEnum()) { + CommandOperationContext ctx = CommandManager.getCurrentCommandOperationContext(); + //noinspection unchecked + ctx.enumCompletionValues = ACFUtil.enumNames((Class>) param.getType()); + return DEFAULT_ENUM_ID; + } + break; + } + } + return null; + } + List getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) { + if (DEFAULT_ENUM_ID.equals(completion)) { + CommandOperationContext ctx = CommandManager.getCurrentCommandOperationContext(); + return ctx.enumCompletionValues; + } if (completion.startsWith("repeat@")) { completion = completion.substring(6); } diff --git a/core/src/main/java/co/aikar/commands/CommandContexts.java b/core/src/main/java/co/aikar/commands/CommandContexts.java index 895f15e1..49c851d3 100644 --- a/core/src/main/java/co/aikar/commands/CommandContexts.java +++ b/core/src/main/java/co/aikar/commands/CommandContexts.java @@ -205,7 +205,7 @@ public class CommandContexts match = ACFUtil.simpleMatch(enumCls, first); if (match == null) { List names = ACFUtil.enumNames(enumCls); - throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", ACFUtil.join(names)); + throw new InvalidCommandArgument(MessageKeys.PLEASE_SPECIFY_ONE_OF, "{valid}", ACFUtil.join(names, ", ")); } return match; }); diff --git a/core/src/main/java/co/aikar/commands/CommandOperationContext.java b/core/src/main/java/co/aikar/commands/CommandOperationContext.java index dac013c1..14207532 100644 --- a/core/src/main/java/co/aikar/commands/CommandOperationContext.java +++ b/core/src/main/java/co/aikar/commands/CommandOperationContext.java @@ -24,11 +24,12 @@ package co.aikar.commands; import java.lang.annotation.Annotation; +import java.util.List; /** * Holds information about the currently executing command on this thread */ -public class CommandOperationContext { +public class CommandOperationContext { private final CommandManager manager; private final I issuer; @@ -37,6 +38,7 @@ public class CommandOperationContext { private final String[] args; private final boolean isAsync; private RegisteredCommand registeredCommand; + List enumCompletionValues; CommandOperationContext(CommandManager manager, I issuer, BaseCommand command, String commandLabel, String[] args, boolean isAsync) { this.manager = manager; @@ -81,6 +83,7 @@ public class CommandOperationContext { /** * This method will not support annotation processors!! use getAnnotationValue or hasAnnotation + * * @deprecated Use {@link #getAnnotationValue(Class)} */ @Deprecated diff --git a/core/src/main/java/co/aikar/commands/RegisteredCommand.java b/core/src/main/java/co/aikar/commands/RegisteredCommand.java index 48007c0a..914aad88 100644 --- a/core/src/main/java/co/aikar/commands/RegisteredCommand.java +++ b/core/src/main/java/co/aikar/commands/RegisteredCommand.java @@ -88,7 +88,7 @@ public class RegisteredCommand possible = new HashSet<>(); CommandCompletions commandCompletions = this.manager.getCommandCompletions(); for (String s : parameter.getValues()) { + if ("*".equals(s) || "@completions".equals(s)) { + s = commandCompletions.findDefaultCompletion(this, origArgs); + } //noinspection unchecked List check = commandCompletions.getCompletionValues(this, sender, s, origArgs, opContext.isAsync()); if (!check.isEmpty()) { diff --git a/example/src/main/java/co/aikar/acfexample/SomeCommand.java b/example/src/main/java/co/aikar/acfexample/SomeCommand.java index 27d7b400..72118064 100644 --- a/example/src/main/java/co/aikar/acfexample/SomeCommand.java +++ b/example/src/main/java/co/aikar/acfexample/SomeCommand.java @@ -63,7 +63,7 @@ public class SomeCommand extends BaseCommand { private String testString3; @Subcommand("testDI") - public void onTestDI(CommandSender sender){ + public void onTestDI(CommandSender sender) { sender.sendMessage("The value for the injected SomeHandler is " + someHandler.getSomeField()); sender.sendMessage("Plugin is null? " + (plugin == null)); sender.sendMessage("Test String 1: " + testString); @@ -117,11 +117,15 @@ public class SomeCommand extends BaseCommand { // /acf Aikar wo with a world named "world" would provide world as a completion. // @test was custom defined in ACFExample as foobar, so only that value would show up as a completion // then finally /acfc Aikar world foobar would show foo1, foo2, foo3 statically + // * means 'default', which is registered to @players for OnlinePlayer. Same for worlds. + // @test is a manually defined handler to a String input, can't default that one. + // the foo1|foo2|foo3 defines a static list of completion values for the foo1 parameter + // Then the enum will also pick up default of its values even though it was left off of the completion @Subcommand("completions") @CommandAlias("acfcompletions|acfc") - @CommandCompletion("@players @worlds @test foo1|foo2|foo3") - public void onTestCompletion(CommandSender sender, OnlinePlayer player, World world, String test, String misc) { - sender.sendMessage("You got " + player.getPlayer().getName() + " - " + world.getName() + " - " + test + " - " + misc); + @CommandCompletion("* * @test foo1|foo2|foo3") + public void onTestCompletion(CommandSender sender, OnlinePlayer player, World world, String test, String foo1, TestEnum e) { + sender.sendMessage("You got " + player.getPlayer().getName() + " - " + world.getName() + " - " + test + " - " + foo1 + " - " + e.name()); } @@ -166,6 +170,7 @@ public class SomeCommand extends BaseCommand { public void onTest1(Player player, String testX) { player.sendMessage("You got test inner inner test3: " + testX); } + // Requires /acf test next test4 or simply /deepinner command alias @CommandAlias("deepinner") @Subcommand("test4|td4") @@ -175,4 +180,9 @@ public class SomeCommand extends BaseCommand { } } } + + public enum TestEnum { + FOOTEST1, + BARTEST2 + } }