/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.repl.console;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Formatter;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jline.Terminal;
import jline.TerminalFactory;
import jline.TerminalSupport;
import jline.UnsupportedTerminal;
import jline.console.ConsoleReader;
import jline.console.completer.CandidateListCompletionHandler;
/**
* {@link ShellConsole} implementation for JLine consoles.
*/
public final class JLineConsole implements ShellConsole {
private final ConsoleReader console;
private final Formatter formatter;
private final JLineReader reader = new JLineReader();
private final PrintWriter writer = new PrintWriter(new JLineWriter(), true);
private final PrintWriter errorWriter = new PrintWriter(System.err, true);
public JLineConsole(String programName) throws IOException {
this.console = newConsoleReader(programName);
this.formatter = new Formatter(console.getOutput());
}
private static ConsoleReader newConsoleReader(String programName) throws IOException {
final boolean isWindows = isWindows();
final String type = System.getProperty(TerminalFactory.JLINE_TERMINAL);
if (isWindows && type == null) {
TerminalFactory.registerFlavor(TerminalFactory.Flavor.WINDOWS, UnsupportedTerminal.class);
} else if (isWindows && type.equalsIgnoreCase(TerminalFactory.UNIX)) {
TerminalFactory.registerFlavor(TerminalFactory.Flavor.UNIX, CygwinTerminal.class);
}
FileInputStream in = new FileInputStream(FileDescriptor.in);
Terminal terminal = TerminalFactory.get();
ConsoleReader consoleReader = new ConsoleReader(programName, in, System.out, terminal, getDefaultEncoding());
consoleReader.setExpandEvents(false);
return consoleReader;
}
private static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}
private static String getDefaultEncoding() {
return Charset.defaultCharset().name();
}
public static final class CygwinTerminal extends TerminalSupport /* implements Terminal2 */ {
private final int width, height;
// private final HashSet<String> bools = new HashSet<>();
// private final HashMap<String, Integer> ints = new HashMap<>();
// private final HashMap<String, String> strings = new HashMap<>();
public CygwinTerminal() {
super(true);
String settings = System.getProperty(TerminalFactory.JLINE_TERMINAL + ".settings", "");
width = getProperty(settings, "columns", DEFAULT_WIDTH);
height = getProperty(settings, "rows", DEFAULT_HEIGHT);
}
private static int getProperty(String settings, String name, int defaultValue) {
Matcher m = Pattern.compile(name + "\\s+(\\d{1,4})").matcher(settings);
return m.find() ? Integer.parseInt(m.group(1)) : defaultValue;
}
@Override
public void init() throws Exception {
super.init();
setEchoEnabled(false);
setAnsiSupported(true);
// InfoCmp.parseInfoCmp(InfoCmp.getAnsiCaps(), bools, ints, strings);
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
// @Override
// public boolean getBooleanCapability(String capability) {
// return bools.contains(capability);
// }
//
// @Override
// public Integer getNumericCapability(String capability) {
// return ints.get(capability);
// }
//
// @Override
// public String getStringCapability(String capability) {
// return strings.get(capability);
// }
}
private final class JLineReader extends Reader {
private final StringBuilder buffer = new StringBuilder(2048);
private int pos;
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (cbuf == null || off < 0 || len < 0 || off + len > cbuf.length) {
throw new IllegalArgumentException();
}
int n = 0;
while (n < len) {
int buffered = buffer.length() - pos;
if (buffered == 0) {
pos = 0;
buffer.setLength(0);
buffer.append(console.readLine()).append(System.lineSeparator());
buffered = buffer.length();
}
int r = Math.min(buffered, len - n);
buffer.getChars(pos, pos + r, cbuf, off + n);
pos += r;
n += r;
}
return n;
}
@Override
public void close() throws IOException {
// not applicable
}
}
private final class JLineWriter extends Writer {
@Override
public void write(int c) throws IOException {
console.print(String.valueOf((char) c));
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
console.print(new String(cbuf, off, len));
}
@Override
public void write(String str, int off, int len) throws IOException {
console.print(str.substring(off, off + len));
}
@Override
public void flush() throws IOException {
console.flush();
}
@Override
public void close() throws IOException {
// not applicable
}
}
@Override
public void printf(String format, Object... args) {
formatter.format(format, args).flush();
}
@Override
public void flush() {
writer.flush();
errorWriter.flush();
}
@Override
public String readLine() {
try {
return console.readLine("");
} catch (IOException e) {
throw new IOError(e);
}
}
@Override
public String readLine(String prompt) {
try {
return console.readLine(prompt);
} catch (IOException e) {
throw new IOError(e);
}
}
@Override
public Reader reader() {
return reader;
}
@Override
public PrintWriter writer() {
return writer;
}
@Override
public PrintWriter errorWriter() {
return errorWriter;
}
@Override
public boolean isAnsiSupported() {
return console.getTerminal().isAnsiSupported();
}
@Override
public void addCompleter(Completer completer) {
CandidateListCompletionHandler handler = new CandidateListCompletionHandler();
// handler.setPrintSpaceAfterFullCompletion(false);
console.setCompletionHandler(handler);
console.addCompleter(new JLineCompleter(completer));
}
private static final class JLineCompleter implements jline.console.completer.Completer {
private final Completer completer;
JLineCompleter(Completer completer) {
this.completer = completer;
}
@Override
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
Optional<Completion> opt = completer.complete(buffer, cursor);
if (!opt.isPresent()) {
return -1;
}
Completion c = opt.get();
if (c.result().isEmpty()) {
return -1;
}
candidates.addAll(c.result());
return c.start();
}
}
}