package io.vertx.ext.shell.term.impl;
import io.termd.core.readline.Keymap;
import io.termd.core.readline.Readline;
import io.termd.core.tty.TtyConnection;
import io.termd.core.util.Helper;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.ext.shell.cli.CliToken;
import io.vertx.ext.shell.cli.Completion;
import io.vertx.ext.shell.session.Session;
import io.vertx.ext.shell.term.SignalHandler;
import io.vertx.ext.shell.term.Term;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class TermImpl implements Term {
private static final List<io.termd.core.readline.Function> readlineFunctions = Helper.loadServices(Thread.currentThread().getContextClassLoader(), io.termd.core.readline.Function.class);
private Vertx vertx;
private final Readline readline;
private final Consumer<int[]> echoHandler;
final TtyConnection conn;
volatile Handler<String> stdinHandler;
private SignalHandler interruptHandler;
private SignalHandler suspendHandler;
private Session session;
private boolean inReadline;
public TermImpl(Vertx vertx, TtyConnection conn) {
this(vertx, io.vertx.ext.shell.term.impl.Helper.defaultKeymap(), conn);
}
public TermImpl(Vertx vertx, Keymap keymap, TtyConnection conn) {
this.vertx = vertx;
this.conn = conn;
readline = new Readline(keymap);
readlineFunctions.forEach(readline::addFunction);
echoHandler = codePoints -> {
echo(codePoints);
readline.queueEvent(codePoints);
};
conn.setStdinHandler(echoHandler);
conn.setEventHandler((event, key) -> {
switch (event) {
case INTR:
if (interruptHandler == null || !interruptHandler.deliver(key)) {
echo(key, '\n');
}
break;
case EOF:
if (stdinHandler != null) {
stdinHandler.handle(Helper.fromCodePoints(new int[]{key}));
} else {
echo(key);
readline.queueEvent(new int[]{key});
}
break;
case SUSP:
if (suspendHandler == null || !suspendHandler.deliver(key)) {
echo(key, 'Z' - 64);
}
break;
}
});
}
@Override
public Term setSession(Session session) {
this.session = session;
return this;
}
@Override
public void readline(String prompt, Handler<String> lineHandler) {
if (conn.getStdinHandler() != echoHandler) {
throw new IllegalStateException();
}
if (inReadline) {
throw new IllegalStateException();
}
inReadline = true;
readline.readline(conn, prompt, line -> {
inReadline = false;
lineHandler.handle(line);
});
}
public void readline(String prompt, Handler<String> lineHandler, Handler<Completion> completionHandler) {
if (conn.getStdinHandler() != echoHandler) {
throw new IllegalStateException();
}
if (inReadline) {
throw new IllegalStateException();
}
inReadline = true;
readline.readline(conn, prompt, line -> {
inReadline = false;
lineHandler.handle(line);
}, abc -> {
String line = Helper.fromCodePoints(abc.line());
List<CliToken> tokens = Collections.unmodifiableList(CliToken.tokenize(line));
Completion comp = new Completion() {
@Override
public Vertx vertx() {
return vertx;
}
@Override
public Session session() {
return session;
}
@Override
public String rawLine() {
return line;
}
@Override
public List<CliToken> lineTokens() {
return tokens;
}
@Override
public void complete(List<String> candidates) {
if (candidates.size() > 0) {
abc.suggest(candidates.stream().
map(Helper::toCodePoints).
collect(Collectors.toList()));
} else {
abc.end();
}
}
@Override
public void complete(String value, boolean terminal) {
abc.complete(Helper.toCodePoints(value), terminal);
}
};
completionHandler.handle(comp);
});
}
@Override
public Term closeHandler(Handler<Void> handler) {
if (handler != null) {
conn.setCloseHandler(handler::handle);
} else {
conn.setCloseHandler(null);
}
return this;
}
public long lastAccessedTime() {
return conn.lastAccessedTime();
}
@Override
public String type() {
return conn.terminalType();
}
@Override
public int width() {
return conn.size() != null ? conn.size().x() : -1;
}
@Override
public int height() {
return conn.size() != null ? conn.size().y() : -1;
}
void checkPending() {
if (stdinHandler != null && readline.hasEvent()) {
stdinHandler.handle(Helper.fromCodePoints(readline.nextEvent().buffer().array()));
vertx.runOnContext(v -> {
checkPending();
});
}
}
@Override
public TermImpl resizehandler(Handler<Void> handler) {
if (inReadline) {
throw new IllegalStateException();
}
if (handler != null) {
conn.setSizeHandler(resize -> {
handler.handle(null);
});
} else {
conn.setSizeHandler(null);
}
return this;
}
@Override
public Term stdinHandler(Handler<String> handler) {
if (inReadline) {
throw new IllegalStateException();
}
stdinHandler = handler;
if (handler != null) {
conn.setStdinHandler(codePoints -> {
handler.handle(Helper.fromCodePoints(codePoints));
});
checkPending();
} else {
conn.setStdinHandler(echoHandler);
}
return this;
}
@Override
public Term write(String data) {
conn.write(data);
return this;
}
public TermImpl interruptHandler(SignalHandler handler) {
interruptHandler = handler;
return this;
}
public TermImpl suspendHandler(SignalHandler handler) {
suspendHandler = handler;
return this;
}
public void close() {
conn.close();
}
public TermImpl echo(String text) {
echo(Helper.toCodePoints(text));
return this;
}
public void echo(int... codePoints) {
Consumer<int[]> out = conn.stdoutHandler();
for (int codePoint : codePoints) {
if (codePoint < 32) {
if (codePoint == '\t') {
out.accept(new int[]{'\t'});
} else if (codePoint == '\b') {
out.accept(new int[]{'\b', ' ', '\b'});
} else if (codePoint == '\r' || codePoint == '\n') {
out.accept(new int[]{'\n'});
} else {
out.accept(new int[]{'^', codePoint + 64});
}
} else {
if (codePoint == 127) {
out.accept(new int[]{'\b', ' ', '\b'});
} else {
out.accept(new int[]{codePoint});
}
}
}
}
}