From 315a86ae9cbec64a1333240cb6277631d7547600 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 3dc6c2a3f..b1f008738 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,10 +41,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.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jline</groupId>
+            <artifactId>jline-terminal-jansi</artifactId>
+            <version>3.12.1</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>
@@ -230,10 +247,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 000000000..cd6e25923
--- /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(false);
+    }
+
+}
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 000000000..685deaa0e
--- /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 4b1f8c537..d34f772fa 100644
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
@@ -86,6 +86,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;
 
                 // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
@@ -125,6 +128,8 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
                     DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
                 }
 
+                */
+                // Paper end
             }
         };
 
@@ -136,6 +141,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) {
@@ -144,6 +152,8 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
         }
 
         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 e41352681..7016dac2d 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -57,7 +57,7 @@ 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;
 import org.bukkit.craftbukkit.Main;
@@ -161,7 +161,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
     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 java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
     public int autosavePeriod;
@@ -212,7 +212,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         this.K = s;
         // 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;
@@ -233,6 +235,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
                 LOGGER.warn((String) null, ex);
             }
         }
+        */
+        // Paper end
         Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
     }
     // CraftBukkit end
@@ -951,7 +955,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
                 org.spigotmc.WatchdogThread.doStop(); // Spigot
                 // CraftBukkit start - Restore terminal to original settings
                 try {
-                    reader.getTerminal().restore();
+                    net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
                 } catch (Exception ignored) {
                 }
                 // CraftBukkit end
@@ -1470,7 +1474,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
 
     @Override
     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 getKeyPair() {
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 1eb53f67e..308bc3baf 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -76,8 +76,7 @@ public abstract class PlayerList {
 
     public PlayerList(MinecraftServer minecraftserver, int i) {
         this.cserver = minecraftserver.server = new CraftServer((DedicatedServer) 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.b);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 2e0f443d9..742c21480 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -42,7 +42,6 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.imageio.ImageIO;
 import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
-import jline.console.ConsoleReader;
 import net.minecraft.server.Advancement;
 import net.minecraft.server.ArgumentEntity;
 import net.minecraft.server.Block;
@@ -1118,9 +1117,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 93fcfd5f3..0e76e3466 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -13,7 +13,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;
@@ -179,6 +179,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'});
@@ -196,10 +198,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) {
@@ -227,7 +237,7 @@ public class Main {
                     System.out.println("Unable to read system info");
                 }
                 // Paper end
-
+                System.setProperty( "library.jansi.version", "Paper" ); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows
                 System.out.println("Loading libraries, please wait...");
                 MinecraftServer.main(options);
             } catch (Throwable t) {
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 fdf2f075e..000000000
--- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.bukkit.craftbukkit.command;
-
-import java.util.EnumMap;
-import java.util.Map;
-import jline.Terminal;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.craftbukkit.CraftServer;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Attribute;
-
-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 befcc19f9..5510266fb 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
@@ -4,20 +4,31 @@ import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.logging.Level;
-import jline.console.completer.Completer;
 import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.util.Waitable;
+
+// 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;
     }
 
+    // Paper start - Change method signature for JLine update
     @Override
-    public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+    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() {
@@ -29,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
+            }
+
+            // Paper start - JLine update
+            for (String completion : offers) {
+                if (completion.isEmpty()) {
+                    continue;
+                }
+
+                candidates.add(new Candidate(completion));
             }
-            candidates.addAll(offers);
+            // 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 70f8d4299..449e99d1b 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -17,7 +17,7 @@ public class ServerShutdownThread extends Thread {
             server.close();
         } 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 99564fed7..000000000
--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.bukkit.craftbukkit.util;
-
-import com.mojang.util.QueueLogAppender;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import jline.console.ConsoleReader;
-import org.bukkit.craftbukkit.Main;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Erase;
-
-public class TerminalConsoleWriterThread extends Thread {
-    private final ConsoleReader reader;
-    private final OutputStream output;
-
-    public TerminalConsoleWriterThread(OutputStream output, ConsoleReader reader) {
-        super("TerminalConsoleWriter");
-        this.output = output;
-        this.reader = reader;
-
-        this.setDaemon(true);
-    }
-
-    @Override
-    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.component.properties b/src/main/resources/log4j2.component.properties
new file mode 100644
index 000000000..0694b2146
--- /dev/null
+++ b/src/main/resources/log4j2.component.properties
@@ -0,0 +1 @@
+log4j.skipJansi=true
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 722ca8496..620b9490e 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,17 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="WARN" packages="com.mojang.util">
     <Appenders>
-        <Console name="SysOut" target="SYSTEM_OUT">
-            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
-        </Console>
         <Queue name="ServerGuiConsole">
             <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
         </Queue>
-        <Queue name="TerminalConsole">
-            <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%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 />
@@ -24,10 +21,9 @@
             <filters>
                 <MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
             </filters>
-            <AppenderRef ref="SysOut" level="info"/>
             <AppenderRef ref="File"/>
-            <AppenderRef ref="ServerGuiConsole" level="info"/>
             <AppenderRef ref="TerminalConsole" level="info"/>
+            <AppenderRef ref="ServerGuiConsole" level="info"/>
         </Root>
     </Loggers>
 </Configuration>
-- 
2.25.0