mirror of
https://github.com/aikar/commands.git
synced 2026-05-31 06:11:55 +00:00
v0.4.0: Refactor Command Completions to look up context - API BREAK
Just add ", c" to your registerCompletion handler:
commandManager.getCommandCompletions().registerCompletion("test", (sender, config, input, c) -> {
});
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<groupId>co.aikar</groupId>
|
||||
<artifactId>acf-core</artifactId>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
<version>0.4.0-SNAPSHOT</version>
|
||||
|
||||
<name>ACF</name>
|
||||
|
||||
|
||||
@@ -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<String> cmds = manager.getCommandCompletions().of(sender, completion, input);
|
||||
if (cmds.isEmpty()) {
|
||||
cmds = ImmutableList.of(input);
|
||||
}
|
||||
return filterTabComplete(args[(argIndex)], cmds);
|
||||
List<String> cmds = manager.getCommandCompletions().of(cmd, sender, completions, args);
|
||||
return filterTabComplete(args[args.length-1], cmds);
|
||||
}
|
||||
|
||||
private static List<String> filterTabComplete(String arg, List<String> cmds) {
|
||||
|
||||
@@ -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<String> 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;
|
||||
|
||||
@@ -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<String, CommandCompletionHandler> 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<String> of(CommandSender sender, String completion, String input) {
|
||||
@NotNull
|
||||
List<String> 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<String> 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<String> 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<String> 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<String> getCompletions(CommandSender sender, String config, String input);
|
||||
Collection<String> 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<String> 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> T getContextValue(Class<? extends T> clazz) throws InvalidCommandArgument {
|
||||
return getContextValue(clazz, null);
|
||||
}
|
||||
|
||||
public <T> 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<String, Object> 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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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<String> possible = Sets.newHashSet();
|
||||
for (String s : split) {
|
||||
List<String> 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<String, Object> 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<String, Object> resolveContexts(CommandSender sender, List<String> args) throws InvalidCommandArgument {
|
||||
return resolveContexts(sender, args, parameters.length);
|
||||
}
|
||||
@Nullable
|
||||
Map<String, Object> resolveContexts(CommandSender sender, List<String> args, int argLimit) throws InvalidCommandArgument {
|
||||
args = Lists.newArrayList(args);
|
||||
String[] origArgs = args.toArray(new String[args.size()]);
|
||||
Map<String, Object> 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<String> possible = Sets.newHashSet();
|
||||
for (String s : split) {
|
||||
List<String> 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);
|
||||
|
||||
Reference in New Issue
Block a user