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.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
|
import net.kyori.adventure.util.ComponentMessageThrowable;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
@ -53,31 +54,29 @@ import java.util.function.Function;
|
||||||
*/
|
*/
|
||||||
public final class MinecraftExceptionHandler<C> {
|
public final class MinecraftExceptionHandler<C> {
|
||||||
|
|
||||||
|
private static final Component NULL = Component.text("null");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default component builder for {@link InvalidSyntaxException}
|
* Default component builder for {@link InvalidSyntaxException}
|
||||||
*/
|
*/
|
||||||
public static final Function<Exception, Component> DEFAULT_INVALID_SYNTAX_FUNCTION =
|
public static final Function<Exception, Component> DEFAULT_INVALID_SYNTAX_FUNCTION =
|
||||||
e -> Component.text()
|
e -> Component.text("Invalid command syntax. Correct command syntax is: ", NamedTextColor.RED)
|
||||||
.append(Component.text("Invalid command syntax. Correct command syntax is: ", NamedTextColor.RED))
|
|
||||||
.append(ComponentHelper.highlight(
|
.append(ComponentHelper.highlight(
|
||||||
Component.text(
|
Component.text(
|
||||||
String.format("/%s", ((InvalidSyntaxException) e).getCorrectSyntax()),
|
String.format("/%s", ((InvalidSyntaxException) e).getCorrectSyntax()),
|
||||||
NamedTextColor.GRAY
|
NamedTextColor.GRAY
|
||||||
),
|
),
|
||||||
NamedTextColor.WHITE
|
NamedTextColor.WHITE
|
||||||
))
|
));
|
||||||
.build();
|
|
||||||
/**
|
/**
|
||||||
* Default component builder for {@link InvalidCommandSenderException}
|
* Default component builder for {@link InvalidCommandSenderException}
|
||||||
*/
|
*/
|
||||||
public static final Function<Exception, Component> DEFAULT_INVALID_SENDER_FUNCTION =
|
public static final Function<Exception, Component> DEFAULT_INVALID_SENDER_FUNCTION =
|
||||||
e -> Component.text()
|
e -> Component.text("Invalid command sender. You must be of type ", NamedTextColor.RED)
|
||||||
.append(Component.text("Invalid command sender. You must be of type ", NamedTextColor.RED))
|
|
||||||
.append(Component.text(
|
.append(Component.text(
|
||||||
((InvalidCommandSenderException) e).getRequiredSender().getSimpleName(),
|
((InvalidCommandSenderException) e).getRequiredSender().getSimpleName(),
|
||||||
NamedTextColor.GRAY
|
NamedTextColor.GRAY
|
||||||
))
|
));
|
||||||
.build();
|
|
||||||
/**
|
/**
|
||||||
* Default component builder for {@link NoPermissionException}
|
* Default component builder for {@link NoPermissionException}
|
||||||
*/
|
*/
|
||||||
|
|
@ -91,10 +90,8 @@ public final class MinecraftExceptionHandler<C> {
|
||||||
* Default component builder for {@link ArgumentParseException}
|
* Default component builder for {@link ArgumentParseException}
|
||||||
*/
|
*/
|
||||||
public static final Function<Exception, Component> DEFAULT_ARGUMENT_PARSING_FUNCTION =
|
public static final Function<Exception, Component> DEFAULT_ARGUMENT_PARSING_FUNCTION =
|
||||||
e -> Component.text()
|
e -> Component.text("Invalid command argument: ", NamedTextColor.RED)
|
||||||
.append(Component.text("Invalid command argument: ", NamedTextColor.RED))
|
.append(getMessage(e.getCause()).colorIfAbsent(NamedTextColor.GRAY));
|
||||||
.append(Component.text(e.getCause().getMessage(), NamedTextColor.GRAY))
|
|
||||||
.build();
|
|
||||||
/**
|
/**
|
||||||
* Default component builder for {@link CommandExecutionException}
|
* Default component builder for {@link CommandExecutionException}
|
||||||
*
|
*
|
||||||
|
|
@ -110,6 +107,7 @@ public final class MinecraftExceptionHandler<C> {
|
||||||
final String stackTrace = writer.toString().replaceAll("\t", " ");
|
final String stackTrace = writer.toString().replaceAll("\t", " ");
|
||||||
final HoverEvent<Component> hover = HoverEvent.showText(
|
final HoverEvent<Component> hover = HoverEvent.showText(
|
||||||
Component.text()
|
Component.text()
|
||||||
|
.append(getMessage(cause))
|
||||||
.append(Component.text(stackTrace))
|
.append(Component.text(stackTrace))
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text(
|
.append(Component.text(
|
||||||
|
|
@ -120,10 +118,8 @@ public final class MinecraftExceptionHandler<C> {
|
||||||
);
|
);
|
||||||
final ClickEvent click = ClickEvent.copyToClipboard(stackTrace);
|
final ClickEvent click = ClickEvent.copyToClipboard(stackTrace);
|
||||||
return Component.text()
|
return Component.text()
|
||||||
.append(Component.text(
|
.content("An internal error occurred while attempting to perform this command.")
|
||||||
"An internal error occurred while attempting to perform this command.",
|
.color(NamedTextColor.RED)
|
||||||
NamedTextColor.RED
|
|
||||||
))
|
|
||||||
.hoverEvent(hover)
|
.hoverEvent(hover)
|
||||||
.clickEvent(click)
|
.clickEvent(click)
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -251,7 +247,7 @@ public final class MinecraftExceptionHandler<C> {
|
||||||
final @NonNull CommandManager<C> manager,
|
final @NonNull CommandManager<C> manager,
|
||||||
final @NonNull Function<@NonNull C, @NonNull Audience> audienceMapper
|
final @NonNull Function<@NonNull C, @NonNull Audience> audienceMapper
|
||||||
) {
|
) {
|
||||||
if (componentBuilders.containsKey(ExceptionType.INVALID_SYNTAX)) {
|
if (this.componentBuilders.containsKey(ExceptionType.INVALID_SYNTAX)) {
|
||||||
manager.registerExceptionHandler(
|
manager.registerExceptionHandler(
|
||||||
InvalidSyntaxException.class,
|
InvalidSyntaxException.class,
|
||||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
(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(
|
manager.registerExceptionHandler(
|
||||||
InvalidCommandSenderException.class,
|
InvalidCommandSenderException.class,
|
||||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
(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(
|
manager.registerExceptionHandler(
|
||||||
NoPermissionException.class,
|
NoPermissionException.class,
|
||||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
(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(
|
manager.registerExceptionHandler(
|
||||||
ArgumentParseException.class,
|
ArgumentParseException.class,
|
||||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
(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(
|
manager.registerExceptionHandler(
|
||||||
CommandExecutionException.class,
|
CommandExecutionException.class,
|
||||||
(c, e) -> audienceMapper.apply(c).sendMessage(
|
(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
|
* 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.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
@ -360,9 +361,16 @@ public final class MinecraftHelp<C> {
|
||||||
return header;
|
return header;
|
||||||
},
|
},
|
||||||
(helpEntry, isLastOfPage) -> {
|
(helpEntry, isLastOfPage) -> {
|
||||||
final Component description = helpEntry.getDescription().isEmpty()
|
final Optional<Component> richDescription =
|
||||||
? this.messageProvider.provide(sender, MESSAGE_CLICK_TO_SHOW_HELP)
|
helpEntry.getCommand().getCommandMeta().get(MinecraftExtrasMetaKeys.DESCRIPTION);
|
||||||
: this.descriptionDecorator.apply(helpEntry.getDescription());
|
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 =
|
final boolean lastBranch =
|
||||||
isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1;
|
isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1;
|
||||||
|
|
@ -439,9 +447,21 @@ public final class MinecraftHelp<C> {
|
||||||
.append(text(": ", this.colors.primary))
|
.append(text(": ", this.colors.primary))
|
||||||
.append(this.highlight(text("/" + command, this.colors.highlight)))
|
.append(this.highlight(text("/" + command, this.colors.highlight)))
|
||||||
);
|
);
|
||||||
final Component topicDescription = helpTopic.getDescription().isEmpty()
|
/* Topics will use the long description if available, but fall back to the short description. */
|
||||||
? this.messageProvider.provide(sender, MESSAGE_NO_DESCRIPTION)
|
final Component richDescription =
|
||||||
: this.descriptionDecorator.apply(helpTopic.getDescription());
|
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;
|
final boolean hasArguments = helpTopic.getCommand().getArguments().size() > 1;
|
||||||
audience.sendMessage(text()
|
audience.sendMessage(text()
|
||||||
.append(text(" "))
|
.append(text(" "))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue