From 1c34de0ea97c6a691915688da7b8e554c4521217 Mon Sep 17 00:00:00 2001
From: Minecrell <minecrell@minecrell.net>
Date: Fri, 9 Jun 2017 19:03:43 +0200
Subject: [PATCH] Use TerminalConsoleAppender for console improvements

Rewrite console improvements (console colors, tab completion,
persistent input line, ...) using JLine 3.x and TerminalConsoleAppender.

New features:
  - Support console colors for Vanilla commands
  - Add console colors for warnings and errors
  - Server can now be turned off safely using CTRL + C. JLine catches
    the signal and the implementation shuts down the server cleanly.
  - Support console colors and persistent input line when running in
    IntelliJ IDEA

Other changes:
  - Server starts 1-2 seconds faster thanks to optimizations in Log4j
    configuration

diff --git a/pom.xml b/pom.xml
index 235d5a1b9b..d93a180de9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,10 +40,27 @@
             <scope>compile</scope>
         </dependency>
         <dependency>
-            <groupId>jline</groupId>
-            <artifactId>jline</artifactId>
-            <version>2.12.1</version>
-            <scope>compile</scope>
+            <groupId>net.minecrell</groupId>
+            <artifactId>terminalconsoleappender</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>4.5.2</version>
+            <scope>runtime</scope>
+        </dependency>
+        <!--
+          Required to add the missing Log4j2Plugins.dat file from log4j-core
+          which has been removed by Mojang. Without it, log4j has to classload
+          all its classes to check if they are plugins.
+          Scanning takes about 1-2 seconds so adding this speeds up the server start.
+        -->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>2.8.1</version>
+            <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>org.ow2.asm</groupId>
@@ -203,10 +220,18 @@
                                 <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                     <resource>META-INF/services/java.sql.Driver</resource>
                                 </transformer>
+                                <transformer implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer" />
                             </transformers>
                         </configuration>
                     </execution>
                 </executions>
+                <dependencies>
+                    <dependency>
+                        <groupId>com.github.edwgiz</groupId>
+                        <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId>
+                        <version>2.8.1</version>
+                    </dependency>
+                </dependencies>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
new file mode 100644
index 0000000000..688b4715eb
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
@@ -0,0 +1,40 @@
+package com.destroystokyo.paper.console;
+
+import net.minecraft.server.DedicatedServer;
+import net.minecrell.terminalconsole.SimpleTerminalConsole;
+import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+
+public final class PaperConsole extends SimpleTerminalConsole {
+
+    private final DedicatedServer server;
+
+    public PaperConsole(DedicatedServer server) {
+        this.server = server;
+    }
+
+    @Override
+    protected LineReader buildReader(LineReaderBuilder builder) {
+        return super.buildReader(builder
+                .appName("Paper")
+                .completer(new ConsoleCommandCompleter(this.server))
+        );
+    }
+
+    @Override
+    protected boolean isRunning() {
+        return !this.server.isStopped() && this.server.isRunning();
+    }
+
+    @Override
+    protected void runCommand(String command) {
+        this.server.issueCommand(command, this.server.getServerCommandListener());
+    }
+
+    @Override
+    protected void shutdown() {
+        this.server.safeShutdown();
+    }
+
+}
diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java
new file mode 100644
index 0000000000..685deaa0e5
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java
@@ -0,0 +1,17 @@
+package com.destroystokyo.paper.console;
+
+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();
+
+    @Override
+    public void sendRawMessage(String message) {
+        // TerminalConsoleAppender supports color codes directly in log messages
+        LOGGER.info(message);
+    }
+
+}
diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java
index 22f3a08e98..9fdb6d100b 100644
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
@@ -79,6 +79,9 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
                 if (!org.bukkit.craftbukkit.Main.useConsole) {
                     return;
                 }
+                // Paper start - Use TerminalConsoleAppender
+                new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
+                /*
                 jline.console.ConsoleReader bufferedreader = reader;
                 // CraftBukkit end
 
@@ -101,6 +104,8 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
                     DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
                 }
 
+                */
+                // Paper end
             }
         };
 
@@ -112,6 +117,9 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
         }
         global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
 
+        // Paper start - Not needed with TerminalConsoleAppender
+        final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger();
+        /*
         final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
         for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
             if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
@@ -120,6 +128,8 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
         }
 
         new Thread(new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader)).start();
+        */
+        // Paper end
 
         System.setOut(new PrintStream(new LoggerOutputStream(logger, Level.INFO), true));
         System.setErr(new PrintStream(new LoggerOutputStream(logger, Level.WARN), true));
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 8d345a0502..6a3d5fdff4 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -61,7 +61,6 @@ import org.apache.commons.lang3.Validate;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 // CraftBukkit start
-import jline.console.ConsoleReader;
 import joptsimple.OptionSet;
 import org.bukkit.Bukkit;
 import org.bukkit.craftbukkit.CraftServer;
@@ -147,7 +146,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
     public OptionSet options;
     public org.bukkit.command.ConsoleCommandSender console;
     public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
-    public ConsoleReader reader;
+    //public ConsoleReader reader; // Paper
     public static int currentTick = 0; // Paper - Further improve tick loop
     public boolean serverAutoSave = false; // Paper
     public final Thread primaryThread;
@@ -193,7 +192,9 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
         this.ac.a((IResourcePackListener) this.al);
         // CraftBukkit start
         this.options = options;
+        // Paper start - Handled by TerminalConsoleAppender
         // Try to see if we're actually running in a terminal, disable jline if not
+        /*
         if (System.console() == null && System.getProperty("jline.terminal") == null) {
             System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
             Main.useJline = false;
@@ -214,6 +215,8 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
                 LOGGER.warn((String) null, ex);
             }
         }
+        */
+        // Paper end
         Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
 
         this.serverThread = primaryThread = new Thread(this, "Server thread"); // Moved from main
@@ -877,7 +880,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
             } finally {
                 // CraftBukkit start - Restore terminal to original settings
                 try {
-                    reader.getTerminal().restore();
+                    net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
                 } catch (Exception ignored) {
                 }
                 // CraftBukkit end
@@ -1365,7 +1368,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
     }
 
     public void sendMessage(IChatBaseComponent ichatbasecomponent) {
-        MinecraftServer.LOGGER.info(ichatbasecomponent.getString());
+        MinecraftServer.LOGGER.info(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(ichatbasecomponent, net.minecraft.server.EnumChatFormat.WHITE));// Paper - Log message with colors
     }
 
     public KeyPair E() {
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index e61a638752..de549d207c 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -78,8 +78,7 @@ public abstract class PlayerList {
 
     public PlayerList(MinecraftServer minecraftserver) {
         this.cserver = minecraftserver.server = new CraftServer(minecraftserver, this);
-        minecraftserver.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance();
-        minecraftserver.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(minecraftserver.server));
+        minecraftserver.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper
         // CraftBukkit end
 
         this.k = new GameProfileBanList(PlayerList.a);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 849dd19dc0..c8d4e93951 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -142,8 +142,8 @@ import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.HashMap;
-import jline.console.ConsoleReader;
 import org.bukkit.Keyed;
+import org.apache.commons.lang.StringUtils;
 import org.bukkit.NamespacedKey;
 import org.bukkit.block.data.BlockData;
 import org.bukkit.craftbukkit.block.data.CraftBlockData;
@@ -1150,9 +1150,13 @@ public final class CraftServer implements Server {
         return logger;
     }
 
+    // Paper start - JLine update
+    /*
     public ConsoleReader getReader() {
         return console.reader;
     }
+    */
+    // Paper end
 
     @Override
     public PluginCommand getPluginCommand(String name) {
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index b726fe947a..393a39fadc 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -14,7 +14,7 @@ import java.util.logging.Logger;
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
 import net.minecraft.server.MinecraftServer;
-import org.fusesource.jansi.AnsiConsole;
+import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper
 
 public class Main {
     public static boolean useJline = true;
@@ -178,6 +178,8 @@ public class Main {
             }
 
             try {
+                // Paper start - Handled by TerminalConsoleAppender
+                /*
                 // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals
                 String jline_UnsupportedTerminal = new String(new char[] {'j','l','i','n','e','.','U','n','s','u','p','p','o','r','t','e','d','T','e','r','m','i','n','a','l'});
                 String jline_terminal = new String(new char[] {'j','l','i','n','e','.','t','e','r','m','i','n','a','l'});
@@ -195,10 +197,18 @@ public class Main {
                     // This ensures the terminal literal will always match the jline implementation
                     System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName());
                 }
+                */
 
+                if (options.has("nojline")) {
+                    System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false");
+                    useJline = false;
+                }
+                // Paper end
 
                 if (options.has("noconsole")) {
                     useConsole = false;
+                    useJline = false; // Paper
+                    System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper
                 }
 
                 if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
deleted file mode 100644
index 26a2fb8942..0000000000
--- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.bukkit.craftbukkit.command;
-
-import java.util.EnumMap;
-import java.util.Map;
-
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Attribute;
-import jline.Terminal;
-
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.craftbukkit.CraftServer;
-
-public class ColouredConsoleSender extends CraftConsoleCommandSender {
-    private final Terminal terminal;
-    private final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
-    private final ChatColor[] colors = ChatColor.values();
-
-    protected ColouredConsoleSender() {
-        super();
-        this.terminal = ((CraftServer) getServer()).getReader().getTerminal();
-
-        replacements.put(ChatColor.BLACK, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString());
-        replacements.put(ChatColor.DARK_BLUE, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString());
-        replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.GREEN).boldOff().toString());
-        replacements.put(ChatColor.DARK_AQUA, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString());
-        replacements.put(ChatColor.DARK_RED, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString());
-        replacements.put(ChatColor.DARK_PURPLE, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff().toString());
-        replacements.put(ChatColor.GOLD, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString());
-        replacements.put(ChatColor.GRAY, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString());
-        replacements.put(ChatColor.DARK_GRAY, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString());
-        replacements.put(ChatColor.BLUE, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString());
-        replacements.put(ChatColor.GREEN, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString());
-        replacements.put(ChatColor.AQUA, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString());
-        replacements.put(ChatColor.RED, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.RED).bold().toString());
-        replacements.put(ChatColor.LIGHT_PURPLE, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.MAGENTA).bold().toString());
-        replacements.put(ChatColor.YELLOW, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString());
-        replacements.put(ChatColor.WHITE, Ansi.ansi().a(Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString());
-        replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Attribute.BLINK_SLOW).toString());
-        replacements.put(ChatColor.BOLD, Ansi.ansi().a(Attribute.UNDERLINE_DOUBLE).toString());
-        replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Attribute.STRIKETHROUGH_ON).toString());
-        replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Attribute.UNDERLINE).toString());
-        replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Attribute.ITALIC).toString());
-        replacements.put(ChatColor.RESET, Ansi.ansi().a(Attribute.RESET).toString());
-    }
-
-    @Override
-    public void sendMessage(String message) {
-        if (terminal.isAnsiSupported()) {
-            if (!conversationTracker.isConversingModaly()) {
-                String result = message;
-                for (ChatColor color : colors) {
-                    if (replacements.containsKey(color)) {
-                        result = result.replaceAll("(?i)" + color.toString(), replacements.get(color));
-                    } else {
-                        result = result.replaceAll("(?i)" + color.toString(), "");
-                    }
-                }
-                System.out.println(result + Ansi.ansi().reset().toString());
-            }
-        } else {
-            super.sendMessage(message);
-        }
-    }
-
-    public static ConsoleCommandSender getInstance() {
-        if (Bukkit.getConsoleSender() != null) {
-            return Bukkit.getConsoleSender();
-        } else {
-            return new ColouredConsoleSender();
-        }
-    }
-}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
index 33e8ea02c4..1e3aae3b8f 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
@@ -8,17 +8,27 @@ import java.util.logging.Level;
 import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.util.Waitable;
 
-import jline.console.completer.Completer;
+// Paper start - JLine update
+import net.minecraft.server.DedicatedServer; // Paper
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+// Paper end
 import org.bukkit.event.server.TabCompleteEvent;
 
 public class ConsoleCommandCompleter implements Completer {
-    private final CraftServer server;
+    private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer
 
-    public ConsoleCommandCompleter(CraftServer server) {
+    public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer
         this.server = server;
     }
 
-    public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+    // Paper start - Change method signature for JLine update
+    public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
+        final CraftServer server = this.server.server;
+        final String buffer = line.line();
+        // Paper end
         Waitable<List<String>> waitable = new Waitable<List<String>>() {
             @Override
             protected List<String> evaluate() {
@@ -30,25 +40,37 @@ public class ConsoleCommandCompleter implements Completer {
                 return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions();
             }
         };
-        this.server.getServer().processQueue.add(waitable);
+        server.getServer().processQueue.add(waitable); // Paper - Remove "this."
         try {
             List<String> offers = waitable.get();
             if (offers == null) {
-                return cursor;
+                return; // Paper - Method returns void
             }
-            candidates.addAll(offers);
 
+            // Paper start - JLine update
+            for (String completion : offers) {
+                if (completion.isEmpty()) {
+                    continue;
+                }
+
+                candidates.add(new Candidate(completion));
+            }
+            // Paper end
+
+            // Paper start - JLine handles cursor now
+            /*
             final int lastSpace = buffer.lastIndexOf(' ');
             if (lastSpace == -1) {
                 return cursor - buffer.length();
             } else {
                 return cursor - (buffer.length() - lastSpace - 1);
             }
+            */
+            // Paper end
         } catch (ExecutionException e) {
-            this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
+            server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); // Paper - Remove "this."
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
-        return cursor;
     }
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
index 984df4083d..bbb5a84f36 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -20,7 +20,7 @@ public class ServerShutdownThread extends Thread {
             ex.printStackTrace();
         } finally {
             try {
-                server.reader.getTerminal().restore();
+                net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
             } catch (Exception e) {
             }
         }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
deleted file mode 100644
index b640971130..0000000000
--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.bukkit.craftbukkit.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import jline.console.ConsoleReader;
-import com.mojang.util.QueueLogAppender;
-import org.bukkit.craftbukkit.Main;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Erase;
-
-public class TerminalConsoleWriterThread implements Runnable {
-    final private ConsoleReader reader;
-    final private OutputStream output;
-
-    public TerminalConsoleWriterThread(OutputStream output, ConsoleReader reader) {
-        this.output = output;
-        this.reader = reader;
-    }
-
-    public void run() {
-        String message;
-
-        // Using name from log4j config in vanilla jar
-        while (true) {
-            message = QueueLogAppender.getNextLogEvent("TerminalConsole");
-            if (message == null) {
-                continue;
-            }
-
-            try {
-                if (Main.useJline) {
-                    reader.print(Ansi.ansi().eraseLine(Erase.ALL).toString() + ConsoleReader.RESET_LINE);
-                    reader.flush();
-                    output.write(message.getBytes());
-                    output.flush();
-
-                    try {
-                        reader.drawLine();
-                    } catch (Throwable ex) {
-                        reader.getCursorBuffer().clear();
-                    }
-                    reader.flush();
-                } else {
-                    output.write(message.getBytes());
-                    output.flush();
-                }
-            } catch (IOException ex) {
-                Logger.getLogger(TerminalConsoleWriterThread.class.getName()).log(Level.SEVERE, null, ex);
-            }
-        }
-    }
-}
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 5cee8f00ef..08b6bb7f97 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,12 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="WARN" packages="com.mojang.util">
+<Configuration status="WARN">
     <Appenders>
-        <Console name="WINDOWS_COMPAT" target="SYSTEM_OUT"></Console>
-        <Queue name="TerminalConsole">
-            <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
-        </Queue>
+        <TerminalConsole name="TerminalConsole">
+            <PatternLayout pattern="%highlightError{[%d{HH:mm:ss} %level]: %minecraftFormatting{%msg}%n%xEx}" />
+        </TerminalConsole>
         <RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
-            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
+            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %minecraftFormatting{%msg}{strip}%n" />
             <Policies>
                 <TimeBasedTriggeringPolicy />
                 <OnStartupTriggeringPolicy />
@@ -19,7 +18,6 @@
             <filters>
                 <MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
             </filters>
-            <AppenderRef ref="WINDOWS_COMPAT" level="info"/>
             <AppenderRef ref="File"/>
             <AppenderRef ref="TerminalConsole" level="info"/>
         </Root>
-- 
2.19.0