From a2f089626066437e1938f20beb9c9b3d00372368 Mon Sep 17 00:00:00 2001 From: Vankka Date: Sat, 16 Sep 2023 14:58:41 +0300 Subject: [PATCH] Parse ANSI escape codes for console --- .../console/message/ConsoleMessage.java | 163 +++++++++++++++++- 1 file changed, 158 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/discordsrv/common/console/message/ConsoleMessage.java b/common/src/main/java/com/discordsrv/common/console/message/ConsoleMessage.java index 135dae93..b2f59d9e 100644 --- a/common/src/main/java/com/discordsrv/common/console/message/ConsoleMessage.java +++ b/common/src/main/java/com/discordsrv/common/console/message/ConsoleMessage.java @@ -6,6 +6,8 @@ import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.*; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -20,10 +22,10 @@ public class ConsoleMessage { // ANSI ANSI_ESCAPE + "\\[" - + "(?\\d{1,3}" - + "(;\\d{1,3}" - + "(;\\d{1,3}" - + "(?:(?:;\\d{1,3}){2})?" + + "(?[0-9]{1,3}" + + "(;[0-9]{1,3}" + + "(;[0-9]{1,3}" + + "(?:(?:;[0-9]{1,3}){2})?" + ")?" + ")?" + ")" @@ -90,7 +92,158 @@ public class ConsoleMessage { } private void parseAnsi(String ansiEscape, Style.Builder style) { - // TODO: implement + String[] ansiParts = ansiEscape.split(";"); + int amount = ansiParts.length; + if (amount == 1 || amount == 2) { + int number = Integer.parseInt(ansiParts[0]); + + if ((number >= 30 && number <= 37) || (number >= 90 && number <= 97)) { + style.color(fourBitAnsiColor(number)); + return; + } + + switch (number) { + case 0: + style.color(null).decorations(EnumSet.allOf(TextDecoration.class), false); + break; + case 1: + style.decoration(TextDecoration.BOLD, true); + break; + case 3: + style.decoration(TextDecoration.ITALIC, true); + break; + case 4: + style.decoration(TextDecoration.UNDERLINED, true); + break; + case 8: + style.decoration(TextDecoration.OBFUSCATED, true); + break; + case 9: + style.decoration(TextDecoration.STRIKETHROUGH, true); + break; + case 22: + style.decoration(TextDecoration.BOLD, false); + break; + case 23: + style.decoration(TextDecoration.ITALIC, false); + break; + case 24: + style.decoration(TextDecoration.UNDERLINED, false); + break; + case 28: + style.decoration(TextDecoration.OBFUSCATED, false); + break; + case 29: + style.decoration(TextDecoration.STRIKETHROUGH, false); + break; + case 39: + style.color(null); + break; + } + } else if (amount == 3 || amount == 5) { + if (Integer.parseInt(ansiParts[0]) != 36 || Integer.parseInt(ansiParts[1]) != 5) { + return; + } + + if (amount == 5) { + int red = Integer.parseInt(ansiParts[2]); + int green = Integer.parseInt(ansiParts[3]); + int blue = Integer.parseInt(ansiParts[4]); + + style.color(TextColor.color(red, green, blue)); + return; + } + + int number = Integer.parseInt(ansiParts[2]); + style.color(eightBitAnsiColor(number)); + } + } + + private enum FourBitColor { + BLACK(30, TextColor.color(0, 0, 0)), + RED(31, TextColor.color(170, 0, 0)), + GREEN(32, TextColor.color(0, 170, 0)), + YELLOW(33, TextColor.color(170, 85, 0)), + BLUE(34, TextColor.color(0, 0, 170)), + MAGENTA(35, TextColor.color(170, 0, 170)), + CYAN(36, TextColor.color(0, 170, 170)), + WHITE(37, TextColor.color(170, 170, 170)), + BRIGHT_BLACK(90, TextColor.color(85, 85, 85)), + BRIGHT_RED(91, TextColor.color(255, 85, 85)), + BRIGHT_GREEN(92, TextColor.color(85, 255, 85)), + BRIGHT_YELLOW(93, TextColor.color(255, 255, 85)), + BRIGHT_BLUE(94, TextColor.color(85, 85, 255)), + BRIGHT_MAGENTA(95, TextColor.color(255, 85, 255)), + BRIGHT_CYAN(96, TextColor.color(85, 255, 255)), + BRIGHT_WHITE(97, TextColor.color(255, 255, 255)); + + private static final Map byFG = new HashMap<>(); + + static { + for (FourBitColor value : values()) { + byFG.put(value.fg, value); + } + } + + public static FourBitColor getByFG(int fg) { + return byFG.get(fg); + } + + private final int fg; + private final TextColor color; + + FourBitColor(int fg, TextColor color) { + this.fg = fg; + this.color = color; + } + + public TextColor color() { + return color; + } + } + + private TextColor fourBitAnsiColor(int color) { + FourBitColor fourBitColor = FourBitColor.getByFG(color); + return fourBitColor != null ? fourBitColor.color() : null; + } + + private TextColor[] colors; + private TextColor eightBitAnsiColor(int color) { + if (colors == null) { + TextColor[] colors = new TextColor[256]; + + FourBitColor[] fourBitColors = FourBitColor.values(); + for (int i = 0; i < fourBitColors.length; i++) { + colors[i] = fourBitColors[i].color(); + } + + // https://gitlab.gnome.org/GNOME/vte/-/blob/19acc51708d9e75ef2b314aa026467570e0bd8ee/src/vte.cc#L2485 + for (int i = 16; i < 232; i++) { + int j = i - 16; + + int red = j / 36; + int green = (j / 6) % 6; + int blue = j % 6; + + red = red == 0 ? 0 : red * 40 + 55; + green = green == 0 ? 0 : green * 40 + 55; + blue = blue == 0 ? 0 : blue * 40 + 55; + + colors[i] = TextColor.color( + red | red << 8, + green | green << 8, + blue | blue << 8 + ); + } + for (int i = 232; i < 256; i++) { + int shade = 8 + (i - 232) * 10; + colors[i] = TextColor.color(shade, shade, shade); + } + + this.colors = colors; + } + + return color <= colors.length && color >= 0 ? colors[color] : null; } private void parseLegacy(String legacy, Style.Builder style) {