Fix text insertion at the end of the buffer

This commit is contained in:
Mike Cifelli 2017-03-20 16:20:28 -04:00
parent 38710e21bf
commit fc96894d14
3 changed files with 158 additions and 113 deletions

View File

@ -13,7 +13,6 @@ class ControlSequenceHandler {
private Command command; private Command command;
public ControlSequenceHandler() { public ControlSequenceHandler() {
// TODO Auto-generated constructor stub
this.inControlSequence = false; this.inControlSequence = false;
this.code = 0; this.code = 0;
this.command = SGR; this.command = SGR;

View File

@ -20,7 +20,7 @@ public class LispTerminal {
private SafePipedOutputStream inputWriter; private SafePipedOutputStream inputWriter;
private SafePipedInputStream outputReader; private SafePipedInputStream outputReader;
private ExecutorService executorService; private ExecutorService executorService;
private ControlSequenceHandler controlSequenceHandler; // private ControlSequenceHandler controlSequenceHandler;
private TerminalSize terminalSize; private TerminalSize terminalSize;
private int originColumn; private int originColumn;
private int originRow; private int originRow;
@ -29,12 +29,11 @@ public class LispTerminal {
private boolean isFinished; private boolean isFinished;
public LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) { public LispTerminal(IOSafeTerminal terminal, PipedOutputStream inputWriter, PipedInputStream outputReader) {
// FIXME - add resize handler
this.terminal = terminal; this.terminal = terminal;
this.inputWriter = new SafePipedOutputStream(inputWriter); this.inputWriter = new SafePipedOutputStream(inputWriter);
this.outputReader = new SafePipedInputStream(outputReader); this.outputReader = new SafePipedInputStream(outputReader);
this.executorService = Executors.newFixedThreadPool(2); this.executorService = Executors.newFixedThreadPool(2);
this.controlSequenceHandler = new ControlSequenceHandler(); // this.controlSequenceHandler = new ControlSequenceHandler();
this.terminalSize = terminal.getTerminalSize(); this.terminalSize = terminal.getTerminalSize();
this.originColumn = terminal.getCursorPosition().getColumn(); this.originColumn = terminal.getCursorPosition().getColumn();
this.originRow = terminal.getCursorPosition().getRow(); this.originRow = terminal.getCursorPosition().getRow();
@ -100,22 +99,10 @@ public class LispTerminal {
} }
private synchronized void doKey(KeyStroke keyStroke) { private synchronized void doKey(KeyStroke keyStroke) {
KeyType keyType = keyStroke.getKeyType();
if (keyStroke.isCtrlDown()) if (keyStroke.isCtrlDown())
doControlKey(keyStroke); doControlKey(keyStroke);
else if (keyType == KeyType.ArrowLeft) else
moveCursorLeft(); doNormalKey(keyStroke);
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);
} }
private synchronized void doControlKey(KeyStroke keyStroke) { private synchronized void doControlKey(KeyStroke keyStroke) {
@ -135,6 +122,23 @@ public class LispTerminal {
finish(); 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() { private synchronized void moveCursorLeft() {
TerminalPosition cursorPosition = terminal.getCursorPosition(); TerminalPosition cursorPosition = terminal.getCursorPosition();
@ -154,15 +158,6 @@ public class LispTerminal {
return columnDifference + (totalColumns * rowDifference); 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) { private synchronized void retractCursor(TerminalPosition cursorPosition) {
TerminalPosition newPosition = cursorPosition.withRelativeColumn(-1); TerminalPosition newPosition = cursorPosition.withRelativeColumn(-1);
@ -189,31 +184,31 @@ public class LispTerminal {
} }
private synchronized void advanceCursor(TerminalPosition cursorPosition) { private synchronized void advanceCursor(TerminalPosition cursorPosition) {
if (isAtEndOfRow(cursorPosition)) if (isEndOfRow(cursorPosition))
moveCursorToNextRow(cursorPosition); moveCursorToNextRow(cursorPosition);
else else
terminal.setCursorPosition(cursorPosition.withRelativeColumn(1)); terminal.setCursorPosition(cursorPosition.withRelativeColumn(1));
} }
private synchronized boolean isAtEndOfRow(TerminalPosition cursorPosition) { private synchronized boolean isEndOfRow(TerminalPosition cursorPosition) {
return cursorPosition.getColumn() >= terminalSize.getColumns() - 1; return cursorPosition.getColumn() >= terminalSize.getColumns() - 1;
} }
private void moveCursorToNextRow(TerminalPosition cursorPosition) { private synchronized void moveCursorToNextRow(TerminalPosition cursorPosition) {
if (isEndOfBuffer()) if (isEndOfBuffer(cursorPosition))
createNewRowForCursor(); createNewRowForCursor(cursorPosition);
else else
terminal.setCursorPosition(cursorPosition.withColumn(0).withRelativeRow(1)); terminal.setCursorPosition(cursorPosition.withColumn(0).withRelativeRow(1));
} }
private boolean isEndOfBuffer() { private synchronized boolean isEndOfBuffer(TerminalPosition cursorPosition) {
return terminal.getCursorPosition().getRow() == terminalSize.getRows() - 1; return cursorPosition.getRow() == terminalSize.getRows() - 1;
} }
private void createNewRowForCursor() { private synchronized void createNewRowForCursor(TerminalPosition cursorPosition) {
TerminalPosition originalPosition = terminal.getCursorPosition(); terminal.setCursorPosition(cursorPosition);
terminal.putCharacter('\n'); terminal.putCharacter('\n');
terminal.setCursorPosition(originalPosition.withColumn(0)); terminal.setCursorPosition(cursorPosition.withColumn(0));
--originRow; --originRow;
} }
@ -231,7 +226,16 @@ public class LispTerminal {
terminal.setCursorPosition(getLeadingEdge()); 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(); TerminalPosition cursorPosition = terminal.getCursorPosition();
originColumn = cursorPosition.getColumn(); originColumn = cursorPosition.getColumn();
originRow = cursorPosition.getRow(); 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(); TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isPossibleToMoveRight(cursorPosition)) { if (isPossibleToMoveRight(cursorPosition))
String remaining = keyStroke.getCharacter() insertCharacter(character, cursorPosition);
+ inputLine.substring(getDistanceFromOrigin(cursorPosition), inputLine.length()); else
inputLine = inputLine.substring(0, getDistanceFromOrigin(cursorPosition)) + remaining; 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()) for (char c : remaining.toCharArray())
terminal.putCharacter(c); terminal.putCharacter(c);
advanceCursor(cursorPosition);
} else {
terminal.putCharacter(keyStroke.getCharacter());
inputLine += keyStroke.getCharacter();
advanceCursor(cursorPosition); 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() { private void takeNap() {
try { try {
Thread.sleep(1); Thread.sleep(1);
@ -326,6 +355,13 @@ public class LispTerminal {
private synchronized void printSegment() { private synchronized void printSegment() {
terminal.setCursorVisible(false); terminal.setCursorVisible(false);
printSegmentCharacters();
terminal.setCursorVisible(true);
outputSegment = "";
updateOrigin();
}
private synchronized void printSegmentCharacters() {
moveCursorToEndOfInput(); moveCursorToEndOfInput();
for (char c : outputSegment.toCharArray()) for (char c : outputSegment.toCharArray())
@ -333,15 +369,12 @@ public class LispTerminal {
moveCursorToNextRowIfNecessary(); moveCursorToNextRowIfNecessary();
terminal.flush(); terminal.flush();
terminal.setCursorVisible(true);
outputSegment = "";
updateOrigin();
} }
private synchronized void moveCursorToNextRowIfNecessary() { private synchronized void moveCursorToNextRowIfNecessary() {
TerminalPosition cursorPosition = terminal.getCursorPosition(); TerminalPosition cursorPosition = terminal.getCursorPosition();
if (isAtEndOfRow(cursorPosition)) if (isEndOfRow(cursorPosition))
moveCursorToNextRow(cursorPosition); moveCursorToNextRow(cursorPosition);
} }

View File

@ -78,11 +78,13 @@ public class LispTerminalTest {
} }
private void setColumns(int columns) { 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) { 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) { private void assertCursorPosition(int column, int row) {
@ -92,7 +94,15 @@ public class LispTerminalTest {
private void assertCharacterAtPosition(char character, int column, int row) { private void assertCharacterAtPosition(char character, int column, int row) {
TerminalPosition position = new TerminalPosition(column, 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) { private void assertInputWritten(String expected) {
@ -101,9 +111,8 @@ public class LispTerminalTest {
try { try {
inputWriter.close(); 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; actual += (char) c;
}
} catch (IOException ignored) {} } catch (IOException ignored) {}
assertEquals(expected, actual); assertEquals(expected, actual);
@ -116,12 +125,6 @@ public class LispTerminalTest {
} catch (IOException ignored) {} } 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 @Before
public void setUp() throws IOException { public void setUp() throws IOException {
inputReader = new PipedInputStream(); inputReader = new PipedInputStream();
@ -211,11 +214,7 @@ public class LispTerminalTest {
enterCharacters("abcd"); enterCharacters("abcd");
pressKeyTimes(KeyType.ArrowLeft, 2); pressKeyTimes(KeyType.ArrowLeft, 2);
enterCharacter('x'); enterCharacter('x');
assertCharacterAtPosition('a', 0, 0); assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } });
assertCharacterAtPosition('b', 1, 0);
assertCharacterAtPosition('x', 2, 0);
assertCharacterAtPosition('c', 3, 0);
assertCharacterAtPosition('d', 4, 0);
} }
@Test @Test
@ -224,11 +223,7 @@ public class LispTerminalTest {
enterCharacters("abcd"); enterCharacters("abcd");
pressKeyTimes(KeyType.ArrowLeft, 2); pressKeyTimes(KeyType.ArrowLeft, 2);
enterCharacter('x'); enterCharacter('x');
assertCharacterAtPosition('a', 0, 0); assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } });
assertCharacterAtPosition('b', 1, 0);
assertCharacterAtPosition('x', 2, 0);
assertCharacterAtPosition('c', 3, 0);
assertCharacterAtPosition('d', 0, 1);
} }
@Test @Test
@ -242,12 +237,7 @@ public class LispTerminalTest {
enterCharacters("12345"); enterCharacters("12345");
pressKeyTimes(KeyType.Backspace, 2); pressKeyTimes(KeyType.Backspace, 2);
assertCursorPosition(3, 0); assertCursorPosition(3, 0);
assertCharacterAtPosition('1', 0, 0); assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } });
assertCharacterAtPosition('2', 1, 0);
assertCharacterAtPosition('3', 2, 0);
assertCharacterAtPosition(' ', 3, 0);
assertCharacterAtPosition(' ', 4, 0);
assertCharacterAtPosition(' ', 5, 0);
} }
@Test @Test
@ -256,13 +246,7 @@ public class LispTerminalTest {
enterCharacters("1234567"); enterCharacters("1234567");
pressKeyTimes(KeyType.Backspace, 5); pressKeyTimes(KeyType.Backspace, 5);
assertCursorPosition(2, 0); assertCursorPosition(2, 0);
assertCharacterAtPosition('1', 0, 0); assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } });
assertCharacterAtPosition('2', 1, 0);
assertCharacterAtPosition(' ', 2, 0);
assertCharacterAtPosition(' ', 3, 0);
assertCharacterAtPosition(' ', 0, 1);
assertCharacterAtPosition(' ', 1, 1);
assertCharacterAtPosition(' ', 2, 1);
} }
@Test @Test
@ -271,10 +255,7 @@ public class LispTerminalTest {
pressKeyTimes(KeyType.ArrowLeft, 2); pressKeyTimes(KeyType.ArrowLeft, 2);
pressKey(KeyType.Backspace); pressKey(KeyType.Backspace);
assertCursorPosition(2, 0); assertCursorPosition(2, 0);
assertCharacterAtPosition('1', 0, 0); assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } });
assertCharacterAtPosition('2', 1, 0);
assertCharacterAtPosition('4', 2, 0);
assertCharacterAtPosition('5', 3, 0);
} }
@Test @Test
@ -288,9 +269,7 @@ public class LispTerminalTest {
enterCharacters("del"); enterCharacters("del");
pressKey(KeyType.Delete); pressKey(KeyType.Delete);
assertCursorPosition(3, 0); assertCursorPosition(3, 0);
assertCharacterAtPosition('d', 0, 0); assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } });
assertCharacterAtPosition('e', 1, 0);
assertCharacterAtPosition('l', 2, 0);
} }
@Test @Test
@ -299,9 +278,7 @@ public class LispTerminalTest {
pressKeyTimes(KeyType.ArrowLeft, 3); pressKeyTimes(KeyType.ArrowLeft, 3);
pressKeyTimes(KeyType.Delete, 3); pressKeyTimes(KeyType.Delete, 3);
assertCursorPosition(0, 0); assertCursorPosition(0, 0);
assertCharacterAtPosition(' ', 0, 0); assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } });
assertCharacterAtPosition(' ', 1, 0);
assertCharacterAtPosition(' ', 2, 0);
} }
@Test @Test
@ -311,12 +288,7 @@ public class LispTerminalTest {
pressKeyTimes(KeyType.ArrowLeft, 5); pressKeyTimes(KeyType.ArrowLeft, 5);
pressKey(KeyType.Delete); pressKey(KeyType.Delete);
assertCursorPosition(1, 0); assertCursorPosition(1, 0);
assertCharacterAtPosition('d', 0, 0); assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } });
assertCharacterAtPosition('l', 1, 0);
assertCharacterAtPosition('e', 2, 0);
assertCharacterAtPosition('t', 3, 0);
assertCharacterAtPosition('e', 0, 1);
assertCharacterAtPosition(' ', 1, 1);
} }
@Test @Test
@ -340,6 +312,32 @@ public class LispTerminalTest {
assertInputWritten("enter\n"); 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 @Test
public void controlDWorks() { public void controlDWorks() {
enterCharacters("control-d"); enterCharacters("control-d");
@ -401,16 +399,31 @@ public class LispTerminalTest {
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } });
} }
// @Test @Test
// public void insertingTextPushesInputPastEndOfBuffer() { public void insertingTextPushesInputPastEndOfBuffer() {
// setColumns(3); setColumns(3);
// setRows(3); setRows(4);
// enterCharacters("00011122"); pressKey(KeyType.Enter);
// pressKeyTimes(KeyType.ArrowLeft, 4); enterCharacters("00011122");
// enterCharacters("zz"); pressKeyTimes(KeyType.ArrowLeft, 4);
// assertCursorPosition(0, 1); assertCursorPosition(1, 2);
// assertCharacterPositions(new char[][] { { '1', 'z', 'z' }, { '1', '1', '2' }, { '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 @Test
public void printedOutputToEndOfRow_MovesCursorToNextRow() { public void printedOutputToEndOfRow_MovesCursorToNextRow() {