Fully implement argument suggestions
Suggesting based on each annotation and the parameter type Moving the parameter name to the end of the suggestion list #82
This commit is contained in:
parent
f5406a8c0e
commit
8344adff1a
9 changed files with 159 additions and 67 deletions
|
@ -13,3 +13,5 @@ tab_width=4
|
||||||
indent_style=space
|
indent_style=space
|
||||||
indent_size=2
|
indent_size=2
|
||||||
|
|
||||||
|
[*.xml]
|
||||||
|
indent_style = tab
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
<artifactSet>
|
<artifactSet>
|
||||||
<includes>
|
<includes>
|
||||||
<include>me.lucko:commodore</include>
|
<include>me.lucko:commodore</include>
|
||||||
|
<include>org.javatuples:javatuples</include>
|
||||||
</includes>
|
</includes>
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
<relocations>
|
<relocations>
|
||||||
|
@ -196,6 +197,11 @@
|
||||||
<version>1.7</version>
|
<version>1.7</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.javatuples</groupId>
|
||||||
|
<artifactId>javatuples</artifactId>
|
||||||
|
<version>1.2</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<organization>
|
<organization>
|
||||||
<name>TBMCPlugins</name>
|
<name>TBMCPlugins</name>
|
||||||
|
|
|
@ -25,9 +25,4 @@ public class ChromaCommand extends ICommand2MC {
|
||||||
public void def(CommandSender sender) {
|
public void def(CommandSender sender) {
|
||||||
sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText());
|
sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command2.Subcommand //TODO: Remove
|
|
||||||
public void test(CommandSender sender, char test) {
|
|
||||||
sender.sendMessage(test + "");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,11 @@ public class ComponentCommand extends ICommand2MC {
|
||||||
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
|
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CustomTabCompleteMethod(param = "plugin")
|
||||||
|
public Iterable<String> list() {
|
||||||
|
return Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
|
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
|
||||||
try {
|
try {
|
||||||
val oc = getComponentOrError(plugin, component, sender);
|
val oc = getComponentOrError(plugin, component, sender);
|
||||||
|
|
|
@ -24,10 +24,12 @@ import lombok.Setter;
|
||||||
import net.milkbowl.vault.economy.Economy;
|
import net.milkbowl.vault.economy.Economy;
|
||||||
import net.milkbowl.vault.permission.Permission;
|
import net.milkbowl.vault.permission.Permission;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.BlockCommandSender;
|
import org.bukkit.command.BlockCommandSender;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.ConsoleCommandSender;
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
import org.bukkit.entity.HumanEntity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.PluginDescriptionFile;
|
import org.bukkit.plugin.PluginDescriptionFile;
|
||||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||||
|
@ -36,10 +38,10 @@ import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class MainPlugin extends ButtonPlugin {
|
public class MainPlugin extends ButtonPlugin {
|
||||||
|
@ -135,6 +137,9 @@ public class MainPlugin extends ButtonPlugin {
|
||||||
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green"));
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green"));
|
||||||
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue"));
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue"));
|
||||||
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple"));
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple"));
|
||||||
|
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator;
|
||||||
|
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
|
||||||
|
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
|
||||||
if (writePluginList().get()) {
|
if (writePluginList().get()) {
|
||||||
try {
|
try {
|
||||||
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
|
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import buttondevteam.lib.chat.ICommand2MC;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.HumanEntity;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -20,8 +19,6 @@ public class MemberCommand extends ICommand2MC {
|
||||||
private final MemberComponent component;
|
private final MemberComponent component;
|
||||||
|
|
||||||
public MemberCommand(MemberComponent component) {
|
public MemberCommand(MemberComponent component) {
|
||||||
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!",
|
|
||||||
() -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator);
|
|
||||||
this.component = component;
|
this.component = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,15 +26,19 @@ import org.bukkit.event.Listener;
|
||||||
import org.bukkit.event.server.TabCompleteEvent;
|
import org.bukkit.event.server.TabCompleteEvent;
|
||||||
import org.bukkit.permissions.Permission;
|
import org.bukkit.permissions.Permission;
|
||||||
import org.bukkit.permissions.PermissionDefault;
|
import org.bukkit.permissions.PermissionDefault;
|
||||||
|
import org.javatuples.Triplet;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
|
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
|
||||||
/**
|
/**
|
||||||
|
@ -236,8 +240,19 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
||||||
var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null);
|
var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null);
|
||||||
cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one
|
cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one
|
||||||
}
|
}
|
||||||
|
final var customTCmethods = Arrays.stream(command2MC.getClass().getDeclaredMethods()) //val doesn't recognize the type arguments
|
||||||
|
.flatMap(method -> Stream.of(Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod.class)))
|
||||||
|
.filter(Optional::isPresent).map(Optional::get) // Java 9 has .stream()
|
||||||
|
.flatMap(ctcm -> {
|
||||||
|
var paths = Optional.of(ctcm.subcommand()).filter(s -> s.length > 0)
|
||||||
|
.orElseGet(() -> new String[]{
|
||||||
|
ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim()
|
||||||
|
});
|
||||||
|
return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm.param(), method));
|
||||||
|
})).collect(Collectors.toList());
|
||||||
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
|
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
|
||||||
String[] subpath = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim().split(" ");
|
String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim();
|
||||||
|
String[] subpath = subpathAsOne.split(" ");
|
||||||
CommandNode<Object> scmd = cmd;
|
CommandNode<Object> scmd = cmd;
|
||||||
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
|
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
|
||||||
for (String s : subpath) {
|
for (String s : subpath) {
|
||||||
|
@ -249,6 +264,9 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
||||||
Parameter parameter = parameters[i];
|
Parameter parameter = parameters[i];
|
||||||
ArgumentType<?> type;
|
ArgumentType<?> type;
|
||||||
final Class<?> ptype = parameter.getType();
|
final Class<?> ptype = parameter.getType();
|
||||||
|
final boolean customParamType;
|
||||||
|
{
|
||||||
|
boolean customParamTypeTemp = false;
|
||||||
if (ptype == String.class)
|
if (ptype == String.class)
|
||||||
if (parameter.isAnnotationPresent(TextArg.class))
|
if (parameter.isAnnotationPresent(TextArg.class))
|
||||||
type = StringArgumentType.greedyString();
|
type = StringArgumentType.greedyString();
|
||||||
|
@ -268,28 +286,91 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
||||||
type = StringArgumentType.word();
|
type = StringArgumentType.word();
|
||||||
else if (ptype == boolean.class || ptype == Boolean.class)
|
else if (ptype == boolean.class || ptype == Boolean.class)
|
||||||
type = BoolArgumentType.bool();
|
type = BoolArgumentType.bool();
|
||||||
else //TODO: Custom parameter types
|
else {
|
||||||
type = StringArgumentType.word();
|
type = StringArgumentType.word();
|
||||||
|
customParamTypeTemp = true;
|
||||||
|
}
|
||||||
|
customParamType = customParamTypeTemp;
|
||||||
|
}
|
||||||
val param = subcmd.parameters[i - 1];
|
val param = subcmd.parameters[i - 1];
|
||||||
|
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
|
||||||
|
.map(CustomTabComplete::value);
|
||||||
|
final Optional<Method> customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0()))
|
||||||
|
.filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1()))
|
||||||
|
.map(Triplet::getValue2).findAny();
|
||||||
var argb = RequiredArgumentBuilder.argument(param, type)
|
var argb = RequiredArgumentBuilder.argument(param, type)
|
||||||
.suggests((SuggestionProvider<Object>) (context, builder) -> {
|
.suggests((SuggestionProvider<Object>) (context, builder) -> {
|
||||||
//TODO
|
if (customTC.isPresent())
|
||||||
return builder.suggest(param).buildFuture();
|
for (val ctc : customTC.get())
|
||||||
|
builder.suggest(ctc);
|
||||||
|
if (customTCmethod.isPresent()) {
|
||||||
|
final var method = customTCmethod.get();
|
||||||
|
val params = method.getParameters();
|
||||||
|
val args = new Object[params.length];
|
||||||
|
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
|
||||||
|
val paramObj = params[j];
|
||||||
|
if (CommandSender.class.isAssignableFrom(paramObj.getType())) {
|
||||||
|
args[j] = commodore.getBukkitSender(context.getSource());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
val paramValueString = context.getArgument(subcmd.parameters[k], String.class);
|
||||||
|
if (paramObj.getType() == String.class) {
|
||||||
|
args[j] = paramValueString;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(params[j].getType());
|
||||||
|
if (converter == null) {
|
||||||
|
TBMCCoreAPI.SendException("Could not find a suitable converter for type " + params[j].getType().getSimpleName(),
|
||||||
|
new NullPointerException("converter is null"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
val paramValue = converter.converter.apply(paramValueString);
|
||||||
|
if (paramValue == null) //For example, the player provided an invalid plugin name
|
||||||
|
break;
|
||||||
|
args[j] = paramValue;
|
||||||
|
k++; //Only increment if not CommandSender
|
||||||
|
}
|
||||||
|
if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely
|
||||||
|
try {
|
||||||
|
val suggestions = method.invoke(command2MC, args);
|
||||||
|
if (suggestions instanceof Iterable) {
|
||||||
|
//noinspection unchecked
|
||||||
|
for (Object suggestion : (Iterable<Object>) suggestions)
|
||||||
|
if (suggestion instanceof String)
|
||||||
|
builder.suggest((String) suggestion);
|
||||||
|
else
|
||||||
|
throw new ClassCastException("Bad return type! It should return an Iterable<String> or a String[].");
|
||||||
|
} else if (suggestions instanceof String[])
|
||||||
|
for (String suggestion : (String[]) suggestions)
|
||||||
|
builder.suggest(suggestion);
|
||||||
|
else
|
||||||
|
throw new ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to run tabcomplete method " + method.getName() + " for command " + command2MC.getClass().getSimpleName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (customParamType) {
|
||||||
|
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(ptype);
|
||||||
|
if (converter == null)
|
||||||
|
TBMCCoreAPI.SendException("Could not find a suitable converter for type " + ptype.getSimpleName(),
|
||||||
|
new NullPointerException("converter is null"));
|
||||||
|
else {
|
||||||
|
var suggestions = converter.allSupplier.get();
|
||||||
|
for (String suggestion : suggestions)
|
||||||
|
builder.suggest(suggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ptype == boolean.class || ptype == Boolean.class)
|
||||||
|
builder.suggest("true").suggest("false");
|
||||||
|
return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered
|
||||||
|
Collections.swap(s.getList(), 0, s.getList().size() - 1)); //So we need to put the <param> at the end after that
|
||||||
});
|
});
|
||||||
var arg = argb.build();
|
var arg = argb.build();
|
||||||
scmd.addChild(arg);
|
scmd.addChild(arg);
|
||||||
scmd = arg;
|
scmd = arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*try {
|
|
||||||
Class.forName("net.minecraft.server.v1_15_R1.ArgumentRegistry").getMethod("a", String.class, Class.class,
|
|
||||||
Class.forName("net.minecraft.server.v1_15_R1.ArgumentSerializer"))
|
|
||||||
.invoke(null, "chroma:string", BetterStringArgumentType.class,
|
|
||||||
Class.forName("net.minecraft.server.v1_15_R1.ArgumentSerializerVoid").getConstructors()[0]
|
|
||||||
.newInstance((Supplier<BetterStringArgumentType>) BetterStringArgumentType::word));
|
|
||||||
} catch (Exception e) { - Client log: Could not deserialize chroma:string
|
|
||||||
e.printStackTrace();
|
|
||||||
}*/
|
|
||||||
commodore.register(maincmd);
|
commodore.register(maincmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method must return with {@link String}[] or {@link Iterable}<{@link String}> and may have the sender and preceding arguments as parameters.
|
* The method must return with {@link String}[] or {@link Iterable}<{@link String}> and may have the sender and preceding arguments as parameters.
|
||||||
|
* The predecing arguments must be in order, from first to whatever is needed. If the nth arg is needed, you need to specify n params.
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@ -19,5 +20,5 @@ public @interface CustomTabCompleteMethod {
|
||||||
/**
|
/**
|
||||||
* The subcommand(s) which have the parameter, by default the method's name
|
* The subcommand(s) which have the parameter, by default the method's name
|
||||||
*/
|
*/
|
||||||
String[] subcommand() default "";
|
String[] subcommand() default {};
|
||||||
}
|
}
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -9,7 +9,7 @@
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>master-SNAPSHOT</version>
|
<version>master-SNAPSHOT</version>
|
||||||
<properties>
|
<properties>
|
||||||
<lombok.version>1.18.10</lombok.version>
|
<lombok.version>1.18.12</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
<name>Chroma Parent</name>
|
<name>Chroma Parent</name>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue