From 0000000000000000000000000000000000000000 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 6cc18aa360c20448fca59cf5490d69267c8e2521..01dcaf4f4bbe03a118958e494544d7fc4a92e300 100644 --- a/pom.xml +++ b/pom.xml @@ -44,10 +44,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> @@ -246,10 +263,18 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/services/java.sql.Driver</resource> </transformer> + <transformer implementation="com.github.edwgiz.maven_shade_plugin.log4j2_cache_transformer.PluginsCacheFileTransformer" /> </transformers> </configuration> </execution> </executions> + <dependencies> + <dependency> + <groupId>com.github.edwgiz</groupId> + <artifactId>maven-shade-plugin.log4j2-cachefile-transformer</artifactId> + <version>2.13.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 0000000000000000000000000000000000000000..cd6e259239b068f1e1b33f45004cf5d37ac5a1a3 --- /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 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/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java index 4b1f8c53737f998fa57859146d5ddb999cdc8d41..d34f772fae3543cec6a130831b1f3eaa67934944 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 8d6a0890073adbbb39db202f80d4b83cef2ceca9..284793e4bf04cddae3e070a1fa0afdd18001fd2e 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 @@ -1475,7 +1479,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 56266a77296bc9a84de1d853a6d739337608b995..77f1ddc8a8d6fa816488de5d8c821bfa59f6c729 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 9eb4acf93c65a1473e385be317e5d93cd4571d41..681a84e5b3e7594a43fac0d47f1df2eef310bf81 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -42,7 +42,7 @@ import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; -import jline.console.ConsoleReader; +//import jline.console.ConsoleReader; // Paper import net.minecraft.server.Advancement; import net.minecraft.server.ArgumentEntity; import net.minecraft.server.Block; @@ -1118,9 +1118,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 afbe711acbd1cbaf307513d4fede44bb17b3caae..fc07fdb590475ccaa09875b8fcbb81adc0db5e66 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,9 +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) { @@ -226,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 index e0bd5adc19de0d4ab156bac95d3b4b2a86f89490..aae615c13b36f8e72b12260de8996317912fa917 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java @@ -2,15 +2,15 @@ package org.bukkit.craftbukkit.command; import java.util.EnumMap; import java.util.Map; -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; +//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<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class); private final ChatColor[] colors = ChatColor.values(); @@ -71,5 +71,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 befcc19f9b56df9096b98a23b0020f1db793ea5b..5510266fb114954322823b72e3199f33c4d7a9a7 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 70f8d42992aa348ef7b2d03d22cdd59d7c73f0fe..449e99d1b673870ed6892f6ab2c715a2db35c35d 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 index 99564fed7ce77e29dbdc591bcfe656af741acf8a..9a2da548b8860b496e396564b2c8f6383f020193 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 @@ <?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>