Fix tabcompletion: use custom suggestion provider
It looks like we can't have custom argument types Checking for permission for each offered option Suggesting the argument's name as well #82 TODO: Handle annotations Remove test command
This commit is contained in:
parent
95a8e92b51
commit
f5406a8c0e
9 changed files with 119 additions and 76 deletions
|
@ -26,7 +26,7 @@ public class ChromaCommand extends ICommand2MC {
|
|||
sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText());
|
||||
}
|
||||
|
||||
@Command2.Subcommand
|
||||
@Command2.Subcommand //TODO: Remove
|
||||
public void test(CommandSender sender, char test) {
|
||||
sender.sendMessage(test + "");
|
||||
}
|
||||
|
|
|
@ -6,13 +6,17 @@ import buttondevteam.lib.architecture.Component;
|
|||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.Command2.Subcommand;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
import buttondevteam.lib.chat.CustomTabCompleteMethod;
|
||||
import buttondevteam.lib.chat.ICommand2MC;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@CommandClass(modOnly = true, helpText = {
|
||||
"Component command",
|
||||
|
@ -20,7 +24,8 @@ import java.util.Optional;
|
|||
})
|
||||
public class ComponentCommand extends ICommand2MC {
|
||||
public ComponentCommand() {
|
||||
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!");
|
||||
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!",
|
||||
() -> Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator);
|
||||
}
|
||||
|
||||
@Subcommand(helpText = {
|
||||
|
@ -57,6 +62,11 @@ public class ComponentCommand extends ICommand2MC {
|
|||
return true;
|
||||
}
|
||||
|
||||
@CustomTabCompleteMethod(param = "component", subcommand = {"enable", "disable"})
|
||||
public Iterable<String> componentTabcomplete(Plugin plugin) {
|
||||
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
|
||||
}
|
||||
|
||||
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
|
||||
try {
|
||||
val oc = getComponentOrError(plugin, component, sender);
|
||||
|
@ -72,10 +82,13 @@ public class ComponentCommand extends ICommand2MC {
|
|||
return true;
|
||||
}
|
||||
|
||||
private Stream<Component<? extends JavaPlugin>> getPluginComponents(Plugin plugin) {
|
||||
return Component.getComponents().values().stream()
|
||||
.filter(c -> plugin.getName().equals(c.getPlugin().getName()));
|
||||
}
|
||||
|
||||
private Optional<Component<?>> getComponentOrError(Plugin plugin, String arg, CommandSender sender) {
|
||||
val oc = Component.getComponents().values().stream()
|
||||
.filter(c -> plugin.getName().equals(c.getPlugin().getName()))
|
||||
.filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny();
|
||||
val oc = getPluginComponents(plugin).filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny();
|
||||
if (!oc.isPresent())
|
||||
sender.sendMessage("§cComponent not found!"); //^ Much simpler to solve in the new command system
|
||||
return oc;
|
||||
|
|
|
@ -7,6 +7,7 @@ import buttondevteam.lib.chat.ICommand2MC;
|
|||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.HumanEntity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -19,7 +20,8 @@ public class MemberCommand extends ICommand2MC {
|
|||
private final MemberComponent component;
|
||||
|
||||
public MemberCommand(MemberComponent component) {
|
||||
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!");
|
||||
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!",
|
||||
() -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator);
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package buttondevteam.core.component.towny;
|
|||
|
||||
import buttondevteam.lib.chat.Command2;
|
||||
import buttondevteam.lib.chat.CommandClass;
|
||||
import buttondevteam.lib.chat.CustomTabComplete;
|
||||
import buttondevteam.lib.chat.ICommand2MC;
|
||||
import com.palmergames.bukkit.towny.TownySettings;
|
||||
import com.palmergames.bukkit.towny.TownyUniverse;
|
||||
|
@ -20,7 +21,7 @@ import java.util.stream.Stream;
|
|||
})
|
||||
public class RemoveResidentsCommand extends ICommand2MC {
|
||||
@Command2.Subcommand
|
||||
public void def(CommandSender sender, @Command2.OptionalArg String remove) {
|
||||
public void def(CommandSender sender, @Command2.OptionalArg @CustomTabComplete("remove") String remove) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> {
|
||||
sender.sendMessage("Starting...");
|
||||
var ds = TownyUniverse.getInstance().getDataSource();
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
||||
|
@ -110,10 +111,11 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
|||
protected static class ParamConverter<T> {
|
||||
public final Function<String, T> converter;
|
||||
public final String errormsg;
|
||||
public final Supplier<Iterable<String>> allSupplier;
|
||||
}
|
||||
|
||||
protected HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
|
||||
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
|
||||
protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
|
||||
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
|
||||
|
||||
private ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
|
||||
|
||||
|
@ -123,12 +125,14 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
|||
* Adds a param converter that obtains a specific object from a string parameter.
|
||||
* The converter may return null.
|
||||
*
|
||||
* @param <T> The type of the result
|
||||
* @param cl The class of the result object
|
||||
* @param converter The converter to use
|
||||
* @param <T> The type of the result
|
||||
* @param allSupplier The supplier of all possible values (ideally)
|
||||
*/
|
||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
|
||||
paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
|
||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg,
|
||||
Supplier<Iterable<String>> allSupplier) {
|
||||
paramConverters.put(cl, new ParamConverter<>(converter, errormsg, allSupplier));
|
||||
}
|
||||
|
||||
public boolean handleCommand(TP sender, String commandline) {
|
||||
|
|
|
@ -4,11 +4,12 @@ import buttondevteam.core.MainPlugin;
|
|||
import buttondevteam.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||
import buttondevteam.lib.architecture.Component;
|
||||
import buttondevteam.lib.chat.commandargs.BetterStringArgumentType;
|
||||
import com.mojang.brigadier.arguments.*;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import lombok.val;
|
||||
import me.lucko.commodore.Commodore;
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
|
@ -20,7 +21,9 @@ import org.bukkit.command.CommandSender;
|
|||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.server.TabCompleteEvent;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
|
@ -31,6 +34,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
|
||||
/**
|
||||
|
@ -139,8 +143,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
* {@see super#addParamConverter}
|
||||
*/
|
||||
@Override
|
||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
|
||||
super.addParamConverter(cl, converter, "§c" + errormsg);
|
||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
|
||||
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
|
||||
}
|
||||
|
||||
public void unregisterCommands(ButtonPlugin plugin) {
|
||||
|
@ -158,6 +162,13 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onTabComplete(TabCompleteEvent event) {
|
||||
//System.out.println("Tabcomplete: " + event.getBuffer());
|
||||
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
|
||||
event.getCompletions().clear();
|
||||
}
|
||||
|
||||
private boolean shouldRegisterOfficially = true;
|
||||
|
||||
private void registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
|
||||
|
@ -202,6 +213,19 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
private static class TabcompleteHelper {
|
||||
private static Commodore commodore;
|
||||
|
||||
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
|
||||
SubcommandData<ICommand2MC> subcommand) {
|
||||
var scmdBuilder = LiteralArgumentBuilder.literal(path);
|
||||
if (subcommand != null)
|
||||
scmdBuilder.requires(o -> {
|
||||
var sender = commodore.getBukkitSender(o);
|
||||
return ButtonPlugin.getCommand2MC().hasPermission(sender, subcommand.command, subcommand.method);
|
||||
});
|
||||
var scmd = scmdBuilder.build();
|
||||
parent.addChild(scmd);
|
||||
return scmd;
|
||||
}
|
||||
|
||||
private static void registerTabcomplete(ICommand2MC command2MC, List<SubcommandData<ICommand2MC>> subcmds, Command bukkitCommand) {
|
||||
if (commodore == null)
|
||||
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance); //Register all to the Core, it's easier
|
||||
|
@ -209,18 +233,15 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
var maincmd = LiteralArgumentBuilder.literal(path[0]).build();
|
||||
var cmd = maincmd;
|
||||
for (int i = 1; i < path.length; i++) {
|
||||
var subcmd = LiteralArgumentBuilder.literal(path[i]).build();
|
||||
cmd.addChild(subcmd);
|
||||
cmd = subcmd; //Add each part of the path as a child of the previous one
|
||||
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
|
||||
}
|
||||
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
|
||||
String[] subpath = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim().split(" ");
|
||||
CommandNode<Object> scmd = cmd;
|
||||
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
|
||||
for (String s : subpath) {
|
||||
var subsubcmd = LiteralArgumentBuilder.literal(s).build();
|
||||
scmd.addChild(subsubcmd);
|
||||
scmd = subsubcmd; //Add method name part of the path (could_be_multiple())
|
||||
scmd = appendSubcommand(s, scmd, subcmd); //Add method name part of the path (could_be_multiple())
|
||||
}
|
||||
}
|
||||
Parameter[] parameters = subcmd.method.getParameters();
|
||||
|
@ -232,7 +253,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
if (parameter.isAnnotationPresent(TextArg.class))
|
||||
type = StringArgumentType.greedyString();
|
||||
else
|
||||
type = BetterStringArgumentType.word();
|
||||
type = StringArgumentType.word();
|
||||
else if (ptype == int.class || ptype == Integer.class
|
||||
|| ptype == byte.class || ptype == Byte.class
|
||||
|| ptype == short.class || ptype == Short.class)
|
||||
|
@ -244,12 +265,18 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
else if (ptype == double.class || ptype == Double.class)
|
||||
type = DoubleArgumentType.doubleArg();
|
||||
else if (ptype == char.class || ptype == Character.class)
|
||||
type = BetterStringArgumentType.word(1);
|
||||
type = StringArgumentType.word();
|
||||
else if (ptype == boolean.class || ptype == Boolean.class)
|
||||
type = BoolArgumentType.bool();
|
||||
else //TODO: Custom parameter types
|
||||
type = BetterStringArgumentType.word();
|
||||
var arg = RequiredArgumentBuilder.argument(subcmd.parameters[i - 1], type).build();
|
||||
type = StringArgumentType.word();
|
||||
val param = subcmd.parameters[i - 1];
|
||||
var argb = RequiredArgumentBuilder.argument(param, type)
|
||||
.suggests((SuggestionProvider<Object>) (context, builder) -> {
|
||||
//TODO
|
||||
return builder.suggest(param).buildFuture();
|
||||
});
|
||||
var arg = argb.build();
|
||||
scmd.addChild(arg);
|
||||
scmd = arg;
|
||||
}
|
||||
|
@ -263,7 +290,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
} catch (Exception e) { - Client log: Could not deserialize chroma:string
|
||||
e.printStackTrace();
|
||||
}*/
|
||||
commodore.register(bukkitCommand, maincmd);
|
||||
commodore.register(maincmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package buttondevteam.lib.chat;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Can be used if an argument should be completed with predefined strings.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CustomTabComplete {
|
||||
String[] value();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package buttondevteam.lib.chat;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CustomTabCompleteMethod {
|
||||
/**
|
||||
* The parameter's name where we want to give completion
|
||||
*/
|
||||
String param();
|
||||
|
||||
/**
|
||||
* The subcommand(s) which have the parameter, by default the method's name
|
||||
*/
|
||||
String[] subcommand() default "";
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package buttondevteam.lib.chat.commandargs;
|
||||
|
||||
import com.mojang.brigadier.LiteralMessage;
|
||||
import com.mojang.brigadier.StringReader;
|
||||
import com.mojang.brigadier.arguments.ArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class BetterStringArgumentType implements ArgumentType<String> {
|
||||
private final int len;
|
||||
|
||||
public static BetterStringArgumentType word() {
|
||||
return new BetterStringArgumentType(-1);
|
||||
}
|
||||
|
||||
public static BetterStringArgumentType word(int maxlen) {
|
||||
return new BetterStringArgumentType(maxlen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(StringReader reader) throws CommandSyntaxException {
|
||||
if (len < 1)
|
||||
return reader.readStringUntil(' ');
|
||||
|
||||
final int start = reader.getCursor();
|
||||
if (reader.canRead(len + 1) && reader.peek(len) != ' ')
|
||||
throw new SimpleCommandExceptionType(new LiteralMessage("String too long")).createWithContext(reader);
|
||||
for (int i = 0; i < len; i++)
|
||||
reader.skip();
|
||||
return reader.getString().substring(start, reader.getCursor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getExamples() {
|
||||
return StringArgumentType.StringType.SINGLE_WORD.getExamples();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue