mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-20 07:02:32 +01:00
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-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"]
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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.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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
Block a user