From 0767f5671180d1b5673bf0450450a94d4dddf9d5 Mon Sep 17 00:00:00 2001 From: Kenzie Togami Date: Sat, 5 Oct 2019 05:06:18 -0400 Subject: [PATCH] Update to Piston 0.5.2 + Doctools/Deprecation improvements (#523) * Update to Piston 0.5.2 * [Doctools] Fix output, be verbose about deprecations * Improve deprecation system, doctools output (cherry picked from commit 03c0cce53e2fe8cf5ce6a0575a023be5eb05b5a3) --- .../bukkit/BukkitBlockCommandSender.java | 3 +- .../worldedit/bukkit/BukkitCommandSender.java | 3 +- .../sk89q/worldedit/bukkit/BukkitPlayer.java | 3 +- .../internal/util/RstWorldEditText.kt | 69 +++++++++ .../sk89q/worldedit/command/ToolCommands.java | 100 ++++++++++--- .../platform/PlatformCommandManager.java | 7 +- .../internal/command/CommandUtil.java | 135 ++++++++++++++++-- .../util/formatting/WorldEditText.java | 35 +++-- .../formatting/component/CommandUsageBox.java | 12 +- 9 files changed, 303 insertions(+), 64 deletions(-) create mode 100644 worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/RstWorldEditText.kt rename worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitTextAdapter.java => worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/WorldEditText.java (54%) diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java index 44f31fdef..36d5c5795 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.util.formatting.WorldEditText; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; @@ -91,7 +92,7 @@ public class BukkitBlockCommandSender extends AbstractNonPlayerActor implements @Override public void print(Component component) { - TextAdapter.sendComponent(sender, component); + TextAdapter.sendComponent(sender, WorldEditText.format(component)); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java index 324687745..ffb0037d5 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java @@ -25,6 +25,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.sk89q.worldedit.extension.platform.AbstractNonPlayerActor; import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.util.formatting.WorldEditText; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import java.util.UUID; @@ -92,7 +93,7 @@ public class BukkitCommandSender extends AbstractNonPlayerActor { @Override public void print(Component component) { - TextAdapter.sendComponent(sender, component); + TextAdapter.sendComponent(sender, WorldEditText.format(component)); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index 23b4a2ae1..3016dc15c 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -38,6 +38,7 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.HandSide; +import com.sk89q.worldedit.util.formatting.WorldEditText; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.adapter.bukkit.TextAdapter; import com.sk89q.worldedit.world.World; @@ -171,7 +172,7 @@ public class BukkitPlayer extends AbstractPlayerActor { @Override public void print(Component component) { - TextAdapter.sendComponent(player, component); + TextAdapter.sendComponent(player, WorldEditText.format(component)); } @Override diff --git a/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/RstWorldEditText.kt b/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/RstWorldEditText.kt new file mode 100644 index 000000000..2b37700f6 --- /dev/null +++ b/worldedit-core/doctools/src/main/kotlin/com/sk89q/worldedit/internal/util/RstWorldEditText.kt @@ -0,0 +1,69 @@ +package com.sk89q.worldedit.internal.util + +import com.sk89q.worldedit.util.formatting.WorldEditText +import com.sk89q.worldedit.util.formatting.text.Component +import com.sk89q.worldedit.util.formatting.text.TextComponent +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration +import org.enginehub.piston.util.TextHelper + +fun reduceToRst(component: Component): String { + val formatted = WorldEditText.format(component) + return formatAsRst(formatted).toString() +} + +private fun formatAsRst(component: Component, currentDeco: String? = null): CharSequence { + return when (component) { + is TextComponent -> { + val content = StringBuilder(component.content()) + + val deco = when { + // Actions that suggest themselves as commands are marked as code + component.isSuggestingAsCommand(content.toString()) -> "``" + component.decorations().any { it == TextDecoration.BOLD } -> "**" + component.decorations().any { it == TextDecoration.ITALIC } -> "*" + else -> null + } + + component.children().joinTo(content, separator = "") { + formatAsRst(it, deco ?: currentDeco) + } + + deco?.let { + require(currentDeco == null) { + "Nested decorations are hell in RST. \n" + + "Existing: $currentDeco; New: $deco\n" + + "Offender: ${TextHelper.reduceToText(component)}" + } + content.rstDeco(deco) + } + + content + } + is TranslatableComponent -> { + val content = StringBuilder(component.key()) + component.children().joinTo(content, separator = "") { formatAsRst(it, currentDeco) } + content + } + else -> component.children().joinToString(separator = "") { formatAsRst(it, currentDeco) } + } +} + +private fun Component.isSuggestingAsCommand(text: String): Boolean { + val ce = clickEvent() + return when (ce?.action()) { + ClickEvent.Action.RUN_COMMAND, + ClickEvent.Action.SUGGEST_COMMAND -> + ce.value() == text + else -> false + } +} + +private fun StringBuilder.rstDeco(deco: String): StringBuilder = apply { + ensureCapacity(length + deco.length * 2) + val insertionPoint = indexOfFirst { !it.isWhitespace() }.coerceAtLeast(0) + insert(insertionPoint, deco) + val insertionPointEnd = (indexOfLast { !it.isWhitespace() } + 1).takeUnless { it < 1 } ?: length + insert(insertionPointEnd, deco) +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java index 0bc487c8d..4d2f7bf1b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java @@ -20,29 +20,19 @@ package com.sk89q.worldedit.command; import com.boydti.fawe.config.BBC; -import com.boydti.fawe.object.brush.BrushSettings; import com.boydti.fawe.object.brush.InspectBrush; -import com.boydti.fawe.object.brush.TargetMode; -import com.boydti.fawe.object.brush.scroll.ScrollAction; -import com.boydti.fawe.object.brush.visualization.VisualMode; -import com.boydti.fawe.object.extent.ResettableExtent; -import com.boydti.fawe.util.MathMan; -import com.boydti.fawe.util.StringMan; -import com.google.common.collect.Iterables; -import com.sk89q.worldedit.EditSession; +import com.google.common.collect.Collections2; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; -import com.sk89q.worldedit.command.argument.Arguments; import com.sk89q.worldedit.command.tool.BlockDataCyler; import com.sk89q.worldedit.command.tool.BlockReplacer; -import com.sk89q.worldedit.command.tool.BrushTool; import com.sk89q.worldedit.command.tool.DistanceWand; import com.sk89q.worldedit.command.tool.FloatingTreeRemover; import com.sk89q.worldedit.command.tool.FloodFillTool; +import com.sk89q.worldedit.command.tool.InvalidToolBindException; import com.sk89q.worldedit.command.tool.LongRangeBuildTool; import com.sk89q.worldedit.command.tool.NavigationWand; import com.sk89q.worldedit.command.tool.QueryTool; @@ -51,25 +41,93 @@ import com.sk89q.worldedit.command.tool.TreePlanter; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.entity.Player; -import com.sk89q.worldedit.event.platform.CommandEvent; -import com.sk89q.worldedit.extension.platform.PlatformCommandManager; -import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; -import com.sk89q.worldedit.internal.annotation.Range; -import com.sk89q.worldedit.internal.command.CommandArgParser; -import com.sk89q.worldedit.internal.expression.Expression; +import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; +import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.util.HandSide; import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.item.ItemType; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import org.enginehub.piston.CommandManager; +import org.enginehub.piston.CommandManagerService; +import org.enginehub.piston.CommandMetadata; +import org.enginehub.piston.CommandParameters; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; -import org.enginehub.piston.annotation.param.Switch; - -import java.util.List; +import org.enginehub.piston.part.SubCommandPart; @CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) public class ToolCommands { + + public static void register(CommandRegistrationHandler registration, + CommandManager commandManager, + CommandManagerService commandManagerService, + WorldEdit worldEdit) { + // Collect the tool commands + CommandManager collect = commandManagerService.newCommandManager(); + + registration.register( + collect, + ToolCommandsRegistration.builder(), + new ToolCommands(worldEdit) + ); + + // Register deprecated global commands + Set commands = collect.getAllCommands() + .collect(Collectors.toSet()); + for (org.enginehub.piston.Command command : commands) { + if (command.getAliases().contains("unbind")) { + // Don't register new /tool unbind alias + command = command.toBuilder().aliases( + Collections2.filter(command.getAliases(), alias -> !"unbind".equals(alias)) + ).build(); + } + commandManager.register(CommandUtil.deprecate( + command, "Global tool names cause conflicts " + + "and will be removed in WorldEdit 8", ToolCommands::asNonGlobal + )); + } + + // Remove aliases with / in them, since it doesn't make sense for sub-commands. + Set nonGlobalCommands = commands.stream() + .map(command -> + command.toBuilder().aliases( + Collections2.filter(command.getAliases(), alias -> !alias.startsWith("/")) + ).build() + ) + .collect(Collectors.toSet()); + commandManager.register("tool", command -> { + command.addPart(SubCommandPart.builder( + TranslatableComponent.of("tool"), + TextComponent.of("The tool to bind") + ) + .withCommands(nonGlobalCommands) + .required() + .build()); + command.description(TextComponent.of("Binds a tool to the item in your hand")); + }); + } + + private static String asNonGlobal(org.enginehub.piston.Command oldCommand, + CommandParameters oldParameters) { + String name = Optional.ofNullable(oldParameters.getMetadata()) + .map(CommandMetadata::getCalledName) + .filter(n -> !n.startsWith("/")) + .orElseGet(oldCommand::getName); + return "/tool " + name; + } + + static void setToolNone(Player player, LocalSession session, String type) + throws InvalidToolBindException { + session.setTool(player.getItemInHand(HandSide.MAIN_HAND).getType(), null); + player.print(type + " unbound from your current item."); + } + private final WorldEdit we; public ToolCommands(WorldEdit we) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java index 0adb95db4..161844052 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformCommandManager.java @@ -144,7 +144,8 @@ import java.util.stream.Stream; import javax.annotation.Nullable; import org.enginehub.piston.Command; import org.enginehub.piston.CommandManager; -import org.enginehub.piston.TextConfig; +import org.enginehub.piston.config.ConfigHolder; +import org.enginehub.piston.config.TextConfig; import org.enginehub.piston.converter.ArgumentConverter; import org.enginehub.piston.converter.ArgumentConverters; import org.enginehub.piston.converter.ConversionResult; @@ -180,10 +181,6 @@ public final class PlatformCommandManager { private static final java.util.logging.Logger COMMAND_LOG = java.util.logging.Logger.getLogger("com.sk89q.worldedit.CommandLog"); - static { - TextConfig.setCommandPrefix("/"); - } - private final WorldEdit worldEdit; private final PlatformManager platformManager; private final CommandManagerServiceImpl commandManagerService; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java index bdcd8e541..01088a956 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/CommandUtil.java @@ -19,30 +19,147 @@ package com.sk89q.worldedit.internal.command; +import static com.google.common.base.Preconditions.checkState; +import static java.util.stream.Collectors.toList; + import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.PlatformCommandManager; import com.sk89q.worldedit.internal.util.Substring; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; -import org.enginehub.piston.Command; -import org.enginehub.piston.exception.CommandException; -import org.enginehub.piston.inject.InjectedValueAccess; -import org.enginehub.piston.inject.Key; -import org.enginehub.piston.part.SubCommandPart; - +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkState; -import static java.util.stream.Collectors.toList; +import org.enginehub.piston.Command; +import org.enginehub.piston.CommandParameters; +import org.enginehub.piston.NoInputCommandParameters; +import org.enginehub.piston.exception.CommandException; +import org.enginehub.piston.inject.InjectedValueAccess; +import org.enginehub.piston.inject.Key; +import org.enginehub.piston.part.SubCommandPart; public class CommandUtil { + private static final Component DEPRECATION_MARKER = TextComponent.of("This command is deprecated."); + + private static Component makeDeprecatedFooter(String reason, Component newCommand) { + return TextComponent.builder() + .append(DEPRECATION_MARKER) + .append(" " + reason + ".") + .append(TextComponent.newline()) + .append(TextComponent.of("Use ", TextColor.GOLD, TextDecoration.ITALIC)) + .append(newCommand) + .append(TextComponent.of(" instead.", TextColor.GOLD, TextDecoration.ITALIC)) + .build(); + } + + public interface NewCommandGenerator { + + String newCommand(Command oldCommand, CommandParameters oldParameters); + + } + + public static Command deprecate(Command command, String reason, + NewCommandGenerator newCommandGenerator) { + Component deprecatedWarning = makeDeprecatedFooter( + reason, + newCommandSuggestion(newCommandGenerator, + NoInputCommandParameters.builder().build(), + command) + ); + return command.toBuilder() + .action(parameters -> + deprecatedCommandWarning(parameters, command, reason, newCommandGenerator)) + .footer(command.getFooter() + .map(existingFooter -> existingFooter + .append(TextComponent.newline()).append(deprecatedWarning)) + .orElse(deprecatedWarning)) + .build(); + } + + public static Optional footerWithoutDeprecation(Command command) { + return command.getFooter() + .filter(footer -> anyComponent(footer, Predicate.isEqual(DEPRECATION_MARKER))) + .map(footer -> Optional.of( + replaceDeprecation(footer) + )) + .orElseGet(command::getFooter); + } + + public static Optional deprecationWarning(Command command) { + return command.getFooter() + .map(CommandUtil::extractDeprecation) + .orElseGet(command::getFooter); + } + + public static boolean isDeprecated(Command command) { + return command.getFooter() + .filter(footer -> anyComponent(footer, Predicate.isEqual(DEPRECATION_MARKER))) + .isPresent(); + } + + private static boolean anyComponent(Component component, Predicate test) { + return test.test(component) || component.children().stream() + .anyMatch(x -> anyComponent(x, test)); + } + + private static Component replaceDeprecation(Component component) { + if (component.children().stream().anyMatch(Predicate.isEqual(DEPRECATION_MARKER))) { + return TextComponent.empty(); + } + return component.children( + component.children().stream() + .map(CommandUtil::replaceDeprecation) + .collect(toList()) + ); + } + + private static Optional extractDeprecation(Component component) { + if (component.children().stream().anyMatch(Predicate.isEqual(DEPRECATION_MARKER))) { + return Optional.of(component); + } + return component.children().stream() + .map(CommandUtil::extractDeprecation) + .filter(Optional::isPresent) + .map(Optional::get) + .findAny(); + } + + private static int deprecatedCommandWarning( + CommandParameters parameters, + Command command, + String reason, + NewCommandGenerator generator + ) throws Exception { + parameters.injectedValue(Key.of(Actor.class)) + .ifPresent(actor -> { + Component suggestion = newCommandSuggestion(generator, parameters, command); + actor.print(TextComponent.of(reason + ". Please use ", TextColor.GOLD) + .append(suggestion) + .append(TextComponent.of(" instead.")) + ); + }); + return command.getAction().run(parameters); + } + + private static Component newCommandSuggestion(NewCommandGenerator generator, + CommandParameters parameters, + Command command) { + String suggestedCommand = generator.newCommand(command, parameters); + return TextComponent.of(suggestedCommand) + .decoration(TextDecoration.UNDERLINED, true) + .clickEvent(ClickEvent.suggestCommand(suggestedCommand)); + } + public static Map getSubCommands(Command currentCommand) { return currentCommand.getParts().stream() .filter(p -> p instanceof SubCommandPart) diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitTextAdapter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/WorldEditText.java similarity index 54% rename from worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitTextAdapter.java rename to worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/WorldEditText.java index fba2c2b1e..897a3fa46 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitTextAdapter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/WorldEditText.java @@ -17,32 +17,29 @@ * along with this program. If not, see . */ -package com.sk89q.worldedit.bukkit; +package com.sk89q.worldedit.util.formatting; import com.sk89q.worldedit.util.formatting.text.Component; -import com.sk89q.worldedit.util.formatting.text.TextComponent; -import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import org.enginehub.piston.config.ConfigHolder; +import org.enginehub.piston.config.TextConfig; +import org.enginehub.piston.util.TextHelper; -public class BukkitTextAdapter { +public class WorldEditText { + public static final ConfigHolder CONFIG_HOLDER = ConfigHolder.create(); + + static { + CONFIG_HOLDER.getConfig(TextConfig.commandPrefix()).setValue("/"); + } + + public static Component format(Component component) { + return CONFIG_HOLDER.replace(component); + } public static String reduceToText(Component component) { - StringBuilder text = new StringBuilder(); - appendTextTo(text, component); - return text.toString(); + return TextHelper.reduceToText(format(component)); } - private static void appendTextTo(StringBuilder builder, Component component) { - if (component instanceof TextComponent) { - builder.append(((TextComponent) component).content()); - } else if (component instanceof TranslatableComponent) { - builder.append(((TranslatableComponent) component).key()); - } - for (Component child : component.children()) { - appendTextTo(builder, child); - } - } - - private BukkitTextAdapter() { + private WorldEditText() { } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java index 2c27e044a..333ff8883 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/component/CommandUsageBox.java @@ -29,9 +29,9 @@ import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; import java.util.List; import javax.annotation.Nullable; -import org.enginehub.piston.ColorConfig; import org.enginehub.piston.Command; import org.enginehub.piston.CommandParameters; +import org.enginehub.piston.config.ColorConfig; import org.enginehub.piston.util.HelpGenerator; /** @@ -71,15 +71,13 @@ public class CommandUsageBox extends TextComponentProducer { .append(HelpGenerator.create(commands).getFullHelp()); if (getSubCommands(Iterables.getLast(commands)).size() > 0) { boxContent.append(TextComponent.newline()) - .append(TextComponent.builder("> ") - .color(ColorConfig.getHelpText()) - .append(TextComponent.builder("List Subcommands") - .color(ColorConfig.getMainText()) + .append(ColorConfig.helpText().wrap(TextComponent.builder("> ") + .append(ColorConfig.mainText().wrap(TextComponent.builder("List Subcommands") .decoration(TextDecoration.ITALIC, true) .clickEvent(ClickEvent.runCommand(helpRootCommand + " -s " + commandString)) .hoverEvent(HoverEvent.showText(TextComponent.of("List all subcommands of this command"))) - .build()) - .build()); + .build())) + .build())); } MessageBox box = new MessageBox("Help for " + commandString, boxContent);