diff --git a/src/main/LispMain.java b/src/main/LispMain.java index 3755dcd..1caafbb 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -41,7 +41,7 @@ public class LispMain { private void run(String[] args) { if (args.length == 0) - lispTerminal.run(); + lispTerminal.start(); LispInterpreter interpreter = buildInterpreter(args); interpreter.interpret(); @@ -85,7 +85,7 @@ public class LispMain { private void shutdown() { try { - lispTerminal.finish(); + lispTerminal.stop(); outputWriter.close(); } catch (IOException e) { // TODO Auto-generated catch block diff --git a/src/terminal/LispTerminal.java b/src/terminal/LispTerminal.java index 33affc1..9880674 100644 --- a/src/terminal/LispTerminal.java +++ b/src/terminal/LispTerminal.java @@ -1,5 +1,6 @@ package terminal; +import static com.googlecode.lanterna.input.KeyType.*; import static terminal.ControlSequenceHandler.isEscape; import static util.Characters.EOF; @@ -54,17 +55,17 @@ public class LispTerminal { }); } - private void resize() { + private synchronized void resize() { terminalSize = terminal.getTerminalSize(); } - public void run() { + public void start() { executorService.execute(this::readInput); executorService.execute(this::writeOutput); executorService.shutdown(); } - public void readInput() { + private void readInput() { while (!isFinished) processNextKey(); } @@ -83,11 +84,10 @@ public class LispTerminal { try { keyStroke = terminal.pollInput(); - } catch (IllegalStateException e) { - // Issue #299 + } catch (IllegalStateException e) { // issue #299 + moveCursorToEndOfInput(); terminal.putCharacter('\n'); - terminal.close(); - System.exit(0); + stop(); } return keyStroke; @@ -108,7 +108,7 @@ public class LispTerminal { private synchronized void doControlKey(KeyStroke keyStroke) { KeyType keyType = keyStroke.getKeyType(); - if (keyType == KeyType.Character) + if (keyType == Character) doControlCharacter(keyStroke); } @@ -119,99 +119,26 @@ public class LispTerminal { private synchronized void doControlD() { doEnter(); - finish(); + stop(); } - private void doNormalKey(KeyStroke keyStroke) { + private synchronized void doNormalKey(KeyStroke keyStroke) { KeyType keyType = keyStroke.getKeyType(); - if (keyType == KeyType.ArrowLeft) - moveCursorLeft(); - else if (keyType == KeyType.ArrowRight) - moveCursorRight(); - else if (keyType == KeyType.Enter) + if (keyType == Enter) doEnter(); - else if (keyType == KeyType.Backspace) + else if (keyType == ArrowLeft) + doLeftArrow(); + else if (keyType == ArrowRight) + doRightArrow(); + else if (keyType == Backspace) doBackspace(); - else if (keyType == KeyType.Delete) + else if (keyType == Delete) doDelete(); - else if (keyType == KeyType.Character) + else if (keyType == Character) doCharacter(keyStroke.getCharacter()); } - private synchronized void moveCursorLeft() { - TerminalPosition cursorPosition = terminal.getCursorPosition(); - - if (isPossibleToMoveLeft(cursorPosition)) - retractCursor(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 retractCursor(TerminalPosition cursorPosition) { - TerminalPosition newPosition = cursorPosition.withRelativeColumn(-1); - - if (isAtStartOfRow(cursorPosition)) - newPosition = cursorPosition.withColumn(terminalSize.getColumns()).withRelativeRow(-1); - - terminal.setCursorPosition(newPosition); - - } - - private boolean isAtStartOfRow(TerminalPosition cursorPosition) { - return cursorPosition.getColumn() == 0; - } - - private synchronized void moveCursorRight() { - TerminalPosition cursorPosition = terminal.getCursorPosition(); - - if (isPossibleToMoveRight(cursorPosition)) - advanceCursor(cursorPosition); - } - - private synchronized boolean isPossibleToMoveRight(TerminalPosition cursorPosition) { - return getDistanceFromOrigin(cursorPosition) < inputLine.length(); - } - - private synchronized void advanceCursor(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 doEnter() { moveCursorToEndOfInput(); terminal.putCharacter('\n'); @@ -241,71 +168,122 @@ public class LispTerminal { originRow = cursorPosition.getRow(); } + 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)) { - String remaining = inputLine.substring(getDistanceFromOrigin(cursorPosition), inputLine.length()); - inputLine = inputLine.substring(0, getDistanceFromOrigin(cursorPosition) - 1) + remaining; + if (isPossibleToMoveLeft(cursorPosition)) + deletePreviousCharacter(cursorPosition); + } - retractCursor(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); + putString(remaining + " "); + moveCursorLeft(cursorPosition); + } - for (char c : remaining.toCharArray()) - terminal.putCharacter(c); - - terminal.putCharacter(' '); - - retractCursor(cursorPosition); - } + private synchronized void putString(String characters) { + for (char c : characters.toCharArray()) + terminal.putCharacter(c); } private synchronized void doDelete() { TerminalPosition cursorPosition = terminal.getCursorPosition(); - if (isPossibleToMoveRight(cursorPosition)) { - String remaining = inputLine.substring(getDistanceFromOrigin(cursorPosition) + 1, inputLine.length()); - inputLine = inputLine.substring(0, getDistanceFromOrigin(cursorPosition)) + remaining; + if (isPossibleToMoveRight(cursorPosition)) + deleteCharacterAtPosition(cursorPosition); + } - for (char c : remaining.toCharArray()) - terminal.putCharacter(c); - - terminal.putCharacter(' '); - terminal.setCursorPosition(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; + putString(remaining + " "); + terminal.setCursorPosition(cursorPosition); } private synchronized void doCharacter(Character character) { TerminalPosition cursorPosition = terminal.getCursorPosition(); - if (isPossibleToMoveRight(cursorPosition)) - insertCharacter(character, cursorPosition); - else - appendCharacter(character, cursorPosition); - } - - private synchronized void insertCharacter(Character character, TerminalPosition cursorPosition) { - if (!isBufferFilled()) { - int oldOriginRow = originRow; - advanceCursor(getLeadingEdge()); - - if (originRow != oldOriginRow) { - terminal.setCursorPosition(new TerminalPosition(originColumn, originRow)); - for (char c : inputLine.toCharArray()) - terminal.putCharacter(c); - cursorPosition = cursorPosition.withRelativeRow(-1); - } - - terminal.setCursorPosition(cursorPosition); - - int distanceFromOrigin = getDistanceFromOrigin(cursorPosition); - String remaining = character + inputLine.substring(distanceFromOrigin, inputLine.length()); - inputLine = inputLine.substring(0, distanceFromOrigin) + remaining; - - for (char c : remaining.toCharArray()) - terminal.putCharacter(c); - - advanceCursor(cursorPosition); - } + if (!isBufferFilled()) + if (isPossibleToMoveRight(cursorPosition)) + insertCharacter(character, cursorPosition); + else + appendCharacter(character, cursorPosition); } private synchronized boolean isBufferFilled() { @@ -315,10 +293,38 @@ public class LispTerminal { 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; + putString(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)); + putString(inputLine); + + return cursorPosition.withRelativeRow(-1); + } + private synchronized void appendCharacter(Character character, TerminalPosition cursorPosition) { terminal.putCharacter(character); inputLine += character; - advanceCursor(cursorPosition); + moveCursorRight(cursorPosition); } private void takeNap() { @@ -329,7 +335,7 @@ public class LispTerminal { } } - public void writeOutput() { + private void writeOutput() { for (int c = outputReader.read(); c != EOF; c = outputReader.read()) processOutput((char) c); @@ -342,7 +348,7 @@ public class LispTerminal { if (isEscape(c)) parseControlSequence(); else if (isEndOfSegment(c)) - printSegment(); + writeSegment(); else outputSegment += c; } @@ -353,7 +359,7 @@ public class LispTerminal { return c == END_OF_SEGMENT; } - private synchronized void printSegment() { + private synchronized void writeSegment() { terminal.setCursorVisible(false); printSegmentCharacters(); terminal.setCursorVisible(true); @@ -363,10 +369,7 @@ public class LispTerminal { private synchronized void printSegmentCharacters() { moveCursorToEndOfInput(); - - for (char c : outputSegment.toCharArray()) - terminal.putCharacter(c); - + putString(outputSegment); moveCursorToNextRowIfNecessary(); terminal.flush(); } @@ -378,7 +381,7 @@ public class LispTerminal { moveCursorToNextRow(cursorPosition); } - public void finish() { + public void stop() { isFinished = true; inputWriter.close(); } diff --git a/test/terminal/LispTerminalTest.java b/test/terminal/LispTerminalTest.java index 53e6f52..b996945 100644 --- a/test/terminal/LispTerminalTest.java +++ b/test/terminal/LispTerminalTest.java @@ -135,12 +135,12 @@ public class LispTerminalTest { flushListener = new FlushListener(); virtualTerminal.addVirtualTerminalListener(flushListener); lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader); - lispTerminal.run(); + lispTerminal.start(); } @After public void tearDown() throws IOException { - lispTerminal.finish(); + lispTerminal.stop(); outputWriter.close(); } @@ -425,6 +425,15 @@ public class LispTerminalTest { assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); } + @Test + public void appendingTextDoesNothingWhenBufferFilled() { + setColumns(3); + setRows(3); + enterCharacters("000111222333444"); + assertCursorPosition(2, 2); + assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); + } + @Test public void printedOutputToEndOfRow_MovesCursorToNextRow() { setColumns(3);