ComponentLogger & Colored Terminal (#1460)

This commit is contained in:
Huynh Tien 2022-10-27 00:52:01 +07:00 committed by GitHub
parent 46f3fede8a
commit d7e958fa07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 170 additions and 21 deletions

View File

@ -47,6 +47,7 @@ adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "ad
adventure-serializer-gson = { group = "net.kyori", name = "adventure-text-serializer-gson", version.ref = "adventure" }
adventure-serializer-legacy = { group = "net.kyori", name = "adventure-text-serializer-legacy", version.ref = "adventure" }
adventure-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
adventure-text-logger-slf4j = { group = "net.kyori", name = "adventure-text-logger-slf4j", version.ref = "adventure" }
# Kotlin
kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" }
@ -100,6 +101,6 @@ jcstress-core = { group = "org.openjdk.jcstress", name = "jcstress-core", versio
kotlin = ["kotlin-stdlib-jdk8", "kotlin-reflect"]
flare = ["flare", "flare-fastutil"]
adventure = ["adventure-api", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain"]
adventure = ["adventure-api", "adventure-serializer-gson", "adventure-serializer-legacy", "adventure-serializer-plain", "adventure-text-logger-slf4j"]
logging = ["tinylog-api", "tinylog-impl", "tinylog-slf4j"]
terminal = ["jline", "jline-jansi"]

View File

@ -1,5 +1,6 @@
package net.minestom.server;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minestom.server.advancements.AdvancementManager;
import net.minestom.server.adventure.bossbar.BossBarManager;
import net.minestom.server.command.CommandManager;
@ -29,8 +30,6 @@ import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -44,7 +43,7 @@ import java.net.SocketAddress;
*/
public final class MinecraftServer {
public final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class);
public static final String VERSION_NAME = "1.19.2";
public static final int PROTOCOL_VERSION = 760;

View File

@ -21,7 +21,7 @@ public final class MinestomAdventure {
* A codec to convert between strings and NBT.
*/
public static final Codec<NBT, String, NBTException, RuntimeException> NBT_CODEC
= Codec.of(encoded -> new SNBTParser(new StringReader(encoded)).parse(), NBT::toSNBT);
= Codec.codec(encoded -> new SNBTParser(new StringReader(encoded)).parse(), NBT::toSNBT);
/**
* If components should be automatically translated in outgoing packets.

View File

@ -0,0 +1,21 @@
package net.minestom.server.adventure.provider;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
@SuppressWarnings("UnstableApiUsage") // we are permitted to provide this
public class MinestomComponentLoggerProvider implements ComponentLoggerProvider {
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.character(LegacyComponentSerializer.SECTION_CHAR)
.flattener(MinestomFlattenerProvider.INSTANCE)
.hexColors()
.build();
@Override
public @NotNull ComponentLogger logger(@NotNull LoggerHelper helper, @NotNull String name) {
return helper.delegating(LoggerFactory.getLogger(name), SERIALIZER::serialize);
}
}

View File

@ -3,12 +3,10 @@ package net.minestom.server.command;
import net.kyori.adventure.audience.MessageType;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minestom.server.permission.Permission;
import net.minestom.server.tag.TagHandler;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
@ -17,8 +15,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
* Represents the console when sending a command to the server.
*/
public class ConsoleSender implements CommandSender {
private static final PlainTextComponentSerializer PLAIN_SERIALIZER = PlainTextComponentSerializer.plainText();
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
private static final ComponentLogger LOGGER = ComponentLogger.logger(ConsoleSender.class);
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
private final TagHandler tagHandler = TagHandler.newHandler();
@ -30,8 +27,7 @@ public class ConsoleSender implements CommandSender {
@Override
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
// we don't use the serializer here as we just need the plain text of the message
this.sendMessage(PLAIN_SERIALIZER.serialize(message));
LOGGER.info(message);
}
@NotNull

View File

@ -1,10 +1,10 @@
package net.minestom.server.extensions;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
@ -64,7 +64,7 @@ public abstract class Extension {
* @return The logger for the extension
*/
@NotNull
public Logger getLogger() {
public ComponentLogger getLogger() {
return getExtensionClassLoader().getLogger();
}

View File

@ -1,11 +1,10 @@
package net.minestom.server.extensions;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.net.URL;
@ -17,7 +16,7 @@ public final class ExtensionClassLoader extends URLClassLoader {
private final List<ExtensionClassLoader> children = new ArrayList<>();
private final DiscoveredExtension discoveredExtension;
private EventNode<Event> eventNode;
private Logger logger;
private ComponentLogger logger;
public ExtensionClassLoader(String name, URL[] urls, DiscoveredExtension discoveredExtension) {
super("Ext_" + name, urls, MinecraftServer.class.getClassLoader());
@ -77,9 +76,9 @@ public final class ExtensionClassLoader extends URLClassLoader {
return eventNode;
}
public Logger getLogger() {
public ComponentLogger getLogger() {
if (logger == null) {
logger = LoggerFactory.getLogger(discoveredExtension.getName());
logger = ComponentLogger.logger(discoveredExtension.getName());
}
return logger;
}

View File

@ -1,5 +1,6 @@
package net.minestom.server.terminal;
import org.fusesource.jansi.AnsiConsole;
import org.tinylog.core.LogEntry;
import org.tinylog.writers.AbstractFormatPatternWriter;
@ -14,10 +15,12 @@ public final class MinestomConsoleWriter extends AbstractFormatPatternWriter {
@Override
public void write(LogEntry logEntry) throws Exception {
String rendered = render(logEntry);
String formatted = TerminalColorConverter.format(rendered);
if (reader != null) {
reader.printAbove(render(logEntry));
reader.printAbove(formatted);
} else {
System.out.print(render(logEntry));
AnsiConsole.out().print(formatted);
}
}

View File

@ -0,0 +1,102 @@
package net.minestom.server.terminal;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.minestom.server.utils.PropertyUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A string converter to convert a string to an ansi-colored one.
*
* @see <a href="https://github.com/Minecrell/TerminalConsoleAppender/blob/master/src/main/java/net/minecrell/terminalconsole/MinecraftFormattingConverter.java">TerminalConsoleAppender</a>
* @see <a href="https://github.com/PaperMC/Paper/blob/41647af74caed955c1fd5b38d458ee59298ae5d4/patches/server/0591-Add-support-for-hex-color-codes-in-console.patch">Paper</a>
*/
final class TerminalColorConverter {
private static final boolean SUPPORT_HEX_COLOR = PropertyUtils.getBoolean("minestom.terminal.support-hex-color", true);
private static final boolean SUPPORT_COLOR = PropertyUtils.getBoolean("minestom.terminal.support-color", true);
private static final String RGB_ANSI = "\u001B[38;2;%d;%d;%dm";
private static final String ANSI_RESET = "\u001B[m";
private static final String LOOKUP = "0123456789abcdefklmnor";
private static final String[] ANSI_CODES = new String[]{
getAnsiColor(NamedTextColor.BLACK, "\u001B[0;30m"), // Black §0
getAnsiColor(NamedTextColor.DARK_BLUE, "\u001B[0;34m"), // Dark Blue §1
getAnsiColor(NamedTextColor.DARK_GREEN, "\u001B[0;32m"), // Dark Green §2
getAnsiColor(NamedTextColor.DARK_AQUA, "\u001B[0;36m"), // Dark Aqua §3
getAnsiColor(NamedTextColor.DARK_RED, "\u001B[0;31m"), // Dark Red §4
getAnsiColor(NamedTextColor.DARK_PURPLE, "\u001B[0;35m"), // Dark Purple §5
getAnsiColor(NamedTextColor.GOLD, "\u001B[0;33m"), // Gold §6
getAnsiColor(NamedTextColor.GRAY, "\u001B[0;37m"), // Gray §7
getAnsiColor(NamedTextColor.DARK_GRAY, "\u001B[0;30;1m"), // Dark Gray §8
getAnsiColor(NamedTextColor.BLUE, "\u001B[0;34;1m"), // Blue §9
getAnsiColor(NamedTextColor.GREEN, "\u001B[0;32;1m"), // Green §a
getAnsiColor(NamedTextColor.AQUA, "\u001B[0;36;1m"), // Aqua §b
getAnsiColor(NamedTextColor.RED, "\u001B[0;31;1m"), // Red §c
getAnsiColor(NamedTextColor.LIGHT_PURPLE, "\u001B[0;35;1m"), // Light Purple §d
getAnsiColor(NamedTextColor.YELLOW, "\u001B[0;33;1m"), // Yellow §e
getAnsiColor(NamedTextColor.WHITE, "\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 static final Pattern RGB_PATTERN = Pattern.compile(LegacyComponentSerializer.SECTION_CHAR + "#([\\da-fA-F]{6})");
private static final Pattern NAMED_PATTERN = Pattern.compile(LegacyComponentSerializer.SECTION_CHAR + "([\\da-fk-orA-FK-OR])");
private TerminalColorConverter() {
}
private static String getAnsiColor(NamedTextColor color, String fallback) {
return getAnsiColorFromHexColor(color.value(), fallback);
}
private static String getAnsiColorFromHexColor(int color, String fallback) {
return SUPPORT_HEX_COLOR ? String.format(RGB_ANSI, (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF) : fallback;
}
private static String getAnsiColorFromHexColor(int color) {
return getAnsiColorFromHexColor(color, "");
}
/**
* Format the colored string to an ansi-colored one.
*
* @param string the string to format
* @return the formatted string
*/
public static String format(String string) {
if (string.indexOf(LegacyComponentSerializer.SECTION_CHAR) == -1) {
return string;
}
string = RGB_PATTERN.matcher(string).replaceAll(match -> {
if (SUPPORT_COLOR) {
String hex = match.group(1);
return getAnsiColorFromHexColor(Integer.parseInt(hex, 16));
} else {
return "";
}
});
Matcher matcher = NAMED_PATTERN.matcher(string);
StringBuilder builder = new StringBuilder();
while (matcher.find()) {
int format = LOOKUP.indexOf(Character.toLowerCase(matcher.group().charAt(1)));
if (format != -1) {
matcher.appendReplacement(builder, SUPPORT_COLOR ? ANSI_CODES[format] : "");
} else {
matcher.appendReplacement(builder, matcher.group());
}
}
matcher.appendTail(builder);
if (SUPPORT_COLOR) {
builder.append(ANSI_RESET);
}
return builder.toString();
}
}

View File

@ -0,0 +1 @@
net.minestom.server.adventure.provider.MinestomComponentLoggerProvider

View File

@ -0,0 +1,27 @@
package net.minestom.server.terminal;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TerminalColorConverterTest {
@Test
void testFormat() {
String input = "§c§lHello §r§b§lWorld";
String expected = "\u001B[38;2;255;85;85m\u001B[1mHello \u001B[m\u001B[38;2;85;255;255m\u001B[1mWorld\u001B[m";
String actual = TerminalColorConverter.format(input);
assertEquals(expected, actual);
}
@Test
void testComponentFormat() {
Component input = Component.text("Hello World").color(NamedTextColor.RED).decorate(TextDecoration.BOLD);
String expected = "\u001B[38;2;255;85;85m\u001B[1mHello World\u001B[m";
String actual = TerminalColorConverter.format(LegacyComponentSerializer.legacySection().serialize(input));
assertEquals(expected, actual);
}
}