package jdk.internal.org.jline.terminal;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import jdk.internal.org.jline.terminal.impl.AbstractPosixTerminal;
import jdk.internal.org.jline.terminal.impl.DumbTerminal;
import jdk.internal.org.jline.terminal.impl.ExecPty;
import jdk.internal.org.jline.terminal.impl.ExternalTerminal;
import jdk.internal.org.jline.terminal.impl.PosixPtyTerminal;
import jdk.internal.org.jline.terminal.impl.PosixSysTerminal;
import jdk.internal.org.jline.terminal.spi.JansiSupport;
import jdk.internal.org.jline.terminal.spi.JnaSupport;
import jdk.internal.org.jline.terminal.spi.Pty;
import jdk.internal.org.jline.utils.Log;
import jdk.internal.org.jline.utils.OSUtils;
import static jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal.TYPE_WINDOWS;
import static jdk.internal.org.jline.terminal.impl.AbstractWindowsTerminal.TYPE_WINDOWS_256_COLOR;
public final class TerminalBuilder {
public static final String PROP_ENCODING = "org.jline.terminal.encoding";
public static final String PROP_CODEPAGE = "org.jline.terminal.codepage";
public static final String PROP_TYPE = "org.jline.terminal.type";
public static final String PROP_JNA = "org.jline.terminal.jna";
public static final String PROP_JANSI = "org.jline.terminal.jansi";
public static final String PROP_EXEC = "org.jline.terminal.exec";
public static final String PROP_DUMB = "org.jline.terminal.dumb";
public static final String PROP_DUMB_COLOR = "org.jline.terminal.dumb.color";
public static final String PROP_NON_BLOCKING_READS = "org.jline.terminal.pty.nonBlockingReads";
public static final String PROP_COLOR_DISTANCE = "org.jline.utils.colorDistance";
public static final String PROP_DISABLE_ALTERNATE_CHARSET = "org.jline.utils.disableAlternateCharset";
public static Terminal terminal() throws IOException {
return builder().build();
}
public static TerminalBuilder builder() {
return new TerminalBuilder();
}
private String name;
private InputStream in;
private OutputStream out;
private String type;
private Charset encoding;
private int codepage;
private Boolean system;
private Boolean jna;
private Boolean jansi;
private Boolean exec;
private Boolean dumb;
private Attributes attributes;
private Size size;
private boolean nativeSignals = false;
private Terminal.SignalHandler signalHandler = Terminal.SignalHandler.SIG_DFL;
private boolean paused = false;
private Function<InputStream, InputStream> inputStreamWrapper = in -> in;
private TerminalBuilder() {
}
public TerminalBuilder name(String name) {
this.name = name;
return this;
}
public TerminalBuilder streams(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
return this;
}
public TerminalBuilder system(boolean system) {
this.system = system;
return this;
}
public TerminalBuilder jna(boolean jna) {
this.jna = jna;
return this;
}
public TerminalBuilder jansi(boolean jansi) {
this.jansi = jansi;
return this;
}
public TerminalBuilder exec(boolean exec) {
this.exec = exec;
return this;
}
public TerminalBuilder dumb(boolean dumb) {
this.dumb = dumb;
return this;
}
public TerminalBuilder type(String type) {
this.type = type;
return this;
}
public TerminalBuilder encoding(String encoding) throws UnsupportedCharsetException {
return encoding(encoding != null ? Charset.forName(encoding) : null);
}
public TerminalBuilder encoding(Charset encoding) {
this.encoding = encoding;
return this;
}
@Deprecated
public TerminalBuilder codepage(int codepage) {
this.codepage = codepage;
return this;
}
public TerminalBuilder attributes(Attributes attributes) {
this.attributes = attributes;
return this;
}
public TerminalBuilder size(Size size) {
this.size = size;
return this;
}
public TerminalBuilder nativeSignals(boolean nativeSignals) {
this.nativeSignals = nativeSignals;
return this;
}
public TerminalBuilder signalHandler(Terminal.SignalHandler signalHandler) {
this.signalHandler = signalHandler;
return this;
}
public TerminalBuilder paused(boolean paused) {
this.paused = paused;
return this;
}
public TerminalBuilder inputStreamWrapper(Function<InputStream, InputStream> wrapper) {
this.inputStreamWrapper = wrapper;
return this;
}
public Terminal build() throws IOException {
Terminal terminal = doBuild();
Log.debug(() -> "Using terminal " + terminal.getClass().getSimpleName());
if (terminal instanceof AbstractPosixTerminal) {
Log.debug(() -> "Using pty " + ((AbstractPosixTerminal) terminal).getPty().getClass().getSimpleName());
}
return terminal;
}
private Terminal doBuild() throws IOException {
String name = this.name;
if (name == null) {
name = "JLine terminal";
}
Charset encoding = this.encoding;
if (encoding == null) {
String charsetName = System.getProperty(PROP_ENCODING);
if (charsetName != null && Charset.isSupported(charsetName)) {
encoding = Charset.forName(charsetName);
}
}
int codepage = this.codepage;
if (codepage <= 0) {
String str = System.getProperty(PROP_CODEPAGE);
if (str != null) {
codepage = Integer.parseInt(str);
}
}
String type = this.type;
if (type == null) {
type = System.getProperty(PROP_TYPE);
}
if (type == null) {
type = System.getenv("TERM");
}
Boolean jna = this.jna;
if (jna == null) {
jna = getBoolean(PROP_JNA, true);
}
Boolean jansi = this.jansi;
if (jansi == null) {
jansi = getBoolean(PROP_JANSI, true);
}
Boolean exec = this.exec;
if (exec == null) {
exec = getBoolean(PROP_EXEC, true);
}
Boolean dumb = this.dumb;
if (dumb == null) {
dumb = getBoolean(PROP_DUMB, null);
}
if ((system != null && system) || (system == null && in == null && out == null)) {
if (attributes != null || size != null) {
Log.warn("Attributes and size fields are ignored when creating a system terminal");
}
IllegalStateException exception = new IllegalStateException("Unable to create a system terminal");
if (OSUtils.IS_WINDOWS) {
boolean cygwinTerm = "cygwin".equals(System.getenv("TERM"));
boolean ansiPassThrough = OSUtils.IS_CONEMU;
if ((OSUtils.IS_CYGWIN || OSUtils.IS_MSYSTEM) && exec && !cygwinTerm) {
try {
Pty pty = ExecPty.current();
if ("xterm".equals(type) && this.type == null && System.getProperty(PROP_TYPE) == null) {
type = "xterm-256color";
}
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (IOException e) {
Log.debug("Error creating EXEC based terminal: ", e.getMessage(), e);
exception.addSuppressed(e);
}
}
if (jna) {
try {
return load(JnaSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused, inputStreamWrapper);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (jansi) {
try {
return load(JansiSupport.class).winSysTerminal(name, type, ansiPassThrough, encoding, codepage, nativeSignals, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
} else {
if (jna) {
try {
Pty pty = load(JnaSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (jansi) {
try {
Pty pty = load(JansiSupport.class).current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
if (exec) {
try {
Pty pty = ExecPty.current();
return new PosixSysTerminal(name, type, pty, inputStreamWrapper.apply(pty.getSlaveInput()), pty.getSlaveOutput(), encoding, nativeSignals, signalHandler);
} catch (Throwable t) {
Log.debug("Error creating EXEC based terminal: ", t.getMessage(), t);
exception.addSuppressed(t);
}
}
}
if (dumb == null || dumb) {
boolean color = getBoolean(PROP_DUMB_COLOR, false);
if (!color) {
color = System.getenv("INSIDE_EMACS") != null;
}
if (!color) {
String command = getParentProcessCommand();
color = command != null && command.contains("idea");
}
if (!color && dumb == null) {
if (Log.isDebugEnabled()) {
Log.warn("Creating a dumb terminal", exception);
} else {
Log.warn("Unable to create a system terminal, creating a dumb terminal (enable debug logging for more information)");
}
}
return new DumbTerminal(name, color ? Terminal.TYPE_DUMB_COLOR : Terminal.TYPE_DUMB,
new FileInputStream(FileDescriptor.in),
new FileOutputStream(FileDescriptor.out),
encoding, signalHandler);
} else {
throw exception;
}
} else {
if (jna) {
try {
Pty pty = load(JnaSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JNA based terminal: ", t.getMessage(), t);
}
}
if (jansi) {
try {
Pty pty = load(JansiSupport.class).open(attributes, size);
return new PosixPtyTerminal(name, type, pty, in, out, encoding, signalHandler, paused);
} catch (Throwable t) {
Log.debug("Error creating JANSI based terminal: ", t.getMessage(), t);
}
}
Terminal terminal = new ExternalTerminal(name, type, in, out, encoding, signalHandler, paused);
if (attributes != null) {
terminal.setAttributes(attributes);
}
if (size != null) {
terminal.setSize(size);
}
return terminal;
}
}
private static String getParentProcessCommand() {
try {
Class<?> phClass = Class.forName("java.lang.ProcessHandle");
Object current = phClass.getMethod("current").invoke(null);
Object parent = ((Optional<?>) phClass.getMethod("parent").invoke(current)).orElse(null);
Method infoMethod = phClass.getMethod("info");
Object info = infoMethod.invoke(parent);
Object command = ((Optional<?>) infoMethod.getReturnType().getMethod("command").invoke(info)).orElse(null);
return (String) command;
} catch (Throwable t) {
return null;
}
}
private static Boolean getBoolean(String name, Boolean def) {
try {
String str = System.getProperty(name);
if (str != null) {
return Boolean.parseBoolean(str);
}
} catch (IllegalArgumentException | NullPointerException e) {
}
return def;
}
private <S> S load(Class<S> clazz) {
return ServiceLoader.load(clazz, clazz.getClassLoader()).iterator().next();
}
}