Expand ability to use Adventure Components in exceptions and metadata (#200)
* cloud-minecraft: Support preserving component error messages Any exception thrown by an argument parser that is a ComponentMessageThrowable will now have its component message directly queried. * minecraft-extras: Add Component-based description handling for help * Apply review comments
This commit is contained in:
parent
12f5c71504
commit
a6f8159410
3 changed files with 107 additions and 27 deletions
|
|
@ -36,6 +36,7 @@ import net.kyori.adventure.text.event.ClickEvent;
|
|||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.util.ComponentMessageThrowable;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
|
@ -53,31 +54,29 @@ import java.util.function.Function;
|
|||
*/
|
||||
public final class MinecraftExceptionHandler<C> {
|
||||
|
||||
private static final Component NULL = Component.text("null");
|
||||
|
||||
/**
|
||||
* Default component builder for {@link InvalidSyntaxException}
|
||||
*/
|
||||
public static final Function<Exception, Component> DEFAULT_INVALID_SYNTAX_FUNCTION =
|
||||
e -> Component.text()
|
||||
.append(Component.text("Invalid command syntax. Correct command syntax is: ", NamedTextColor.RED))
|
||||
e -> Component.text("Invalid command syntax. Correct command syntax is: ", NamedTextColor.RED)
|
||||
.append(ComponentHelper.highlight(
|
||||
Component.text(
|
||||
String.format("/%s", ((InvalidSyntaxException) e).getCorrectSyntax()),
|
||||
NamedTextColor.GRAY
|
||||
),
|
||||
NamedTextColor.WHITE
|
||||
))
|
||||
.build();
|
||||
));
|
||||
/**
|
||||
* Default component builder for {@link InvalidCommandSenderException}
|
||||
*/
|
||||
public static final Function<Exception, Component> DEFAULT_INVALID_SENDER_FUNCTION =
|
||||
e -> Component.text()
|
||||
.append(Component.text("Invalid command sender. You must be of type ", NamedTextColor.RED))
|
||||
e -> Component.text("Invalid command sender. You must be of type ", NamedTextColor.RED)
|
||||
.append(Component.text(
|
||||
((InvalidCommandSenderException) e).getRequiredSender().getSimpleName(),
|
||||
NamedTextColor.GRAY
|
||||
))
|
||||
.build();
|
||||
));
|
||||
/**
|
||||
* Default component builder for {@link NoPermissionException}
|
||||
*/
|
||||
|
|
@ -91,10 +90,8 @@ public final class MinecraftExceptionHandler<C> {
|
|||
* Default component builder for {@link ArgumentParseException}
|
||||
*/
|
||||
public static final Function<Exception, Component> DEFAULT_ARGUMENT_PARSING_FUNCTION =
|
||||
e -> Component.text()
|
||||
.append(Component.text("Invalid command argument: ", NamedTextColor.RED))
|
||||
.append(Component.text(e.getCause().getMessage(), NamedTextColor.GRAY))
|
||||
.build();
|
||||
e -> Component.text("Invalid command argument: ", NamedTextColor.RED)
|
||||
.append(getMessage(e.getCause()).colorIfAbsent(NamedTextColor.GRAY));
|
||||
/**
|
||||
* Default component builder for {@link CommandExecutionException}
|
||||
*
|
||||
|
|
@ -110,6 +107,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
final String stackTrace = writer.toString().replaceAll("\t", " ");
|
||||
final HoverEvent<Component> hover = HoverEvent.showText(
|
||||
Component.text()
|
||||
.append(getMessage(cause))
|
||||
.append(Component.text(stackTrace))
|
||||
.append(Component.newline())
|
||||
.append(Component.text(
|
||||
|
|
@ -120,10 +118,8 @@ public final class MinecraftExceptionHandler<C> {
|
|||
);
|
||||
final ClickEvent click = ClickEvent.copyToClipboard(stackTrace);
|
||||
return Component.text()
|
||||
.append(Component.text(
|
||||
"An internal error occurred while attempting to perform this command.",
|
||||
NamedTextColor.RED
|
||||
))
|
||||
.content("An internal error occurred while attempting to perform this command.")
|
||||
.color(NamedTextColor.RED)
|
||||
.hoverEvent(hover)
|
||||
.clickEvent(click)
|
||||
.build();
|
||||
|
|
@ -251,7 +247,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
final @NonNull CommandManager<C> manager,
|
||||
final @NonNull Function<@NonNull C, @NonNull Audience> audienceMapper
|
||||
) {
|
||||
if (componentBuilders.containsKey(ExceptionType.INVALID_SYNTAX)) {
|
||||
if (this.componentBuilders.containsKey(ExceptionType.INVALID_SYNTAX)) {
|
||||
manager.registerExceptionHandler(
|
||||
InvalidSyntaxException.class,
|
||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
||||
|
|
@ -260,7 +256,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
)
|
||||
);
|
||||
}
|
||||
if (componentBuilders.containsKey(ExceptionType.INVALID_SENDER)) {
|
||||
if (this.componentBuilders.containsKey(ExceptionType.INVALID_SENDER)) {
|
||||
manager.registerExceptionHandler(
|
||||
InvalidCommandSenderException.class,
|
||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
||||
|
|
@ -269,7 +265,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
)
|
||||
);
|
||||
}
|
||||
if (componentBuilders.containsKey(ExceptionType.NO_PERMISSION)) {
|
||||
if (this.componentBuilders.containsKey(ExceptionType.NO_PERMISSION)) {
|
||||
manager.registerExceptionHandler(
|
||||
NoPermissionException.class,
|
||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
||||
|
|
@ -278,7 +274,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
)
|
||||
);
|
||||
}
|
||||
if (componentBuilders.containsKey(ExceptionType.ARGUMENT_PARSING)) {
|
||||
if (this.componentBuilders.containsKey(ExceptionType.ARGUMENT_PARSING)) {
|
||||
manager.registerExceptionHandler(
|
||||
ArgumentParseException.class,
|
||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
||||
|
|
@ -287,7 +283,7 @@ public final class MinecraftExceptionHandler<C> {
|
|||
)
|
||||
);
|
||||
}
|
||||
if (componentBuilders.containsKey(ExceptionType.COMMAND_EXECUTION)) {
|
||||
if (this.componentBuilders.containsKey(ExceptionType.COMMAND_EXECUTION)) {
|
||||
manager.registerExceptionHandler(
|
||||
CommandExecutionException.class,
|
||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
||||
|
|
@ -298,6 +294,10 @@ public final class MinecraftExceptionHandler<C> {
|
|||
}
|
||||
}
|
||||
|
||||
private static Component getMessage(final Throwable throwable) {
|
||||
final Component msg = ComponentMessageThrowable.getOrConvertMessage(throwable);
|
||||
return msg == null ? NULL : msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception types
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// 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.minecraft.extras;
|
||||
|
||||
import cloud.commandframework.meta.CommandMeta;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
/**
|
||||
* Extra command metadata for providing rich text.
|
||||
*
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class MinecraftExtrasMetaKeys {
|
||||
|
||||
/**
|
||||
* A component short description.
|
||||
*
|
||||
* <p>This will not set the plain-text description, but will be used in place of that meta key in help.</p>
|
||||
*/
|
||||
public static final CommandMeta.Key<Component> DESCRIPTION = CommandMeta.Key.of(
|
||||
Component.class,
|
||||
"cloud:minecraft_extras/description"
|
||||
);
|
||||
|
||||
/**
|
||||
* A component long description.
|
||||
*
|
||||
* <p>This will not set the plain-text long description, but will be used in place of that meta key in help.</p>
|
||||
*/
|
||||
public static final CommandMeta.Key<Component> LONG_DESCRIPTION = CommandMeta.Key.of(
|
||||
Component.class,
|
||||
"cloud:minecraft_extras/long_description"
|
||||
);
|
||||
|
||||
private MinecraftExtrasMetaKeys() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
|
@ -360,9 +361,16 @@ public final class MinecraftHelp<C> {
|
|||
return header;
|
||||
},
|
||||
(helpEntry, isLastOfPage) -> {
|
||||
final Component description = helpEntry.getDescription().isEmpty()
|
||||
? this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP)
|
||||
: this.descriptionDecorator.apply(helpEntry.getDescription());
|
||||
final Optional<Component> richDescription =
|
||||
helpEntry.getCommand().getCommandMeta().get(MinecraftExtrasMetaKeys.DESCRIPTION);
|
||||
final Component description;
|
||||
if (richDescription.isPresent()) {
|
||||
description = richDescription.get();
|
||||
} else if (helpEntry.getDescription().isEmpty()) {
|
||||
description = this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP);
|
||||
} else {
|
||||
description = this.descriptionDecorator.apply(helpEntry.getDescription());
|
||||
}
|
||||
|
||||
final boolean lastBranch =
|
||||
isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1;
|
||||
|
|
@ -439,9 +447,21 @@ public final class MinecraftHelp<C> {
|
|||
.append(text(": ", this.colors.primary))
|
||||
.append(this.highlight(text("/" + command, this.colors.highlight)))
|
||||
);
|
||||
final Component topicDescription = helpTopic.getDescription().isEmpty()
|
||||
? this.messageProvider.provide(sender, MESSAGE_NO_DESCRIPTION)
|
||||
: this.descriptionDecorator.apply(helpTopic.getDescription());
|
||||
/* Topics will use the long description if available, but fall back to the short description. */
|
||||
final Component richDescription =
|
||||
helpTopic.getCommand().getCommandMeta().get(MinecraftExtrasMetaKeys.LONG_DESCRIPTION)
|
||||
.orElse(helpTopic.getCommand().getCommandMeta().get(MinecraftExtrasMetaKeys.DESCRIPTION)
|
||||
.orElse(null));
|
||||
|
||||
final Component topicDescription;
|
||||
if (richDescription != null) {
|
||||
topicDescription = richDescription;
|
||||
} else if (helpTopic.getDescription().isEmpty()) {
|
||||
topicDescription = this.messageProvider.provide(sender, MESSAGE_NO_DESCRIPTION);
|
||||
} else {
|
||||
topicDescription = this.descriptionDecorator.apply(helpTopic.getDescription());
|
||||
}
|
||||
|
||||
final boolean hasArguments = helpTopic.getCommand().getArguments().size() > 1;
|
||||
audience.sendMessage(text()
|
||||
.append(text(" "))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue