diff --git a/pom.xml b/pom.xml
index 1defd19..a27c2d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -211,7 +211,7 @@
com.github.stefanbirkner
system-rules
- 1.16.1
+ 1.18.0
test
diff --git a/src/main/kotlin/application/LispMain.kt b/src/main/kotlin/application/LispMain.kt
index 70bcdd5..f4be730 100644
--- a/src/main/kotlin/application/LispMain.kt
+++ b/src/main/kotlin/application/LispMain.kt
@@ -5,7 +5,7 @@ import com.googlecode.lanterna.terminal.IOSafeTerminalAdapter.createRuntimeExcep
import interpreter.LispInterpreter
import interpreter.LispInterpreterBuilder
import terminal.LispTerminal
-import terminal.LispTerminal.END_OF_SEGMENT
+import terminal.LispTerminal.Companion.END_OF_SEGMENT
import terminal.TerminalConfiguration
import java.io.PipedInputStream
import java.io.PipedOutputStream
diff --git a/src/main/kotlin/terminal/LispTerminal.java b/src/main/kotlin/terminal/LispTerminal.java
deleted file mode 100644
index 9fb0ed6..0000000
--- a/src/main/kotlin/terminal/LispTerminal.java
+++ /dev/null
@@ -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();
- }
-}
diff --git a/src/main/kotlin/terminal/LispTerminal.kt b/src/main/kotlin/terminal/LispTerminal.kt
new file mode 100644
index 0000000..6dffa1b
--- /dev/null
+++ b/src/main/kotlin/terminal/LispTerminal.kt
@@ -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
+ }
+}
diff --git a/src/test/kotlin/application/MainTest.kt b/src/test/kotlin/application/MainTest.kt
index 24f1a93..97b30a8 100644
--- a/src/test/kotlin/application/MainTest.kt
+++ b/src/test/kotlin/application/MainTest.kt
@@ -13,6 +13,8 @@ import org.junit.Test
import org.junit.contrib.java.lang.system.ExpectedSystemExit
import org.junit.contrib.java.lang.system.SystemErrRule
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.VirtualTerminalInteractor
import testutil.SymbolAndFunctionCleaner
@@ -21,6 +23,7 @@ import java.io.PipedOutputStream
import java.text.MessageFormat.format
import java.util.concurrent.CountDownLatch
+@TestInstance(PER_CLASS)
class MainTest : SymbolAndFunctionCleaner() {
companion object {
diff --git a/src/test/kotlin/error/ErrorManagerTest.kt b/src/test/kotlin/error/ErrorManagerTest.kt
index 5d02b12..130e615 100644
--- a/src/test/kotlin/error/ErrorManagerTest.kt
+++ b/src/test/kotlin/error/ErrorManagerTest.kt
@@ -8,9 +8,12 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
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.PrintStream
+@TestInstance(PER_CLASS)
class ErrorManagerTest {
companion object {
diff --git a/src/test/kotlin/function/ArgumentValidatorTest.kt b/src/test/kotlin/function/ArgumentValidatorTest.kt
index 43060bf..f69f30f 100644
--- a/src/test/kotlin/function/ArgumentValidatorTest.kt
+++ b/src/test/kotlin/function/ArgumentValidatorTest.kt
@@ -7,6 +7,8 @@ import function.ArgumentValidator.TooManyArgumentsException
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
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.LispString
import sexpression.Nil
@@ -15,6 +17,7 @@ import sexpression.Symbol
import sexpression.Symbol.Companion.T
import testutil.TestUtilities.assertIsErrorWithMessage
+@TestInstance(PER_CLASS)
class ArgumentValidatorTest {
companion object {
diff --git a/src/test/kotlin/function/LispFunctionTest.kt b/src/test/kotlin/function/LispFunctionTest.kt
index 975f140..8c16459 100644
--- a/src/test/kotlin/function/LispFunctionTest.kt
+++ b/src/test/kotlin/function/LispFunctionTest.kt
@@ -2,9 +2,12 @@ package function
import org.assertj.core.api.Assertions.assertThat
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.Nil
+@TestInstance(PER_CLASS)
class LispFunctionTest {
@Test
diff --git a/src/test/kotlin/function/LispSpecialFunctionTest.kt b/src/test/kotlin/function/LispSpecialFunctionTest.kt
index 6ee4c77..86c5bfe 100644
--- a/src/test/kotlin/function/LispSpecialFunctionTest.kt
+++ b/src/test/kotlin/function/LispSpecialFunctionTest.kt
@@ -2,9 +2,12 @@ package function
import org.assertj.core.api.Assertions.assertThat
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.Nil
+@TestInstance(PER_CLASS)
class LispSpecialFunctionTest {
@Test
diff --git a/src/test/kotlin/function/UserDefinedFunctionTest.kt b/src/test/kotlin/function/UserDefinedFunctionTest.kt
index fd4992f..660bb5c 100644
--- a/src/test/kotlin/function/UserDefinedFunctionTest.kt
+++ b/src/test/kotlin/function/UserDefinedFunctionTest.kt
@@ -6,6 +6,8 @@ import function.UserDefinedFunction.IllegalKeywordRestPositionException
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
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.LispNumber
import sexpression.Nil
@@ -13,6 +15,7 @@ import sexpression.Symbol
import testutil.TestUtilities.assertIsErrorWithMessage
import testutil.TestUtilities.assertSExpressionsMatch
+@TestInstance(PER_CLASS)
class UserDefinedFunctionTest {
companion object {
diff --git a/src/test/kotlin/interpreter/LispInterpreterTest.kt b/src/test/kotlin/interpreter/LispInterpreterTest.kt
index 96cbef8..fef63e3 100644
--- a/src/test/kotlin/interpreter/LispInterpreterTest.kt
+++ b/src/test/kotlin/interpreter/LispInterpreterTest.kt
@@ -6,10 +6,13 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
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 java.io.ByteArrayOutputStream
import java.io.PrintStream
+@TestInstance(PER_CLASS)
class LispInterpreterTest {
companion object {
diff --git a/src/test/kotlin/recursion/TailCallTest.kt b/src/test/kotlin/recursion/TailCallTest.kt
index 7dfdf84..5bc23fd 100644
--- a/src/test/kotlin/recursion/TailCallTest.kt
+++ b/src/test/kotlin/recursion/TailCallTest.kt
@@ -2,8 +2,11 @@ package recursion
import org.junit.jupiter.api.Assertions.assertThrows
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
+@TestInstance(PER_CLASS)
class TailCallTest {
@Test
diff --git a/src/test/kotlin/scanner/LispCommentRemovingInputStreamTest.kt b/src/test/kotlin/scanner/LispCommentRemovingInputStreamTest.kt
index 6ef094e..f18f89c 100644
--- a/src/test/kotlin/scanner/LispCommentRemovingInputStreamTest.kt
+++ b/src/test/kotlin/scanner/LispCommentRemovingInputStreamTest.kt
@@ -4,11 +4,14 @@ import error.Severity.CRITICAL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
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 stream.LispIOException
import testutil.TestUtilities.createIOExceptionThrowingInputStream
import testutil.TestUtilities.createInputStreamFromString
+@TestInstance(PER_CLASS)
class LispCommentRemovingInputStreamTest {
private fun getLispCommentRemovingInputStreamResult(inputString: String) =
diff --git a/src/test/kotlin/scanner/LispScannerLineColumnTest.kt b/src/test/kotlin/scanner/LispScannerLineColumnTest.kt
index 770920d..e41c803 100644
--- a/src/test/kotlin/scanner/LispScannerLineColumnTest.kt
+++ b/src/test/kotlin/scanner/LispScannerLineColumnTest.kt
@@ -3,8 +3,11 @@ package scanner
import file.FilePosition
import org.assertj.core.api.Assertions.assertThat
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
+@TestInstance(PER_CLASS)
class LispScannerLineColumnTest {
companion object {
diff --git a/src/test/kotlin/scanner/LispScannerTextTest.kt b/src/test/kotlin/scanner/LispScannerTextTest.kt
index edd4a6f..8f2dcbb 100644
--- a/src/test/kotlin/scanner/LispScannerTextTest.kt
+++ b/src/test/kotlin/scanner/LispScannerTextTest.kt
@@ -2,8 +2,11 @@ package scanner
import org.assertj.core.api.Assertions.assertThat
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
+@TestInstance(PER_CLASS)
class LispScannerTextTest {
private fun assertTokenTextMatches(input: String, expectedText: Array) {
diff --git a/src/test/kotlin/scanner/LispScannerTypeTest.kt b/src/test/kotlin/scanner/LispScannerTypeTest.kt
index a86028b..cbfecc6 100644
--- a/src/test/kotlin/scanner/LispScannerTypeTest.kt
+++ b/src/test/kotlin/scanner/LispScannerTypeTest.kt
@@ -4,6 +4,8 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
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 testutil.TestUtilities.assertIsErrorWithMessage
import testutil.TestUtilities.createInputStreamFromString
@@ -20,6 +22,7 @@ import token.RightParenthesis
import token.Token
import token.TokenFactory.BadCharacterException
+@TestInstance(PER_CLASS)
class LispScannerTypeTest {
private val expectedTypes = mutableListOf>()
diff --git a/src/test/kotlin/sexpression/SExpressionTest.kt b/src/test/kotlin/sexpression/SExpressionTest.kt
index d24c933..4387442 100644
--- a/src/test/kotlin/sexpression/SExpressionTest.kt
+++ b/src/test/kotlin/sexpression/SExpressionTest.kt
@@ -4,6 +4,8 @@ import function.UserDefinedFunction
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
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 testutil.TestUtilities.assertIsErrorWithMessage
import testutil.TestUtilities.assertSExpressionsMatch
@@ -11,6 +13,7 @@ import testutil.TestUtilities.makeList
import java.math.BigInteger
import java.util.Locale
+@TestInstance(PER_CLASS)
class SExpressionTest {
private fun assertSExpressionMatchesString(expected: String, sExpression: SExpression) {
diff --git a/src/test/kotlin/stream/SafeInputStreamTest.kt b/src/test/kotlin/stream/SafeInputStreamTest.kt
index d27920e..32ab168 100644
--- a/src/test/kotlin/stream/SafeInputStreamTest.kt
+++ b/src/test/kotlin/stream/SafeInputStreamTest.kt
@@ -4,10 +4,13 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
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.createInputStreamFromString
import util.Characters.EOF
+@TestInstance(PER_CLASS)
class SafeInputStreamTest {
private lateinit var safe: SafeInputStream
diff --git a/src/test/kotlin/stream/SafeOutputStreamTest.kt b/src/test/kotlin/stream/SafeOutputStreamTest.kt
index 1f840da..af69d46 100644
--- a/src/test/kotlin/stream/SafeOutputStreamTest.kt
+++ b/src/test/kotlin/stream/SafeOutputStreamTest.kt
@@ -4,9 +4,12 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
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 java.io.ByteArrayOutputStream
+@TestInstance(PER_CLASS)
class SafeOutputStreamTest {
private lateinit var safe: SafeOutputStream
diff --git a/src/test/kotlin/terminal/ControlSequenceHandlerTest.kt b/src/test/kotlin/terminal/ControlSequenceHandlerTest.kt
index f8b043d..298f3a9 100644
--- a/src/test/kotlin/terminal/ControlSequenceHandlerTest.kt
+++ b/src/test/kotlin/terminal/ControlSequenceHandlerTest.kt
@@ -3,6 +3,8 @@ package terminal
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
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 terminal.ControlSequence.NullControlSequence
import terminal.ControlSequenceHandler.Companion.isEscape
@@ -14,6 +16,7 @@ import terminal.SelectGraphicRendition.YELLOW
import testutil.TestUtilities
import util.Characters.EOF
+@TestInstance(PER_CLASS)
class ControlSequenceHandlerTest {
private lateinit var handler: ControlSequenceHandler
diff --git a/src/test/kotlin/terminal/ControlSequenceTest.kt b/src/test/kotlin/terminal/ControlSequenceTest.kt
index 0b39135..4d24a6d 100644
--- a/src/test/kotlin/terminal/ControlSequenceTest.kt
+++ b/src/test/kotlin/terminal/ControlSequenceTest.kt
@@ -6,6 +6,8 @@ import com.googlecode.lanterna.terminal.virtual.VirtualTerminal
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
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.SelectGraphicRendition.GREEN
import terminal.SelectGraphicRendition.PURPLE
@@ -14,6 +16,7 @@ import terminal.SelectGraphicRendition.RESET
import terminal.SelectGraphicRendition.YELLOW
import java.util.HashSet
+@TestInstance(PER_CLASS)
class ControlSequenceTest {
private lateinit var indicatorSet: MutableSet
diff --git a/src/test/kotlin/terminal/LispTerminalTest.kt b/src/test/kotlin/terminal/LispTerminalTest.kt
index d4629ee..60756e5 100644
--- a/src/test/kotlin/terminal/LispTerminalTest.kt
+++ b/src/test/kotlin/terminal/LispTerminalTest.kt
@@ -11,8 +11,11 @@ import com.googlecode.lanterna.input.KeyType.Escape
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
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 {
private lateinit var terminal: VirtualTerminalInteractor
diff --git a/src/test/kotlin/terminal/TerminalConfigurationTest.kt b/src/test/kotlin/terminal/TerminalConfigurationTest.kt
index a73e085..bc5a6a6 100644
--- a/src/test/kotlin/terminal/TerminalConfigurationTest.kt
+++ b/src/test/kotlin/terminal/TerminalConfigurationTest.kt
@@ -3,11 +3,14 @@ package terminal
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.BeforeEach
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 java.io.IOException
import java.io.PipedInputStream
import java.io.PipedOutputStream
+@TestInstance(PER_CLASS)
class TerminalConfigurationTest {
private lateinit var configuration: TerminalConfiguration
diff --git a/src/test/kotlin/terminal/TerminalHistoryTest.kt b/src/test/kotlin/terminal/TerminalHistoryTest.kt
index 3e84d43..fca32ea 100644
--- a/src/test/kotlin/terminal/TerminalHistoryTest.kt
+++ b/src/test/kotlin/terminal/TerminalHistoryTest.kt
@@ -4,6 +4,10 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
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 {
private lateinit var history: TerminalHistory
diff --git a/src/test/kotlin/terminal/VirtualTerminalInteractor.kt b/src/test/kotlin/terminal/VirtualTerminalInteractor.kt
index b0596b6..6e77941 100644
--- a/src/test/kotlin/terminal/VirtualTerminalInteractor.kt
+++ b/src/test/kotlin/terminal/VirtualTerminalInteractor.kt
@@ -8,7 +8,7 @@ import com.googlecode.lanterna.terminal.virtual.DefaultVirtualTerminal
import com.googlecode.lanterna.terminal.virtual.VirtualTerminal
import org.assertj.core.api.Assertions.assertThat
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.PipedInputStream
import java.io.PipedOutputStream