transcendental-lisp/src/terminal/LispTerminal.java

411 lines
13 KiB
Java

package terminal;
import static com.googlecode.lanterna.input.KeyType.*;
import static terminal.ControlSequenceHandler.isEscape;
import static util.Characters.EOF;
import static util.Characters.UNICODE_NULL;
import java.io.*;
import java.util.concurrent.*;
import com.googlecode.lanterna.*;
import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.terminal.IOSafeTerminal;
import stream.*;
public class LispTerminal {
public static final char END_OF_SEGMENT = UNICODE_NULL;
private IOSafeTerminal terminal;
private SafeOutputStream inputWriter;
private SafeInputStream outputReader;
private ControlSequenceHandler controlSequenceHandler;
private ExecutorService executorService;
private TerminalSize terminalSize;
private String inputLine;
private String outputSegment;
private boolean isStopped;
private int originColumn;
private int originRow;
public LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) {
this.terminal = terminal;
this.inputWriter = new SafeOutputStream(inputWriter);
this.outputReader = new SafeInputStream(outputReader);
this.controlSequenceHandler = new ControlSequenceHandler();
this.executorService = Executors.newFixedThreadPool(2);
this.terminalSize = terminal.getTerminalSize();
this.inputLine = "";
this.outputSegment = "";
this.isStopped = false;
setOriginToCurrentPosition();
terminal.addResizeListener((t, newSize) -> resize());
}
private synchronized void setOriginToCurrentPosition() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
originColumn = cursorPosition.getColumn();
originRow = cursorPosition.getRow();
}
private synchronized void resize() {
terminalSize = terminal.getTerminalSize();
terminal.clearScreen();
terminal.setCursorPosition(0, 0);
redisplayInput();
}
private synchronized void redisplayInput() {
setOriginToCurrentPosition();
putStringToTerminal(inputLine);
moveCursorToEndOfInput();
}
private synchronized void putStringToTerminal(String characters) {
for (char c : characters.toCharArray())
terminal.putCharacter(c);
}
private synchronized void moveCursorToEndOfInput() {
terminal.setCursorPosition(getLeadingEdge());
}
private synchronized TerminalPosition getLeadingEdge() {
int inputLength = inputLine.length();
int totalColumns = terminalSize.getColumns();
int rowDifference = inputLength / totalColumns;
int columnDifference = inputLength % totalColumns;
return new TerminalPosition(originColumn + columnDifference, originRow + rowDifference);
}
public void start() {
executorService.execute(this::readInput);
executorService.execute(this::writeOutput);
executorService.shutdown();
}
private void readInput() {
while (!isStopped)
processNextKey();
}
private void processNextKey() {
KeyStroke keyStroke = getKeyStroke();
if (keyStroke != null)
handleKey(keyStroke);
else
takeNap();
}
private KeyStroke getKeyStroke() {
KeyStroke keyStroke = null;
// issue #299
try {
keyStroke = terminal.pollInput();
} catch (IllegalStateException e) {
doControlC();
}
return keyStroke;
}
private synchronized void handleKey(KeyStroke keyStroke) {
doKey(keyStroke);
terminal.flush();
}
private synchronized void doKey(KeyStroke keyStroke) {
if (keyStroke.isCtrlDown())
doControlKey(keyStroke);
else
doNormalKey(keyStroke);
}
private synchronized void doControlKey(KeyStroke keyStroke) {
KeyType keyType = keyStroke.getKeyType();
if (keyType == Character)
doControlCharacter(keyStroke);
}
private synchronized void doControlCharacter(KeyStroke keyStroke) {
if (keyStroke.getCharacter() == 'c')
doControlC();
else if (keyStroke.getCharacter() == 'd')
doControlD();
}
private void doControlC() {
moveCursorToEndOfInput();
terminal.putCharacter('\n');
inputLine = "";
setOriginToCurrentPosition();
stop();
}
private synchronized void doControlD() {
doEnter();
stop();
}
private synchronized void doNormalKey(KeyStroke keyStroke) {
KeyType keyType = keyStroke.getKeyType();
if (keyType == Enter)
doEnter();
else if (keyType == ArrowLeft)
doLeftArrow();
else if (keyType == ArrowRight)
doRightArrow();
else if (keyType == Backspace)
doBackspace();
else if (keyType == Delete)
doDelete();
else if (keyType == Character)
doCharacter(keyStroke.getCharacter());
}
private synchronized void doEnter() {
moveCursorToEndOfInput();
terminal.putCharacter('\n');
inputLine += "\n";
inputWriter.write(inputLine.getBytes());
inputWriter.flush();
inputLine = "";
setOriginToCurrentPosition();
}
private synchronized void doLeftArrow() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isPossibleToMoveLeft(cursorPosition))
moveCursorLeft(cursorPosition);
}
private synchronized boolean isPossibleToMoveLeft(TerminalPosition cursorPosition) {
return getDistanceFromOrigin(cursorPosition) > 0;
}
private synchronized int getDistanceFromOrigin(TerminalPosition cursorPosition) {
int columnDifference = cursorPosition.getColumn() - originColumn;
int rowDifference = cursorPosition.getRow() - originRow;
int totalColumns = terminalSize.getColumns();
return columnDifference + (totalColumns * rowDifference);
}
private synchronized void moveCursorLeft(TerminalPosition cursorPosition) {
TerminalPosition newPosition = cursorPosition.withRelativeColumn(-1);
if (isAtStartOfRow(cursorPosition))
newPosition = cursorPosition.withColumn(terminalSize.getColumns()).withRelativeRow(-1);
terminal.setCursorPosition(newPosition);
}
private synchronized boolean isAtStartOfRow(TerminalPosition cursorPosition) {
return cursorPosition.getColumn() == 0;
}
private synchronized void doRightArrow() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isPossibleToMoveRight(cursorPosition))
moveCursorRight(cursorPosition);
}
private synchronized boolean isPossibleToMoveRight(TerminalPosition cursorPosition) {
return getDistanceFromOrigin(cursorPosition) < inputLine.length();
}
private synchronized void moveCursorRight(TerminalPosition cursorPosition) {
if (isEndOfRow(cursorPosition))
moveCursorToNextRow(cursorPosition);
else
terminal.setCursorPosition(cursorPosition.withRelativeColumn(1));
}
private synchronized boolean isEndOfRow(TerminalPosition cursorPosition) {
return cursorPosition.getColumn() >= terminalSize.getColumns() - 1;
}
private synchronized void moveCursorToNextRow(TerminalPosition cursorPosition) {
if (isEndOfBuffer(cursorPosition))
createNewRowForCursor(cursorPosition);
else
terminal.setCursorPosition(cursorPosition.withColumn(0).withRelativeRow(1));
}
private synchronized boolean isEndOfBuffer(TerminalPosition cursorPosition) {
return cursorPosition.getRow() == terminalSize.getRows() - 1;
}
private synchronized void createNewRowForCursor(TerminalPosition cursorPosition) {
terminal.setCursorPosition(cursorPosition);
terminal.putCharacter('\n');
terminal.setCursorPosition(cursorPosition.withColumn(0));
--originRow;
}
private synchronized void doBackspace() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isPossibleToMoveLeft(cursorPosition))
deletePreviousCharacter(cursorPosition);
}
private synchronized void deletePreviousCharacter(TerminalPosition cursorPosition) {
int distanceFromOrigin = getDistanceFromOrigin(cursorPosition);
String remaining = inputLine.substring(distanceFromOrigin, inputLine.length());
inputLine = inputLine.substring(0, distanceFromOrigin - 1) + remaining;
moveCursorLeft(cursorPosition);
putStringToTerminal(remaining + " ");
moveCursorLeft(cursorPosition);
}
private synchronized void doDelete() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isPossibleToMoveRight(cursorPosition))
deleteCharacterAtPosition(cursorPosition);
}
private synchronized void deleteCharacterAtPosition(TerminalPosition cursorPosition) {
int distanceFromOrigin = getDistanceFromOrigin(cursorPosition);
String remaining = inputLine.substring(distanceFromOrigin + 1, inputLine.length());
inputLine = inputLine.substring(0, distanceFromOrigin) + remaining;
putStringToTerminal(remaining + " ");
terminal.setCursorPosition(cursorPosition);
}
private synchronized void doCharacter(Character character) {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (!isBufferFilled())
if (isPossibleToMoveRight(cursorPosition))
insertCharacter(character, cursorPosition);
else
appendCharacter(character, cursorPosition);
}
private synchronized boolean isBufferFilled() {
int row = getLeadingEdge().getRow();
int column = getLeadingEdge().getColumn();
return (row == terminalSize.getRows() - 1) && (column >= terminalSize.getColumns() - 1) && (originRow <= 0);
}
private synchronized void insertCharacter(Character character, TerminalPosition cursorPosition) {
cursorPosition = shiftPositionIfNewRowAdded(cursorPosition);
terminal.setCursorPosition(cursorPosition);
int distanceFromOrigin = getDistanceFromOrigin(cursorPosition);
String remaining = character + inputLine.substring(distanceFromOrigin, inputLine.length());
inputLine = inputLine.substring(0, distanceFromOrigin) + remaining;
putStringToTerminal(remaining);
moveCursorRight(cursorPosition);
}
private synchronized TerminalPosition shiftPositionIfNewRowAdded(TerminalPosition cursorPosition) {
int oldOriginRow = originRow;
moveCursorRight(getLeadingEdge());
return isNewRowAdded(oldOriginRow) ? adjustCursorPosition(cursorPosition) : cursorPosition;
}
private synchronized boolean isNewRowAdded(int oldOriginRow) {
return originRow != oldOriginRow;
}
private synchronized TerminalPosition adjustCursorPosition(TerminalPosition cursorPosition) {
terminal.setCursorPosition(new TerminalPosition(originColumn, originRow));
putStringToTerminal(inputLine);
return cursorPosition.withRelativeRow(-1);
}
private synchronized void appendCharacter(Character character, TerminalPosition cursorPosition) {
terminal.putCharacter(character);
inputLine += character;
moveCursorRight(cursorPosition);
}
private void takeNap() {
try {
Thread.sleep(1);
} catch (InterruptedException ignored) {}
}
private void writeOutput() {
for (int c = outputReader.read(); c != EOF; c = outputReader.read())
processOutput((char) c);
terminal.setCursorVisible(true);
terminal.flush();
terminal.close();
}
private synchronized void processOutput(char c) {
if (isEndOfSegment(c))
writeSegment();
else
outputSegment += c;
}
private synchronized boolean isEndOfSegment(char c) {
return c == END_OF_SEGMENT;
}
private synchronized void writeSegment() {
terminal.setCursorVisible(false);
printSegmentCharacters();
terminal.setCursorVisible(true);
outputSegment = "";
redisplayInput();
terminal.flush();
}
private synchronized void printSegmentCharacters() {
moveCursorToEndOfInput();
putOutputToTerminal();
moveCursorToNextRowIfNecessary();
}
private synchronized void putOutputToTerminal() {
SafeInputStream input = convertOutputToStream();
for (int c = input.read(); c != EOF; c = input.read())
if (isEscape((char) c))
applyControlSequence(input);
else
terminal.putCharacter((char) c);
}
private synchronized SafeInputStream convertOutputToStream() {
return new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes()));
}
private void applyControlSequence(SafeInputStream input) {
ControlSequence controlSequence = controlSequenceHandler.parse(input);
controlSequence.applyTo(terminal);
}
private synchronized void moveCursorToNextRowIfNecessary() {
TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isEndOfRow(cursorPosition))
moveCursorToNextRow(cursorPosition);
}
public void stop() {
isStopped = true;
inputWriter.close();
}
}