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.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 java.io.IOException import java.io.PipedInputStream import java.io.PipedOutputStream class VirtualTerminalInteractor { private val inputWriter = PipedOutputStream() private val inputReader = PipedInputStream() private val outputWriter = PipedOutputStream() private val outputReader = PipedInputStream() private val flushListener = FlushListener() private val virtualTerminal: VirtualTerminal = DefaultVirtualTerminal().apply { addVirtualTerminalListener(flushListener) } val configuration = TerminalConfiguration().also { it.setInputPair(inputWriter, inputReader) it.setOutputPair(outputWriter, outputReader) it.terminal = virtualTerminal } private val lispTerminal = LispTerminal(configuration) fun start() { lispTerminal.start() } fun stop() { lispTerminal.stop() outputWriter.close() } fun pressKey(keyType: KeyType) { virtualTerminal.addInput(KeyStroke(keyType)) waitForFlushes(1) } fun pressKeyTimes(keyType: KeyType, times: Int) { for (i in times downTo 1) virtualTerminal.addInput(KeyStroke(keyType)) waitForFlushes(times) } fun pressControlKey(keyType: KeyType) { virtualTerminal.addInput(KeyStroke(keyType, true, false)) waitForFlushes(1) } fun enterCharacter(character: Char) { virtualTerminal.addInput(KeyStroke(character, false, false)) waitForFlushes(1) } fun enterControlCharacter(character: Char) { virtualTerminal.addInput(KeyStroke(character, true, false)) waitForFlushes(1) } fun enterCharacters(characters: String) { for (c in characters.toCharArray()) virtualTerminal.addInput(KeyStroke(c, false, false)) waitForFlushes(characters.length) } fun produceOutput(output: String) { for (c in output.toCharArray()) outputWriter.write(c.toInt()) outputWriter.write(END_OF_SEGMENT.toInt()) outputWriter.flush() waitForFlushes(1) } private fun waitForFlushes(flushCount: Int) { synchronized(flushListener.lock) { while (flushListener.flushCount < flushCount) flushListener.lock.wait() flushListener.reduceFlushCount(flushCount) } } fun waitForPrompt() { waitForFlushes(1) } fun setColumns(columns: Int) { val rows = virtualTerminal.terminalSize.rows virtualTerminal.terminalSize = TerminalSize(columns, rows) } fun setRows(rows: Int) { val columns = virtualTerminal.terminalSize.columns virtualTerminal.terminalSize = TerminalSize(columns, rows) } fun assertCursorPosition(column: Int, row: Int) { assertThat(virtualTerminal.cursorPosition.column).isEqualTo(column) assertThat(virtualTerminal.cursorPosition.row).isEqualTo(row) } fun assertScreenText(vararg rows: String) { for (row in rows.indices) for (column in 0 until rows[row].length) assertCharacterAtPosition(rows[row][column], column, row) } private fun assertCharacterAtPosition(character: Char, column: Int, row: Int) { val position = TerminalPosition(column, row) val expected = character.toString() val actual = virtualTerminal.getCharacter(position).character.toString() assertThat(actual).isEqualTo(expected) } fun assertInputWritten(expected: String) { var actual = "" inputWriter.close() var c = inputReader.read() while (c != -1) { actual += c.toChar() c = inputReader.read() } assertThat(actual).isEqualTo(expected) } fun assertInputStreamClosed() { try { inputWriter.write(0) fail("input stream not closed") } catch (ignored: IOException) { } } }