Convert terminal to kotlin

This commit is contained in:
Mike Cifelli 2018-10-03 19:09:50 -04:00
parent ae48d9c2ca
commit 3ec8e72a17
25 changed files with 559 additions and 464 deletions

View File

@ -211,7 +211,7 @@
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.16.1</version>
<version>1.18.0</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -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

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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) =

View File

@ -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 {

View File

@ -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<String>) {

View File

@ -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<Class<out Token>>()

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<String>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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