From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 20 Feb 2021 13:09:59 -0500 Subject: [PATCH] Add support for hex color codes in console Converts upstream's hex color code legacy format into actual hex color codes in the console. diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java index 685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869..8f07539a82f449ad217e316a7513a1708781fb63 100644 --- a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java +++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java @@ -1,16 +1,25 @@ package com.destroystokyo.paper.console; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.bukkit.craftbukkit.command.CraftConsoleCommandSender; public class TerminalConsoleCommandSender extends CraftConsoleCommandSender { - private static final Logger LOGGER = LogManager.getRootLogger(); + private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName()); @Override public void sendRawMessage(String message) { - // TerminalConsoleAppender supports color codes directly in log messages + final Component msg = LegacyComponentSerializer.legacySection().deserialize(message); + this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM); + } + + @Override + public void sendMessage(Identity identity, Component message, MessageType type) { LOGGER.info(message); } diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java index c3631efda9c7fa531a8a9f18fbee7b5f8655382b..9a3c1314d5a0aa20380662595359580b1a97be89 100644 --- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java +++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java @@ -1,9 +1,11 @@ package io.papermc.paper.adventure.providers; -import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.console.HexFormattingConverter; +import java.util.Locale; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider; +import net.kyori.adventure.translation.GlobalTranslator; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; @@ -14,6 +16,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider { } private String serialize(final Component message) { - return PaperAdventure.asPlain(message, null); + return HexFormattingConverter.SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault())); } } diff --git a/src/main/java/io/papermc/paper/console/HexFormattingConverter.java b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..b9922b07cb105618390187d98acdf89e728e1f5a --- /dev/null +++ b/src/main/java/io/papermc/paper/console/HexFormattingConverter.java @@ -0,0 +1,213 @@ +package io.papermc.paper.console; + +import io.papermc.paper.configuration.GlobalConfiguration; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.minecrell.terminalconsole.TerminalConsoleAppender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.core.pattern.PatternFormatter; +import org.apache.logging.log4j.core.pattern.PatternParser; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.apache.logging.log4j.util.PropertiesUtil; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static net.minecrell.terminalconsole.MinecraftFormattingConverter.KEEP_FORMATTING_PROPERTY; + +/** + * Modified version of + * TerminalConsoleAppender's MinecraftFormattingConverter to support hex color codes using the Adventure [char]#rrggbb format. + */ +@Plugin(name = "paperMinecraftFormatting", category = PatternConverter.CATEGORY) +@ConverterKeys({"paperMinecraftFormatting"}) +@PerformanceSensitive("allocation") +public final class HexFormattingConverter extends LogEventPatternConverter { + + private static final boolean KEEP_FORMATTING = PropertiesUtil.getProperties().getBooleanProperty(KEEP_FORMATTING_PROPERTY); + + private static final String ANSI_RESET = "\u001B[m"; + + private static final char COLOR_CHAR = 0x7f; + public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder() + .hexColors() + .flattener(PaperAdventure.FLATTENER) + .character(HexFormattingConverter.COLOR_CHAR) + .build(); + private static final String LOOKUP = "0123456789abcdefklmnor"; + + private static final String RGB_ANSI = "\u001B[38;2;%d;%d;%dm"; + private static final String RESET_RGB_ANSI = ANSI_RESET + RGB_ANSI; + private static final Pattern NAMED_PATTERN = Pattern.compile(COLOR_CHAR + "[0-9a-fk-orA-FK-OR]"); + private static final Pattern RGB_PATTERN = Pattern.compile(COLOR_CHAR + "#([0-9a-fA-F]){6}"); + + private static final String[] RGB_ANSI_CODES = new String[]{ + formatHexAnsi(NamedTextColor.BLACK), // Black §0 + formatHexAnsi(NamedTextColor.DARK_BLUE), // Dark Blue §1 + formatHexAnsi(NamedTextColor.DARK_GREEN), // Dark Green §2 + formatHexAnsi(NamedTextColor.DARK_AQUA), // Dark Aqua §3 + formatHexAnsi(NamedTextColor.DARK_RED), // Dark Red §4 + formatHexAnsi(NamedTextColor.DARK_PURPLE), // Dark Purple §5 + formatHexAnsi(NamedTextColor.GOLD), // Gold §6 + formatHexAnsi(NamedTextColor.GRAY), // Gray §7 + formatHexAnsi(NamedTextColor.DARK_GRAY), // Dark Gray §8 + formatHexAnsi(NamedTextColor.BLUE), // Blue §9 + formatHexAnsi(NamedTextColor.GREEN), // Green §a + formatHexAnsi(NamedTextColor.AQUA), // Aqua §b + formatHexAnsi(NamedTextColor.RED), // Red §c + formatHexAnsi(NamedTextColor.LIGHT_PURPLE), // Light Purple §d + formatHexAnsi(NamedTextColor.YELLOW), // Yellow §e + formatHexAnsi(NamedTextColor.WHITE), // White §f + "\u001B[5m", // Obfuscated §k + "\u001B[1m", // Bold §l + "\u001B[9m", // Strikethrough §m + "\u001B[4m", // Underline §n + "\u001B[3m", // Italic §o + ANSI_RESET, // Reset §r + }; + private static final String[] ANSI_ANSI_CODES = new String[]{ + ANSI_RESET + "\u001B[0;30m", // Black §0 + ANSI_RESET + "\u001B[0;34m", // Dark Blue §1 + ANSI_RESET + "\u001B[0;32m", // Dark Green §2 + ANSI_RESET + "\u001B[0;36m", // Dark Aqua §3 + ANSI_RESET + "\u001B[0;31m", // Dark Red §4 + ANSI_RESET + "\u001B[0;35m", // Dark Purple §5 + ANSI_RESET + "\u001B[0;33m", // Gold §6 + ANSI_RESET + "\u001B[0;37m", // Gray §7 + ANSI_RESET + "\u001B[0;30;1m", // Dark Gray §8 + ANSI_RESET + "\u001B[0;34;1m", // Blue §9 + ANSI_RESET + "\u001B[0;32;1m", // Green §a + ANSI_RESET + "\u001B[0;36;1m", // Aqua §b + ANSI_RESET + "\u001B[0;31;1m", // Red §c + ANSI_RESET + "\u001B[0;35;1m", // Light Purple §d + ANSI_RESET + "\u001B[0;33;1m", // Yellow §e + ANSI_RESET + "\u001B[0;37;1m", // White §f + "\u001B[5m", // Obfuscated §k + "\u001B[1m", // Bold §l + "\u001B[9m", // Strikethrough §m + "\u001B[4m", // Underline §n + "\u001B[3m", // Italic §o + ANSI_RESET, // Reset §r + }; + + private final boolean ansi; + private final List formatters; + + /** + * Construct the converter. + * + * @param formatters The pattern formatters to generate the text to manipulate + * @param strip If true, the converter will strip all formatting codes + */ + protected HexFormattingConverter(List formatters, boolean strip) { + super("paperMinecraftFormatting", null); + this.formatters = formatters; + this.ansi = !strip; + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + int start = toAppendTo.length(); + //noinspection ForLoopReplaceableByForEach + for (int i = 0, size = formatters.size(); i < size; i++) { + formatters.get(i).format(event, toAppendTo); + } + + if (KEEP_FORMATTING || toAppendTo.length() == start) { + // Skip replacement if disabled or if the content is empty + return; + } + + boolean useAnsi = ansi && TerminalConsoleAppender.isAnsiSupported(); + String content = toAppendTo.substring(start); + content = useAnsi ? convertRGBColors(content) : stripRGBColors(content); + format(content, toAppendTo, start, useAnsi); + } + + private static String convertRGBColors(final String input) { + return RGB_PATTERN.matcher(input).replaceAll(result -> { + final int hex = Integer.decode(result.group().substring(1)); + return formatHexAnsi(hex); + }); + } + + private static String formatHexAnsi(final TextColor color) { + return formatHexAnsi(color.value()); + } + + private static String formatHexAnsi(final int color) { + final int red = color >> 16 & 0xFF; + final int green = color >> 8 & 0xFF; + final int blue = color & 0xFF; + return String.format(RESET_RGB_ANSI, red, green, blue); + } + + private static String stripRGBColors(final String input) { + return RGB_PATTERN.matcher(input).replaceAll(""); + } + + static void format(String content, StringBuilder result, int start, boolean ansi) { + int next = content.indexOf(COLOR_CHAR); + int last = content.length() - 1; + if (next == -1 || next == last) { + result.setLength(start); + result.append(content); + if (ansi) { + result.append(ANSI_RESET); + } + return; + } + + Matcher matcher = NAMED_PATTERN.matcher(content); + StringBuilder buffer = new StringBuilder(); + final String[] ansiCodes = GlobalConfiguration.get().logging.useRgbForNamedTextColors ? RGB_ANSI_CODES : ANSI_ANSI_CODES; + while (matcher.find()) { + int format = LOOKUP.indexOf(Character.toLowerCase(matcher.group().charAt(1))); + if (format != -1) { + matcher.appendReplacement(buffer, ansi ? ansiCodes[format] : ""); + } + } + matcher.appendTail(buffer); + + result.setLength(start); + result.append(buffer); + if (ansi) { + result.append(ANSI_RESET); + } + } + + /** + * Gets a new instance of the {@link HexFormattingConverter} with the + * specified options. + * + * @param config The current configuration + * @param options The pattern options + * @return The new instance + * @see HexFormattingConverter + */ + public static HexFormattingConverter newInstance(Configuration config, String[] options) { + if (options.length < 1 || options.length > 2) { + LOGGER.error("Incorrect number of options on paperMinecraftFormatting. Expected at least 1, max 2 received " + options.length); + return null; + } + if (options[0] == null) { + LOGGER.error("No pattern supplied on paperMinecraftFormatting"); + return null; + } + + PatternParser parser = PatternLayout.createPatternParser(config); + List formatters = parser.parse(options[0]); + boolean strip = options.length > 1 && "strip".equals(options[1]); + return new HexFormattingConverter(formatters, strip); + } + +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 2f8026fffde9976e28d3263cbc4296a51fce8221..64dd0211ac2ceed34dba900b5af3a56ac5a4a65a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1682,7 +1682,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop - + + pattern="%highlightError{[%d{HH:mm:ss} %level]: %paperMinecraftFormatting{%msg}%n%xEx{full}}" /> - + + pattern="[%d{HH:mm:ss}] [%t/%level]: %paperMinecraftFormatting{%msg}{strip}%n%xEx{full}" />