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
This commit is contained in:
Norbi Peti 2020-04-07 22:08:09 +02:00
parent 82858b0a41
commit 9dae442950
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 79 additions and 35 deletions

View file

@ -194,7 +194,7 @@
<dependency> <dependency>
<groupId>me.lucko</groupId> <groupId>me.lucko</groupId>
<artifactId>commodore</artifactId> <artifactId>commodore</artifactId>
<version>1.7</version> <version>1.8</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -4,18 +4,28 @@ import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.ICommand2MC;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import java.util.Arrays;
import java.util.Optional;
@CommandClass @CommandClass
public class ChromaCommand extends ICommand2MC { 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 @Command2.Subcommand
public void reload(CommandSender sender, @Command2.OptionalArg Plugin plugin) { public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) {
if (plugin == null) if (plugin == null)
plugin = MainPlugin.Instance; plugin = MainPlugin.Instance;
if (!(plugin instanceof ButtonPlugin)) //Probably not a good idea to allow reloading any plugin's config if (plugin.tryReloadConfig())
sender.sendMessage("§c" + plugin.getName() + " doesn't support this.");
else if (((ButtonPlugin) plugin).tryReloadConfig())
sender.sendMessage("§b" + plugin.getName() + " config reloaded."); sender.sendMessage("§b" + plugin.getName() + " config reloaded.");
else else
sender.sendMessage("§cFailed to reload config. Check console."); sender.sendMessage("§cFailed to reload config. Check console.");

View file

@ -67,9 +67,10 @@ 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") @CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true)
public Iterable<String> list() { public Iterable<String> pluginTabcomplete() {
return Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator; 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) { private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {

View file

@ -19,9 +19,7 @@ import org.bukkit.Location;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.command.*; import org.bukkit.command.*;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
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 org.javatuples.Triplet;
@ -33,6 +31,7 @@ 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.concurrent.atomic.AtomicBoolean;
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.Collectors;
@ -70,9 +69,9 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
} }
String pg = permGroup(command, method); String pg = permGroup(command, method);
if (pg.length() == 0) continue; if (pg.length() == 0) continue;
perm = "chroma." + pg; String permGroup = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(perm, Bukkit.getPluginManager().addPermission(new Permission(permGroup,
PermissionDefault.OP)); //Do not allow any commands that belong to a group PermissionDefault.OP)); //Do not allow any commands that belong to a group
} }
@ -172,12 +171,16 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false)); .map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
} }
@EventHandler /*@EventHandler
public void onTabComplete(TabCompleteEvent event) { public void onTabComplete(TabCompleteEvent event) {
//System.out.println("Tabcomplete: " + event.getBuffer()); try {
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions")); event.getCompletions().clear(); //Remove player names
event.getCompletions().clear(); } 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 @Override
public boolean handleCommand(Command2MCSender sender, String commandline) { public boolean handleCommand(Command2MCSender sender, String commandline) {
@ -231,11 +234,13 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
@Override @Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
//System.out.println("Correct tabcomplete queried");
return Collections.emptyList(); return Collections.emptyList();
} }
@Override @Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
//System.out.println("Correct tabcomplete queried");
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@ -245,22 +250,33 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent, private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
SubcommandData<ICommand2MC> subcommand) { SubcommandData<ICommand2MC> subcommand) {
LiteralCommandNode<Object> scmd;
if ((scmd = (LiteralCommandNode<Object>) parent.getChild(path)) != null)
return scmd;
var scmdBuilder = LiteralArgumentBuilder.literal(path); var scmdBuilder = LiteralArgumentBuilder.literal(path);
if (subcommand != null) if (subcommand != null)
scmdBuilder.requires(o -> { scmdBuilder.requires(o -> {
var sender = commodore.getBukkitSender(o); var sender = commodore.getBukkitSender(o);
return ButtonPlugin.getCommand2MC().hasPermission(sender, subcommand.command, subcommand.method); return ButtonPlugin.getCommand2MC().hasPermission(sender, subcommand.command, subcommand.method);
}); });
var scmd = scmdBuilder.build(); scmd = scmdBuilder.build();
parent.addChild(scmd); parent.addChild(scmd);
return scmd; return scmd;
} }
private static void registerTabcomplete(ICommand2MC command2MC, List<SubcommandData<ICommand2MC>> subcmds, Command bukkitCommand) { private static void registerTabcomplete(ICommand2MC command2MC, List<SubcommandData<ICommand2MC>> subcmds, Command bukkitCommand) {
if (commodore == null) if (commodore == null) {
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance); //Register all to the Core, it's easier 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(" "); 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<Object>) node).findAny()
.orElseGet(() -> LiteralArgumentBuilder.literal(path[0]).build()); //Commodore 1.8 removes previous nodes
var cmd = maincmd; var cmd = maincmd;
for (int i = 1; i < path.length; i++) { for (int i = 1; i < path.length; i++) {
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);
@ -274,7 +290,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
.orElseGet(() -> new String[]{ .orElseGet(() -> new String[]{
ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim() 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()); })).collect(Collectors.toList());
for (SubcommandData<ICommand2MC> subcmd : subcmds) { for (SubcommandData<ICommand2MC> subcmd : subcmds) {
String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim(); String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim();
@ -323,20 +339,24 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
val param = subcmd.parameters[i - 1]; val param = subcmd.parameters[i - 1];
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class)) val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
.map(CustomTabComplete::value); .map(CustomTabComplete::value);
final Optional<Method> customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0())) var customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0()))
.filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1())) .filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1().param()))
.map(Triplet::getValue2).findAny(); .findAny();
var argb = RequiredArgumentBuilder.argument(param, type) var argb = RequiredArgumentBuilder.argument(param, type)
.suggests((SuggestionProvider<Object>) (context, builder) -> { .suggests((SuggestionProvider<Object>) (context, builder) -> {
if (parameter.isVarArgs()) { //Do it before the builder is used if (parameter.isVarArgs()) { //Do it before the builder is used
int x = context.getInput().lastIndexOf(' ') + 1; int nextTokenStart = context.getInput().lastIndexOf(' ') + 1;
builder = builder.createOffset(x); builder = builder.createOffset(nextTokenStart);
} }
if (customTC.isPresent()) if (customTC.isPresent())
for (val ctc : customTC.get()) for (val ctc : customTC.get())
builder.suggest(ctc); builder.suggest(ctc);
boolean ignoreCustomParamType = false;
if (customTCmethod.isPresent()) { 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 params = method.getParameters();
val args = new Object[params.length]; val args = new Object[params.length];
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) { for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
@ -382,7 +402,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
} }
} }
} }
if (customParamType) { if (!ignoreCustomParamType && customParamType) {
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(ptype); val converter = ButtonPlugin.getCommand2MC().paramConverters.get(ptype);
if (converter == null) if (converter == null)
TBMCCoreAPI.SendException("Could not find a suitable converter for type " + ptype.getSimpleName(), TBMCCoreAPI.SendException("Could not find a suitable converter for type " + ptype.getSimpleName(),
@ -395,19 +415,27 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
} }
if (ptype == boolean.class || ptype == Boolean.class) if (ptype == boolean.class || ptype == Boolean.class)
builder.suggest("true").suggest("false"); builder.suggest("true").suggest("false");
final String loweredInput = builder.getRemaining().toLowerCase();
return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered 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 <param> at the end after that s.getList().add(s.getList().remove(0))) //So we need to put the <param> 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(); var arg = argb.build();
scmd.addChild(arg); scmd.addChild(arg);
scmd = arg; scmd = arg;
} }
} }
commodore.register(maincmd); if (shouldRegister.get()) {
var prefixedcmd = new LiteralCommandNode<>(command2MC.getPlugin().getName().toLowerCase() + ":" + path[0], maincmd.getCommand(), maincmd.getRequirement(), maincmd.getRedirect(), maincmd.getRedirectModifier(), maincmd.isFork()); commodore.register(maincmd);
for (var child : maincmd.getChildren()) //MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
prefixedcmd.addChild(child); var prefixedcmd = new LiteralCommandNode<>(command2MC.getPlugin().getName().toLowerCase() + ":" + path[0], maincmd.getCommand(), maincmd.getRequirement(), maincmd.getRedirect(), maincmd.getRedirectModifier(), maincmd.isFork());
commodore.register(prefixedcmd); for (var child : maincmd.getChildren())
prefixedcmd.addChild(child);
commodore.register(prefixedcmd);
}
} }
} }
} }

View file

@ -21,4 +21,9 @@ 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 {};
/**
* Parameter types can provide tab completions. This allows disabling that.
*/
boolean ignoreTypeCompletion() default false;
} }