From a49974fc6a244b79944d3ce52a64d9c0da8d4c6c Mon Sep 17 00:00:00 2001
From: Minecrell <dev@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:
  - Update JLine to 3.3.1 (from 2.12.1)
  - Server starts 1-2 seconds faster thanks to optimizations in Log4j
    configuration

diff --git a/pom.xml b/pom.xml
index 6a0a02883..3c20c8f34 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,12 +53,6 @@
             <version>5.0.4</version>
             <scope>compile</scope>
         </dependency>
-        <dependency>
-            <groupId>jline</groupId>
-            <artifactId>jline</artifactId>
-            <version>2.12.1</version>
-            <scope>compile</scope>
-        </dependency>
         <dependency>
             <groupId>org.xerial</groupId>
             <artifactId>sqlite-jdbc</artifactId>
@@ -77,6 +71,38 @@
             <version>3.0.3</version>
             <scope>compile</scope>
         </dependency>
+
+        <dependency>
+            <groupId>net.minecrell</groupId>
+            <artifactId>terminalconsoleappender</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jline</groupId>
+            <artifactId>jline-terminal-jna</artifactId>
+            <version>3.4.0</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <version>4.4.0</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>
+
         <!-- testing -->
         <dependency>
             <groupId>junit</groupId>
@@ -230,10 +256,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/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/com/destroystokyo/paper/console/TerminalHandler.java b/src/main/java/com/destroystokyo/paper/console/TerminalHandler.java
new file mode 100644
index 000000000..d5bc61490
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/console/TerminalHandler.java
@@ -0,0 +1,60 @@
+package com.destroystokyo.paper.console;
+
+import net.minecraft.server.DedicatedServer;
+import net.minecrell.terminalconsole.TerminalConsoleAppender;
+import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.UserInterruptException;
+import org.jline.terminal.Terminal;
+
+public class TerminalHandler {
+
+    private TerminalHandler() {
+    }
+
+    public static boolean handleCommands(DedicatedServer server) {
+        final Terminal terminal = TerminalConsoleAppender.getTerminal();
+        if (terminal == null) {
+            return false;
+        }
+
+        LineReader reader = LineReaderBuilder.builder()
+                .appName("Paper")
+                .terminal(terminal)
+                .completer(new ConsoleCommandCompleter(server))
+                .build();
+        reader.unsetOpt(LineReader.Option.INSERT_TAB);
+
+        TerminalConsoleAppender.setReader(reader);
+
+        try {
+            String line;
+            while (!server.isStopped() && server.isRunning()) {
+                try {
+                    line = reader.readLine("> ");
+                } catch (EndOfFileException ignored) {
+                    // Continue reading after EOT
+                    continue;
+                }
+
+                if (line == null) {
+                    break;
+                }
+
+                line = line.trim();
+                if (!line.isEmpty()) {
+                    server.issueCommand(line, server);
+                }
+            }
+        } catch (UserInterruptException e) {
+            server.safeShutdown();
+        } finally {
+            TerminalConsoleAppender.setReader(null);
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java
index 8f2afcc32..b3f1aa999 100644
--- a/src/main/java/net/minecraft/server/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/DedicatedServer.java
@@ -73,7 +73,10 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
                 if (!org.bukkit.craftbukkit.Main.useConsole) {
                     return;
                 }
-                jline.console.ConsoleReader bufferedreader = reader;
+                // Paper start - Use TerminalConsoleAppender implementation
+                if (com.destroystokyo.paper.console.TerminalHandler.handleCommands(DedicatedServer.this)) return;
+                BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+                // Paper end
                 // CraftBukkit end
 
                 String s;
@@ -81,11 +84,17 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
                 try {
                     // CraftBukkit start - JLine disabling compatibility
                     while (!isStopped() && isRunning()) {
+                        // Paper start - code is not used for jline
+                        /*
                         if (org.bukkit.craftbukkit.Main.useJline) {
                             s = bufferedreader.readLine(">", null);
                         } else {
                             s = bufferedreader.readLine();
                         }
+                        */
+                        s = bufferedreader.readLine();
+                        // Paper end
+
                         if (s != null && s.trim().length() > 0) { // Trim to filter lines which are just spaces
                             issueCommand(s, DedicatedServer.this);
                         }
@@ -106,6 +115,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) {
@@ -114,6 +126,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 908a5d273..e8bddc171 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -42,7 +42,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;
@@ -115,7 +114,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
     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 final Thread primaryThread;
     public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
@@ -141,7 +140,9 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
         this.dataConverterManager = dataconvertermanager;
         // 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;
@@ -162,6 +163,8 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
                 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
@@ -701,7 +704,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
             } 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
@@ -1229,7 +1232,8 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
     }
 
     public void sendMessage(IChatBaseComponent ichatbasecomponent) {
-        MinecraftServer.LOGGER.info(ichatbasecomponent.toPlainText());
+        // Paper - Log message with colors
+        MinecraftServer.LOGGER.info(org.bukkit.craftbukkit.util.CraftChatMessage.fromComponent(ichatbasecomponent, net.minecraft.server.EnumChatFormat.WHITE));
     }
 
     public boolean a(int i, String s) {
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index b8a0b67a9..ebca377e5 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 5c36e7373..cb4b78e0e 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -128,7 +128,6 @@ import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufOutputStream;
 import io.netty.buffer.Unpooled;
 import io.netty.handler.codec.base64.Base64;
-import jline.console.ConsoleReader;
 import org.apache.commons.lang.StringUtils;
 import org.bukkit.NamespacedKey;
 import org.bukkit.craftbukkit.util.CraftNamespacedKey;
@@ -1097,9 +1096,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 ec9508e90..d3d848f8c 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;
@@ -170,6 +170,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'});
@@ -187,10 +189,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 26a2fb894..000000000
--- 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 33e8ea02c..1e3aae3b8 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 a0cdd2317..0a1812883 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -19,7 +19,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 b64097113..000000000
--- 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 5cee8f00e..08b6bb7f9 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.14.1