Convert terminal to kotlin
This commit is contained in:
parent
ae48d9c2ca
commit
3ec8e72a17
2
pom.xml
2
pom.xml
|
@ -211,7 +211,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.stefanbirkner</groupId>
|
<groupId>com.github.stefanbirkner</groupId>
|
||||||
<artifactId>system-rules</artifactId>
|
<artifactId>system-rules</artifactId>
|
||||||
<version>1.16.1</version>
|
<version>1.18.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import com.googlecode.lanterna.terminal.IOSafeTerminalAdapter.createRuntimeExcep
|
||||||
import interpreter.LispInterpreter
|
import interpreter.LispInterpreter
|
||||||
import interpreter.LispInterpreterBuilder
|
import interpreter.LispInterpreterBuilder
|
||||||
import terminal.LispTerminal
|
import terminal.LispTerminal
|
||||||
import terminal.LispTerminal.END_OF_SEGMENT
|
import terminal.LispTerminal.Companion.END_OF_SEGMENT
|
||||||
import terminal.TerminalConfiguration
|
import terminal.TerminalConfiguration
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
import java.io.PipedOutputStream
|
import java.io.PipedOutputStream
|
||||||
|
|
|
@ -1,460 +0,0 @@
|
||||||
package terminal;
|
|
||||||
|
|
||||||
import com.googlecode.lanterna.TerminalPosition;
|
|
||||||
import com.googlecode.lanterna.TerminalSize;
|
|
||||||
import com.googlecode.lanterna.input.KeyStroke;
|
|
||||||
import com.googlecode.lanterna.input.KeyType;
|
|
||||||
import com.googlecode.lanterna.terminal.IOSafeTerminal;
|
|
||||||
import stream.SafeInputStream;
|
|
||||||
import stream.SafeOutputStream;
|
|
||||||
import util.Characters;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.ArrowDown;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.ArrowLeft;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.ArrowRight;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.ArrowUp;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.Backspace;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.Character;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.Delete;
|
|
||||||
import static com.googlecode.lanterna.input.KeyType.Enter;
|
|
||||||
import static terminal.ControlSequenceHandler.isEscape;
|
|
||||||
|
|
||||||
public class LispTerminal {
|
|
||||||
|
|
||||||
public static final char END_OF_SEGMENT = Characters.UNICODE_NULL;
|
|
||||||
|
|
||||||
private IOSafeTerminal terminal;
|
|
||||||
private SafeOutputStream inputWriter;
|
|
||||||
private SafeInputStream outputReader;
|
|
||||||
private ControlSequenceHandler controlSequenceHandler;
|
|
||||||
private TerminalHistory history;
|
|
||||||
private ExecutorService executorService;
|
|
||||||
private TerminalSize terminalSize;
|
|
||||||
private String inputLine;
|
|
||||||
private String outputSegment;
|
|
||||||
private boolean isStopped;
|
|
||||||
private int originColumn;
|
|
||||||
private int originRow;
|
|
||||||
|
|
||||||
public LispTerminal(TerminalConfiguration configuration) {
|
|
||||||
this.terminal = configuration.getTerminal();
|
|
||||||
this.inputWriter = new SafeOutputStream(configuration.getInputWriter());
|
|
||||||
this.outputReader = new SafeInputStream(configuration.getOutputReader());
|
|
||||||
this.controlSequenceHandler = new ControlSequenceHandler();
|
|
||||||
this.history = new TerminalHistory();
|
|
||||||
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 = terminal.pollInput();
|
|
||||||
|
|
||||||
if (keyStroke != null)
|
|
||||||
handleKey(keyStroke);
|
|
||||||
else
|
|
||||||
takeNap();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() == 'd')
|
|
||||||
doControlD();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void doControlD() {
|
|
||||||
doEnter();
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void doNormalKey(KeyStroke keyStroke) {
|
|
||||||
KeyType keyType = keyStroke.getKeyType();
|
|
||||||
|
|
||||||
if (keyType == Enter)
|
|
||||||
doEnter();
|
|
||||||
else if (keyType == ArrowUp)
|
|
||||||
doUpArrow();
|
|
||||||
else if (keyType == ArrowDown)
|
|
||||||
doDownArrow();
|
|
||||||
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();
|
|
||||||
history.addLine(inputLine);
|
|
||||||
terminal.putCharacter('\n');
|
|
||||||
writeInputLine();
|
|
||||||
setOriginToCurrentPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void writeInputLine() {
|
|
||||||
inputLine += "\n";
|
|
||||||
inputWriter.write(inputLine.getBytes());
|
|
||||||
inputWriter.flush();
|
|
||||||
inputLine = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void doUpArrow() {
|
|
||||||
if (!history.isBeginning())
|
|
||||||
replaceInputWithPreviousLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void replaceInputWithPreviousLine() {
|
|
||||||
history.updateCurrentLine(inputLine);
|
|
||||||
clearInput();
|
|
||||||
displayPreviousInputLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void clearInput() {
|
|
||||||
terminal.setCursorPosition(originColumn, originRow);
|
|
||||||
putStringToTerminal(inputLine.replaceAll(".{1}", " "));
|
|
||||||
terminal.setCursorPosition(originColumn, originRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void displayPreviousInputLine() {
|
|
||||||
inputLine = history.getPreviousLine();
|
|
||||||
char[] inputCharacters = inputLine.toCharArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < inputCharacters.length; i++)
|
|
||||||
displayCharacter(inputCharacters[i], i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void displayCharacter(char character, int offsetFromOrigin) {
|
|
||||||
if (isLastColumn(offsetFromOrigin)) {
|
|
||||||
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
|
||||||
terminal.putCharacter(character);
|
|
||||||
moveCursorToNextRow(cursorPosition);
|
|
||||||
} else
|
|
||||||
terminal.putCharacter(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized boolean isLastColumn(int offsetFromOrigin) {
|
|
||||||
int totalColumns = terminalSize.getColumns();
|
|
||||||
|
|
||||||
return (originColumn + offsetFromOrigin) % totalColumns == totalColumns - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void doDownArrow() {
|
|
||||||
if (!history.isEnd())
|
|
||||||
replaceInputWithNextLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void replaceInputWithNextLine() {
|
|
||||||
history.updateCurrentLine(inputLine);
|
|
||||||
clearInput();
|
|
||||||
displayNextInputLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void displayNextInputLine() {
|
|
||||||
inputLine = history.getNextLine();
|
|
||||||
putStringToTerminal(inputLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 != Characters.EOF; c = outputReader.read())
|
|
||||||
processOutput((char) c);
|
|
||||||
|
|
||||||
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() {
|
|
||||||
printSegmentCharacters();
|
|
||||||
outputSegment = "";
|
|
||||||
redisplayInput();
|
|
||||||
terminal.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void printSegmentCharacters() {
|
|
||||||
moveCursorToEndOfInput();
|
|
||||||
putOutputToTerminal();
|
|
||||||
moveCursorToNextRowIfNecessary();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void putOutputToTerminal() {
|
|
||||||
SafeInputStream input = convertOutputToStream();
|
|
||||||
|
|
||||||
for (int c = input.read(); c != Characters.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 synchronized 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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,494 @@
|
||||||
|
package terminal
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalPosition
|
||||||
|
import com.googlecode.lanterna.input.KeyStroke
|
||||||
|
import com.googlecode.lanterna.input.KeyType.ArrowDown
|
||||||
|
import com.googlecode.lanterna.input.KeyType.ArrowLeft
|
||||||
|
import com.googlecode.lanterna.input.KeyType.ArrowRight
|
||||||
|
import com.googlecode.lanterna.input.KeyType.ArrowUp
|
||||||
|
import com.googlecode.lanterna.input.KeyType.Backspace
|
||||||
|
import com.googlecode.lanterna.input.KeyType.Character
|
||||||
|
import com.googlecode.lanterna.input.KeyType.Delete
|
||||||
|
import com.googlecode.lanterna.input.KeyType.Enter
|
||||||
|
import stream.SafeInputStream
|
||||||
|
import stream.SafeOutputStream
|
||||||
|
import terminal.ControlSequenceHandler.Companion.isEscape
|
||||||
|
import util.Characters
|
||||||
|
import util.Characters.EOF
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class LispTerminal(configuration: TerminalConfiguration) {
|
||||||
|
|
||||||
|
private val terminal = configuration.terminal!!
|
||||||
|
private val inputWriter = SafeOutputStream(configuration.inputWriter)
|
||||||
|
private val outputReader = SafeInputStream(configuration.outputReader)
|
||||||
|
private val controlSequenceHandler = ControlSequenceHandler()
|
||||||
|
private val history = TerminalHistory()
|
||||||
|
private val executorService = Executors.newFixedThreadPool(2)
|
||||||
|
private var terminalSize = terminal.terminalSize
|
||||||
|
private var inputLine = ""
|
||||||
|
private var outputSegment = ""
|
||||||
|
private var isStopped = false
|
||||||
|
private var originColumn = 0
|
||||||
|
private var originRow = 0
|
||||||
|
|
||||||
|
private val leadingEdge: TerminalPosition
|
||||||
|
@Synchronized get() {
|
||||||
|
val inputLength = inputLine.length
|
||||||
|
val totalColumns = terminalSize.columns
|
||||||
|
val rowDifference = inputLength / totalColumns
|
||||||
|
val columnDifference = inputLength % totalColumns
|
||||||
|
|
||||||
|
return TerminalPosition(originColumn + columnDifference, originRow + rowDifference)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isBufferFilled: Boolean
|
||||||
|
@Synchronized get() {
|
||||||
|
val row = leadingEdge.row
|
||||||
|
val column = leadingEdge.column
|
||||||
|
|
||||||
|
return row == terminalSize.rows - 1
|
||||||
|
&& column >= terminalSize.columns - 1
|
||||||
|
&& originRow <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
setOriginToCurrentPosition()
|
||||||
|
terminal.addResizeListener { _, _ -> resize() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun setOriginToCurrentPosition() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
originColumn = cursorPosition.column
|
||||||
|
originRow = cursorPosition.row
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun resize() {
|
||||||
|
terminalSize = terminal.terminalSize
|
||||||
|
terminal.clearScreen()
|
||||||
|
terminal.setCursorPosition(0, 0)
|
||||||
|
redisplayInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun redisplayInput() {
|
||||||
|
setOriginToCurrentPosition()
|
||||||
|
putStringToTerminal(inputLine)
|
||||||
|
moveCursorToEndOfInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun putStringToTerminal(characters: String) {
|
||||||
|
for (c in characters.toCharArray())
|
||||||
|
terminal.putCharacter(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun moveCursorToEndOfInput() {
|
||||||
|
terminal.cursorPosition = leadingEdge
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
executorService.execute { this.readInput() }
|
||||||
|
executorService.execute { this.writeOutput() }
|
||||||
|
executorService.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readInput() {
|
||||||
|
while (!isStopped)
|
||||||
|
processNextKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processNextKey() {
|
||||||
|
val keyStroke = terminal.pollInput()
|
||||||
|
|
||||||
|
if (keyStroke != null)
|
||||||
|
handleKey(keyStroke)
|
||||||
|
else
|
||||||
|
takeNap()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun handleKey(keyStroke: KeyStroke) {
|
||||||
|
doKey(keyStroke)
|
||||||
|
terminal.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doKey(keyStroke: KeyStroke) {
|
||||||
|
if (keyStroke.isCtrlDown)
|
||||||
|
doControlKey(keyStroke)
|
||||||
|
else
|
||||||
|
doNormalKey(keyStroke)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doControlKey(keyStroke: KeyStroke) {
|
||||||
|
if (keyStroke.keyType == Character)
|
||||||
|
doControlCharacter(keyStroke)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doControlCharacter(keyStroke: KeyStroke) {
|
||||||
|
if (keyStroke.character == 'd')
|
||||||
|
doControlD()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doControlD() {
|
||||||
|
doEnter()
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||||
|
@Synchronized
|
||||||
|
private fun doNormalKey(keyStroke: KeyStroke) {
|
||||||
|
when (keyStroke.keyType) {
|
||||||
|
Enter -> doEnter()
|
||||||
|
ArrowUp -> doUpArrow()
|
||||||
|
ArrowDown -> doDownArrow()
|
||||||
|
ArrowLeft -> doLeftArrow()
|
||||||
|
ArrowRight -> doRightArrow()
|
||||||
|
Backspace -> doBackspace()
|
||||||
|
Delete -> doDelete()
|
||||||
|
Character -> doCharacter(keyStroke.character!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doEnter() {
|
||||||
|
moveCursorToEndOfInput()
|
||||||
|
history.addLine(inputLine)
|
||||||
|
terminal.putCharacter('\n')
|
||||||
|
writeInputLine()
|
||||||
|
setOriginToCurrentPosition()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun writeInputLine() {
|
||||||
|
inputLine += "\n"
|
||||||
|
inputWriter.write(inputLine.toByteArray())
|
||||||
|
inputWriter.flush()
|
||||||
|
inputLine = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doUpArrow() {
|
||||||
|
if (!history.isBeginning())
|
||||||
|
replaceInputWithPreviousLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun replaceInputWithPreviousLine() {
|
||||||
|
history.updateCurrentLine(inputLine)
|
||||||
|
clearInput()
|
||||||
|
displayPreviousInputLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun clearInput() {
|
||||||
|
terminal.setCursorPosition(originColumn, originRow)
|
||||||
|
putStringToTerminal(inputLine.replace(".".toRegex(), " "))
|
||||||
|
terminal.setCursorPosition(originColumn, originRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun displayPreviousInputLine() {
|
||||||
|
inputLine = history.getPreviousLine()
|
||||||
|
val inputCharacters = inputLine.toCharArray()
|
||||||
|
|
||||||
|
for (i in inputCharacters.indices)
|
||||||
|
displayCharacter(inputCharacters[i], i)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun displayCharacter(character: Char, offsetFromOrigin: Int) {
|
||||||
|
if (isLastColumn(offsetFromOrigin)) {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
terminal.putCharacter(character)
|
||||||
|
moveCursorToNextRow(cursorPosition)
|
||||||
|
} else
|
||||||
|
terminal.putCharacter(character)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isLastColumn(offsetFromOrigin: Int): Boolean {
|
||||||
|
val totalColumns = terminalSize.columns
|
||||||
|
|
||||||
|
return (originColumn + offsetFromOrigin) % totalColumns == totalColumns - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doDownArrow() {
|
||||||
|
if (!history.isEnd())
|
||||||
|
replaceInputWithNextLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun replaceInputWithNextLine() {
|
||||||
|
history.updateCurrentLine(inputLine)
|
||||||
|
clearInput()
|
||||||
|
displayNextInputLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun displayNextInputLine() {
|
||||||
|
inputLine = history.getNextLine()
|
||||||
|
putStringToTerminal(inputLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doLeftArrow() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (isPossibleToMoveLeft(cursorPosition))
|
||||||
|
moveCursorLeft(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isPossibleToMoveLeft(cursorPosition: TerminalPosition): Boolean {
|
||||||
|
return getDistanceFromOrigin(cursorPosition) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun getDistanceFromOrigin(cursorPosition: TerminalPosition): Int {
|
||||||
|
val columnDifference = cursorPosition.column - originColumn
|
||||||
|
val rowDifference = cursorPosition.row - originRow
|
||||||
|
val totalColumns = terminalSize.columns
|
||||||
|
|
||||||
|
return columnDifference + totalColumns * rowDifference
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun moveCursorLeft(cursorPosition: TerminalPosition) {
|
||||||
|
var newPosition = cursorPosition.withRelativeColumn(-1)
|
||||||
|
|
||||||
|
if (isAtStartOfRow(cursorPosition))
|
||||||
|
newPosition = cursorPosition.withColumn(terminalSize.columns).withRelativeRow(-1)
|
||||||
|
|
||||||
|
terminal.cursorPosition = newPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isAtStartOfRow(cursorPosition: TerminalPosition): Boolean {
|
||||||
|
return cursorPosition.column == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doRightArrow() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (isPossibleToMoveRight(cursorPosition))
|
||||||
|
moveCursorRight(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isPossibleToMoveRight(cursorPosition: TerminalPosition): Boolean {
|
||||||
|
return getDistanceFromOrigin(cursorPosition) < inputLine.length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun moveCursorRight(cursorPosition: TerminalPosition) {
|
||||||
|
if (isEndOfRow(cursorPosition))
|
||||||
|
moveCursorToNextRow(cursorPosition)
|
||||||
|
else
|
||||||
|
terminal.cursorPosition = cursorPosition.withRelativeColumn(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isEndOfRow(cursorPosition: TerminalPosition): Boolean {
|
||||||
|
return cursorPosition.column >= terminalSize.columns - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun moveCursorToNextRow(cursorPosition: TerminalPosition) {
|
||||||
|
if (isEndOfBuffer(cursorPosition))
|
||||||
|
createNewRowForCursor(cursorPosition)
|
||||||
|
else
|
||||||
|
terminal.cursorPosition = cursorPosition.withColumn(0).withRelativeRow(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isEndOfBuffer(cursorPosition: TerminalPosition): Boolean {
|
||||||
|
return cursorPosition.row == terminalSize.rows - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun createNewRowForCursor(cursorPosition: TerminalPosition) {
|
||||||
|
terminal.cursorPosition = cursorPosition
|
||||||
|
terminal.putCharacter('\n')
|
||||||
|
terminal.cursorPosition = cursorPosition.withColumn(0)
|
||||||
|
--originRow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doBackspace() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (isPossibleToMoveLeft(cursorPosition))
|
||||||
|
deletePreviousCharacter(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun deletePreviousCharacter(cursorPosition: TerminalPosition) {
|
||||||
|
val distanceFromOrigin = getDistanceFromOrigin(cursorPosition)
|
||||||
|
val remaining = inputLine.substring(distanceFromOrigin, inputLine.length)
|
||||||
|
inputLine = inputLine.substring(0, distanceFromOrigin - 1) + remaining
|
||||||
|
moveCursorLeft(cursorPosition)
|
||||||
|
putStringToTerminal("$remaining ")
|
||||||
|
moveCursorLeft(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doDelete() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (isPossibleToMoveRight(cursorPosition))
|
||||||
|
deleteCharacterAtPosition(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun deleteCharacterAtPosition(cursorPosition: TerminalPosition) {
|
||||||
|
val distanceFromOrigin = getDistanceFromOrigin(cursorPosition)
|
||||||
|
val remaining = inputLine.substring(distanceFromOrigin + 1, inputLine.length)
|
||||||
|
inputLine = inputLine.substring(0, distanceFromOrigin) + remaining
|
||||||
|
putStringToTerminal("$remaining ")
|
||||||
|
terminal.cursorPosition = cursorPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun doCharacter(character: Char) {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (!isBufferFilled)
|
||||||
|
if (isPossibleToMoveRight(cursorPosition))
|
||||||
|
insertCharacter(character, cursorPosition)
|
||||||
|
else
|
||||||
|
appendCharacter(character, cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun insertCharacter(character: Char, cursorPosition: TerminalPosition) {
|
||||||
|
val shiftedPosition = shiftPositionIfNewRowAdded(cursorPosition)
|
||||||
|
terminal.cursorPosition = shiftedPosition
|
||||||
|
|
||||||
|
val distanceFromOrigin = getDistanceFromOrigin(shiftedPosition)
|
||||||
|
val remaining = character + inputLine.substring(distanceFromOrigin, inputLine.length)
|
||||||
|
inputLine = inputLine.substring(0, distanceFromOrigin) + remaining
|
||||||
|
putStringToTerminal(remaining)
|
||||||
|
moveCursorRight(shiftedPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun shiftPositionIfNewRowAdded(cursorPosition: TerminalPosition): TerminalPosition {
|
||||||
|
val oldOriginRow = originRow
|
||||||
|
moveCursorRight(leadingEdge)
|
||||||
|
|
||||||
|
return if (isNewRowAdded(oldOriginRow)) adjustCursorPosition(cursorPosition) else cursorPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun isNewRowAdded(oldOriginRow: Int): Boolean {
|
||||||
|
return originRow != oldOriginRow
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun adjustCursorPosition(cursorPosition: TerminalPosition): TerminalPosition {
|
||||||
|
terminal.cursorPosition = TerminalPosition(originColumn, originRow)
|
||||||
|
putStringToTerminal(inputLine)
|
||||||
|
|
||||||
|
return cursorPosition.withRelativeRow(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun appendCharacter(character: Char, cursorPosition: TerminalPosition) {
|
||||||
|
terminal.putCharacter(character)
|
||||||
|
inputLine += character
|
||||||
|
moveCursorRight(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takeNap() {
|
||||||
|
Thread.sleep(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeOutput() {
|
||||||
|
var c = outputReader.read()
|
||||||
|
|
||||||
|
while (c != EOF) {
|
||||||
|
processOutput(c.toChar())
|
||||||
|
c = outputReader.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.flush()
|
||||||
|
terminal.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun processOutput(c: Char) {
|
||||||
|
if (c == END_OF_SEGMENT)
|
||||||
|
writeSegment()
|
||||||
|
else
|
||||||
|
outputSegment += c
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun writeSegment() {
|
||||||
|
printSegmentCharacters()
|
||||||
|
outputSegment = ""
|
||||||
|
redisplayInput()
|
||||||
|
terminal.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun printSegmentCharacters() {
|
||||||
|
moveCursorToEndOfInput()
|
||||||
|
putOutputToTerminal()
|
||||||
|
moveCursorToNextRowIfNecessary()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun putOutputToTerminal() {
|
||||||
|
val input = convertOutputToStream()
|
||||||
|
var c = input.read()
|
||||||
|
|
||||||
|
while (c != EOF) {
|
||||||
|
if (isEscape(c.toChar()))
|
||||||
|
applyControlSequence(input)
|
||||||
|
else
|
||||||
|
terminal.putCharacter(c.toChar())
|
||||||
|
|
||||||
|
c = input.read()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun convertOutputToStream(): SafeInputStream {
|
||||||
|
return SafeInputStream(ByteArrayInputStream(outputSegment.toByteArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun applyControlSequence(input: SafeInputStream) {
|
||||||
|
val controlSequence = controlSequenceHandler.parse(input)
|
||||||
|
controlSequence.applyTo(terminal)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun moveCursorToNextRowIfNecessary() {
|
||||||
|
val cursorPosition = terminal.cursorPosition
|
||||||
|
|
||||||
|
if (isEndOfRow(cursorPosition))
|
||||||
|
moveCursorToNextRow(cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
isStopped = true
|
||||||
|
inputWriter.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val END_OF_SEGMENT = Characters.UNICODE_NULL
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,8 @@ import org.junit.Test
|
||||||
import org.junit.contrib.java.lang.system.ExpectedSystemExit
|
import org.junit.contrib.java.lang.system.ExpectedSystemExit
|
||||||
import org.junit.contrib.java.lang.system.SystemErrRule
|
import org.junit.contrib.java.lang.system.SystemErrRule
|
||||||
import org.junit.contrib.java.lang.system.SystemOutRule
|
import org.junit.contrib.java.lang.system.SystemOutRule
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import terminal.TerminalConfiguration
|
import terminal.TerminalConfiguration
|
||||||
import terminal.VirtualTerminalInteractor
|
import terminal.VirtualTerminalInteractor
|
||||||
import testutil.SymbolAndFunctionCleaner
|
import testutil.SymbolAndFunctionCleaner
|
||||||
|
@ -21,6 +23,7 @@ import java.io.PipedOutputStream
|
||||||
import java.text.MessageFormat.format
|
import java.text.MessageFormat.format
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class MainTest : SymbolAndFunctionCleaner() {
|
class MainTest : SymbolAndFunctionCleaner() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -8,9 +8,12 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class ErrorManagerTest {
|
class ErrorManagerTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import function.ArgumentValidator.TooManyArgumentsException
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import sexpression.Cons
|
import sexpression.Cons
|
||||||
import sexpression.LispString
|
import sexpression.LispString
|
||||||
import sexpression.Nil
|
import sexpression.Nil
|
||||||
|
@ -15,6 +17,7 @@ import sexpression.Symbol
|
||||||
import sexpression.Symbol.Companion.T
|
import sexpression.Symbol.Companion.T
|
||||||
import testutil.TestUtilities.assertIsErrorWithMessage
|
import testutil.TestUtilities.assertIsErrorWithMessage
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class ArgumentValidatorTest {
|
class ArgumentValidatorTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -2,9 +2,12 @@ package function
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import sexpression.Cons
|
import sexpression.Cons
|
||||||
import sexpression.Nil
|
import sexpression.Nil
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispFunctionTest {
|
class LispFunctionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,9 +2,12 @@ package function
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import sexpression.Cons
|
import sexpression.Cons
|
||||||
import sexpression.Nil
|
import sexpression.Nil
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispSpecialFunctionTest {
|
class LispSpecialFunctionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -6,6 +6,8 @@ import function.UserDefinedFunction.IllegalKeywordRestPositionException
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import sexpression.Cons
|
import sexpression.Cons
|
||||||
import sexpression.LispNumber
|
import sexpression.LispNumber
|
||||||
import sexpression.Nil
|
import sexpression.Nil
|
||||||
|
@ -13,6 +15,7 @@ import sexpression.Symbol
|
||||||
import testutil.TestUtilities.assertIsErrorWithMessage
|
import testutil.TestUtilities.assertIsErrorWithMessage
|
||||||
import testutil.TestUtilities.assertSExpressionsMatch
|
import testutil.TestUtilities.assertSExpressionsMatch
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class UserDefinedFunctionTest {
|
class UserDefinedFunctionTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -6,10 +6,13 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispInterpreterTest {
|
class LispInterpreterTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -2,8 +2,11 @@ package recursion
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import recursion.TailCalls.done
|
import recursion.TailCalls.done
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class TailCallTest {
|
class TailCallTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -4,11 +4,14 @@ import error.Severity.CRITICAL
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import scanner.LispInputStream.MaximumUnreadsExceededException
|
import scanner.LispInputStream.MaximumUnreadsExceededException
|
||||||
import stream.LispIOException
|
import stream.LispIOException
|
||||||
import testutil.TestUtilities.createIOExceptionThrowingInputStream
|
import testutil.TestUtilities.createIOExceptionThrowingInputStream
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispCommentRemovingInputStreamTest {
|
class LispCommentRemovingInputStreamTest {
|
||||||
|
|
||||||
private fun getLispCommentRemovingInputStreamResult(inputString: String) =
|
private fun getLispCommentRemovingInputStreamResult(inputString: String) =
|
||||||
|
|
|
@ -3,8 +3,11 @@ package scanner
|
||||||
import file.FilePosition
|
import file.FilePosition
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispScannerLineColumnTest {
|
class LispScannerLineColumnTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -2,8 +2,11 @@ package scanner
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispScannerTextTest {
|
class LispScannerTextTest {
|
||||||
|
|
||||||
private fun assertTokenTextMatches(input: String, expectedText: Array<String>) {
|
private fun assertTokenTextMatches(input: String, expectedText: Array<String>) {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import scanner.LispScanner.UnterminatedStringException
|
import scanner.LispScanner.UnterminatedStringException
|
||||||
import testutil.TestUtilities.assertIsErrorWithMessage
|
import testutil.TestUtilities.assertIsErrorWithMessage
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
|
@ -20,6 +22,7 @@ import token.RightParenthesis
|
||||||
import token.Token
|
import token.Token
|
||||||
import token.TokenFactory.BadCharacterException
|
import token.TokenFactory.BadCharacterException
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispScannerTypeTest {
|
class LispScannerTypeTest {
|
||||||
|
|
||||||
private val expectedTypes = mutableListOf<Class<out Token>>()
|
private val expectedTypes = mutableListOf<Class<out Token>>()
|
||||||
|
|
|
@ -4,6 +4,8 @@ import function.UserDefinedFunction
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import sexpression.LispNumber.InvalidNumberException
|
import sexpression.LispNumber.InvalidNumberException
|
||||||
import testutil.TestUtilities.assertIsErrorWithMessage
|
import testutil.TestUtilities.assertIsErrorWithMessage
|
||||||
import testutil.TestUtilities.assertSExpressionsMatch
|
import testutil.TestUtilities.assertSExpressionsMatch
|
||||||
|
@ -11,6 +13,7 @@ import testutil.TestUtilities.makeList
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class SExpressionTest {
|
class SExpressionTest {
|
||||||
|
|
||||||
private fun assertSExpressionMatchesString(expected: String, sExpression: SExpression) {
|
private fun assertSExpressionMatchesString(expected: String, sExpression: SExpression) {
|
||||||
|
|
|
@ -4,10 +4,13 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import testutil.TestUtilities.createIOExceptionThrowingInputStream
|
import testutil.TestUtilities.createIOExceptionThrowingInputStream
|
||||||
import testutil.TestUtilities.createInputStreamFromString
|
import testutil.TestUtilities.createInputStreamFromString
|
||||||
import util.Characters.EOF
|
import util.Characters.EOF
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class SafeInputStreamTest {
|
class SafeInputStreamTest {
|
||||||
|
|
||||||
private lateinit var safe: SafeInputStream
|
private lateinit var safe: SafeInputStream
|
||||||
|
|
|
@ -4,9 +4,12 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import testutil.TestUtilities.createIOExceptionThrowingOutputStream
|
import testutil.TestUtilities.createIOExceptionThrowingOutputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class SafeOutputStreamTest {
|
class SafeOutputStreamTest {
|
||||||
|
|
||||||
private lateinit var safe: SafeOutputStream
|
private lateinit var safe: SafeOutputStream
|
||||||
|
|
|
@ -3,6 +3,8 @@ package terminal
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import stream.SafeInputStream
|
import stream.SafeInputStream
|
||||||
import terminal.ControlSequence.NullControlSequence
|
import terminal.ControlSequence.NullControlSequence
|
||||||
import terminal.ControlSequenceHandler.Companion.isEscape
|
import terminal.ControlSequenceHandler.Companion.isEscape
|
||||||
|
@ -14,6 +16,7 @@ import terminal.SelectGraphicRendition.YELLOW
|
||||||
import testutil.TestUtilities
|
import testutil.TestUtilities
|
||||||
import util.Characters.EOF
|
import util.Characters.EOF
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class ControlSequenceHandlerTest {
|
class ControlSequenceHandlerTest {
|
||||||
|
|
||||||
private lateinit var handler: ControlSequenceHandler
|
private lateinit var handler: ControlSequenceHandler
|
||||||
|
|
|
@ -6,6 +6,8 @@ import com.googlecode.lanterna.terminal.virtual.VirtualTerminal
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import terminal.ControlSequence.NullControlSequence
|
import terminal.ControlSequence.NullControlSequence
|
||||||
import terminal.SelectGraphicRendition.GREEN
|
import terminal.SelectGraphicRendition.GREEN
|
||||||
import terminal.SelectGraphicRendition.PURPLE
|
import terminal.SelectGraphicRendition.PURPLE
|
||||||
|
@ -14,6 +16,7 @@ import terminal.SelectGraphicRendition.RESET
|
||||||
import terminal.SelectGraphicRendition.YELLOW
|
import terminal.SelectGraphicRendition.YELLOW
|
||||||
import java.util.HashSet
|
import java.util.HashSet
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class ControlSequenceTest {
|
class ControlSequenceTest {
|
||||||
|
|
||||||
private lateinit var indicatorSet: MutableSet<String>
|
private lateinit var indicatorSet: MutableSet<String>
|
||||||
|
|
|
@ -11,8 +11,11 @@ import com.googlecode.lanterna.input.KeyType.Escape
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import terminal.LispTerminal.END_OF_SEGMENT
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
|
import terminal.LispTerminal.Companion.END_OF_SEGMENT
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class LispTerminalTest {
|
class LispTerminalTest {
|
||||||
|
|
||||||
private lateinit var terminal: VirtualTerminalInteractor
|
private lateinit var terminal: VirtualTerminalInteractor
|
||||||
|
|
|
@ -3,11 +3,14 @@ package terminal
|
||||||
import org.junit.jupiter.api.Assertions.assertThrows
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
import stream.LispIOException
|
import stream.LispIOException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
import java.io.PipedOutputStream
|
import java.io.PipedOutputStream
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class TerminalConfigurationTest {
|
class TerminalConfigurationTest {
|
||||||
|
|
||||||
private lateinit var configuration: TerminalConfiguration
|
private lateinit var configuration: TerminalConfiguration
|
||||||
|
|
|
@ -4,6 +4,10 @@ import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||||
|
|
||||||
|
@TestInstance(PER_CLASS)
|
||||||
class TerminalHistoryTest {
|
class TerminalHistoryTest {
|
||||||
|
|
||||||
private lateinit var history: TerminalHistory
|
private lateinit var history: TerminalHistory
|
||||||
|
|
|
@ -8,7 +8,7 @@ import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal
|
||||||
import com.googlecode.lanterna.terminal.virtual.VirtualTerminal
|
import com.googlecode.lanterna.terminal.virtual.VirtualTerminal
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import terminal.LispTerminal.END_OF_SEGMENT
|
import terminal.LispTerminal.Companion.END_OF_SEGMENT
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.PipedInputStream
|
import java.io.PipedInputStream
|
||||||
import java.io.PipedOutputStream
|
import java.io.PipedOutputStream
|
||||||
|
|
Loading…
Reference in New Issue