diff --git a/example/src/main/java/co/aikar/acfexample/ACFExample.java b/example/src/main/java/co/aikar/acfexample/ACFExample.java
index 0fb71b0f..8ddc3116 100644
--- a/example/src/main/java/co/aikar/acfexample/ACFExample.java
+++ b/example/src/main/java/co/aikar/acfexample/ACFExample.java
@@ -41,7 +41,7 @@ public final class ACFExample extends JavaPlugin {
private void registerCommands() {
commandManager = ACF.createManager(this);
commandManager.getCommandContexts().registerContext(SomeObject.class, SomeObject.getContextResolver());
- commandManager.getCommandCompletions().registerCompletion("test", (sender, completionConfig, input) -> (
+ commandManager.getCommandCompletions().registerCompletion("test", (sender, config, input, c) -> (
Lists.newArrayList("foo", "bar", "baz")
));
commandManager.registerCommand(new SomeCommand());
diff --git a/pom.xml b/pom.xml
index 99515aef..99f4ee17 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
co.aikar
acf-core
- 0.3.0-SNAPSHOT
+ 0.4.0-SNAPSHOT
ACF
diff --git a/src/main/java/co/aikar/commands/BaseCommand.java b/src/main/java/co/aikar/commands/BaseCommand.java
index 8de2523e..45232b6f 100644
--- a/src/main/java/co/aikar/commands/BaseCommand.java
+++ b/src/main/java/co/aikar/commands/BaseCommand.java
@@ -393,16 +393,9 @@ public abstract class BaseCommand extends Command {
}
String[] completions = ACFPatterns.SPACE.split(cmd.complete.value());
- final int argIndex = args.length - 1;
- String input = args[argIndex];
- final String completion = argIndex < completions.length ? completions[argIndex] : null;
-
- List cmds = manager.getCommandCompletions().of(sender, completion, input);
- if (cmds.isEmpty()) {
- cmds = ImmutableList.of(input);
- }
- return filterTabComplete(args[(argIndex)], cmds);
+ List cmds = manager.getCommandCompletions().of(cmd, sender, completions, args);
+ return filterTabComplete(args[args.length-1], cmds);
}
private static List filterTabComplete(String arg, List cmds) {
diff --git a/src/main/java/co/aikar/commands/BukkitCommandCompletions.java b/src/main/java/co/aikar/commands/BukkitCommandCompletions.java
index ec4e6d26..69017039 100644
--- a/src/main/java/co/aikar/commands/BukkitCommandCompletions.java
+++ b/src/main/java/co/aikar/commands/BukkitCommandCompletions.java
@@ -38,16 +38,16 @@ import java.util.stream.Stream;
public class BukkitCommandCompletions extends CommandCompletions {
BukkitCommandCompletions() {
super();
- registerCompletion("mobs", (sender, completionConfig, input) -> {
+ registerCompletion("mobs", (sender, config, input, c) -> {
final Stream normal = Stream.of(EntityType.values())
.map(entityType -> ACFUtil.simplifyString(entityType.getName()));
return normal.collect(Collectors.toList());
});
- registerCompletion("worlds", (sender, completionConfig, input) -> (
+ registerCompletion("worlds", (sender, config, input, c) -> (
Bukkit.getWorlds().stream().map(World::getName).collect(Collectors.toList())
));
- registerCompletion("players", (sender, completionConfig, input) -> {
+ registerCompletion("players", (sender, config, input, c) -> {
Validate.notNull(sender, "Sender cannot be null");
Player senderPlayer = sender instanceof Player ? (Player) sender : null;
diff --git a/src/main/java/co/aikar/commands/CommandCompletions.java b/src/main/java/co/aikar/commands/CommandCompletions.java
index 77bcfb2b..ab01ef08 100644
--- a/src/main/java/co/aikar/commands/CommandCompletions.java
+++ b/src/main/java/co/aikar/commands/CommandCompletions.java
@@ -26,7 +26,10 @@ package co.aikar.commands;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+import java.lang.reflect.Parameter;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -39,11 +42,11 @@ public class CommandCompletions {
private Map completionMap = new HashMap<>();
public CommandCompletions() {
- registerCompletion("range", (sender, completionConfig, input) -> {
- if (completionConfig == null) {
+ registerCompletion("range", (sender, config, input, c) -> {
+ if (config == null) {
return ImmutableList.of();
}
- final String[] ranges = ACFPatterns.DASH.split(completionConfig);
+ final String[] ranges = ACFPatterns.DASH.split(config);
int start;
int end;
if (ranges.length != 2) {
@@ -55,36 +58,131 @@ public class CommandCompletions {
}
return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
});
- registerCompletion("timeunits", (sender, completionConfig, input) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
+ registerCompletion("timeunits", (sender, config, input, c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years"));
}
public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler handler) {
return this.completionMap.put("@" + id.toLowerCase(), handler);
}
- public List of(CommandSender sender, String completion, String input) {
+ @NotNull
+ List of(RegisteredCommand command, CommandSender sender, String[] completionInfo, String[] args) {
+ final int argIndex = args.length - 1;
+
+ String input = args[argIndex];
+ final String completion = argIndex < completionInfo.length ? completionInfo[argIndex] : null;
if (completion == null) {
- return ImmutableList.of();
- }
- if (input == null) {
- input = "";
+ return ImmutableList.of(input);
}
+ return getCompletionValues(command, sender, completion, args);
+ }
+
+ @NotNull
+ List getCompletionValues(RegisteredCommand command, CommandSender sender, String completion, String[] args) {
+ final int argIndex = args.length - 1;
+
+ String input = args[argIndex];
String[] complete = ACFPatterns.COLON.split(completion, 2);
CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase());
if (handler != null) {
- List completions = handler.getCompletions(sender, complete.length == 1 ? null : complete[1], input);
- if (completions == null) {
- return ImmutableList.of();
- } else {
- return completions;
+ String config = complete.length == 1 ? null : complete[1];
+ CommandCompletionContext context = new CommandCompletionContext(command, sender, input, config, args);
+
+ try {
+ Collection completions = handler.getCompletions(sender, config, input, context);
+ if (completions != null) {
+ return Lists.newArrayList(completions);
+ }
+ //noinspection ConstantIfStatement,ConstantConditions
+ if (false) { // Hack to fool compiler. since its sneakily thrown.
+ throw new CommandCompletionTextLookupException();
+ }
+ } catch (CommandCompletionTextLookupException ignored) {
+ // This should only happen if some other feedback error occured.
+ } catch (Exception e) {
+ ACFLog.exception(e);
}
+ // Something went wrong in lookup, fall back to input
+ return ImmutableList.of(input);
}
+ // Plaintext values.
return Lists.newArrayList(ACFPatterns.PIPE.split(completion));
}
public interface CommandCompletionHandler {
- List getCompletions(CommandSender sender, String config, String input);
+ Collection getCompletions(CommandSender sender, String config, String input, CommandCompletionContext context) throws InvalidCommandArgument;
+ }
+
+ public class CommandCompletionContext {
+ private final RegisteredCommand command;
+ private final CommandSender sender;
+ private final String input;
+ private final String config;
+ private final List args;
+
+ CommandCompletionContext(RegisteredCommand command, CommandSender sender, String input, String config, String[] args) {
+ this.command = command;
+ this.sender = sender;
+ this.input = input;
+ this.config = config;
+ this.args = Lists.newArrayList(args);
+ }
+
+ public T getContextValue(Class extends T> clazz) throws InvalidCommandArgument {
+ return getContextValue(clazz, null);
+ }
+
+ public T getContextValue(Class extends T> clazz, Integer paramIdx) throws InvalidCommandArgument {
+ String name = null;
+ if (paramIdx != null) {
+ if (paramIdx >= command.parameters.length) {
+ throw new IllegalArgumentException("Param index is higher than number of parameters");
+ }
+ Parameter param = command.parameters[paramIdx];
+ Class> paramType = param.getType();
+ if (!clazz.isAssignableFrom(paramType)) {
+ throw new IllegalArgumentException(param.getName() +":" + paramType.getName() + " can not satisfy " + clazz.getName());
+ }
+ name = param.getName();
+ } else {
+ Parameter[] parameters = command.parameters;
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter param = parameters[i];
+ if (clazz.isAssignableFrom(param.getType())) {
+ paramIdx = i;
+ name = param.getName();
+ break;
+ }
+ }
+ if (paramIdx == null) {
+ throw new IllegalStateException("Can not find any parameter that can satisfy " + clazz.getName());
+ }
+ }
+ Map resolved = command.resolveContexts(sender, args, args.size());
+ if (resolved == null || paramIdx > resolved.size()) {
+ ACFLog.error("resolved: " + resolved + " paramIdx: " + paramIdx + " - size: " + (resolved != null ? resolved.size() : null ));
+ ACFUtil.sneaky(new CommandCompletionTextLookupException());
+ }
+
+ //noinspection unchecked
+ return (T) resolved.get(name);
+ }
+
+ public CommandSender getSender() {
+ return sender;
+ }
+
+ public String getInput() {
+ return input;
+ }
+
+ public String getConfig() {
+ return config;
+ }
+ }
+
+ private class CommandCompletionTextLookupException extends Throwable {
}
}
diff --git a/src/main/java/co/aikar/commands/RegisteredCommand.java b/src/main/java/co/aikar/commands/RegisteredCommand.java
index 10eeebc9..8b220e6e 100644
--- a/src/main/java/co/aikar/commands/RegisteredCommand.java
+++ b/src/main/java/co/aikar/commands/RegisteredCommand.java
@@ -32,12 +32,14 @@ import co.aikar.commands.annotation.Syntax;
import co.aikar.commands.annotation.Values;
import co.aikar.commands.contexts.ContextResolver;
import co.aikar.commands.contexts.SenderAwareContextResolver;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -125,49 +127,8 @@ public class RegisteredCommand {
return;
}
try {
- Map passedArgs = Maps.newLinkedHashMap();
- for (int i = 0; i < parameters.length; i++) {
- boolean isLast = i == parameters.length - 1;
- final Parameter parameter = parameters[i];
- final String parameterName = parameter.getName();
- final Class> type = parameter.getType();
- final ContextResolver> resolver = resolvers[i];
- CommandExecutionContext context = new CommandExecutionContext(this, parameter, sender, args, i, passedArgs);
- if (args.isEmpty() && !(isLast && type == String[].class)) {
- Default def = parameter.getAnnotation(Default.class);
- Optional opt = parameter.getAnnotation(Optional.class);
- if (isLast && def != null) {
- args.add(def.value());
- } else if (isLast && opt != null) {
- passedArgs.put(parameterName, resolver instanceof SenderAwareContextResolver ? resolver.getContext(context) : null);
- //noinspection UnnecessaryContinue
- continue;
- } else if (!(resolver instanceof SenderAwareContextResolver)) {
- scope.showSyntax(sender, this);
- return;
- }
- }
- final Values values = parameter.getAnnotation(Values.class);
- if (values != null) {
- String arg = args.get(0);
-
- final String[] split = ACFPatterns.PIPE.split(values.value());
- Set possible = Sets.newHashSet();
- for (String s : split) {
- List check = this.scope.manager.getCommandCompletions().of(sender, s, arg);
- if (!check.isEmpty()) {
- possible.addAll(check.stream().map(String::toLowerCase).collect(Collectors.toList()));
- } else {
- possible.add(s.toLowerCase());
- }
- }
-
- if (!possible.contains(arg.toLowerCase())) {
- throw new InvalidCommandArgument("Must be one of: " + ACFUtil.join(possible, ", "));
- }
- }
- passedArgs.put(parameterName, resolver.getContext(context));
- }
+ Map passedArgs = resolveContexts(sender, args);
+ if (passedArgs == null) return;
method.invoke(scope, passedArgs.values().toArray());
} catch (Exception e) {
@@ -175,7 +136,6 @@ public class RegisteredCommand {
e = (Exception) e.getCause();
}
if (e instanceof InvalidCommandArgument) {
-
if (e.getMessage() != null && !e.getMessage().isEmpty()) {
ACFUtil.sendMsg(sender, "&cError: " + e.getMessage());
}
@@ -189,6 +149,60 @@ public class RegisteredCommand {
}
}
+ @Nullable
+ Map resolveContexts(CommandSender sender, List args) throws InvalidCommandArgument {
+ return resolveContexts(sender, args, parameters.length);
+ }
+ @Nullable
+ Map resolveContexts(CommandSender sender, List args, int argLimit) throws InvalidCommandArgument {
+ args = Lists.newArrayList(args);
+ String[] origArgs = args.toArray(new String[args.size()]);
+ Map passedArgs = Maps.newLinkedHashMap();
+ for (int i = 0; i < parameters.length && i < argLimit; i++) {
+ boolean isLast = i == parameters.length - 1;
+ final Parameter parameter = parameters[i];
+ final String parameterName = parameter.getName();
+ final Class> type = parameter.getType();
+ final ContextResolver> resolver = resolvers[i];
+ CommandExecutionContext context = new CommandExecutionContext(this, parameter, sender, args, i, passedArgs);
+ if (args.isEmpty() && !(isLast && type == String[].class)) {
+ Default def = parameter.getAnnotation(Default.class);
+ Optional opt = parameter.getAnnotation(Optional.class);
+ if (isLast && def != null) {
+ args.add(def.value());
+ } else if (isLast && opt != null) {
+ passedArgs.put(parameterName, resolver instanceof SenderAwareContextResolver ? resolver.getContext(context) : null);
+ //noinspection UnnecessaryContinue
+ continue;
+ } else if (!(resolver instanceof SenderAwareContextResolver)) {
+ scope.showSyntax(sender, this);
+ return null;
+ }
+ }
+ final Values values = parameter.getAnnotation(Values.class);
+ if (values != null) {
+ String arg = args.get(0);
+
+ final String[] split = ACFPatterns.PIPE.split(values.value());
+ Set possible = Sets.newHashSet();
+ for (String s : split) {
+ List check = this.scope.manager.getCommandCompletions().getCompletionValues(this, sender, s, origArgs);
+ if (!check.isEmpty()) {
+ possible.addAll(check.stream().map(String::toLowerCase).collect(Collectors.toList()));
+ } else {
+ possible.add(s.toLowerCase());
+ }
+ }
+
+ if (!possible.contains(arg.toLowerCase())) {
+ throw new InvalidCommandArgument("Must be one of: " + ACFUtil.join(possible, ", "));
+ }
+ }
+ passedArgs.put(parameterName, resolver.getContext(context));
+ }
+ return passedArgs;
+ }
+
CommandTiming getTiming() {
if (this.timing == null) {
this.timing = ACFUtil.getTiming(scope, command);