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>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.7</version>
<version>1.8</version>
<scope>compile</scope>
</dependency>
<dependency>

View file

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

View file

@ -67,9 +67,10 @@ public class ComponentCommand extends ICommand2MC {
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;
@CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true)
public Iterable<String> 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) {

View file

@ -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<ICommand2MC, Command2MCSender> 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<ICommand2MC, Command2MCSender> implemen
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
}
@EventHandler
/*@EventHandler
public void onTabComplete(TabCompleteEvent event) {
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"));
event.getCompletions().clear();
//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<ICommand2MC, Command2MCSender> implemen
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
//System.out.println("Correct tabcomplete queried");
return Collections.emptyList();
}
@Override
public List<String> 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<ICommand2MC, Command2MCSender> implemen
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
SubcommandData<ICommand2MC> subcommand) {
LiteralCommandNode<Object> scmd;
if ((scmd = (LiteralCommandNode<Object>) 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<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.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<Object>) 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<ICommand2MC, Command2MCSender> 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<ICommand2MC> subcmd : subcmds) {
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 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 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<Object>) (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<ICommand2MC, Command2MCSender> 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<ICommand2MC, Command2MCSender> 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 <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();
scmd.addChild(arg);
scmd = arg;
}
}
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);
}
}
}
}

View file

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