From ef15db682d87b4f645d5199f257ad58123b26575 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Jan 2021 22:19:48 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Allow=20for=20more=20easily=20using?= =?UTF-8?q?=20translatable=20components=20with=20MinecraftHelp=20(#197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Allow for more easily using translatable components with MinecraftHelp * Add missing Javadoc --- .../exceptions/parsing/ParserException.java | 23 +++ .../minecraft/extras/ComponentHelper.java | 5 +- .../minecraft/extras/MinecraftHelp.java | 163 +++++++++++++----- 3 files changed, 144 insertions(+), 47 deletions(-) diff --git a/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java b/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java index 98b4e637d..dfbcfbcc5 100644 --- a/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java +++ b/cloud-core/src/main/java/cloud/commandframework/exceptions/parsing/ParserException.java @@ -28,6 +28,8 @@ import cloud.commandframework.context.CommandContext; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Arrays; + public class ParserException extends IllegalArgumentException { private static final long serialVersionUID = -4409795575435072170L; @@ -56,6 +58,27 @@ public final String getMessage() { ); } + /** + * Get the error caption for this parser exception + * + * @return The caption + * @since 1.4.0 + */ + public @NonNull Caption errorCaption() { + return this.errorCaption; + } + + /** + * Get a copy of the caption variables present in this parser exception. + * The returned array may be empty if no variables are present. + * + * @return The caption variables + * @since 1.4.0 + */ + public @NonNull CaptionVariable @NonNull [] captionVariables() { + return Arrays.copyOf(this.captionVariables, this.captionVariables.length); + } + /** * Get the argument parser * diff --git a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ComponentHelper.java b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ComponentHelper.java index 8b43336eb..e7b6aafdc 100644 --- a/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ComponentHelper.java +++ b/cloud-minecraft/cloud-minecraft-extras/src/main/java/cloud/commandframework/minecraft/extras/ComponentHelper.java @@ -26,8 +26,10 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.translation.GlobalTranslator; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Locale; import java.util.regex.Pattern; final class ComponentHelper { @@ -63,7 +65,8 @@ public static int length(final @NonNull Component component) { if (component instanceof TextComponent) { length += ((TextComponent) component).content().length(); } - for (final Component child : component.children()) { + final Component translated = GlobalTranslator.render(component, Locale.getDefault()); + for (final Component child : translated.children()) { length += length(child); } return length; 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 d777dcf7e..76cb07077 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 @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; import static net.kyori.adventure.text.Component.space; @@ -92,7 +93,10 @@ public final class MinecraftHelp { private final Map messageMap = new HashMap<>(); private Predicate> commandFilter = c -> true; - private BiFunction messageProvider = (sender, key) -> this.messageMap.get(key); + private BiFunction stringMessageProvider = (sender, key) -> this.messageMap.get(key); + private MessageProvider messageProvider = + (sender, key, args) -> text(this.stringMessageProvider.apply(sender, key)); + private Function descriptionDecorator = Component::text; private HelpColors colors = DEFAULT_HELP_COLORS; private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH; private int maxResultsPerPage = DEFAULT_MAX_RESULTS_PER_PAGE; @@ -159,15 +163,30 @@ public MinecraftHelp( /** * Sets a filter for what commands are visible inside the help menu. - * When the {@link Predicate} tests true, then the command + * When the {@link Predicate} tests {@code true}, then the command * is included in the listings. + *

+ * The default filter will return true for all commands. * * @param commandPredicate Predicate to filter commands by + * @since 1.4.0 */ - public void setCommandFilter(final @NonNull Predicate> commandPredicate) { + public void commandFilter(final @NonNull Predicate> commandPredicate) { this.commandFilter = commandPredicate; } + /** + * Set the description decorator which will turn command and argument description strings into components. + *

+ * The default decorator simply calls {@link Component#text(String)} + * + * @param decorator description decorator + * @since 1.4.0 + */ + public void descriptionDecorator(final @NonNull Function<@NonNull String, @NonNull Component> decorator) { + this.descriptionDecorator = decorator; + } + /** * Configure a message * @@ -189,6 +208,21 @@ public void setMessage( * @param messageProvider The message provider to use */ public void setMessageProvider(final @NonNull BiFunction messageProvider) { + this.stringMessageProvider = messageProvider; + } + + /** + * Set a custom message provider function to be used for getting messages from keys. + *

+ * The keys are constants in {@link MinecraftHelp}. + *

+ * This version of the method which takes a {@link MessageProvider} will have priority over a message provider + * registered through {@link #setMessageProvider(BiFunction)} + * + * @param messageProvider The message provider to use + * @since 1.4.0 + */ + public void messageProvider(final @NonNull MessageProvider messageProvider) { this.messageProvider = messageProvider; } @@ -286,7 +320,8 @@ private void printNoResults( final Audience audience = this.getAudience(sender); audience.sendMessage(this.basicHeader(sender)); audience.sendMessage(LinearComponents.linear( - text(this.messageProvider.apply(sender, MESSAGE_NO_RESULTS_FOR_QUERY) + ": \"", this.colors.text), + this.messageProvider.provide(sender, MESSAGE_NO_RESULTS_FOR_QUERY).color(this.colors.text), + text(": \"", this.colors.text), this.highlight(text("/" + query, this.colors.highlight)), text("\"", this.colors.text) )); @@ -312,18 +347,22 @@ private void printIndexHelpTopic( header.add(this.showingResults(sender, query)); header.add(text() .append(this.lastBranch()) - .append(text( - String.format(" %s:", this.messageProvider.apply(sender, MESSAGE_AVAILABLE_COMMANDS)), - this.colors.text - )) + .append(space()) + .append( + this.messageProvider.provide( + sender, + MESSAGE_AVAILABLE_COMMANDS + ).color(this.colors.text) + ) + .append(text(":", this.colors.text)) .build() ); return header; }, (helpEntry, isLastOfPage) -> { - final String description = helpEntry.getDescription().isEmpty() - ? this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP) - : helpEntry.getDescription(); + final Component description = helpEntry.getDescription().isEmpty() + ? this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP) + : this.descriptionDecorator.apply(helpEntry.getDescription()); final boolean lastBranch = isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1; @@ -335,7 +374,7 @@ private void printIndexHelpTopic( String.format(" /%s", helpEntry.getSyntaxString()), this.colors.highlight )) - .hoverEvent(text(description, this.colors.text)) + .hoverEvent(description.color(this.colors.text)) .clickEvent(runCommand(this.commandPrefix + " " + helpEntry.getSyntaxString())) ) .build(); @@ -374,10 +413,8 @@ private void printMultiHelpTopic( return ComponentHelper.repeat(space(), headerIndentation) .append(lastBranch ? this.lastBranch() : this.branch()) .append(this.highlight(text(" /" + suggestion, this.colors.highlight)) - .hoverEvent(text( - this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP), - this.colors.text - )) + .hoverEvent(this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP) + .color(this.colors.text)) .clickEvent(runCommand(this.commandPrefix + " " + suggestion))); }, (currentPage, maxPages) -> this.paginatedFooter(sender, currentPage, maxPages, query), @@ -397,24 +434,30 @@ private void printVerboseHelpTopic( .apply(helpTopic.getCommand().getArguments(), null); audience.sendMessage(text() .append(this.lastBranch()) - .append(text(" " + this.messageProvider.apply(sender, MESSAGE_COMMAND) + ": ", this.colors.primary)) + .append(space()) + .append(this.messageProvider.provide(sender, MESSAGE_COMMAND).color(this.colors.primary)) + .append(text(": ", this.colors.primary)) .append(this.highlight(text("/" + command, this.colors.highlight))) ); - final String topicDescription = helpTopic.getDescription().isEmpty() - ? this.messageProvider.apply(sender, MESSAGE_NO_DESCRIPTION) - : helpTopic.getDescription(); + final Component topicDescription = helpTopic.getDescription().isEmpty() + ? this.messageProvider.provide(sender, MESSAGE_NO_DESCRIPTION) + : this.descriptionDecorator.apply(helpTopic.getDescription()); final boolean hasArguments = helpTopic.getCommand().getArguments().size() > 1; audience.sendMessage(text() .append(text(" ")) .append(hasArguments ? this.branch() : this.lastBranch()) - .append(text(" " + this.messageProvider.apply(sender, MESSAGE_DESCRIPTION) + ": ", this.colors.primary)) - .append(text(topicDescription, this.colors.text)) + .append(space()) + .append(this.messageProvider.provide(sender, MESSAGE_DESCRIPTION).color(this.colors.primary)) + .append(text(": ", this.colors.primary)) + .append(topicDescription.color(this.colors.text)) ); if (hasArguments) { audience.sendMessage(text() .append(text(" ")) .append(this.lastBranch()) - .append(text(" " + this.messageProvider.apply(sender, MESSAGE_ARGUMENTS) + ":", this.colors.primary)) + .append(space()) + .append(this.messageProvider.provide(sender, MESSAGE_ARGUMENTS).color(this.colors.primary)) + .append(text(":", this.colors.primary)) ); final Iterator> iterator = helpTopic.getCommand().getComponents().iterator(); @@ -433,15 +476,16 @@ private void printVerboseHelpTopic( .append(iterator.hasNext() ? this.branch() : this.lastBranch()) .append(this.highlight(text(" " + syntax, this.colors.highlight))); if (!argument.isRequired()) { - textComponent.append(text( - " (" + this.messageProvider.apply(sender, MESSAGE_OPTIONAL) + ")", - this.colors.alternateHighlight - )); + textComponent.append(text(" (", this.colors.alternateHighlight)); + textComponent.append( + this.messageProvider.provide(sender, MESSAGE_OPTIONAL).color(this.colors.alternateHighlight) + ); + textComponent.append(text(")", this.colors.alternateHighlight)); } final String description = component.getDescription().getDescription(); if (!description.isEmpty()) { textComponent.append(text(" - ", this.colors.accent)); - textComponent.append(text(description, this.colors.text)); + textComponent.append(this.descriptionDecorator.apply(description).color(this.colors.text)); } audience.sendMessage(textComponent); @@ -455,7 +499,8 @@ private void printVerboseHelpTopic( final @NonNull String query ) { return text() - .append(text(this.messageProvider.apply(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY) + ": \"", this.colors.text)) + .append(this.messageProvider.provide(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY).color(this.colors.text)) + .append(text(": \"", this.colors.text)) .append(this.highlight(text("/" + query, this.colors.highlight))) .append(text("\"", this.colors.text)) .build(); @@ -464,7 +509,7 @@ private void printVerboseHelpTopic( private @NonNull Component button( final char icon, final @NonNull String command, - final @NonNull String hoverText + final @NonNull Component hoverText ) { return text() .append(space()) @@ -473,7 +518,7 @@ private void printVerboseHelpTopic( .append(text(']', this.colors.accent)) .append(space()) .clickEvent(runCommand(command)) - .hoverEvent(text(hoverText, this.colors.text)) + .hoverEvent(hoverText) .build(); } @@ -496,7 +541,7 @@ private void printVerboseHelpTopic( final String nextPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage + 1); final Component nextPageButton = this.button('→', nextPageCommand, - this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_NEXT_PAGE) + this.messageProvider.provide(sender, MESSAGE_CLICK_FOR_NEXT_PAGE).color(this.colors.text) ); if (firstPage) { return this.header(sender, nextPageButton); @@ -504,7 +549,7 @@ private void printVerboseHelpTopic( final String previousPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage - 1); final Component previousPageButton = this.button('←', previousPageCommand, - this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_PREVIOUS_PAGE) + this.messageProvider.provide(sender, MESSAGE_CLICK_FOR_PREVIOUS_PAGE).color(this.colors.text) ); if (lastPage) { return this.header(sender, previousPageButton); @@ -530,9 +575,10 @@ private void printVerboseHelpTopic( } private @NonNull Component basicHeader(final @NonNull C sender) { - return this.header(sender, text( - " " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ", - this.colors.highlight + return this.header(sender, LinearComponents.linear( + space(), + this.messageProvider.provide(sender, MESSAGE_HELP_TITLE).color(this.colors.highlight), + space() )); } @@ -542,10 +588,9 @@ private void printVerboseHelpTopic( final int pages ) { return this.header(sender, text() - .append(text( - " " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ", - this.colors.highlight - )) + .append(space()) + .append(this.messageProvider.provide(sender, MESSAGE_HELP_TITLE).color(this.colors.highlight)) + .append(space()) .append(text("(", this.colors.alternateHighlight)) .append(text(currentPage, this.colors.text)) .append(text("/", this.colors.alternateHighlight)) @@ -580,12 +625,38 @@ private void printVerboseHelpTopic( final int attemptedPage, final int maxPages ) { - return this.highlight(text( - this.messageProvider.apply(sender, MESSAGE_PAGE_OUT_OF_RANGE) - .replace("", String.valueOf(attemptedPage)) - .replace("", String.valueOf(maxPages)), - this.colors.text - )); + return this.highlight( + this.messageProvider.provide( + sender, + MESSAGE_PAGE_OUT_OF_RANGE, + String.valueOf(attemptedPage), + String.valueOf(maxPages) + ) + .color(this.colors.text) + .replaceText(config -> { + config.matchLiteral(""); + config.replacement(String.valueOf(attemptedPage)); + }) + .replaceText(config -> { + config.matchLiteral(""); + config.replacement(String.valueOf(maxPages)); + }) + ); + } + + @FunctionalInterface + public interface MessageProvider { + + /** + * Creates a component from a command sender, key, and arguments + * + * @param sender command sender + * @param key message key (constants in {@link MinecraftHelp} + * @param args args + * @return component + */ + @NonNull Component provide(@NonNull C sender, @NonNull String key, @NonNull String... args); + } /**