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:
Norbi Peti 2020-03-19 20:19:41 +01:00
parent f5406a8c0e
commit 8344adff1a
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
9 changed files with 159 additions and 67 deletions

View file

@ -13,3 +13,5 @@ tab_width=4
indent_style=space indent_style=space
indent_size=2 indent_size=2
[*.xml]
indent_style = tab

View file

@ -39,8 +39,9 @@
<configuration> <configuration>
<artifactSet> <artifactSet>
<includes> <includes>
<include>me.lucko:commodore</include> <include>me.lucko:commodore</include>
</includes> <include>org.javatuples:javatuples</include>
</includes>
</artifactSet> </artifactSet>
<relocations> <relocations>
<relocation> <relocation>
@ -170,33 +171,38 @@
<groupId>com.github.TBMCPlugins.ChromaCore</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId> <artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version> <version>master-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.ess3</groupId> <groupId>net.ess3</groupId>
<artifactId>EssentialsX</artifactId> <artifactId>EssentialsX</artifactId>
<version>2.17.1</version> <version>2.17.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.vexsoftware</groupId> <groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier-universal</artifactId> <artifactId>nuvotifier-universal</artifactId>
<version>2.3.4</version> <version>2.3.4</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.onarandombox.multiversecore</groupId> <groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId> <artifactId>Multiverse-Core</artifactId>
<version>4.0.1</version> <version>4.0.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>me.lucko</groupId> <groupId>me.lucko</groupId>
<artifactId>commodore</artifactId> <artifactId>commodore</artifactId>
<version>1.7</version> <version>1.7</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<organization> <organization>
<name>TBMCPlugins</name> <name>TBMCPlugins</name>
<url>https://github.com/TBMCPlugins</url> <url>https://github.com/TBMCPlugins</url>

View file

@ -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 + "");
}
} }

View file

@ -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);

View file

@ -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);

View file

@ -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;
} }

View file

@ -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,47 +264,113 @@ 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();
if (ptype == String.class) final boolean customParamType;
if (parameter.isAnnotationPresent(TextArg.class)) {
type = StringArgumentType.greedyString(); boolean customParamTypeTemp = false;
else if (ptype == String.class)
if (parameter.isAnnotationPresent(TextArg.class))
type = StringArgumentType.greedyString();
else
type = StringArgumentType.word();
else if (ptype == int.class || ptype == Integer.class
|| ptype == byte.class || ptype == Byte.class
|| ptype == short.class || ptype == Short.class)
type = IntegerArgumentType.integer(); //TODO: Min, max
else if (ptype == long.class || ptype == Long.class)
type = LongArgumentType.longArg();
else if (ptype == float.class || ptype == Float.class)
type = FloatArgumentType.floatArg();
else if (ptype == double.class || ptype == Double.class)
type = DoubleArgumentType.doubleArg();
else if (ptype == char.class || ptype == Character.class)
type = StringArgumentType.word(); type = StringArgumentType.word();
else if (ptype == int.class || ptype == Integer.class else if (ptype == boolean.class || ptype == Boolean.class)
|| ptype == byte.class || ptype == Byte.class type = BoolArgumentType.bool();
|| ptype == short.class || ptype == Short.class) else {
type = IntegerArgumentType.integer(); //TODO: Min, max type = StringArgumentType.word();
else if (ptype == long.class || ptype == Long.class) customParamTypeTemp = true;
type = LongArgumentType.longArg(); }
else if (ptype == float.class || ptype == Float.class) customParamType = customParamTypeTemp;
type = FloatArgumentType.floatArg(); }
else if (ptype == double.class || ptype == Double.class)
type = DoubleArgumentType.doubleArg();
else if (ptype == char.class || ptype == Character.class)
type = StringArgumentType.word();
else if (ptype == boolean.class || ptype == Boolean.class)
type = BoolArgumentType.bool();
else //TODO: Custom parameter types
type = StringArgumentType.word();
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);
} }
} }

View file

@ -7,6 +7,7 @@ import java.lang.annotation.Target;
/** /**
* The method must return with {@link String}[] or {@link Iterable}&lt;{@link String}&gt; and may have the sender and preceding arguments as parameters. * The method must return with {@link String}[] or {@link Iterable}&lt;{@link String}&gt; 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 {};
} }

View file

@ -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>