From 5224050c99087ca96933eab91225eaf559e2003a Mon Sep 17 00:00:00 2001 From: Irmo van den Berge Date: Wed, 16 Dec 2020 12:31:30 +0100 Subject: [PATCH] :sparkles: Support repeating literals or argument names (#168) Signed-off-by: Irmo van den Berge --- .../java/cloud/commandframework/Command.java | 175 ++++++++++++++---- .../commandframework/CommandComponent.java | 111 +++++++++++ .../CommandHelpHandlerTest.java | 9 +- .../commandframework/CommandTreeTest.java | 72 +++++++ .../minecraft/extras/MinecraftHelp.java | 16 +- 5 files changed, 330 insertions(+), 53 deletions(-) create mode 100644 cloud-core/src/main/java/cloud/commandframework/CommandComponent.java diff --git a/cloud-core/src/main/java/cloud/commandframework/Command.java b/cloud-core/src/main/java/cloud/commandframework/Command.java index f81a87ac..f0c18e21 100644 --- a/cloud-core/src/main/java/cloud/commandframework/Command.java +++ b/cloud-core/src/main/java/cloud/commandframework/Command.java @@ -43,13 +43,13 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * A command consists out of a chain of {@link CommandArgument command arguments}. @@ -58,7 +58,8 @@ import java.util.function.Consumer; */ public class Command { - private final Map<@NonNull CommandArgument, @NonNull Description> arguments; + private final List<@NonNull CommandComponent> components; + private final List<@NonNull CommandArgument> arguments; private final CommandExecutionHandler commandExecutionHandler; private final Class senderType; private final CommandPermission commandPermission; @@ -67,26 +68,27 @@ public class Command { /** * Construct a new command * - * @param commandArguments Command argument and description pairs + * @param commandComponents Command component argument and description * @param commandExecutionHandler Execution handler * @param senderType Required sender type. May be {@code null} * @param commandPermission Command permission * @param commandMeta Command meta instance */ public Command( - final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments, + final @NonNull List<@NonNull CommandComponent> commandComponents, final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, final @Nullable Class senderType, final @NonNull CommandPermission commandPermission, final @NonNull CommandMeta commandMeta ) { - this.arguments = Objects.requireNonNull(commandArguments, "Command arguments may not be null"); - if (this.arguments.size() == 0) { - throw new IllegalArgumentException("At least one command argument is required"); + this.components = Objects.requireNonNull(commandComponents, "Command components may not be null"); + this.arguments = this.components.stream().map(CommandComponent::getArgument).collect(Collectors.toList()); + if (this.components.isEmpty()) { + throw new IllegalArgumentException("At least one command component is required"); } // Enforce ordering of command arguments boolean foundOptional = false; - for (final CommandArgument argument : this.arguments.keySet()) { + for (final CommandArgument argument : this.arguments) { if (argument.getName().isEmpty()) { throw new IllegalArgumentException("Argument names may not be empty"); } @@ -109,18 +111,77 @@ public class Command { /** * Construct a new command * - * @param commandArguments Command arguments + * @param commandComponents Command components * @param commandExecutionHandler Execution handler * @param senderType Required sender type. May be {@code null} * @param commandMeta Command meta instance */ + public Command( + final @NonNull List<@NonNull CommandComponent> commandComponents, + final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, + final @Nullable Class senderType, + final @NonNull CommandMeta commandMeta + ) { + this(commandComponents, commandExecutionHandler, senderType, Permission.empty(), commandMeta); + } + + /** + * Construct a new command + * + * @param commandComponents Command components + * @param commandExecutionHandler Execution handler + * @param commandPermission Command permission + * @param commandMeta Command meta instance + */ + public Command( + final @NonNull List<@NonNull CommandComponent> commandComponents, + final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, + final @NonNull CommandPermission commandPermission, + final @NonNull CommandMeta commandMeta + ) { + this(commandComponents, commandExecutionHandler, null, commandPermission, commandMeta); + } + + /** + * Construct a new command + * + * @param commandArguments Command argument and description pairs + * @param commandExecutionHandler Execution handler + * @param senderType Required sender type. May be {@code null} + * @param commandPermission Command permission + * @param commandMeta Command meta instance + * @deprecated Map does not allow for the same literal or variable argument name to repeat + * @see #Command(List, CommandExecutionHandler, Class, CommandPermission, CommandMeta) + */ + @Deprecated + public Command( + final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments, + final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, + final @Nullable Class senderType, + final @NonNull CommandPermission commandPermission, + final @NonNull CommandMeta commandMeta + ) { + this(mapToComponents(commandArguments), commandExecutionHandler, senderType, commandPermission, commandMeta); + } + + /** + * Construct a new command + * + * @param commandArguments Command arguments + * @param commandExecutionHandler Execution handler + * @param senderType Required sender type. May be {@code null} + * @param commandMeta Command meta instance + * @deprecated Map does not allow for the same literal or variable argument name to repeat + * @see #Command(List, CommandExecutionHandler, Class, CommandMeta) + */ + @Deprecated public Command( final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments, final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, final @Nullable Class senderType, final @NonNull CommandMeta commandMeta ) { - this(commandArguments, commandExecutionHandler, senderType, Permission.empty(), commandMeta); + this(mapToComponents(commandArguments), commandExecutionHandler, senderType, commandMeta); } /** @@ -130,14 +191,27 @@ public class Command { * @param commandExecutionHandler Execution handler * @param commandPermission Command permission * @param commandMeta Command meta instance + * @deprecated Map does not allow for the same literal or variable argument name to repeat + * @see #Command(List, CommandExecutionHandler, CommandPermission, CommandMeta) */ + @Deprecated public Command( final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments, final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, final @NonNull CommandPermission commandPermission, final @NonNull CommandMeta commandMeta ) { - this(commandArguments, commandExecutionHandler, null, commandPermission, commandMeta); + this(mapToComponents(commandArguments), commandExecutionHandler, commandPermission, commandMeta); + } + + // Converts a map of CommandArgument and Description pairs to a List of CommandComponent + // Used for backwards-compatibility + private static @NonNull List<@NonNull CommandComponent> mapToComponents( + final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments + ) { + return commandArguments.entrySet().stream() + .map(e -> CommandComponent.of(e.getKey(), e.getValue())) + .collect(Collectors.toList()); } /** @@ -157,13 +231,13 @@ public class Command { final @NonNull Description description, final @NonNull String... aliases ) { - final Map<@NonNull CommandArgument, @NonNull Description> map = new LinkedHashMap<>(); - map.put(StaticArgument.of(commandName, aliases), description); + final List> commands = new ArrayList<>(); + commands.add(CommandComponent.of(StaticArgument.of(commandName, aliases), description)); return new Builder<>( null, commandMeta, null, - map, + commands, new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty(), Collections.emptyList() @@ -185,13 +259,13 @@ public class Command { final @NonNull CommandMeta commandMeta, final @NonNull String... aliases ) { - final Map, Description> map = new LinkedHashMap<>(); - map.put(StaticArgument.of(commandName, aliases), Description.empty()); + final List> commands = new ArrayList<>(); + commands.add(CommandComponent.of(StaticArgument.of(commandName, aliases), Description.empty())); return new Builder<>( null, commandMeta, null, - map, + commands, new CommandExecutionHandler.NullCommandExecutionHandler<>(), Permission.empty(), Collections.emptyList() @@ -201,10 +275,19 @@ public class Command { /** * Return a copy of the command argument array * - * @return Copy of the command argument array + * @return Copy of the command argument array. This List is mutable. */ public @NonNull List> getArguments() { - return new ArrayList<>(this.arguments.keySet()); + return new ArrayList<>(this.arguments); + } + + /** + * Returns a copy of the command component array + * + * @return Copy of the command component array. This List is mutable. + */ + public @NonNull List> getComponents() { + return new ArrayList<>(this.components); } /** @@ -248,9 +331,18 @@ public class Command { * * @param argument Argument * @return Argument description + * @throws IllegalArgumentException If the command argument does not exist + * @deprecated More than one matching command argument may exist per command. + * Use {@link #getArguments()} and search in that, instead. */ + @Deprecated public @NonNull String getArgumentDescription(final @NonNull CommandArgument argument) { - return this.arguments.get(argument).getDescription(); + for (final CommandComponent component : this.components) { + if (component.getArgument().equals(argument)) { + return component.getDescription().getDescription(); + } + } + throw new IllegalArgumentException("Command argument not found: " + argument); } @Override @@ -282,7 +374,7 @@ public class Command { public static final class Builder { private final CommandMeta commandMeta; - private final Map, Description> commandArguments; + private final List> commandComponents; private final CommandExecutionHandler commandExecutionHandler; private final Class senderType; private final CommandPermission commandPermission; @@ -293,14 +385,14 @@ public class Command { final @Nullable CommandManager commandManager, final @NonNull CommandMeta commandMeta, final @Nullable Class senderType, - final @NonNull Map<@NonNull CommandArgument, @NonNull Description> commandArguments, + final @NonNull List<@NonNull CommandComponent> commandComponents, final @NonNull CommandExecutionHandler<@NonNull C> commandExecutionHandler, final @NonNull CommandPermission commandPermission, final @NonNull Collection> flags ) { this.commandManager = commandManager; this.senderType = senderType; - this.commandArguments = Objects.requireNonNull(commandArguments, "Arguments may not be null"); + this.commandComponents = Objects.requireNonNull(commandComponents, "Components may not be null"); this.commandExecutionHandler = Objects.requireNonNull(commandExecutionHandler, "Execution handler may not be null"); this.commandPermission = Objects.requireNonNull(commandPermission, "Permission may not be null"); this.commandMeta = Objects.requireNonNull(commandMeta, "Meta may not be null"); @@ -346,7 +438,7 @@ public class Command { this.commandManager, commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, this.commandPermission, this.flags @@ -388,7 +480,7 @@ public class Command { commandManager, this.commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, this.commandPermission, this.flags @@ -465,13 +557,13 @@ public class Command { + " Use CommandArgument#copy to create a copy of the argument."); } argument.setArgumentRegistered(); - final Map, Description> commandArgumentMap = new LinkedHashMap<>(this.commandArguments); - commandArgumentMap.put(argument, description); + final List> commandComponents = new ArrayList<>(this.commandComponents); + commandComponents.add(CommandComponent.of(argument, description)); return new Builder<>( this.commandManager, this.commandMeta, this.senderType, - commandArgumentMap, + commandComponents, this.commandExecutionHandler, this.commandPermission, this.flags @@ -491,13 +583,13 @@ public class Command { final CommandArgument.@NonNull Builder builder, final @NonNull Description description ) { - final Map, Description> commandArgumentMap = new LinkedHashMap<>(this.commandArguments); - commandArgumentMap.put(builder.build(), description); + final List> commandComponents = new ArrayList<>(this.commandComponents); + commandComponents.add(CommandComponent.of(builder.build(), description)); return new Builder<>( this.commandManager, this.commandMeta, this.senderType, - commandArgumentMap, + commandComponents, this.commandExecutionHandler, this.commandPermission, this.flags @@ -676,7 +768,7 @@ public class Command { this.commandManager, this.commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, commandExecutionHandler, this.commandPermission, this.flags @@ -694,7 +786,7 @@ public class Command { this.commandManager, this.commandMeta, senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, this.commandPermission, this.flags @@ -712,7 +804,7 @@ public class Command { this.commandManager, this.commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, permission, this.flags @@ -730,7 +822,7 @@ public class Command { this.commandManager, this.commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, Permission.of(permission), this.flags @@ -750,12 +842,13 @@ public class Command { */ public @NonNull Builder proxies(final @NonNull Command command) { Builder builder = this; - for (final CommandArgument argument : command.getArguments()) { + for (final CommandComponent component : command.getComponents()) { + final CommandArgument argument = component.getArgument(); if (argument instanceof StaticArgument) { continue; } final CommandArgument builtArgument = argument.copy(); - builder = builder.argument(builtArgument, Description.of(command.getArgumentDescription(argument))); + builder = builder.argument(builtArgument, component.getDescription()); } if (this.commandPermission.toString().isEmpty()) { builder = builder.permission(command.getCommandPermission()); @@ -787,7 +880,7 @@ public class Command { this.commandManager, this.commandMeta, this.senderType, - this.commandArguments, + this.commandComponents, this.commandExecutionHandler, this.commandPermission, Collections.unmodifiableList(flags) @@ -811,14 +904,14 @@ public class Command { * @return Built command */ public @NonNull Command build() { - final LinkedHashMap, Description> commandArguments = new LinkedHashMap<>(this.commandArguments); + final List> commandComponents = new ArrayList<>(this.commandComponents); /* Construct flag node */ if (!flags.isEmpty()) { final FlagArgument flagArgument = new FlagArgument<>(this.flags); - commandArguments.put(flagArgument, Description.of("Command flags")); + commandComponents.add(CommandComponent.of(flagArgument, Description.of("Command flags"))); } return new Command<>( - Collections.unmodifiableMap(commandArguments), + Collections.unmodifiableList(commandComponents), this.commandExecutionHandler, this.senderType, this.commandPermission, diff --git a/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java b/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java new file mode 100644 index 00000000..2a5fbcfc --- /dev/null +++ b/cloud-core/src/main/java/cloud/commandframework/CommandComponent.java @@ -0,0 +1,111 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework; + +import java.util.Objects; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import cloud.commandframework.arguments.CommandArgument; + +/** + * A single literal or argument component of a command + * + * @param Command sender type + */ +public final class CommandComponent { + private final CommandArgument argument; + private final Description description; + + /** + * Initializes a new CommandComponent + * + * @param commandArgument Command Component Argument + * @param commandDescription Command Component Description + */ + private CommandComponent( + final @NonNull CommandArgument commandArgument, + final @NonNull Description commandDescription + ) { + this.argument = commandArgument; + this.description = commandDescription; + } + + /** + * Gets the command component argument details + * + * @return command component argument details + */ + public @NonNull CommandArgument getArgument() { + return this.argument; + } + + /** + * Gets the command component description + * + * @return command component description + */ + public @NonNull Description getDescription() { + return this.description; + } + + @Override + public int hashCode() { + return Objects.hash(getArgument(), getDescription()); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } else if (o instanceof CommandComponent) { + CommandComponent that = (CommandComponent) o; + return getArgument().equals(that.getArgument()) + && getDescription().equals(that.getDescription()); + } else { + return false; + } + } + + @Override + public @NonNull String toString() { + return String.format("%s{argument=%s,description=%s}", this.getClass().getSimpleName(), + this.argument, this.description); + } + + /** + * Creates a new CommandComponent with the provided argument and description + * + * @param Command sender type + * @param commandArgument Command Component Argument + * @param commandDescription Command Component Description + * @return new CommandComponent + */ + public static @NonNull CommandComponent of( + final @NonNull CommandArgument commandArgument, + final @NonNull Description commandDescription + ) { + return new CommandComponent(commandArgument, commandDescription); + } +} diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java index a1c41c81..0f096763 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandHelpHandlerTest.java @@ -23,7 +23,6 @@ // package cloud.commandframework; -import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; @@ -142,17 +141,17 @@ class CommandHelpHandlerTest { .apply(helpTopic.getCommand().getArguments(), null)); System.out.printf(" ├── Description: %s\n", helpTopic.getDescription()); System.out.println(" └── Args: "); - final Iterator> iterator = helpTopic.getCommand().getArguments().iterator(); + final Iterator> iterator = helpTopic.getCommand().getComponents().iterator(); while (iterator.hasNext()) { - final CommandArgument argument = iterator.next(); + final CommandComponent component = iterator.next(); - String description = helpTopic.getCommand().getArgumentDescription(argument); + String description = component.getDescription().getDescription(); if (!description.isEmpty()) { description = ": " + description; } System.out.printf(" %s %s%s\n", iterator.hasNext() ? "├──" : "└──", manager.getCommandSyntaxFormatter().apply( - Collections.singletonList(argument), null), description); + Collections.singletonList(component.getArgument()), null), description); } } diff --git a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java index 4513be33..236a421f 100644 --- a/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java +++ b/cloud-core/src/test/java/cloud/commandframework/CommandTreeTest.java @@ -35,6 +35,8 @@ import cloud.commandframework.exceptions.AmbiguousNodeException; import cloud.commandframework.exceptions.NoPermissionException; import cloud.commandframework.meta.SimpleCommandMeta; import cloud.commandframework.types.tuples.Pair; +import io.leangen.geantyref.TypeToken; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,6 +44,7 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; +import java.util.List; import java.util.concurrent.CompletionException; class CommandTreeTest { @@ -220,6 +223,45 @@ class CommandTreeTest { )); } + @Test + void getArgumentsAndComponents() { + // Create and register a command + Command command = manager.commandBuilder("component") + .literal("literal", "literalalias") + .literal("detail", Description.of("detaildescription")) + .argument(CommandArgument.ofType(int.class, "argument"), + Description.of("argumentdescription")) + .build(); + manager.command(command); + + // Verify all the details we have configured are present + List> arguments = command.getArguments(); + List> components = command.getComponents(); + Assertions.assertEquals(arguments.size(), components.size()); + Assertions.assertEquals(4, components.size()); + + // Arguments should exactly match the component getArgument() result, in same order + for (int i = 0; i < components.size(); i++) { + Assertions.assertEquals(components.get(i).getArgument(), arguments.get(i)); + } + + // Argument configuration, we know component has the same argument so no need to test those + // TODO: Aliases + Assertions.assertEquals("component", arguments.get(0).getName()); + Assertions.assertEquals("literal", arguments.get(1).getName()); + Assertions.assertEquals("detail", arguments.get(2).getName()); + Assertions.assertEquals("argument", arguments.get(3).getName()); + + // Check argument is indeed a command argument + Assertions.assertEquals(TypeToken.get(int.class), arguments.get(3).getValueType()); + + // Check description is set for all components, is empty when not specified + Assertions.assertEquals("", components.get(0).getDescription().getDescription()); + Assertions.assertEquals("", components.get(1).getDescription().getDescription()); + Assertions.assertEquals("detaildescription", components.get(2).getDescription().getDescription()); + Assertions.assertEquals("argumentdescription", components.get(3).getDescription().getDescription()); + } + @Test void getSuggestions() { Assertions.assertFalse( @@ -319,6 +361,36 @@ class CommandTreeTest { newTree(); } + @Test + void testLiteralRepeatingArgument() { + // Build a command with a literal repeating + Command command = manager.commandBuilder("repeatingargscommand") + .literal("repeat") + .literal("middle") + .literal("repeat") + .build(); + + // Verify built command has the repeat argument twice + List> args = command.getArguments(); + Assertions.assertEquals(4, args.size()); + Assertions.assertEquals("repeatingargscommand", args.get(0).getName()); + Assertions.assertEquals("repeat", args.get(1).getName()); + Assertions.assertEquals("middle", args.get(2).getName()); + Assertions.assertEquals("repeat", args.get(3).getName()); + + // Register + manager.command(command); + + // If internally it drops repeating arguments, then it would register: + // > /repeatingargscommand repeat middle + // So check that we can register that exact command without an ambiguity exception + manager.command( + manager.commandBuilder("repeatingargscommand") + .literal("repeat") + .literal("middle") + ); + } + @Test void testDuplicateArgument() { final CommandArgument argument = StringArgument.of("test"); diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java index 449a7bdf..9102438d 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/MinecraftHelp.java @@ -23,6 +23,7 @@ // package cloud.commandframework.minecraft.extras; +import cloud.commandframework.CommandComponent; import cloud.commandframework.CommandHelpHandler; import cloud.commandframework.CommandManager; import cloud.commandframework.arguments.CommandArgument; @@ -391,34 +392,35 @@ public final class MinecraftHelp { )) ); - final Iterator> iterator = helpTopic.getCommand().getArguments().iterator(); + final Iterator> iterator = helpTopic.getCommand().getComponents().iterator(); /* Skip the first one because it's the command literal */ iterator.next(); while (iterator.hasNext()) { - final CommandArgument argument = iterator.next(); + final CommandComponent component = iterator.next(); + final CommandArgument argument = component.getArgument(); String syntax = this.commandManager.getCommandSyntaxFormatter() .apply(Collections.singletonList(argument), null); - final TextComponent.Builder component = Component.text() + final TextComponent.Builder textComponent = Component.text() .append(Component.text(" ")) .append(iterator.hasNext() ? this.branch() : this.lastBranch()) .append(this.highlight(Component.text(" " + syntax, this.colors.highlight))); if (!argument.isRequired()) { - component.append(Component.text( + textComponent.append(Component.text( " (" + this.messageProvider.apply(sender, MESSAGE_OPTIONAL) + ")", this.colors.alternateHighlight )); } - final String description = helpTopic.getCommand().getArgumentDescription(argument); + final String description = component.getDescription().getDescription(); if (!description.isEmpty()) { - component + textComponent .append(Component.text(" - ", this.colors.accent)) .append(Component.text(description, this.colors.text)); } - audience.sendMessage(Identity.nil(), component); + audience.sendMessage(Identity.nil(), textComponent); } } audience.sendMessage(Identity.nil(), this.footer(sender));