mirror of https://github.com/Minestom/Minestom.git
ComponentLogger & Colored Terminal (#1460)
This commit is contained in:
parent
46f3fede8a
commit
d7e958fa07
|
@ -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-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-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-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
|
||||||
kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "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"]
|
kotlin = ["kotlin-stdlib-jdk8", "kotlin-reflect"]
|
||||||
flare = ["flare", "flare-fastutil"]
|
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"]
|
logging = ["tinylog-api", "tinylog-impl", "tinylog-slf4j"]
|
||||||
terminal = ["jline", "jline-jansi"]
|
terminal = ["jline", "jline-jansi"]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package net.minestom.server;
|
package net.minestom.server;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||||
import net.minestom.server.advancements.AdvancementManager;
|
import net.minestom.server.advancements.AdvancementManager;
|
||||||
import net.minestom.server.adventure.bossbar.BossBarManager;
|
import net.minestom.server.adventure.bossbar.BossBarManager;
|
||||||
import net.minestom.server.command.CommandManager;
|
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.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.UnknownNullability;
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
@ -44,7 +43,7 @@ import java.net.SocketAddress;
|
||||||
*/
|
*/
|
||||||
public final class MinecraftServer {
|
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 String VERSION_NAME = "1.19.2";
|
||||||
public static final int PROTOCOL_VERSION = 760;
|
public static final int PROTOCOL_VERSION = 760;
|
||||||
|
|
|
@ -21,7 +21,7 @@ public final class MinestomAdventure {
|
||||||
* A codec to convert between strings and NBT.
|
* A codec to convert between strings and NBT.
|
||||||
*/
|
*/
|
||||||
public static final Codec<NBT, String, NBTException, RuntimeException> NBT_CODEC
|
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.
|
* If components should be automatically translated in outgoing packets.
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,10 @@ package net.minestom.server.command;
|
||||||
import net.kyori.adventure.audience.MessageType;
|
import net.kyori.adventure.audience.MessageType;
|
||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import net.kyori.adventure.text.Component;
|
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.permission.Permission;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
@ -17,8 +15,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
* Represents the console when sending a command to the server.
|
* Represents the console when sending a command to the server.
|
||||||
*/
|
*/
|
||||||
public class ConsoleSender implements CommandSender {
|
public class ConsoleSender implements CommandSender {
|
||||||
private static final PlainTextComponentSerializer PLAIN_SERIALIZER = PlainTextComponentSerializer.plainText();
|
private static final ComponentLogger LOGGER = ComponentLogger.logger(ConsoleSender.class);
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSender.class);
|
|
||||||
|
|
||||||
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
|
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
|
||||||
private final TagHandler tagHandler = TagHandler.newHandler();
|
private final TagHandler tagHandler = TagHandler.newHandler();
|
||||||
|
@ -30,8 +27,7 @@ public class ConsoleSender implements CommandSender {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
|
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
|
LOGGER.info(message);
|
||||||
this.sendMessage(PLAIN_SERIALIZER.serialize(message));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package net.minestom.server.extensions;
|
package net.minestom.server.extensions;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||||
import net.minestom.server.event.Event;
|
import net.minestom.server.event.Event;
|
||||||
import net.minestom.server.event.EventNode;
|
import net.minestom.server.event.EventNode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -64,7 +64,7 @@ public abstract class Extension {
|
||||||
* @return The logger for the extension
|
* @return The logger for the extension
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public Logger getLogger() {
|
public ComponentLogger getLogger() {
|
||||||
return getExtensionClassLoader().getLogger();
|
return getExtensionClassLoader().getLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package net.minestom.server.extensions;
|
package net.minestom.server.extensions;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.event.Event;
|
import net.minestom.server.event.Event;
|
||||||
import net.minestom.server.event.EventNode;
|
import net.minestom.server.event.EventNode;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -17,7 +16,7 @@ public final class ExtensionClassLoader extends URLClassLoader {
|
||||||
private final List<ExtensionClassLoader> children = new ArrayList<>();
|
private final List<ExtensionClassLoader> children = new ArrayList<>();
|
||||||
private final DiscoveredExtension discoveredExtension;
|
private final DiscoveredExtension discoveredExtension;
|
||||||
private EventNode<Event> eventNode;
|
private EventNode<Event> eventNode;
|
||||||
private Logger logger;
|
private ComponentLogger logger;
|
||||||
|
|
||||||
public ExtensionClassLoader(String name, URL[] urls, DiscoveredExtension discoveredExtension) {
|
public ExtensionClassLoader(String name, URL[] urls, DiscoveredExtension discoveredExtension) {
|
||||||
super("Ext_" + name, urls, MinecraftServer.class.getClassLoader());
|
super("Ext_" + name, urls, MinecraftServer.class.getClassLoader());
|
||||||
|
@ -77,9 +76,9 @@ public final class ExtensionClassLoader extends URLClassLoader {
|
||||||
return eventNode;
|
return eventNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Logger getLogger() {
|
public ComponentLogger getLogger() {
|
||||||
if (logger == null) {
|
if (logger == null) {
|
||||||
logger = LoggerFactory.getLogger(discoveredExtension.getName());
|
logger = ComponentLogger.logger(discoveredExtension.getName());
|
||||||
}
|
}
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package net.minestom.server.terminal;
|
package net.minestom.server.terminal;
|
||||||
|
|
||||||
|
import org.fusesource.jansi.AnsiConsole;
|
||||||
import org.tinylog.core.LogEntry;
|
import org.tinylog.core.LogEntry;
|
||||||
import org.tinylog.writers.AbstractFormatPatternWriter;
|
import org.tinylog.writers.AbstractFormatPatternWriter;
|
||||||
|
|
||||||
|
@ -14,10 +15,12 @@ public final class MinestomConsoleWriter extends AbstractFormatPatternWriter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(LogEntry logEntry) throws Exception {
|
public void write(LogEntry logEntry) throws Exception {
|
||||||
|
String rendered = render(logEntry);
|
||||||
|
String formatted = TerminalColorConverter.format(rendered);
|
||||||
if (reader != null) {
|
if (reader != null) {
|
||||||
reader.printAbove(render(logEntry));
|
reader.printAbove(formatted);
|
||||||
} else {
|
} else {
|
||||||
System.out.print(render(logEntry));
|
AnsiConsole.out().print(formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
net.minestom.server.adventure.provider.MinestomComponentLoggerProvider
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue