mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-12 08:17:35 +00:00
Merge branch 'jline' of https://github.com/Minecrell/MinecraftForge into Minecrell-jline
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user