From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Minecrell 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/build.gradle.kts b/build.gradle.kts index f08a6a00ad4d4c96166b386b3e79dc1ea021db3a..6c86bb7ff8c7bfcfc29fe79ea8c6f79656a3d7ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,17 @@ repositories { dependencies { implementation(project(":paper-api")) - implementation("jline:jline:2.12.1") + // Paper start + implementation("org.jline:jline-terminal-jansi:3.21.0") + implementation("net.minecrell:terminalconsoleappender:1.3.0") + /* + 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. + */ + runtimeOnly("org.apache.logging.log4j:log4j-core:2.14.1") + // Paper end implementation("org.apache.logging.log4j:log4j-iostreams:2.17.1") // Paper implementation("org.ow2.asm:asm:9.2") implementation("org.ow2.asm:asm-commons:9.2") // Paper - ASM event executor generation 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 0000000000000000000000000000000000000000..a4070b59e261f0f1ac4beec47b11492f4724bf27 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java @@ -0,0 +1,41 @@ +package com.destroystokyo.paper.console; + +import net.minecraft.server.dedicated.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") + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + ); + } + + @Override + protected boolean isRunning() { + return !this.server.isStopped() && this.server.isRunning(); + } + + @Override + protected void runCommand(String command) { + this.server.handleConsoleInput(command, this.server.createCommandSourceStack()); + } + + @Override + protected void shutdown() { + this.server.halt(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 0000000000000000000000000000000000000000..685deaa0e5d1ddc13e3a7c0471b1cfcf1710c869 --- /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/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index aa02ab4ea7890d9a477be55ce6c0be0a2a0d4b52..f846d9163ca97901ac1508ef8a476dabbf6dc454 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -169,7 +169,7 @@ import org.slf4j.Logger; // CraftBukkit start import com.mojang.serialization.DynamicOps; import com.mojang.serialization.Lifecycle; -import jline.console.ConsoleReader; +// import jline.console.ConsoleReader; // Paper import joptsimple.OptionSet; import net.minecraft.server.bossevents.CustomBossEvents; import net.minecraft.server.dedicated.DedicatedServer; @@ -284,7 +284,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; @@ -367,7 +367,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { // Trim to filter lines which are just spaces - DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack()); + DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); } // CraftBukkit end } @@ -149,6 +152,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.error("Exception handling console input", ioexception); } + */ + // Paper end } }; @@ -160,6 +165,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } 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) { @@ -168,6 +176,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader).start(); + */ + // Paper end System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream()); System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream()); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 5b1dcf148d24bbd61728d8c2af611b7c4f8dd166..03fc0bca0c1a11db43d315236ca05dfea137ddb3 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -155,8 +155,7 @@ public abstract class PlayerList { public PlayerList(MinecraftServer server, RegistryAccess.Frozen registryManager, PlayerDataStorage saveHandler, int maxPlayers) { this.cserver = server.server = new CraftServer((DedicatedServer) server, this); - server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance(); - server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server)); + server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper // CraftBukkit end this.bans = new UserBanList(PlayerList.USERBANLIST_FILE); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 57999f25a820cc9b395f6c3037e0fcce9bc4a80c..044609ec67ea3fc6240437807a329e136c8e6633 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -45,7 +45,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.imageio.ImageIO; -import jline.console.ConsoleReader; import net.minecraft.advancements.Advancement; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; @@ -1282,9 +1281,13 @@ public final class CraftServer implements Server { return this.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 b3d62affc6558d2c3082a75793ebd8b521eb1df6..75789a104025469fbee0d934396f65e5e47e0e41 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -12,7 +12,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import joptsimple.OptionParser; import joptsimple.OptionSet; -import org.fusesource.jansi.AnsiConsole; +import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper public class Main { public static boolean useJline = true; @@ -195,6 +195,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'}); @@ -212,9 +214,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")) { Main.useConsole = false; + useJline = false; // Paper + System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper } if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { @@ -242,7 +253,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..."); net.minecraft.server.Main.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 index 76fb1ecd47cb86b50486effe8cc7fe4abf8e311c..21f889ddec72b40f5954eec07417e08d192b4661 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java @@ -5,15 +5,13 @@ import java.util.EnumMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jline.Terminal; +//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 { +public class ColouredConsoleSender /*extends CraftConsoleCommandSender */{/* // Paper - disable private final Terminal terminal; private final Map replacements = new EnumMap(ChatColor.class); private final ChatColor[] colors = ChatColor.values(); @@ -93,5 +91,5 @@ public class ColouredConsoleSender extends CraftConsoleCommandSender { } else { return new ColouredConsoleSender(); } - } + }*/ // Paper } diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java index 0b4c62387c1093652ac15b64a8703249de4cf088..b996fde481cebbbcce80a6c267591136db7cc0bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java +++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java @@ -4,50 +4,73 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import jline.console.completer.Completer; +import net.minecraft.server.dedicated.DedicatedServer; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.util.Waitable; + +// Paper start - JLine update +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 candidates) { + public void complete(LineReader reader, ParsedLine line, List candidates) { + final CraftServer server = this.server.server; + final String buffer = line.line(); + // Paper end Waitable> waitable = new Waitable>() { @Override protected List evaluate() { - List offers = ConsoleCommandCompleter.this.server.getCommandMap().tabComplete(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer); + List offers = server.getCommandMap().tabComplete(server.getConsoleSender(), buffer); // Paper - fix remap - TabCompleteEvent tabEvent = new TabCompleteEvent(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); - ConsoleCommandCompleter.this.server.getPluginManager().callEvent(tabEvent); + TabCompleteEvent tabEvent = new TabCompleteEvent(server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); // Paper - fix remap + server.getPluginManager().callEvent(tabEvent); // Paper - fix remap return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions(); } }; - this.server.getServer().processQueue.add(waitable); + server.getServer().processQueue.add(waitable); // Paper - Remove "this." try { List 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 6a073a9dc44d93eba296a0e18a9c7be8a7881725..b4a19d80bbf71591f25729fd0e98590350cb31d0 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 { this.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 index 1d8b279f3cbe6fde6bb1bfc4985c4133b0d4a95d..cdc52bbe5c6ae4688615a7732b10071f7f51718e 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java +++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java @@ -5,12 +5,12 @@ import java.io.IOException; import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; -import jline.console.ConsoleReader; +//import jline.console.ConsoleReader; import org.bukkit.craftbukkit.Main; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Erase; +//import org.fusesource.jansi.Ansi; +//import org.fusesource.jansi.Ansi.Erase; -public class TerminalConsoleWriterThread extends Thread { +public class TerminalConsoleWriterThread /*extends Thread*/ {/* // Paper - disable private final ConsoleReader reader; private final OutputStream output; @@ -54,5 +54,5 @@ public class TerminalConsoleWriterThread extends Thread { 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 0000000000000000000000000000000000000000..0694b21465fb9e4164e71862ff24b62241b191f2 --- /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 722ca84968cbbbdeffd09939abff0cccd0a84010..620b9490e5f159080e50289d127404a1b56adbef 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,17 +1,14 @@ - - - - - - + + + - + @@ -24,10 +21,9 @@ - - +