From c88b267758240748e1049163445a4d7a06acbc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Tue, 15 Sep 2020 17:27:41 +0200 Subject: [PATCH] Make the brigadier mapper a bit smarter --- .../exceptions/InvalidSyntaxException.java | 10 ++ .../exceptions/NoPermissionException.java | 10 ++ .../exceptions/NoSuchCommandException.java | 10 ++ .../brigadier/CloudBrigadierManager.java | 159 ++++++++++++++---- .../commands/BukkitCommand.java | 2 + .../commands/PaperBrigadierListener.java | 11 +- 6 files changed, 160 insertions(+), 42 deletions(-) diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java index 653bb988..c9e79c79 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/InvalidSyntaxException.java @@ -67,4 +67,14 @@ public class InvalidSyntaxException extends CommandParseException { return String.format("Invalid command syntax. Correct syntax is: %s", this.correctSyntax); } + @Override + public final synchronized Throwable fillInStackTrace() { + return this; + } + + @Override + public final synchronized Throwable initCause(final Throwable cause) { + return this; + } + } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java index 7d441f4b..87fff804 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoPermissionException.java @@ -67,4 +67,14 @@ public class NoPermissionException extends CommandParseException { return this.missingPermission; } + @Override + public final synchronized Throwable fillInStackTrace() { + return this; + } + + @Override + public final synchronized Throwable initCause(final Throwable cause) { + return this; + } + } diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java index 79beaec7..0765a4ff 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/exceptions/NoSuchCommandException.java @@ -75,4 +75,14 @@ public final class NoSuchCommandException extends CommandParseException { return this.suppliedCommand; } + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + @Override + public synchronized Throwable initCause(final Throwable cause) { + return this; + } + } diff --git a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java index 10e298b6..12030160 100644 --- a/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java +++ b/cloud-minecraft/cloud-brigadier/src/main/java/com/intellectualsites/commands/brigadier/CloudBrigadierManager.java @@ -25,6 +25,7 @@ package com.intellectualsites.commands.brigadier; import com.google.common.collect.Maps; import com.google.common.reflect.TypeToken; +import com.intellectualsites.commands.CommandManager; import com.intellectualsites.commands.CommandTree; import com.intellectualsites.commands.components.CommandComponent; import com.intellectualsites.commands.components.StaticComponent; @@ -35,21 +36,31 @@ import com.intellectualsites.commands.components.standard.FloatComponent; import com.intellectualsites.commands.components.standard.IntegerComponent; import com.intellectualsites.commands.components.standard.ShortComponent; import com.intellectualsites.commands.components.standard.StringComponent; +import com.intellectualsites.commands.execution.preprocessor.CommandPreprocessingContext; import com.intellectualsites.commands.sender.CommandSender; +import com.mojang.brigadier.LiteralMessage; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Supplier; @@ -69,13 +80,22 @@ public final class CloudBrigadierManager { private final Map, Function, ? extends ArgumentType>> mappers; private final Map, Supplier>> defaultArgumentTypeSuppliers; + private final Supplier> dummyContextProvider; + private final CommandManager commandManager; /** * Create a new cloud brigadier manager + * + * @param commandManager Command manager + * @param dummyContextProvider Provider of dummy context for completions */ - public CloudBrigadierManager() { + public CloudBrigadierManager(@Nonnull final CommandManager commandManager, + @Nonnull final Supplier> + dummyContextProvider) { this.mappers = Maps.newHashMap(); this.defaultArgumentTypeSuppliers = Maps.newHashMap(); + this.commandManager = commandManager; + this.dummyContextProvider = dummyContextProvider; this.registerInternalMappings(); } @@ -195,25 +215,29 @@ public final class CloudBrigadierManager { * @param cloud component type (generic) * @return Brigadier argument type */ - @Nonnull + @Nullable @SuppressWarnings("all") - public > ArgumentType getArgument(@Nonnull final TypeToken componentType, - @Nonnull final K component) { + private > Pair, Boolean> getArgument( + @Nonnull final TypeToken componentType, + @Nonnull final K component) { final CommandComponent commandComponent = (CommandComponent) component; - final Function function = this.mappers.getOrDefault(componentType.getRawType(), t -> - createDefaultMapper((CommandComponent) component)); - return (ArgumentType) function.apply(commandComponent); + Function function = this.mappers.get(componentType.getRawType()); + if (function == null) { + return this.createDefaultMapper(commandComponent); + } + return new Pair<>((ArgumentType) function.apply(commandComponent), !component.getValueType().equals(String.class)); } @Nonnull - private > ArgumentType createDefaultMapper(@Nonnull final CommandComponent - component) { + private > Pair, Boolean> createDefaultMapper( + @Nonnull final CommandComponent + component) { final Supplier> argumentTypeSupplier = this.defaultArgumentTypeSuppliers.get(component.getValueType()); if (argumentTypeSupplier != null) { - return argumentTypeSupplier.get(); + return new Pair<>(argumentTypeSupplier.get(), true); } System.err.printf("Found not native mapping for '%s'\n", component.getValueType().getCanonicalName()); - return StringArgumentType.string(); + return new Pair<>(StringArgumentType.string(), false); } /** @@ -233,45 +257,106 @@ public final class CloudBrigadierManager { @Nonnull final com.mojang.brigadier.Command executor, @Nonnull final BiPredicate permissionChecker) { final LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder.literal(root.getLiteral()) - .requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", ""))); - if (cloudCommand.isLeaf() && cloudCommand.getValue() != null && cloudCommand.getValue().getOwningCommand() != null) { - literalArgumentBuilder.executes(executor); - } + .requires(sender -> permissionChecker.test(sender, cloudCommand.getNodeMeta().getOrDefault("permission", ""))) + .executes(executor); final LiteralCommandNode constructedRoot = literalArgumentBuilder.build(); for (final CommandTree.Node> child : cloudCommand.getChildren()) { - constructedRoot.addChild(this.constructCommandNode(child, permissionChecker, executor, suggestionProvider)); + constructedRoot.addChild(this.constructCommandNode(child, permissionChecker, executor, suggestionProvider).build()); } return constructedRoot; } - private CommandNode constructCommandNode(@Nonnull final CommandTree.Node> root, + private ArgumentBuilder constructCommandNode(@Nonnull final CommandTree.Node> root, @Nonnull final BiPredicate permissionChecker, @Nonnull final com.mojang.brigadier.Command executor, @Nonnull final SuggestionProvider suggestionProvider) { - CommandNode commandNode; + + ArgumentBuilder argumentBuilder; if (root.getValue() instanceof StaticComponent) { - final LiteralArgumentBuilder argumentBuilder = LiteralArgumentBuilder.literal(root.getValue().getName()) - .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))); - if (root.isLeaf()) { - argumentBuilder.executes(executor); - } - commandNode = argumentBuilder.build(); + argumentBuilder = LiteralArgumentBuilder.literal(root.getValue().getName()) + .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))) + .executes(executor); } else { - @SuppressWarnings("unchecked") final RequiredArgumentBuilder builder = RequiredArgumentBuilder - .argument(root.getValue().getName(), - (ArgumentType) getArgument(TypeToken.of(root.getValue().getClass()), - root.getValue())) - .suggests(suggestionProvider) - .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))); - if (root.isLeaf() || !root.getValue().isRequired()) { - builder.executes(executor); - } - commandNode = builder.build(); + final Pair, Boolean> pair = this.getArgument(TypeToken.of(root.getValue().getClass()), + root.getValue()); + final SuggestionProvider provider = pair.getRight() ? null : suggestionProvider; + argumentBuilder = RequiredArgumentBuilder + .argument(root.getValue().getName(), (ArgumentType) pair.getLeft()) + .suggests(provider) + .requires(sender -> permissionChecker.test(sender, root.getNodeMeta().getOrDefault("permission", ""))) + .executes(executor); } for (final CommandTree.Node> node : root.getChildren()) { - commandNode.addChild(constructCommandNode(node, permissionChecker, executor, suggestionProvider)); + argumentBuilder.then(constructCommandNode(node, permissionChecker, executor, suggestionProvider)); } - return commandNode; + return argumentBuilder; + } + + @Nonnull + private CompletableFuture buildSuggestions(@Nonnull final CommandComponent component, + @Nonnull final CommandContext s, + @Nonnull final SuggestionsBuilder builder) { + final com.intellectualsites.commands.context.CommandContext commandContext = this.dummyContextProvider.get(); + final LinkedList inputQueue = new LinkedList<>(Collections.singletonList(builder.getInput())); + final CommandPreprocessingContext commandPreprocessingContext = + new CommandPreprocessingContext<>(commandContext, inputQueue); + /* + List results = server.tabComplete(context.getSource().getBukkitSender(), builder.getInput(), + context.getSource().getWorld(), context.getSource().getPosition(), true); + */ + + String command = builder.getInput(); + if (command.startsWith("/") /* Minecraft specific */) { + command = command.substring(1); + } + final List suggestions = this.commandManager.suggest(commandContext.getSender(), command); + /*component.getParser().suggestions(commandContext, builder.getInput());*/ + for (final String suggestion : suggestions) { + System.out.printf("- %s\n", suggestion); + } + /* + System.out.println("Filtering out with: " + builder.getInput()); + final CommandSuggestionProcessor processor = this.commandManager.getCommandSuggestionProcessor(); + final List filteredSuggestions = processor.apply(commandPreprocessingContext, suggestions); + System.out.println("Current suggestions: "); + for (final String suggestion : filteredSuggestions) { + System.out.printf("- %s\n", suggestion); + }*/ + for (final String suggestion : suggestions) { + String tooltip = component.getName(); + if (!(component instanceof StaticComponent)) { + if (component.isRequired()) { + tooltip = '<' + tooltip + '>'; + } else { + tooltip = '[' + tooltip + ']'; + } + } + builder.suggest(suggestion, new LiteralMessage(tooltip)); + } + return builder.buildFuture(); + } + + + private static final class Pair { + + private final L left; + private final R right; + + private Pair(@Nonnull final L left, @Nonnull final R right) { + this.left = left; + this.right = right; + } + + @Nonnull + private L getLeft() { + return this.left; + } + + @Nonnull + private R getRight() { + return this.right; + } + } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java index e260cb74..77830744 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/com/intellectualsites/commands/BukkitCommand.java @@ -59,8 +59,10 @@ final class BukkitCommand { if (throwable != null) { + commandSender.sendMessage(ChatColor.RED + throwable.getMessage()); commandSender.sendMessage(ChatColor.RED + throwable.getCause().getMessage()); throwable.printStackTrace(); + throwable.getCause().printStackTrace(); } else { // Do something... commandSender.sendMessage("All good!"); diff --git a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java index 1e94660d..52f909d2 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/com/intellectualsites/commands/PaperBrigadierListener.java @@ -27,11 +27,11 @@ import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; import com.destroystokyo.paper.event.brigadier.CommandRegisteredEvent; import com.intellectualsites.commands.brigadier.CloudBrigadierManager; import com.intellectualsites.commands.components.CommandComponent; +import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.sender.CommandSender; import com.mojang.brigadier.arguments.ArgumentType; import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.event.EventHandler; @@ -50,15 +50,15 @@ class PaperBrigadierListener implements Listener { PaperBrigadierListener(@Nonnull final PaperCommandManager paperCommandManager) throws Exception { this.paperCommandManager = paperCommandManager; - this.brigadierManager = new CloudBrigadierManager<>(); + this.brigadierManager = new CloudBrigadierManager<>(this.paperCommandManager, + () -> new CommandContext<>(this.paperCommandManager.getCommandSenderMapper() + .apply(Bukkit.getConsoleSender()))); /* Register default mappings */ final String version = Bukkit.getServer().getClass().getPackage().getName(); this.nmsVersion = version.substring(version.lastIndexOf(".") + 1); try { /* Map UUID */ this.mapSimpleNMS(UUID.class, this.getNMSArgument("UUID").getConstructor()); - /* Map World */ - this.mapSimpleNMS(World.class, this.getNMSArgument("Dimension").getConstructor()); /* Map Enchantment */ this.mapSimpleNMS(Enchantment.class, this.getNMSArgument("Enchantment").getConstructor()); /* Map EntityType */ @@ -120,7 +120,8 @@ class PaperBrigadierListener implements Listener { event.getLiteral(), event.getBrigadierCommand(), event.getBrigadierCommand(), - (s, p) -> s.getBukkitSender().hasPermission(p))); + (s, p) -> p.isEmpty() + || s.getBukkitSender().hasPermission(p))); } }