diff --git a/src/terminal/ControlSequenceHandler.java b/src/terminal/ControlSequenceHandler.java index 341d7de..8e03194 100644 --- a/src/terminal/ControlSequenceHandler.java +++ b/src/terminal/ControlSequenceHandler.java @@ -13,7 +13,6 @@ class ControlSequenceHandler { private Command command; public ControlSequenceHandler() { - // TODO Auto-generated constructor stub this.inControlSequence = false; this.code = 0; this.command = SGR; diff --git a/src/terminal/LispTerminal.java b/src/terminal/LispTerminal.java index 38c9289..33affc1 100644 --- a/src/terminal/LispTerminal.java +++ b/src/terminal/LispTerminal.java @@ -20,7 +20,7 @@ public class LispTerminal { private SafePipedOutputStream inputWriter; private SafePipedInputStream outputReader; private ExecutorService executorService; - private ControlSequenceHandler controlSequenceHandler; + // private ControlSequenceHandler controlSequenceHandler; private TerminalSize terminalSize; private int originColumn; private int originRow; @@ -29,12 +29,11 @@ public class LispTerminal { private boolean isFinished; public LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) { - // FIXME - add resize handler this.terminal = terminal; this.inputWriter = new SafePipedOutputStream(inputWriter); this.outputReader = new SafePipedInputStream(outputReader); this.executorService = Executors.newFixedThreadPool(2); - this.controlSequenceHandler = new ControlSequenceHandler(); + // this.controlSequenceHandler = new ControlSequenceHandler(); this.terminalSize = terminal.getTerminalSize(); this.originColumn = terminal.getCursorPosition().getColumn(); this.originRow = terminal.getCursorPosition().getRow(); @@ -100,22 +99,10 @@ public class LispTerminal { } private synchronized void doKey(KeyStroke keyStroke) { - KeyType keyType = keyStroke.getKeyType(); - if (keyStroke.isCtrlDown()) doControlKey(keyStroke); - else if (keyType == KeyType.ArrowLeft) - moveCursorLeft(); - else if (keyType == KeyType.ArrowRight) - moveCursorRight(); - else if (keyType == KeyType.Enter) - doEnter(); - else if (keyType == KeyType.Backspace) - doBackspace(); - else if (keyType == KeyType.Delete) - doDelete(); - else if (keyType == KeyType.Character) - doCharacter(keyStroke); + else + doNormalKey(keyStroke); } private synchronized void doControlKey(KeyStroke keyStroke) { @@ -135,6 +122,23 @@ public class LispTerminal { finish(); } + private void doNormalKey(KeyStroke keyStroke) { + KeyType keyType = keyStroke.getKeyType(); + + if (keyType == KeyType.ArrowLeft) + moveCursorLeft(); + else if (keyType == KeyType.ArrowRight) + moveCursorRight(); + else if (keyType == KeyType.Enter) + doEnter(); + else if (keyType == KeyType.Backspace) + doBackspace(); + else if (keyType == KeyType.Delete) + doDelete(); + else if (keyType == KeyType.Character) + doCharacter(keyStroke.getCharacter()); + } + private synchronized void moveCursorLeft() { TerminalPosition cursorPosition = terminal.getCursorPosition(); @@ -154,15 +158,6 @@ public class LispTerminal { return columnDifference + (totalColumns * rowDifference); } - 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); - } - private synchronized void retractCursor(TerminalPosition cursorPosition) { TerminalPosition newPosition = cursorPosition.withRelativeColumn(-1); @@ -189,31 +184,31 @@ public class LispTerminal { } private synchronized void advanceCursor(TerminalPosition cursorPosition) { - if (isAtEndOfRow(cursorPosition)) + if (isEndOfRow(cursorPosition)) moveCursorToNextRow(cursorPosition); else terminal.setCursorPosition(cursorPosition.withRelativeColumn(1)); } - private synchronized boolean isAtEndOfRow(TerminalPosition cursorPosition) { + private synchronized boolean isEndOfRow(TerminalPosition cursorPosition) { return cursorPosition.getColumn() >= terminalSize.getColumns() - 1; } - private void moveCursorToNextRow(TerminalPosition cursorPosition) { - if (isEndOfBuffer()) - createNewRowForCursor(); + private synchronized void moveCursorToNextRow(TerminalPosition cursorPosition) { + if (isEndOfBuffer(cursorPosition)) + createNewRowForCursor(cursorPosition); else terminal.setCursorPosition(cursorPosition.withColumn(0).withRelativeRow(1)); } - private boolean isEndOfBuffer() { - return terminal.getCursorPosition().getRow() == terminalSize.getRows() - 1; + private synchronized boolean isEndOfBuffer(TerminalPosition cursorPosition) { + return cursorPosition.getRow() == terminalSize.getRows() - 1; } - private void createNewRowForCursor() { - TerminalPosition originalPosition = terminal.getCursorPosition(); + private synchronized void createNewRowForCursor(TerminalPosition cursorPosition) { + terminal.setCursorPosition(cursorPosition); terminal.putCharacter('\n'); - terminal.setCursorPosition(originalPosition.withColumn(0)); + terminal.setCursorPosition(cursorPosition.withColumn(0)); --originRow; } @@ -231,7 +226,16 @@ public class LispTerminal { terminal.setCursorPosition(getLeadingEdge()); } - private void updateOrigin() { + 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); + } + + private synchronized void updateOrigin() { TerminalPosition cursorPosition = terminal.getCursorPosition(); originColumn = cursorPosition.getColumn(); originRow = cursorPosition.getRow(); @@ -270,28 +274,53 @@ public class LispTerminal { } } - private synchronized void doCharacter(KeyStroke keyStroke) { + private synchronized void doCharacter(Character character) { TerminalPosition cursorPosition = terminal.getCursorPosition(); - if (isPossibleToMoveRight(cursorPosition)) { - String remaining = keyStroke.getCharacter() - + inputLine.substring(getDistanceFromOrigin(cursorPosition), inputLine.length()); - inputLine = inputLine.substring(0, getDistanceFromOrigin(cursorPosition)) + remaining; + if (isPossibleToMoveRight(cursorPosition)) + insertCharacter(character, cursorPosition); + else + appendCharacter(character, cursorPosition); + } - // FIXME - must have a way to push remainder on to a new line at the end of the buffer + 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); - } else { - terminal.putCharacter(keyStroke.getCharacter()); - inputLine += keyStroke.getCharacter(); - advanceCursor(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 appendCharacter(Character character, TerminalPosition cursorPosition) { + terminal.putCharacter(character); + inputLine += character; + advanceCursor(cursorPosition); + } + private void takeNap() { try { Thread.sleep(1); @@ -326,6 +355,13 @@ public class LispTerminal { private synchronized void printSegment() { terminal.setCursorVisible(false); + printSegmentCharacters(); + terminal.setCursorVisible(true); + outputSegment = ""; + updateOrigin(); + } + + private synchronized void printSegmentCharacters() { moveCursorToEndOfInput(); for (char c : outputSegment.toCharArray()) @@ -333,15 +369,12 @@ public class LispTerminal { moveCursorToNextRowIfNecessary(); terminal.flush(); - terminal.setCursorVisible(true); - outputSegment = ""; - updateOrigin(); } private synchronized void moveCursorToNextRowIfNecessary() { TerminalPosition cursorPosition = terminal.getCursorPosition(); - if (isAtEndOfRow(cursorPosition)) + if (isEndOfRow(cursorPosition)) moveCursorToNextRow(cursorPosition); } diff --git a/test/terminal/LispTerminalTest.java b/test/terminal/LispTerminalTest.java index 68d944a..53e6f52 100644 --- a/test/terminal/LispTerminalTest.java +++ b/test/terminal/LispTerminalTest.java @@ -78,11 +78,13 @@ public class LispTerminalTest { } private void setColumns(int columns) { - virtualTerminal.setTerminalSize(new TerminalSize(columns, virtualTerminal.getTerminalSize().getRows())); + int rows = virtualTerminal.getTerminalSize().getRows(); + virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); } private void setRows(int rows) { - virtualTerminal.setTerminalSize(new TerminalSize(virtualTerminal.getTerminalSize().getColumns(), rows)); + int columns = virtualTerminal.getTerminalSize().getColumns(); + virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); } private void assertCursorPosition(int column, int row) { @@ -92,7 +94,15 @@ public class LispTerminalTest { private void assertCharacterAtPosition(char character, int column, int row) { TerminalPosition position = new TerminalPosition(column, row); - assertEquals(String.valueOf(character), String.valueOf(virtualTerminal.getCharacter(position).getCharacter())); + String expected = String.valueOf(character); + String actual = String.valueOf(virtualTerminal.getCharacter(position).getCharacter()); + assertEquals(expected, actual); + } + + private void assertCharacterPositions(char[][] positions) { + for (int row = 0; row < positions.length; row++) + for (int column = 0; column < positions[row].length; column++) + assertCharacterAtPosition(positions[row][column], column, row); } private void assertInputWritten(String expected) { @@ -101,9 +111,8 @@ public class LispTerminalTest { try { inputWriter.close(); - for (int c = inputReader.read(); c != -1; c = inputReader.read()) { + for (int c = inputReader.read(); c != -1; c = inputReader.read()) actual += (char) c; - } } catch (IOException ignored) {} assertEquals(expected, actual); @@ -116,12 +125,6 @@ public class LispTerminalTest { } catch (IOException ignored) {} } - private void assertCharacterPositions(char[][] positions) { - for (int row = 0; row < positions.length; row++) - for (int column = 0; column < positions[row].length; column++) - assertCharacterAtPosition(positions[row][column], column, row); - } - @Before public void setUp() throws IOException { inputReader = new PipedInputStream(); @@ -211,11 +214,7 @@ public class LispTerminalTest { enterCharacters("abcd"); pressKeyTimes(KeyType.ArrowLeft, 2); enterCharacter('x'); - assertCharacterAtPosition('a', 0, 0); - assertCharacterAtPosition('b', 1, 0); - assertCharacterAtPosition('x', 2, 0); - assertCharacterAtPosition('c', 3, 0); - assertCharacterAtPosition('d', 4, 0); + assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } }); } @Test @@ -224,11 +223,7 @@ public class LispTerminalTest { enterCharacters("abcd"); pressKeyTimes(KeyType.ArrowLeft, 2); enterCharacter('x'); - assertCharacterAtPosition('a', 0, 0); - assertCharacterAtPosition('b', 1, 0); - assertCharacterAtPosition('x', 2, 0); - assertCharacterAtPosition('c', 3, 0); - assertCharacterAtPosition('d', 0, 1); + assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } }); } @Test @@ -242,12 +237,7 @@ public class LispTerminalTest { enterCharacters("12345"); pressKeyTimes(KeyType.Backspace, 2); assertCursorPosition(3, 0); - assertCharacterAtPosition('1', 0, 0); - assertCharacterAtPosition('2', 1, 0); - assertCharacterAtPosition('3', 2, 0); - assertCharacterAtPosition(' ', 3, 0); - assertCharacterAtPosition(' ', 4, 0); - assertCharacterAtPosition(' ', 5, 0); + assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } }); } @Test @@ -256,13 +246,7 @@ public class LispTerminalTest { enterCharacters("1234567"); pressKeyTimes(KeyType.Backspace, 5); assertCursorPosition(2, 0); - assertCharacterAtPosition('1', 0, 0); - assertCharacterAtPosition('2', 1, 0); - assertCharacterAtPosition(' ', 2, 0); - assertCharacterAtPosition(' ', 3, 0); - assertCharacterAtPosition(' ', 0, 1); - assertCharacterAtPosition(' ', 1, 1); - assertCharacterAtPosition(' ', 2, 1); + assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } }); } @Test @@ -271,10 +255,7 @@ public class LispTerminalTest { pressKeyTimes(KeyType.ArrowLeft, 2); pressKey(KeyType.Backspace); assertCursorPosition(2, 0); - assertCharacterAtPosition('1', 0, 0); - assertCharacterAtPosition('2', 1, 0); - assertCharacterAtPosition('4', 2, 0); - assertCharacterAtPosition('5', 3, 0); + assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } }); } @Test @@ -288,9 +269,7 @@ public class LispTerminalTest { enterCharacters("del"); pressKey(KeyType.Delete); assertCursorPosition(3, 0); - assertCharacterAtPosition('d', 0, 0); - assertCharacterAtPosition('e', 1, 0); - assertCharacterAtPosition('l', 2, 0); + assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } }); } @Test @@ -299,9 +278,7 @@ public class LispTerminalTest { pressKeyTimes(KeyType.ArrowLeft, 3); pressKeyTimes(KeyType.Delete, 3); assertCursorPosition(0, 0); - assertCharacterAtPosition(' ', 0, 0); - assertCharacterAtPosition(' ', 1, 0); - assertCharacterAtPosition(' ', 2, 0); + assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } }); } @Test @@ -311,12 +288,7 @@ public class LispTerminalTest { pressKeyTimes(KeyType.ArrowLeft, 5); 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); + assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } }); } @Test @@ -340,6 +312,32 @@ public class LispTerminalTest { assertInputWritten("enter\n"); } + @Test + public void enterAfterInsertedText_WritesLineToPipedStream() { + enterCharacters("enter"); + pressKeyTimes(KeyType.ArrowLeft, 2); + enterCharacters("||"); + pressKey(KeyType.Enter); + assertInputWritten("ent||er\n"); + } + + @Test + public void enterAfterBackspace_WritesLineToPipedStream() { + enterCharacters("enter"); + pressKeyTimes(KeyType.Backspace, 2); + pressKey(KeyType.Enter); + assertInputWritten("ent\n"); + } + + @Test + public void enterAfterDelete_WritesLineToPipedStream() { + enterCharacters("enter"); + pressKeyTimes(KeyType.ArrowLeft, 2); + pressKeyTimes(KeyType.Delete, 2); + pressKey(KeyType.Enter); + assertInputWritten("ent\n"); + } + @Test public void controlDWorks() { enterCharacters("control-d"); @@ -401,16 +399,31 @@ public class LispTerminalTest { assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); } -// @Test -// public void insertingTextPushesInputPastEndOfBuffer() { -// setColumns(3); -// setRows(3); -// enterCharacters("00011122"); -// pressKeyTimes(KeyType.ArrowLeft, 4); -// enterCharacters("zz"); -// assertCursorPosition(0, 1); -// assertCharacterPositions(new char[][] { { '1', 'z', 'z' }, { '1', '1', '2' }, { '2', ' ', ' ' } }); -// } + @Test + public void insertingTextPushesInputPastEndOfBuffer() { + setColumns(3); + setRows(4); + pressKey(KeyType.Enter); + enterCharacters("00011122"); + pressKeyTimes(KeyType.ArrowLeft, 4); + assertCursorPosition(1, 2); + enterCharacters("zz"); + assertCursorPosition(0, 2); + assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', 'z', 'z' }, { '1', '1', '2' }, + { '2', ' ', ' ' } }); + } + + @Test + public void insertingTextDoesNothingWhenBufferFilled() { + setColumns(3); + setRows(3); + enterCharacters("00011122"); + pressKeyTimes(KeyType.ArrowLeft, 4); + assertCursorPosition(1, 1); + enterCharacters("zz"); + assertCursorPosition(1, 1); + assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); + } @Test public void printedOutputToEndOfRow_MovesCursorToNextRow() {