From 9dae442950077004afcd5279ca5470f3cf91c6db Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 7 Apr 2020 22:08:09 +0200 Subject: [PATCH] Improve parameter tab completion Fixed bad permissions being used for subcommands (older change) Updated Commodore and made it only register a command node once - this fixes main command argument handling as Bukkit's handler is removed Added option to ignore the tab completion provided by the parameter type (param converter) Only showing matching completions (it has to start with the text typed in) #82 --- Chroma-Core/pom.xml | 2 +- .../buttondevteam/core/ChromaCommand.java | 18 +++- .../buttondevteam/core/ComponentCommand.java | 7 +- .../buttondevteam/lib/chat/Command2MC.java | 82 +++++++++++++------ .../lib/chat/CustomTabCompleteMethod.java | 5 ++ 5 files changed, 79 insertions(+), 35 deletions(-) diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index 32357d5..c8d0e79 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -194,7 +194,7 @@ me.lucko commodore - 1.7 + 1.8 compile diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java index 499818f..a9d8699 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ChromaCommand.java @@ -4,18 +4,28 @@ import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.ICommand2MC; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; +import java.util.Arrays; +import java.util.Optional; + @CommandClass public class ChromaCommand extends ICommand2MC { + public ChromaCommand() { + getManager().addParamConverter(ButtonPlugin.class, name -> + (ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name)) + .filter(plugin -> plugin instanceof ButtonPlugin).orElse(null), + "No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins()) + .filter(plugin -> plugin instanceof ButtonPlugin).map(Plugin::getName)::iterator); + } + @Command2.Subcommand - public void reload(CommandSender sender, @Command2.OptionalArg Plugin plugin) { + public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) { if (plugin == null) plugin = MainPlugin.Instance; - if (!(plugin instanceof ButtonPlugin)) //Probably not a good idea to allow reloading any plugin's config - sender.sendMessage("§c" + plugin.getName() + " doesn't support this."); - else if (((ButtonPlugin) plugin).tryReloadConfig()) + if (plugin.tryReloadConfig()) sender.sendMessage("§b" + plugin.getName() + " config reloaded."); else sender.sendMessage("§cFailed to reload config. Check console."); diff --git a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java index d2b8bfd..4c601d8 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/ComponentCommand.java @@ -67,9 +67,10 @@ public class ComponentCommand extends ICommand2MC { return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator; } - @CustomTabCompleteMethod(param = "plugin") - public Iterable list() { - return Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator; + @CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true) + public Iterable pluginTabcomplete() { + return Component.getComponents().values().stream().map(Component::getPlugin) + .distinct().map(Plugin::getName)::iterator; } private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) { diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java index bb2eccf..49875db 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -19,9 +19,7 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.command.*; 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; import org.javatuples.Triplet; @@ -33,6 +31,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -70,9 +69,9 @@ public class Command2MC extends Command2 implemen } String pg = permGroup(command, method); if (pg.length() == 0) continue; - perm = "chroma." + pg; - if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times - Bukkit.getPluginManager().addPermission(new Permission(perm, + String permGroup = "chroma." + pg; + if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times + Bukkit.getPluginManager().addPermission(new Permission(permGroup, PermissionDefault.OP)); //Do not allow any commands that belong to a group } @@ -172,12 +171,16 @@ public class Command2MC extends Command2 implemen .map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false)); } - @EventHandler + /*@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(); - } + try { + event.getCompletions().clear(); //Remove player names + } catch (UnsupportedOperationException e) { + //System.out.println("Tabcomplete: " + event.getBuffer()); + //System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions")); + //System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners())); + } + }*/ @Override public boolean handleCommand(Command2MCSender sender, String commandline) { @@ -231,11 +234,13 @@ public class Command2MC extends Command2 implemen @Override public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + //System.out.println("Correct tabcomplete queried"); return Collections.emptyList(); } @Override public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + //System.out.println("Correct tabcomplete queried"); return Collections.emptyList(); } } @@ -245,22 +250,33 @@ public class Command2MC extends Command2 implemen private static LiteralCommandNode appendSubcommand(String path, CommandNode parent, SubcommandData subcommand) { + LiteralCommandNode scmd; + if ((scmd = (LiteralCommandNode) parent.getChild(path)) != null) + return scmd; 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(); + scmd = scmdBuilder.build(); parent.addChild(scmd); return scmd; } private static void registerTabcomplete(ICommand2MC command2MC, List> subcmds, Command bukkitCommand) { - if (commodore == null) + if (commodore == null) { commodore = CommodoreProvider.getCommodore(MainPlugin.Instance); //Register all to the Core, it's easier + commodore.register(LiteralArgumentBuilder.literal("un").redirect(RequiredArgumentBuilder.argument("unsomething", + StringArgumentType.word()).suggests((context, builder) -> builder.suggest("untest").buildFuture()).build())); + } String[] path = command2MC.getCommandPath().split(" "); - var maincmd = LiteralArgumentBuilder.literal(path[0]).build(); + var shouldRegister = new AtomicBoolean(true); + @SuppressWarnings("unchecked") var maincmd = commodore.getRegisteredNodes().stream() + .filter(node -> node.getLiteral().equalsIgnoreCase(path[0])) + .filter(node -> { shouldRegister.set(false); return true; }) + .map(node -> (LiteralCommandNode) node).findAny() + .orElseGet(() -> LiteralArgumentBuilder.literal(path[0]).build()); //Commodore 1.8 removes previous nodes var cmd = maincmd; for (int i = 1; i < path.length; i++) { var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null); @@ -274,7 +290,7 @@ public class Command2MC extends Command2 implemen .orElseGet(() -> new String[]{ ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim() }); - return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm.param(), method)); + return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm, method)); })).collect(Collectors.toList()); for (SubcommandData subcmd : subcmds) { String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim(); @@ -323,20 +339,24 @@ public class Command2MC extends Command2 implemen val param = subcmd.parameters[i - 1]; val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class)) .map(CustomTabComplete::value); - final Optional customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0())) - .filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1())) - .map(Triplet::getValue2).findAny(); + var customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0())) + .filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1().param())) + .findAny(); var argb = RequiredArgumentBuilder.argument(param, type) .suggests((SuggestionProvider) (context, builder) -> { if (parameter.isVarArgs()) { //Do it before the builder is used - int x = context.getInput().lastIndexOf(' ') + 1; - builder = builder.createOffset(x); + int nextTokenStart = context.getInput().lastIndexOf(' ') + 1; + builder = builder.createOffset(nextTokenStart); } if (customTC.isPresent()) for (val ctc : customTC.get()) builder.suggest(ctc); + boolean ignoreCustomParamType = false; if (customTCmethod.isPresent()) { - final var method = customTCmethod.get(); + var tr = customTCmethod.get(); + if (tr.getValue1().ignoreTypeCompletion()) + ignoreCustomParamType = true; + final var method = tr.getValue2(); val params = method.getParameters(); val args = new Object[params.length]; for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) { @@ -382,7 +402,7 @@ public class Command2MC extends Command2 implemen } } } - if (customParamType) { + if (!ignoreCustomParamType && customParamType) { val converter = ButtonPlugin.getCommand2MC().paramConverters.get(ptype); if (converter == null) TBMCCoreAPI.SendException("Could not find a suitable converter for type " + ptype.getSimpleName(), @@ -395,19 +415,27 @@ public class Command2MC extends Command2 implemen } if (ptype == boolean.class || ptype == Boolean.class) builder.suggest("true").suggest("false"); + final String loweredInput = builder.getRemaining().toLowerCase(); return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered - s.getList().add(s.getList().remove(0))); //So we need to put the at the end after that + s.getList().add(s.getList().remove(0))) //So we need to put the at the end after that + .whenComplete((ss, e) -> ss.getList().removeIf(s -> { + String text = s.getText(); + return !text.startsWith("<") && !text.startsWith("[") && !text.toLowerCase().startsWith(loweredInput); + })); }); var arg = argb.build(); scmd.addChild(arg); scmd = arg; } } - commodore.register(maincmd); - var prefixedcmd = new LiteralCommandNode<>(command2MC.getPlugin().getName().toLowerCase() + ":" + path[0], maincmd.getCommand(), maincmd.getRequirement(), maincmd.getRedirect(), maincmd.getRedirectModifier(), maincmd.isFork()); - for (var child : maincmd.getChildren()) - prefixedcmd.addChild(child); - commodore.register(prefixedcmd); + if (shouldRegister.get()) { + commodore.register(maincmd); + //MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft("")) + var prefixedcmd = new LiteralCommandNode<>(command2MC.getPlugin().getName().toLowerCase() + ":" + path[0], maincmd.getCommand(), maincmd.getRequirement(), maincmd.getRedirect(), maincmd.getRedirectModifier(), maincmd.isFork()); + for (var child : maincmd.getChildren()) + prefixedcmd.addChild(child); + commodore.register(prefixedcmd); + } } } } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java index 74c95aa..4c425c1 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CustomTabCompleteMethod.java @@ -21,4 +21,9 @@ public @interface CustomTabCompleteMethod { * The subcommand(s) which have the parameter, by default the method's name */ String[] subcommand() default {}; + + /** + * Parameter types can provide tab completions. This allows disabling that. + */ + boolean ignoreTypeCompletion() default false; }