package com.googlecode.lanterna.terminal.ansi;
import com.googlecode.lanterna.SGR;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.terminal.ExtendedTerminal;
import com.googlecode.lanterna.terminal.MouseCaptureMode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
public abstract class ANSITerminal extends StreamBasedTerminal implements ExtendedTerminal {
private MouseCaptureMode requestedMouseCaptureMode;
private MouseCaptureMode mouseCaptureMode;
private boolean inPrivateMode;
@SuppressWarnings("WeakerAccess")
protected ANSITerminal(
InputStream terminalInput,
OutputStream terminalOutput,
Charset terminalCharset) {
super(terminalInput, terminalOutput, terminalCharset);
this.inPrivateMode = false;
this.requestedMouseCaptureMode = null;
this.mouseCaptureMode = null;
getInputDecoder().addProfile(getDefaultKeyDecodingProfile());
}
protected KeyDecodingProfile getDefaultKeyDecodingProfile() {
return new DefaultKeyDecodingProfile();
}
private void writeCSISequenceToTerminal(byte... tail) throws IOException {
byte[] completeSequence = new byte[tail.length + 2];
completeSequence[0] = (byte)0x1b;
completeSequence[1] = (byte)'[';
System.arraycopy(tail, 0, completeSequence, 2, tail.length);
writeToTerminal(completeSequence);
}
private void writeSGRSequenceToTerminal(byte... sgrParameters) throws IOException {
byte[] completeSequence = new byte[sgrParameters.length + 3];
completeSequence[0] = (byte)0x1b;
completeSequence[1] = (byte)'[';
completeSequence[completeSequence.length - 1] = (byte)'m';
System.arraycopy(sgrParameters, 0, completeSequence, 2, sgrParameters.length);
writeToTerminal(completeSequence);
}
private void writeOSCSequenceToTerminal(byte... tail) throws IOException {
byte[] completeSequence = new byte[tail.length + 2];
completeSequence[0] = (byte)0x1b;
completeSequence[1] = (byte)']';
System.arraycopy(tail, 0, completeSequence, 2, tail.length);
writeToTerminal(completeSequence);
}
@Override
public final synchronized TerminalSize getTerminalSize() throws IOException {
TerminalSize size = findTerminalSize();
onResized(size);
return size;
}
protected TerminalSize findTerminalSize() throws IOException {
saveCursorPosition();
setCursorPosition(5000, 5000);
resetMemorizedCursorPosition();
reportPosition();
restoreCursorPosition();
TerminalPosition terminalPosition = waitForCursorPositionReport();
if (terminalPosition == null) {
terminalPosition = new TerminalPosition(80,24);
}
return new TerminalSize(terminalPosition.getColumn(), terminalPosition.getRow());
}
@Override
public void setTerminalSize(int columns, int rows) throws IOException {
writeCSISequenceToTerminal(("8;" + rows + ";" + columns + "t").getBytes());
getTerminalSize();
}
@Override
public void setTitle(String title) throws IOException {
title = title.replace("\007", "");
writeOSCSequenceToTerminal(("2;" + title + "\007").getBytes());
}
@Override
public void setForegroundColor(TextColor color) throws IOException {
writeSGRSequenceToTerminal(color.getForegroundSGRSequence());
}
@Override
public void setBackgroundColor(TextColor color) throws IOException {
writeSGRSequenceToTerminal(color.getBackgroundSGRSequence());
}
@Override
public void enableSGR(SGR sgr) throws IOException {
switch(sgr) {
case BLINK:
writeCSISequenceToTerminal((byte) '5', (byte) 'm');
break;
case BOLD:
writeCSISequenceToTerminal((byte) '1', (byte) 'm');
break;
case BORDERED:
writeCSISequenceToTerminal((byte) '5', (byte) '1', (byte) 'm');
break;
case CIRCLED:
writeCSISequenceToTerminal((byte) '5', (byte) '2', (byte) 'm');
break;
case CROSSED_OUT:
writeCSISequenceToTerminal((byte) '9', (byte) 'm');
break;
case FRAKTUR:
writeCSISequenceToTerminal((byte) '2', (byte) '0', (byte) 'm');
break;
case REVERSE:
writeCSISequenceToTerminal((byte) '7', (byte) 'm');
break;
case UNDERLINE:
writeCSISequenceToTerminal((byte) '4', (byte) 'm');
break;
case ITALIC:
writeCSISequenceToTerminal((byte) '3', (byte) 'm');
break;
}
}
@Override
public void disableSGR(SGR sgr) throws IOException {
switch(sgr) {
case BLINK:
writeCSISequenceToTerminal((byte) '2', (byte) '5', (byte) 'm');
break;
case BOLD:
writeCSISequenceToTerminal((byte) '2', (byte) '2', (byte) 'm');
break;
case BORDERED:
writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
break;
case CIRCLED:
writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
break;
case CROSSED_OUT:
writeCSISequenceToTerminal((byte) '2', (byte) '9', (byte) 'm');
break;
case FRAKTUR:
writeCSISequenceToTerminal((byte) '2', (byte) '3', (byte) 'm');
break;
case REVERSE:
writeCSISequenceToTerminal((byte) '2', (byte) '7', (byte) 'm');
break;
case UNDERLINE:
writeCSISequenceToTerminal((byte) '2', (byte) '4', (byte) 'm');
break;
case ITALIC:
writeCSISequenceToTerminal((byte) '2', (byte) '3', (byte) 'm');
break;
}
}
@Override
public void resetColorAndSGR() throws IOException {
writeCSISequenceToTerminal((byte) '0', (byte) 'm');
}
@Override
public void clearScreen() throws IOException {
writeCSISequenceToTerminal((byte) '2', (byte) 'J');
}
@Override
public void enterPrivateMode() throws IOException {
if(inPrivateMode) {
throw new IllegalStateException("Cannot call enterPrivateMode() when already in private mode");
}
writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'h');
if (requestedMouseCaptureMode != null) {
this.mouseCaptureMode = requestedMouseCaptureMode;
updateMouseCaptureMode(this.mouseCaptureMode, 'h');
}
flush();
inPrivateMode = true;
}
@Override
public void exitPrivateMode() throws IOException {
if(!inPrivateMode) {
throw new IllegalStateException("Cannot call exitPrivateMode() when not in private mode");
}
resetColorAndSGR();
setCursorVisible(true);
writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'l');
if (null != mouseCaptureMode) {
updateMouseCaptureMode(this.mouseCaptureMode, 'l');
this.mouseCaptureMode = null;
}
flush();
inPrivateMode = false;
}
@Override
public void close() throws IOException {
if(isInPrivateMode()) {
exitPrivateMode();
}
super.close();
}
@Override
public void setCursorPosition(int x, int y) throws IOException {
writeCSISequenceToTerminal(((y + 1) + ";" + (x + 1) + "H").getBytes());
}
@Override
public void setCursorPosition(TerminalPosition position) throws IOException {
setCursorPosition(position.getColumn(), position.getRow());
}
@Override
public synchronized TerminalPosition getCursorPosition() throws IOException {
resetMemorizedCursorPosition();
reportPosition();
TerminalPosition terminalPosition = waitForCursorPositionReport();
if (terminalPosition == null) {
terminalPosition = TerminalPosition.OFFSET_1x1;
}
return terminalPosition.withRelative(-1, -1);
}
@Override
public void setCursorVisible(boolean visible) throws IOException {
writeCSISequenceToTerminal(("?25" + (visible ? "h" : "l")).getBytes());
}
@Override
public KeyStroke readInput() throws IOException {
KeyStroke keyStroke;
do {
keyStroke = filterMouseEvents(super.readInput());
} while(keyStroke == null);
return keyStroke;
}
@Override
public KeyStroke pollInput() throws IOException {
return filterMouseEvents(super.pollInput());
}
private KeyStroke filterMouseEvents(KeyStroke keyStroke) {
if(keyStroke == null || keyStroke.getKeyType() != KeyType.MouseEvent) {
return keyStroke;
}
MouseAction mouseAction = (MouseAction)keyStroke;
switch(mouseAction.getActionType()) {
case CLICK_RELEASE:
if(mouseCaptureMode == MouseCaptureMode.CLICK) {
return null;
}
break;
case DRAG:
if(mouseCaptureMode == MouseCaptureMode.CLICK ||
mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE) {
return null;
}
break;
case MOVE:
if(mouseCaptureMode == MouseCaptureMode.CLICK ||
mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE ||
mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE_DRAG) {
return null;
}
break;
default:
}
return mouseAction;
}
@Override
public void pushTitle() throws IOException {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void popTitle() throws IOException {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
public void iconify() throws IOException {
writeCSISequenceToTerminal((byte)'2', (byte)'t');
}
@Override
public void deiconify() throws IOException {
writeCSISequenceToTerminal((byte)'1', (byte)'t');
}
@Override
public void maximize() throws IOException {
writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'1', (byte)'t');
}
@Override
public void unmaximize() throws IOException {
writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'0', (byte)'t');
}
private void updateMouseCaptureMode(MouseCaptureMode mouseCaptureMode, char l_or_h) throws IOException {
if (mouseCaptureMode == null) { return; }
switch(mouseCaptureMode) {
case CLICK:
writeCSISequenceToTerminal((byte)'?', (byte)'9', (byte)l_or_h);
break;
case CLICK_RELEASE:
writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'0', (byte)l_or_h);
break;
case CLICK_RELEASE_DRAG:
writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'2', (byte)l_or_h);
break;
case CLICK_RELEASE_DRAG_MOVE:
writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'3', (byte)l_or_h);
break;
}
if(getCharset().equals(Charset.forName("UTF-8"))) {
writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'5', (byte)l_or_h);
}
}
@Override
public void setMouseCaptureMode(MouseCaptureMode mouseCaptureMode) throws IOException {
requestedMouseCaptureMode = mouseCaptureMode;
if (inPrivateMode && requestedMouseCaptureMode != this.mouseCaptureMode) {
updateMouseCaptureMode(this.mouseCaptureMode, 'l');
this.mouseCaptureMode = requestedMouseCaptureMode;
updateMouseCaptureMode(this.mouseCaptureMode, 'h');
}
}
@Override
public void scrollLines(int firstLine, int lastLine, int distance) throws IOException {
final String CSI = "\033[";
if (distance == 0) { return; }
if (firstLine < 0) { firstLine = 0; }
if (lastLine < firstLine) { return; }
StringBuilder sb = new StringBuilder();
sb.append(CSI).append(firstLine+1)
.append(';').append(lastLine+1).append('r');
int target = distance > 0 ? lastLine : firstLine;
sb.append(CSI).append(target+1).append(";1H");
if (distance > 0) {
int num = Math.min( distance, lastLine - firstLine + 1);
for (int i = 0; i < num; i++) { sb.append('\n'); }
} else {
int num = Math.min( -distance, lastLine - firstLine + 1);
for (int i = 0; i < num; i++) { sb.append("\033M"); }
}
sb.append(CSI).append('r');
writeToTerminal(sb.toString().getBytes());
}
boolean isInPrivateMode() {
return inPrivateMode;
}
void reportPosition() throws IOException {
writeCSISequenceToTerminal("6n".getBytes());
}
void restoreCursorPosition() throws IOException {
writeCSISequenceToTerminal("u".getBytes());
}
void saveCursorPosition() throws IOException {
writeCSISequenceToTerminal("s".getBytes());
}
}