Clean up terminal code and unit tests
The terminal unit tests were updated so that they don't rely on an arbitrary delay.
This commit is contained in:
parent
072a432026
commit
38ab1144fb
|
@ -1,8 +1,12 @@
|
||||||
package main;
|
package main;
|
||||||
|
|
||||||
|
import static terminal.LispTerminal.END_OF_SEGMENT;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.terminal.*;
|
||||||
|
|
||||||
import interpreter.*;
|
import interpreter.*;
|
||||||
import terminal.LispTerminal;
|
import terminal.LispTerminal;
|
||||||
|
|
||||||
|
@ -17,25 +21,27 @@ public class LispMain {
|
||||||
private PipedOutputStream inputWriter;
|
private PipedOutputStream inputWriter;
|
||||||
private PipedInputStream outputReader;
|
private PipedInputStream outputReader;
|
||||||
private PipedOutputStream outputWriter;
|
private PipedOutputStream outputWriter;
|
||||||
private LispTerminal terminal;
|
private LispTerminal lispTerminal;
|
||||||
|
|
||||||
private LispMain() throws IOException {
|
private LispMain() throws IOException {
|
||||||
inputReader = new PipedInputStream();
|
inputReader = new PipedInputStream();
|
||||||
inputWriter = new PipedOutputStream(inputReader);
|
inputWriter = new PipedOutputStream(inputReader);
|
||||||
outputReader = new PipedInputStream();
|
outputReader = new PipedInputStream();
|
||||||
outputWriter = new PipedOutputStream(outputReader);
|
outputWriter = new PipedOutputStream(outputReader);
|
||||||
terminal = new LispTerminal(inputWriter, outputReader);
|
lispTerminal = new LispTerminal(createIOSafeTerminal(), inputWriter, outputReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IOSafeTerminal createIOSafeTerminal() throws IOException {
|
||||||
|
return IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter(new DefaultTerminalFactory().createTerminal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
LispMain main = new LispMain();
|
new LispMain().run(args);
|
||||||
|
|
||||||
main.run(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(String[] args) {
|
private void run(String[] args) {
|
||||||
if (args.length == 0)
|
if (args.length == 0)
|
||||||
terminal.run();
|
lispTerminal.run();
|
||||||
|
|
||||||
LispInterpreter interpreter = buildInterpreter(args);
|
LispInterpreter interpreter = buildInterpreter(args);
|
||||||
interpreter.interpret();
|
interpreter.interpret();
|
||||||
|
@ -79,7 +85,7 @@ public class LispMain {
|
||||||
|
|
||||||
private void shutdown() {
|
private void shutdown() {
|
||||||
try {
|
try {
|
||||||
terminal.finish();
|
lispTerminal.finish();
|
||||||
outputWriter.close();
|
outputWriter.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
|
@ -120,7 +126,7 @@ public class LispMain {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String apply(String s) {
|
public String apply(String s) {
|
||||||
return s + 'x';
|
return s + END_OF_SEGMENT;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package terminal;
|
||||||
|
|
||||||
|
import static terminal.ControlSequenceHandler.Command.SGR;
|
||||||
|
|
||||||
|
class ControlSequenceHandler {
|
||||||
|
|
||||||
|
public static final boolean isEscape(char c) {
|
||||||
|
return c == '\u001B';
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean inControlSequence;
|
||||||
|
private int code;
|
||||||
|
private Command command;
|
||||||
|
|
||||||
|
public ControlSequenceHandler() {
|
||||||
|
// TODO Auto-generated constructor stub
|
||||||
|
this.inControlSequence = false;
|
||||||
|
this.code = 0;
|
||||||
|
this.command = SGR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum Command {
|
||||||
|
SGR
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,95 +1,55 @@
|
||||||
package terminal;
|
package terminal;
|
||||||
|
|
||||||
|
import static terminal.ControlSequenceHandler.isEscape;
|
||||||
|
import static util.Characters.EOF;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import com.googlecode.lanterna.TerminalPosition;
|
import com.googlecode.lanterna.TerminalPosition;
|
||||||
import com.googlecode.lanterna.input.*;
|
import com.googlecode.lanterna.input.*;
|
||||||
import com.googlecode.lanterna.terminal.*;
|
import com.googlecode.lanterna.terminal.IOSafeTerminal;
|
||||||
|
|
||||||
public class LispTerminal {
|
public class LispTerminal {
|
||||||
|
|
||||||
// private static final String ANSI_RESET = "001B[0m";
|
public static final char END_OF_SEGMENT = 'x';
|
||||||
// private static final String ANSI_RED = "\u001B[31m";
|
|
||||||
// private static final String ANSI_GREEN = "\u001B[32m";
|
|
||||||
// private static final String ANSI_YELLOW = "\u001B[33m";
|
|
||||||
// private static final String ANSI_PURPLE = "\u001B[35m";
|
|
||||||
|
|
||||||
private IOSafeTerminal terminal;
|
private IOSafeTerminal terminal;
|
||||||
private boolean isFinished;
|
|
||||||
private String currentLine;
|
|
||||||
private TerminalPosition origin;
|
|
||||||
private PipedOutputStream inputWriter;
|
private PipedOutputStream inputWriter;
|
||||||
private PipedInputStream outputReader;
|
private PipedInputStream outputReader;
|
||||||
private ExecutorService executor;
|
private ExecutorService executorService;
|
||||||
|
private ControlSequenceHandler controlSequenceHandler;
|
||||||
|
private TerminalPosition origin;
|
||||||
|
private String inputLine;
|
||||||
|
private String outputSegment;
|
||||||
|
private boolean isFinished;
|
||||||
|
|
||||||
LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) {
|
public LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) {
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
initialize(inputWriter, outputReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LispTerminal(PipedOutputStream inputWriter, PipedInputStream outputReader) {
|
|
||||||
try {
|
|
||||||
Terminal unsafe = new DefaultTerminalFactory().createTerminal();
|
|
||||||
this.terminal = IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter(unsafe);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(inputWriter, outputReader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize(PipedOutputStream inputWriter, PipedInputStream outputReader) {
|
|
||||||
this.isFinished = false;
|
|
||||||
this.currentLine = "";
|
|
||||||
this.origin = terminal.getCursorPosition();
|
|
||||||
this.inputWriter = inputWriter;
|
this.inputWriter = inputWriter;
|
||||||
this.outputReader = outputReader;
|
this.outputReader = outputReader;
|
||||||
this.executor = Executors.newFixedThreadPool(2);
|
this.executorService = Executors.newFixedThreadPool(2);
|
||||||
|
this.controlSequenceHandler = new ControlSequenceHandler();
|
||||||
|
this.origin = terminal.getCursorPosition();
|
||||||
|
this.inputLine = "";
|
||||||
|
this.outputSegment = "";
|
||||||
|
this.isFinished = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
executor.execute(this::readInput);
|
executorService.execute(this::readInput);
|
||||||
executor.execute(this::writeOutput);
|
executorService.execute(this::writeOutput);
|
||||||
executor.shutdown();
|
executorService.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readInput() {
|
public void readInput() {
|
||||||
while (!isFinished) {
|
while (!isFinished)
|
||||||
handleKeyStroke(getKeyStroke());
|
processNextKey();
|
||||||
terminal.flush();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(1);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
isFinished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeOutput() {
|
private void processNextKey() {
|
||||||
try {
|
handleKey(getKeyStroke());
|
||||||
for (int c = outputReader.read(); c != -1; c = outputReader.read()) {
|
takeNap();
|
||||||
synchronized (this) {
|
|
||||||
terminal.setCursorVisible(false);
|
|
||||||
|
|
||||||
if (c == 'x') {
|
|
||||||
terminal.flush();
|
|
||||||
origin = terminal.getCursorPosition();
|
|
||||||
terminal.setCursorVisible(true);
|
|
||||||
} else {
|
|
||||||
terminal.putCharacter((char) c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.setCursorVisible(true);
|
|
||||||
terminal.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke getKeyStroke() {
|
private KeyStroke getKeyStroke() {
|
||||||
|
@ -98,6 +58,7 @@ public class LispTerminal {
|
||||||
try {
|
try {
|
||||||
keyStroke = terminal.pollInput();
|
keyStroke = terminal.pollInput();
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
|
// TODO - Issue #299
|
||||||
terminal.putCharacter('\n');
|
terminal.putCharacter('\n');
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
@ -105,14 +66,19 @@ public class LispTerminal {
|
||||||
return keyStroke;
|
return keyStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void handleKeyStroke(KeyStroke keyStroke) {
|
private synchronized void handleKey(KeyStroke keyStroke) {
|
||||||
if (keyStroke == null)
|
if (keyStroke == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
doKey(keyStroke);
|
||||||
|
terminal.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void doKey(KeyStroke keyStroke) {
|
||||||
KeyType keyType = keyStroke.getKeyType();
|
KeyType keyType = keyStroke.getKeyType();
|
||||||
|
|
||||||
if (keyStroke.isCtrlDown())
|
if (keyStroke.isCtrlDown())
|
||||||
doControlKeyCharacter(keyStroke);
|
doControlKey(keyStroke);
|
||||||
else if (keyType == KeyType.ArrowLeft)
|
else if (keyType == KeyType.ArrowLeft)
|
||||||
moveCursorLeft();
|
moveCursorLeft();
|
||||||
else if (keyType == KeyType.ArrowRight)
|
else if (keyType == KeyType.ArrowRight)
|
||||||
|
@ -127,40 +93,28 @@ public class LispTerminal {
|
||||||
doCharacter(keyStroke);
|
doCharacter(keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void doControlKeyCharacter(KeyStroke keyStroke) {
|
private synchronized void doControlKey(KeyStroke keyStroke) {
|
||||||
KeyType keyType = keyStroke.getKeyType();
|
KeyType keyType = keyStroke.getKeyType();
|
||||||
|
|
||||||
if (keyType == KeyType.Character)
|
if (keyType == KeyType.Character)
|
||||||
if (keyStroke.getCharacter() == 'd') {
|
doControlCharacter(keyStroke);
|
||||||
doEnter();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void doEnter() {
|
private synchronized void doControlCharacter(KeyStroke keyStroke) {
|
||||||
terminal.putCharacter('\n');
|
if (keyStroke.getCharacter() == 'd')
|
||||||
currentLine += "\n";
|
doControlD();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
private synchronized void doControlD() {
|
||||||
inputWriter.write(currentLine.getBytes());
|
doEnter();
|
||||||
inputWriter.flush();
|
finish();
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLine = "";
|
|
||||||
origin = terminal.getCursorPosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void moveCursorLeft() {
|
private synchronized void moveCursorLeft() {
|
||||||
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
||||||
|
|
||||||
if (isPossibleToMoveLeft(cursorPosition))
|
if (isPossibleToMoveLeft(cursorPosition))
|
||||||
if (cursorPosition.getColumn() == 0)
|
retractCursor(cursorPosition);
|
||||||
terminal.setCursorPosition(terminal.getTerminalSize().getColumns(), cursorPosition.getRow() - 1);
|
|
||||||
else
|
|
||||||
terminal.setCursorPosition(cursorPosition.withRelativeColumn(-1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean isPossibleToMoveLeft(TerminalPosition cursorPosition) {
|
private synchronized boolean isPossibleToMoveLeft(TerminalPosition cursorPosition) {
|
||||||
|
@ -168,13 +122,22 @@ public class LispTerminal {
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized int distanceFromOrigin(TerminalPosition cursorPosition) {
|
private synchronized int distanceFromOrigin(TerminalPosition cursorPosition) {
|
||||||
int cursorColumn = cursorPosition.getColumn();
|
int columnDifference = cursorPosition.getColumn() - origin.getColumn();
|
||||||
int cursorRow = cursorPosition.getRow();
|
int rowDifference = cursorPosition.getRow() - origin.getRow();
|
||||||
int originColumn = origin.getColumn();
|
|
||||||
int originRow = origin.getRow();
|
|
||||||
int totalColumns = terminal.getTerminalSize().getColumns();
|
int totalColumns = terminal.getTerminalSize().getColumns();
|
||||||
|
|
||||||
return cursorColumn - originColumn + (totalColumns * (cursorRow - originRow));
|
return columnDifference + (totalColumns * rowDifference);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void retractCursor(TerminalPosition cursorPosition) {
|
||||||
|
if (isAtStartOfRow(cursorPosition))
|
||||||
|
terminal.setCursorPosition(terminal.getTerminalSize().getColumns(), cursorPosition.getRow() - 1);
|
||||||
|
else
|
||||||
|
terminal.setCursorPosition(cursorPosition.withRelativeColumn(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAtStartOfRow(TerminalPosition cursorPosition) {
|
||||||
|
return cursorPosition.getColumn() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void moveCursorRight() {
|
private synchronized void moveCursorRight() {
|
||||||
|
@ -184,42 +147,52 @@ public class LispTerminal {
|
||||||
advanceCursor(cursorPosition);
|
advanceCursor(cursorPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized boolean isPossibleToMoveRight(TerminalPosition cursorPosition) {
|
||||||
|
return distanceFromOrigin(cursorPosition) < inputLine.length();
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void advanceCursor(TerminalPosition cursorPosition) {
|
private synchronized void advanceCursor(TerminalPosition cursorPosition) {
|
||||||
if (isCursorAtEndOfRow(cursorPosition))
|
if (isAtEndOfRow(cursorPosition))
|
||||||
terminal.setCursorPosition(0, cursorPosition.getRow() + 1);
|
terminal.setCursorPosition(0, cursorPosition.getRow() + 1);
|
||||||
else
|
else
|
||||||
terminal.setCursorPosition(cursorPosition.withRelativeColumn(1));
|
terminal.setCursorPosition(cursorPosition.withRelativeColumn(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean isCursorAtEndOfRow(TerminalPosition cursorPosition) {
|
private synchronized boolean isAtEndOfRow(TerminalPosition cursorPosition) {
|
||||||
return cursorPosition.getColumn() == terminal.getTerminalSize().getColumns() - 1;
|
return cursorPosition.getColumn() == terminal.getTerminalSize().getColumns() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean isPossibleToMoveRight(TerminalPosition cursorPosition) {
|
private synchronized void doEnter() {
|
||||||
return distanceFromOrigin(cursorPosition) < currentLine.length();
|
terminal.putCharacter('\n');
|
||||||
|
inputLine += "\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
inputWriter.write(inputLine.getBytes());
|
||||||
|
inputWriter.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
inputLine = "";
|
||||||
|
origin = terminal.getCursorPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void doBackspace() {
|
private synchronized void doBackspace() {
|
||||||
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
||||||
|
|
||||||
if (isPossibleToMoveLeft(cursorPosition)) {
|
if (isPossibleToMoveLeft(cursorPosition)) {
|
||||||
String remaining = currentLine.substring(distanceFromOrigin(cursorPosition), currentLine.length());
|
String remaining = inputLine.substring(distanceFromOrigin(cursorPosition), inputLine.length());
|
||||||
currentLine = currentLine.substring(0, distanceFromOrigin(cursorPosition) - 1) + remaining;
|
inputLine = inputLine.substring(0, distanceFromOrigin(cursorPosition) - 1) + remaining;
|
||||||
|
|
||||||
if (cursorPosition.getColumn() == 0)
|
retractCursor(cursorPosition);
|
||||||
terminal.setCursorPosition(terminal.getTerminalSize().getColumns(), cursorPosition.getRow() - 1);
|
|
||||||
else
|
|
||||||
terminal.setCursorPosition(cursorPosition.withRelativeColumn(-1));
|
|
||||||
|
|
||||||
for (char c : remaining.toCharArray())
|
for (char c : remaining.toCharArray())
|
||||||
terminal.putCharacter(c);
|
terminal.putCharacter(c);
|
||||||
|
|
||||||
terminal.putCharacter(' ');
|
terminal.putCharacter(' ');
|
||||||
|
|
||||||
if (cursorPosition.getColumn() == 0)
|
retractCursor(cursorPosition);
|
||||||
terminal.setCursorPosition(terminal.getTerminalSize().getColumns(), cursorPosition.getRow() - 1);
|
|
||||||
else
|
|
||||||
terminal.setCursorPosition(cursorPosition.withRelativeColumn(-1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,8 +200,8 @@ public class LispTerminal {
|
||||||
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
TerminalPosition cursorPosition = terminal.getCursorPosition();
|
||||||
|
|
||||||
if (isPossibleToMoveRight(cursorPosition)) {
|
if (isPossibleToMoveRight(cursorPosition)) {
|
||||||
String remaining = currentLine.substring(distanceFromOrigin(cursorPosition) + 1, currentLine.length());
|
String remaining = inputLine.substring(distanceFromOrigin(cursorPosition) + 1, inputLine.length());
|
||||||
currentLine = currentLine.substring(0, distanceFromOrigin(cursorPosition)) + remaining;
|
inputLine = inputLine.substring(0, distanceFromOrigin(cursorPosition)) + remaining;
|
||||||
|
|
||||||
for (char c : remaining.toCharArray())
|
for (char c : remaining.toCharArray())
|
||||||
terminal.putCharacter(c);
|
terminal.putCharacter(c);
|
||||||
|
@ -243,8 +216,8 @@ public class LispTerminal {
|
||||||
|
|
||||||
if (isPossibleToMoveRight(cursorPosition)) {
|
if (isPossibleToMoveRight(cursorPosition)) {
|
||||||
String remaining = keyStroke.getCharacter()
|
String remaining = keyStroke.getCharacter()
|
||||||
+ currentLine.substring(distanceFromOrigin(cursorPosition), currentLine.length());
|
+ inputLine.substring(distanceFromOrigin(cursorPosition), inputLine.length());
|
||||||
currentLine = currentLine.substring(0, distanceFromOrigin(cursorPosition)) + remaining;
|
inputLine = inputLine.substring(0, distanceFromOrigin(cursorPosition)) + remaining;
|
||||||
|
|
||||||
for (char c : remaining.toCharArray())
|
for (char c : remaining.toCharArray())
|
||||||
terminal.putCharacter(c);
|
terminal.putCharacter(c);
|
||||||
|
@ -252,12 +225,61 @@ public class LispTerminal {
|
||||||
advanceCursor(cursorPosition);
|
advanceCursor(cursorPosition);
|
||||||
} else {
|
} else {
|
||||||
terminal.putCharacter(keyStroke.getCharacter());
|
terminal.putCharacter(keyStroke.getCharacter());
|
||||||
currentLine += keyStroke.getCharacter();
|
inputLine += keyStroke.getCharacter();
|
||||||
|
|
||||||
advanceCursor(cursorPosition);
|
advanceCursor(cursorPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void takeNap() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
isFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeOutput() {
|
||||||
|
try {
|
||||||
|
for (int c = outputReader.read(); c != EOF; c = outputReader.read())
|
||||||
|
processOutput((char) c);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.setCursorVisible(true);
|
||||||
|
terminal.flush();
|
||||||
|
terminal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void processOutput(char c) {
|
||||||
|
if (isEscape(c))
|
||||||
|
parseControlSequence();
|
||||||
|
else if (isEndOfSegment(c))
|
||||||
|
printSegment();
|
||||||
|
else
|
||||||
|
outputSegment += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void parseControlSequence() {}
|
||||||
|
|
||||||
|
private synchronized boolean isEndOfSegment(char c) {
|
||||||
|
return c == END_OF_SEGMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void printSegment() {
|
||||||
|
terminal.setCursorVisible(false);
|
||||||
|
|
||||||
|
for (char c : outputSegment.toCharArray())
|
||||||
|
terminal.putCharacter(c);
|
||||||
|
|
||||||
|
terminal.flush();
|
||||||
|
terminal.setCursorVisible(true);
|
||||||
|
outputSegment = "";
|
||||||
|
origin = terminal.getCursorPosition();
|
||||||
|
}
|
||||||
|
|
||||||
public void finish() {
|
public void finish() {
|
||||||
isFinished = true;
|
isFinished = true;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package terminal;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalSize;
|
||||||
|
import com.googlecode.lanterna.terminal.Terminal;
|
||||||
|
import com.googlecode.lanterna.terminal.virtual.VirtualTerminalListener;
|
||||||
|
|
||||||
|
public class FlushListener implements VirtualTerminalListener {
|
||||||
|
|
||||||
|
private int flushCount;
|
||||||
|
|
||||||
|
public FlushListener() {
|
||||||
|
this.flushCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFlushCount() {
|
||||||
|
return flushCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetFlushCount() {
|
||||||
|
flushCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResized(Terminal terminal, TerminalSize newSize) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onFlush() {
|
||||||
|
flushCount++;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBell() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose() {}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package terminal;
|
package terminal;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.*;
|
||||||
|
import static terminal.LispTerminal.END_OF_SEGMENT;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
|
@ -10,52 +11,100 @@ import com.googlecode.lanterna.*;
|
||||||
import com.googlecode.lanterna.input.*;
|
import com.googlecode.lanterna.input.*;
|
||||||
import com.googlecode.lanterna.terminal.virtual.*;
|
import com.googlecode.lanterna.terminal.virtual.*;
|
||||||
|
|
||||||
public class LispTerminalAttempt {
|
public class LispTerminalTest {
|
||||||
|
|
||||||
private PipedInputStream inputReader;
|
private PipedInputStream inputReader;
|
||||||
private PipedOutputStream inputWriter;
|
private PipedOutputStream inputWriter;
|
||||||
private PipedInputStream outputReader;
|
private PipedInputStream outputReader;
|
||||||
private PipedOutputStream outputWriter;
|
private PipedOutputStream outputWriter;
|
||||||
|
private FlushListener flushListener;
|
||||||
private VirtualTerminal virtualTerminal;
|
private VirtualTerminal virtualTerminal;
|
||||||
private LispTerminal lispTerminal;
|
private LispTerminal lispTerminal;
|
||||||
|
|
||||||
private void pressKey(KeyType keyType) {
|
private void pressKey(KeyType keyType) {
|
||||||
virtualTerminal.addInput(new KeyStroke(keyType));
|
virtualTerminal.addInput(new KeyStroke(keyType));
|
||||||
sleep();
|
waitForFlushes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pressControlKey(KeyType keyType) {
|
||||||
|
virtualTerminal.addInput(new KeyStroke(keyType, true, false));
|
||||||
|
waitForFlushes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterCharacter(char character) {
|
private void enterCharacter(char character) {
|
||||||
virtualTerminal.addInput(new KeyStroke(character, false, false));
|
virtualTerminal.addInput(new KeyStroke(character, false, false));
|
||||||
sleep();
|
waitForFlushes(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enterControlCharacter(char character) {
|
||||||
|
virtualTerminal.addInput(new KeyStroke(character, true, false));
|
||||||
|
waitForFlushes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterCharacters(String characters) {
|
private void enterCharacters(String characters) {
|
||||||
for (char c : characters.toCharArray())
|
for (char c : characters.toCharArray())
|
||||||
virtualTerminal.addInput(new KeyStroke(c, false, false));
|
virtualTerminal.addInput(new KeyStroke(c, false, false));
|
||||||
|
|
||||||
sleep();
|
waitForFlushes(characters.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sleep() {
|
private void produceOutput(String output) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(15);
|
for (char c : output.toCharArray())
|
||||||
|
outputWriter.write(c);
|
||||||
|
|
||||||
|
outputWriter.write(END_OF_SEGMENT);
|
||||||
|
outputWriter.flush();
|
||||||
|
waitForFlushes(1);
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForFlushes(int flushCount) {
|
||||||
|
try {
|
||||||
|
synchronized (flushListener) {
|
||||||
|
while (flushListener.getFlushCount() < flushCount)
|
||||||
|
flushListener.wait();
|
||||||
|
|
||||||
|
flushListener.resetFlushCount();
|
||||||
|
}
|
||||||
} catch (InterruptedException e) {}
|
} catch (InterruptedException e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCursorPosition(int column, int row) {
|
|
||||||
assertTrue(virtualTerminal.getCursorBufferPosition().getColumn() == column);
|
|
||||||
assertTrue(virtualTerminal.getCursorBufferPosition().getRow() == row);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertCharacterAtPosition(char character, int column, int row) {
|
|
||||||
TerminalPosition position = new TerminalPosition(column, row);
|
|
||||||
assertTrue(virtualTerminal.getBufferCharacter(position).getCharacter() == character);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setColumns(int columns) {
|
private void setColumns(int columns) {
|
||||||
virtualTerminal.setTerminalSize(new TerminalSize(columns, virtualTerminal.getTerminalSize().getRows()));
|
virtualTerminal.setTerminalSize(new TerminalSize(columns, virtualTerminal.getTerminalSize().getRows()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertCursorPosition(int column, int row) {
|
||||||
|
assertEquals(column, virtualTerminal.getCursorBufferPosition().getColumn());
|
||||||
|
assertEquals(row, virtualTerminal.getCursorBufferPosition().getRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCharacterAtPosition(char character, int column, int row) {
|
||||||
|
TerminalPosition position = new TerminalPosition(column, row);
|
||||||
|
assertEquals(character, virtualTerminal.getBufferCharacter(position).getCharacter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInputWritten(String expected) {
|
||||||
|
String actual = "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
inputWriter.close();
|
||||||
|
|
||||||
|
for (int c = inputReader.read(); c != -1; c = inputReader.read()) {
|
||||||
|
actual += (char) c;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {}
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInputStreamClosed() {
|
||||||
|
try {
|
||||||
|
inputWriter.write(0);
|
||||||
|
fail("input stream not closed");
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws IOException {
|
public void setUp() throws IOException {
|
||||||
inputReader = new PipedInputStream();
|
inputReader = new PipedInputStream();
|
||||||
|
@ -63,6 +112,8 @@ public class LispTerminalAttempt {
|
||||||
outputReader = new PipedInputStream();
|
outputReader = new PipedInputStream();
|
||||||
outputWriter = new PipedOutputStream(outputReader);
|
outputWriter = new PipedOutputStream(outputReader);
|
||||||
virtualTerminal = new DefaultVirtualTerminal();
|
virtualTerminal = new DefaultVirtualTerminal();
|
||||||
|
flushListener = new FlushListener();
|
||||||
|
virtualTerminal.addVirtualTerminalListener(flushListener);
|
||||||
lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader);
|
lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader);
|
||||||
lispTerminal.run();
|
lispTerminal.run();
|
||||||
}
|
}
|
||||||
|
@ -222,4 +273,135 @@ public class LispTerminalAttempt {
|
||||||
assertCharacterAtPosition('5', 3, 0);
|
assertCharacterAtPosition('5', 3, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteDoesNothingAtOrigin() {
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteDoesNothingAtEndOfInput() {
|
||||||
|
enterCharacters("del");
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
assertCursorPosition(3, 0);
|
||||||
|
assertCharacterAtPosition('d', 0, 0);
|
||||||
|
assertCharacterAtPosition('e', 1, 0);
|
||||||
|
assertCharacterAtPosition('l', 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteWorksAtStartOfInput() {
|
||||||
|
enterCharacters("del");
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertCharacterAtPosition(' ', 0, 0);
|
||||||
|
assertCharacterAtPosition(' ', 1, 0);
|
||||||
|
assertCharacterAtPosition(' ', 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteWorksAcrossRow() {
|
||||||
|
setColumns(4);
|
||||||
|
enterCharacters("delete");
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.Delete);
|
||||||
|
assertCursorPosition(1, 0);
|
||||||
|
assertCharacterAtPosition('d', 0, 0);
|
||||||
|
assertCharacterAtPosition('l', 1, 0);
|
||||||
|
assertCharacterAtPosition('e', 2, 0);
|
||||||
|
assertCharacterAtPosition('t', 3, 0);
|
||||||
|
assertCharacterAtPosition('e', 0, 1);
|
||||||
|
assertCharacterAtPosition(' ', 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enterMovesToNextLine() {
|
||||||
|
pressKey(KeyType.Enter);
|
||||||
|
assertCursorPosition(0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enterWritesLineToPipedStream() {
|
||||||
|
enterCharacters("enter");
|
||||||
|
pressKey(KeyType.Enter);
|
||||||
|
assertInputWritten("enter\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() {
|
||||||
|
enterCharacters("enter");
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.Enter);
|
||||||
|
assertInputWritten("enter\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void controlDWorks() {
|
||||||
|
enterCharacters("control-d");
|
||||||
|
enterControlCharacter('d');
|
||||||
|
assertInputStreamClosed();
|
||||||
|
assertInputWritten("control-d\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void controlDWorksInMiddleOfInput() {
|
||||||
|
enterCharacters("control-d");
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
pressKey(KeyType.ArrowLeft);
|
||||||
|
enterControlCharacter('d');
|
||||||
|
assertInputStreamClosed();
|
||||||
|
assertInputWritten("control-d\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void escapeDoesNothing() {
|
||||||
|
pressKey(KeyType.Escape);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertInputWritten("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void controlQDoesNothing() {
|
||||||
|
enterControlCharacter('q');
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertInputWritten("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void controlEnterDoesNothing() {
|
||||||
|
pressControlKey(KeyType.Enter);
|
||||||
|
assertCursorPosition(0, 0);
|
||||||
|
assertInputWritten("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void outputIsWritten() {
|
||||||
|
produceOutput("output");
|
||||||
|
assertCharacterAtPosition('o', 0, 0);
|
||||||
|
assertCharacterAtPosition('u', 1, 0);
|
||||||
|
assertCharacterAtPosition('t', 2, 0);
|
||||||
|
assertCharacterAtPosition('p', 3, 0);
|
||||||
|
assertCharacterAtPosition('u', 4, 0);
|
||||||
|
assertCharacterAtPosition('t', 5, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void endOfSegmentCharacterIsNotPrinted() {
|
||||||
|
produceOutput("> " + END_OF_SEGMENT);
|
||||||
|
assertCursorPosition(2, 0);
|
||||||
|
assertCharacterAtPosition('>', 0, 0);
|
||||||
|
assertCharacterAtPosition(' ', 1, 0);
|
||||||
|
assertCharacterAtPosition(' ', 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue