Merge branch 'jline' of https://github.com/Minecrell/MinecraftForge into Minecrell-jline

This commit is contained in:
cpw committed 2015-11-30 13:14:49 -05:00
commit 321665efff
12 files changed
+537 -1

No files matched your search

@@ -1,9 +1,28 @@
package net.minecraftforge.fml.common.launcher;
import java.io.File;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
public class FMLServerTweaker extends FMLTweaker {
@Override
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile)
{
super.acceptOptions(args, gameDir, assetsDir, profile);
if (System.getProperty("log4j.configurationFile") == null)
{
System.setProperty("log4j.configurationFile", "log4j2_server.xml");
((LoggerContext) LogManager.getContext(false)).reconfigure();
}
}
@Override
public String getLaunchTarget()
{
@@ -16,6 +35,11 @@ public class FMLServerTweaker extends FMLTweaker {
// The log4j2 queue is excluded so it is correctly visible from the obfuscated
// and deobfuscated parts of the code. Without, the UI won't show anything
classLoader.addClassLoaderExclusion("com.mojang.util.QueueLogAppender");
classLoader.addClassLoaderExclusion("jline.");
classLoader.addClassLoaderExclusion("org.fusesource.");
classLoader.addClassLoaderExclusion("net.minecraftforge.server.console.TerminalConsoleAppender");
FMLLaunchHandler.configureForServerLaunch(classLoader, this);
FMLLaunchHandler.appendCoreMods();
}
@@ -0,0 +1,96 @@
package net.minecraftforge.server.console;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import jline.console.completer.Completer;
import net.minecraft.server.dedicated.DedicatedServer;
public final class ConsoleCommandCompleter implements Completer
{
private static final Logger logger = LogManager.getLogger();
private final DedicatedServer server;
public ConsoleCommandCompleter(DedicatedServer server)
{
this.server = checkNotNull(server, "server");
}
@Override
public int complete(String buffer, int cursor, List<CharSequence> candidates)
{
int len = buffer.length();
buffer = buffer.trim();
if (buffer.isEmpty())
{
return cursor;
}
boolean prefix;
if (buffer.charAt(0) != '/')
{
buffer = '/' + buffer;
prefix = false;
}
else
{
prefix = true;
}
final String input = buffer;
@SuppressWarnings("unchecked")
Future<List<String>> tabComplete = this.server.callFromMainThread(new Callable<List<String>>() {
@Override
public List<String> call() throws Exception
{
return ConsoleCommandCompleter.this.server.getTabCompletions(ConsoleCommandCompleter.this.server, input,
ConsoleCommandCompleter.this.server.getPosition());
}
});
try
{
List<String> completions = tabComplete.get();
if (prefix)
{
candidates.addAll(completions);
}
else
{
for (String completion : completions)
{
candidates.add(completion.charAt(0) == '/' ? completion.substring(1) : completion);
}
}
int pos = buffer.lastIndexOf(' ');
if (pos == -1)
{
return cursor - len;
}
else
{
return cursor - len + pos;
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
catch (ExecutionException e)
{
logger.error("Failed to tab complete", e);
}
return cursor;
}
}
@@ -0,0 +1,63 @@
package net.minecraftforge.server.console;
import java.util.Map;
import java.util.regex.Pattern;
import org.fusesource.jansi.Ansi;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import net.minecraft.util.EnumChatFormatting;
public final class ConsoleFormatter implements Function<String, String>
{
public ConsoleFormatter()
{
}
private static final String RESET = Ansi.ansi().reset().toString();
private static final ImmutableMap<Pattern, String> REPLACEMENTS = ImmutableMap.<Pattern, String> builder()
.put(compile(EnumChatFormatting.BLACK), Ansi.ansi().reset().fg(Ansi.Color.BLACK).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_BLUE), Ansi.ansi().reset().fg(Ansi.Color.BLUE).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_GREEN), Ansi.ansi().reset().fg(Ansi.Color.GREEN).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_AQUA), Ansi.ansi().reset().fg(Ansi.Color.CYAN).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_RED), Ansi.ansi().reset().fg(Ansi.Color.RED).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_PURPLE), Ansi.ansi().reset().fg(Ansi.Color.MAGENTA).boldOff().toString())
.put(compile(EnumChatFormatting.GOLD), Ansi.ansi().reset().fg(Ansi.Color.YELLOW).boldOff().toString())
.put(compile(EnumChatFormatting.GRAY), Ansi.ansi().reset().fg(Ansi.Color.WHITE).boldOff().toString())
.put(compile(EnumChatFormatting.DARK_GRAY), Ansi.ansi().reset().fg(Ansi.Color.BLACK).bold().toString())
.put(compile(EnumChatFormatting.BLUE), Ansi.ansi().reset().fg(Ansi.Color.BLUE).bold().toString())
.put(compile(EnumChatFormatting.GREEN), Ansi.ansi().reset().fg(Ansi.Color.GREEN).bold().toString())
.put(compile(EnumChatFormatting.AQUA), Ansi.ansi().reset().fg(Ansi.Color.CYAN).bold().toString())
.put(compile(EnumChatFormatting.RED), Ansi.ansi().reset().fg(Ansi.Color.RED).bold().toString())
.put(compile(EnumChatFormatting.LIGHT_PURPLE), Ansi.ansi().reset().fg(Ansi.Color.MAGENTA).bold().toString())
.put(compile(EnumChatFormatting.YELLOW), Ansi.ansi().reset().fg(Ansi.Color.YELLOW).bold().toString())
.put(compile(EnumChatFormatting.WHITE), Ansi.ansi().reset().fg(Ansi.Color.WHITE).bold().toString())
.put(compile(EnumChatFormatting.OBFUSCATED), Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString())
.put(compile(EnumChatFormatting.BOLD), Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString())
.put(compile(EnumChatFormatting.STRIKETHROUGH), Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString())
.put(compile(EnumChatFormatting.UNDERLINE), Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString())
.put(compile(EnumChatFormatting.ITALIC), Ansi.ansi().a(Ansi.Attribute.ITALIC).toString())
.put(compile(EnumChatFormatting.RESET), RESET)
.build();
private static Pattern compile(EnumChatFormatting formatting)
{
return Pattern.compile(formatting.toString(), Pattern.LITERAL | Pattern.CASE_INSENSITIVE);
}
@Override
public String apply(String text)
{
for (Map.Entry<Pattern, String> entry : REPLACEMENTS.entrySet())
{
text = entry.getKey().matcher(text).replaceAll(entry.getValue());
}
return text + RESET;
}
}
@@ -0,0 +1,168 @@
package net.minecraftforge.server.console;
import static jline.TerminalFactory.OFF;
import static jline.console.ConsoleReader.RESET_LINE;
import static org.apache.logging.log4j.core.helpers.Booleans.parseBoolean;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.Writer;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.fusesource.jansi.AnsiConsole;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import jline.TerminalFactory;
import jline.console.ConsoleReader;
@Plugin(name = "TerminalConsole", category = "Core", elementType = "appender", printObject = true)
public class TerminalConsoleAppender extends AbstractAppender
{
private static final boolean ENABLE_JLINE = PropertiesUtil.getProperties().getBooleanProperty("jline.enable", true);
private static final PrintStream out = System.out;
private static boolean initialized;
private static ConsoleReader reader;
public static ConsoleReader getReader()
{
return reader;
}
private static Function<String, String> formatter = Functions.identity();
public static void setFormatter(Function<String, String> format)
{
formatter = format != null ? format : Functions.<String> identity();
}
protected TerminalConsoleAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions)
{
super(name, filter, layout, ignoreExceptions);
}
@PluginFactory
public static TerminalConsoleAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filters") Filter filter,
@PluginElement("Layout") Layout<? extends Serializable> layout, @PluginAttribute("ignoreExceptions") String ignore)
{
if (name == null)
{
LOGGER.error("No name provided for TerminalConsoleAppender");
return null;
}
if (layout == null)
{
layout = PatternLayout.createLayout(null, null, null, null, null);
}
boolean ignoreExceptions = parseBoolean(ignore, true);
// This is handled by jline
System.setProperty("log4j.skipJansi", "true");
return new TerminalConsoleAppender(name, filter, layout, ignoreExceptions);
}
@Override
public void start()
{
// Initialize the reader if that hasn't happened yet
if (!initialized && reader == null)
{
initialized = true;
if (ENABLE_JLINE)
{
final boolean hasConsole = System.console() != null;
if (hasConsole)
{
try
{
AnsiConsole.systemInstall();
reader = new ConsoleReader();
reader.setExpandEvents(false);
}
catch (Exception e)
{
LOGGER.warn("Failed to initialize terminal. Falling back to default.", e);
}
}
if (reader == null)
{
// Eclipse doesn't support colors and characters like \r so enabling jline2 on it will
// just cause a lot of issues with empty lines and weird characters.
// Enable jline2 only on IntelliJ IDEA to prevent that.
// Also see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=76936
if (hasConsole || System.getProperty("java.class.path").contains("idea_rt.jar"))
{
// Disable advanced jline features
TerminalFactory.configure(OFF);
TerminalFactory.reset();
try
{
reader = new ConsoleReader();
reader.setExpandEvents(false);
}
catch (Exception e)
{
LOGGER.warn("Failed to initialize fallback terminal. Falling back to standard output console.", e);
}
}
else
{
LOGGER.warn("Disabling terminal, you're running in an unsupported environment.");
}
}
}
}
super.start();
}
@Override
public void append(LogEvent event)
{
if (reader != null)
{
try
{
Writer out = reader.getOutput();
out.write(RESET_LINE);
out.write(formatEvent(event));
reader.drawLine();
reader.flush();
}
catch (IOException ignored)
{
}
}
else
{
out.print(formatEvent(event));
}
}
protected String formatEvent(LogEvent event)
{
return formatter.apply(getLayout().toSerializable(event).toString());
}
}
@@ -0,0 +1,70 @@
package net.minecraftforge.server.console;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Function;
import jline.console.ConsoleReader;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.util.EnumChatFormatting;
public final class TerminalHandler
{
private static final Logger logger = LogManager.getLogger();
private TerminalHandler()
{
}
public static boolean handleCommands(DedicatedServer server)
{
final ConsoleReader reader = TerminalConsoleAppender.getReader();
if (reader != null)
{
TerminalConsoleAppender.setFormatter(new ConsoleFormatter());
reader.addCompleter(new ConsoleCommandCompleter(server));
String line;
while (!server.isServerStopped() && server.isServerRunning())
{
try
{
line = reader.readLine("> ");
if (line != null)
{
line = line.trim();
if (!line.isEmpty())
{
server.addPendingCommand(line, server);
}
}
}
catch (IOException e)
{
logger.error("Exception handling console input", e);
}
}
return true;
}
else
{
TerminalConsoleAppender.setFormatter(new Function<String, String>() {
@Override
public String apply(String text)
{
return EnumChatFormatting.getTextWithoutFormattingCodes(text);
}
});
return false;
}
}
}