Chat preview
Chat preview is a feature introduced in Minecraft 1.19 that allows the server to display a preview of a chat message to the client before the client sends their message to the server. This chat preview feature is also compatible with /say
and /msg
, as well as the ChatArgument
and AdventureChatArgument
classes.
Minecraft version support
The chat preview feature is only present in Minecraft versions 1.19, 1.19.1 and 1.19.2. Chat preview was removed in 1.19.3, so this feature is unfortunately no longer usable in Minecraft 1.19.3 and beyond.
Enabling chat preview
To use chat preview, your server must have previews-chat
set to true
in the server.properties
file:
...
previews-chat=true
...
For players that want to use chat preview, they must have Chat Preview
enabled in Options > Chat Settings...
Specifying a chat preview function
The ChatArgument
and AdventureChatArgument
classes include a method, withPreview
:
public T withPreview(PreviewableFunction preview);
The method withPreview(PreviewableFunction preview)
lets you generate a preview to send to the client. This method takes in the PreviewableFunction
functional interface, which is a function that takes in a PreviewInfo
and returns either a BaseComponent[]
(for ChatArgument
) or a Component
(for AdventureChatArgument
):
public T generatePreview(PreviewInfo info) throws WrapperCommandSyntaxException;
The PreviewInfo
class is a record containing the following:
public record PreviewInfo<T> {
Player player();
String input();
String fullInput();
T parsedInput();
}
The following methods are as follows:
Player player();
player()
is the player that is currently typing a chat preview.
String input();
input()
is the current input for the current ChatArgument
or AdventureChatArgument
. If a user is typing /mycommand hellowor¦
and the command syntax is /mycommand <ChatArgument>
, the result of input()
would be "hellowor"
.
String fullInput();
fullInput()
is the full input that the player has typed, including the leading /
symbol which is required to start a command. If a user is typing /mycommand hellowor¦
, the result of fullInput()
would be "/mycommand hellowor"
.
T parsedInput();
parsedInput()
is similar to input()
, except it has been parsed by the CommandAPI's argument parser. This is a representation of what the argument in the executor would look like. For a ChatArgument
the return type is BaseComponent[]
, and for AdventureChatArgument
the return type is Component
.
Using the chat preview function as the argument's value
The ChatArgument
and AdventureChatArgument
classes also include a method, usePreview
:
public T usePreview(boolean usePreview);
The usePreview(boolean usePreview)
method lets you specify whether you would like the previewing function to be used as the argument's value during execution. If set to true
, when the command's .executes()
method is called, the argument value (e.g. arg[0]
) will be the same as the content generated by the function provided to withPreview()
.
Chat preview examples
Example - Using chat preview
Say we wanted to make our own /broadcast
command that allowed the user to use &
chat colors. We can use chat preview to show users what the result of their /broadcast
command would look like before running the command. We'll use the following command syntax:
/broadcast <message>
Because the ChatArgument
and AdventureChatArgument
can support entity selectors (such as @p
), it's best to use the info.parsedInput()
method to handle parsed entity selectors. In our code, we use the .withPreview()
method and take the parsed input and convert it to plain text. We then convert the plain text with &
characters into component text to be displayed to the user.
For execution, we do the same procedure, because the text that the user enters still has &
characters that need to be converted into a component.
new CommandAPICommand("broadcast")
.withArguments(new ChatArgument("message").withPreview(info -> {
// Convert parsed BaseComponent[] to plain text
String plainText = BaseComponent.toPlainText(info.parsedInput());
// Translate the & in plain text and generate a new BaseComponent[]
return TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText));
}))
.executesPlayer((player, args) -> {
// The user still entered legacy text. We need to properly convert this
// to a BaseComponent[] by converting to plain text then to BaseComponent[]
String plainText = BaseComponent.toPlainText((BaseComponent[]) args.get("message"));
Bukkit.spigot().broadcast(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText)));
})
.register();
new CommandAPICommand("broadcast")
.withArguments(new AdventureChatArgument("message").withPreview(info -> {
// Convert parsed Component to plain text
String plainText = PlainTextComponentSerializer.plainText().serialize(info.parsedInput());
// Translate the & in plain text and generate a new Component
return LegacyComponentSerializer.legacyAmpersand().deserialize(plainText);
}))
.executesPlayer((player, args) -> {
// The user still entered legacy text. We need to properly convert this
// to a Component by converting to plain text then to Component
String plainText = PlainTextComponentSerializer.plainText().serialize((Component) args.get("broadcast"));
Bukkit.broadcast(LegacyComponentSerializer.legacyAmpersand().deserialize(plainText));
})
.register();
CommandAPICommand("broadcast")
.withArguments(ChatArgument("message").withPreview { info ->
// Convert parsed BaseComponent[] to plain text
val plainText: String = BaseComponent.toPlainText(*info.parsedInput() as Array<BaseComponent>)
// Translate the & in plain text and generate a new BaseComponent[]
TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText))
} )
.executesPlayer(PlayerCommandExecutor { _, args ->
// The user still entered legacy text. We need to properly convert this
// to a BaseComponent[] by converting to plain text then to BaseComponent[]
val plainText: String = BaseComponent.toPlainText(*args["message"] as Array<BaseComponent>)
val baseComponents: Array<BaseComponent> = TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText))
Bukkit.spigot().broadcast(*baseComponents)
})
.register()
CommandAPICommand("broadcast")
.withArguments(AdventureChatArgument("message").withPreview { info ->
// Convert parsed Component to plain text
val plainText: String = PlainTextComponentSerializer.plainText().serialize(info.parsedInput() as Component)
// Translate the & in plain text and generate a new Component
LegacyComponentSerializer.legacyAmpersand().deserialize(plainText)
} )
.executesPlayer(PlayerCommandExecutor { _, args ->
// The user still entered legacy text. We need to properly convert this
// to a Component by converting to plain text then to Component
val plainText: String = PlainTextComponentSerializer.plainText().serialize(args["message"] as Component)
Bukkit.broadcast(LegacyComponentSerializer.legacyAmpersand().deserialize(plainText))
})
.register()
Example - Using chat preview with usePreview()
Extending on the example above where we created a /broadcast
command with chat preview support, we can simplify the code by using .usePreview(true)
to use the preview function as the value of our argument in our executor function. We'll use the same command syntax as the previous example:
/broadcast <message>
By using .usePreview(true)
, we don't have to re-translate &
formatting codes into their corresponding components because that has already been done by the preview function specified in .withPreview()
method.
new CommandAPICommand("broadcast")
.withArguments(new ChatArgument("message").usePreview(true).withPreview(info -> {
// Convert parsed BaseComponent[] to plain text
String plainText = BaseComponent.toPlainText(info.parsedInput());
// Translate the & in plain text and generate a new BaseComponent[]
return TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText));
}))
.executesPlayer((player, args) -> {
Bukkit.spigot().broadcast((BaseComponent[]) args.get("message"));
})
.register();
new CommandAPICommand("broadcast")
.withArguments(new AdventureChatArgument("message").usePreview(true).withPreview(info -> {
// Convert parsed Component to plain text
String plainText = PlainTextComponentSerializer.plainText().serialize(info.parsedInput());
// Translate the & in plain text and generate a new Component
return LegacyComponentSerializer.legacyAmpersand().deserialize(plainText);
}))
.executesPlayer((player, args) -> {
Bukkit.broadcast((Component) args.get("message"));
})
.register();
CommandAPICommand("broadcast")
.withArguments(ChatArgument("message").usePreview(true).withPreview { info ->
// Convert parsed BaseComponent[] to plain text
val plainText = BaseComponent.toPlainText(*info.parsedInput() as Array<BaseComponent>)
// Translate the & in plain text and generate a new BaseComponent[]
TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', plainText))
} )
.executesPlayer(PlayerCommandExecutor { _, args ->
Bukkit.spigot().broadcast(*args["message"] as Array<BaseComponent>)
})
.register()
CommandAPICommand("broadcast")
.withArguments(AdventureChatArgument("message").usePreview(true).withPreview { info ->
// Convert parsed Component to plain text
val plainText = PlainTextComponentSerializer.plainText().serialize(info.parsedInput() as Component)
// Translate the & in plain text and generate a new Component
LegacyComponentSerializer.legacyAmpersand().deserialize(plainText)
} )
.executesPlayer(PlayerCommandExecutor { _, args ->
Bukkit.broadcast(args["message"] as Component)
})
.register()