Add history feature to the terminal

The history is accessed through the up/down arrow keys.

Resolves #8
This commit is contained in:
Mike Cifelli 2017-04-04 15:37:57 -04:00
parent 243f8a83ac
commit 63fd890ac6
4 changed files with 214 additions and 37 deletions

View File

@ -22,6 +22,7 @@ public class LispTerminal {
private SafeOutputStream inputWriter;
private SafeInputStream outputReader;
private ControlSequenceHandler controlSequenceHandler;
private TerminalHistory history;
private ExecutorService executorService;
private TerminalSize terminalSize;
private String inputLine;
@ -35,6 +36,7 @@ public class LispTerminal {
this.inputWriter = new SafeOutputStream(configuration.getInputWriter());
this.outputReader = new SafeInputStream(configuration.getOutputReader());
this.controlSequenceHandler = new ControlSequenceHandler();
this.history = new TerminalHistory();
this.executorService = Executors.newFixedThreadPool(2);
this.terminalSize = terminal.getTerminalSize();
this.inputLine = "";
@ -141,7 +143,7 @@ public class LispTerminal {
doControlD();
}
private void doControlC() {
private synchronized void doControlC() {
moveCursorToEndOfInput();
terminal.putCharacter('\n');
inputLine = "";
@ -159,6 +161,10 @@ public class LispTerminal {
if (keyType == Enter)
doEnter();
else if (keyType == ArrowUp)
doUpArrow();
else if (keyType == ArrowDown)
doDownArrow();
else if (keyType == ArrowLeft)
doLeftArrow();
else if (keyType == ArrowRight)
@ -173,12 +179,73 @@ public class LispTerminal {
private synchronized void doEnter() {
moveCursorToEndOfInput();
history.addLine(inputLine);
terminal.putCharacter('\n');
writeInputLine();
setOriginToCurrentPosition();
}
private synchronized void writeInputLine() {
inputLine += "\n";
inputWriter.write(inputLine.getBytes());
inputWriter.flush();
inputLine = "";
setOriginToCurrentPosition();
}
private synchronized void doUpArrow() {
if (!history.isBeginning())
replaceInputWithPreviousLine();
}
private synchronized void replaceInputWithPreviousLine() {
history.updateCurrentLine(inputLine);
clearInput();
displayPreviousInputLine();
}
private synchronized void clearInput() {
terminal.setCursorPosition(originColumn, originRow);
putStringToTerminal(inputLine.replaceAll(".{1}", " "));
terminal.setCursorPosition(originColumn, originRow);
}
private synchronized void displayPreviousInputLine() {
inputLine = history.getPreviousLine();
char[] inputCharacters = inputLine.toCharArray();
for (int i = 0; i < inputCharacters.length; i++)
displayCharacter(inputCharacters[i], i);
}
private synchronized void displayCharacter(char character, int offsetFromOrigin) {
if (isLastColumn(offsetFromOrigin)) {
TerminalPosition cursorPosition = terminal.getCursorPosition();
terminal.putCharacter(character);
moveCursorToNextRow(cursorPosition);
} else
terminal.putCharacter(character);
}
private synchronized boolean isLastColumn(int offsetFromOrigin) {
int totalColumns = terminalSize.getColumns();
return (originColumn + offsetFromOrigin) % totalColumns == totalColumns - 1;
}
private synchronized void doDownArrow() {
if (!history.isEnd())
replaceInputWithNextLine();
}
private synchronized void replaceInputWithNextLine() {
history.updateCurrentLine(inputLine);
clearInput();
displayNextInputLine();
}
private synchronized void displayNextInputLine() {
inputLine = history.getNextLine();
putStringToTerminal(inputLine);
}
private synchronized void doLeftArrow() {
@ -390,7 +457,7 @@ public class LispTerminal {
return new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes()));
}
private void applyControlSequence(SafeInputStream input) {
private synchronized void applyControlSequence(SafeInputStream input) {
ControlSequence controlSequence = controlSequenceHandler.parse(input);
controlSequence.applyTo(terminal);
}

View File

@ -5,38 +5,48 @@ import java.util.*;
public class TerminalHistory {
private List<String> history;
private int currentLineIndex;
private int lineIndex;
public TerminalHistory() {
this.history = new ArrayList<>();
this.currentLineIndex = 0;
this.history.add("");
this.lineIndex = 0;
}
public void addLine(String line) {
history.add(line);
currentLineIndex = history.size();
history.set(lastIndex(), line);
history.add("");
lineIndex = lastIndex();
}
private int lastIndex() {
return history.size() - 1;
}
public void updateCurrentLine(String line) {
history.set(lineIndex, line);
}
public String getPreviousLine() {
if (isBeginning())
return null;
return history.get(--currentLineIndex);
return history.get(lineIndex);
return history.get(--lineIndex);
}
private boolean isBeginning() {
return currentLineIndex == 0;
public boolean isBeginning() {
return lineIndex == 0;
}
public String getNextLine() {
if (isEnd())
return null;
return history.get(lineIndex);
return history.get(++currentLineIndex);
return history.get(++lineIndex);
}
private boolean isEnd() {
return currentLineIndex >= history.size() - 1;
public boolean isEnd() {
return lineIndex == lastIndex();
}
}

View File

@ -434,4 +434,70 @@ public class LispTerminalTest {
terminal.assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } });
}
@Test
public void upArrowWorks() {
terminal.enterCharacters("(one)");
terminal.pressKey(Enter);
terminal.enterCharacters("(two)");
terminal.pressKey(Enter);
terminal.pressKey(ArrowUp);
terminal.assertScreenText("(one)", "(two)", "(two)");
terminal.assertCursorPosition(5, 2);
}
@Test
public void upArrowErasesCurrentLine() {
terminal.enterCharacters("(one)");
terminal.pressKey(Enter);
terminal.enterCharacters("(two)");
terminal.pressKey(Enter);
terminal.enterCharacters("(three)");
terminal.pressKey(ArrowUp);
terminal.assertScreenText("(one)", "(two)", "(two) ");
terminal.assertCursorPosition(5, 2);
}
@Test
public void upArrowStopsAtFirstLine() {
terminal.enterCharacters("(one)");
terminal.pressKey(Enter);
terminal.enterCharacters("(two)");
terminal.pressKey(Enter);
terminal.pressKeyTimes(ArrowUp, 5);
terminal.assertScreenText("(one)", "(two)", "(one)");
terminal.assertCursorPosition(5, 2);
}
@Test
public void originIsUpdatedWhenPreviousLineMovesPastEndOfBuffer() {
terminal.setRows(3);
terminal.setColumns(3);
terminal.enterCharacters("12345");
terminal.pressKey(Enter);
terminal.pressKey(ArrowUp);
terminal.assertScreenText("45 ", "123", "45 ");
terminal.pressKeyTimes(ArrowLeft, 10);
terminal.assertCursorPosition(0, 1);
}
@Test
public void downArrowWorks() {
terminal.enterCharacters("(one)");
terminal.pressKey(Enter);
terminal.enterCharacters("(two)");
terminal.pressKey(Enter);
terminal.enterCharacters("(three)");
terminal.pressKey(ArrowUp);
terminal.pressKey(ArrowDown);
terminal.assertScreenText("(one)", "(two)", "(three)");
terminal.assertCursorPosition(7, 2);
}
@Test
public void downArrowStopsAtLastLine() {
terminal.pressKeyTimes(ArrowDown, 5);
terminal.assertScreenText(" ");
terminal.assertCursorPosition(0, 0);
}
}

View File

@ -9,11 +9,11 @@ public class TerminalHistoryTest {
private TerminalHistory history;
private void assertAtBeginning() {
assertNull(history.getPreviousLine());
assertTrue(history.isBeginning());
}
private void assertAtEnd() {
assertNull(history.getNextLine());
assertTrue(history.isEnd());
}
private void assertPrevious(String expected) {
@ -44,7 +44,14 @@ public class TerminalHistoryTest {
assertPrevious("test line");
assertAtBeginning();
assertAtEnd();
}
@Test
public void currentLineIsEmpty() {
history.addLine("one");
history.getPreviousLine();
assertNext("");
}
@Test
@ -65,24 +72,7 @@ public class TerminalHistoryTest {
history.getPreviousLine();
assertNext("two");
assertAtEnd();
}
@Test
public void moveBackAndForth() {
history.addLine("one");
history.addLine("two");
assertAtEnd();
assertPrevious("two");
assertAtEnd();
assertPrevious("one");
assertAtBeginning();
assertNext("two");
assertAtEnd();
assertPrevious("one");
assertAtBeginning();
assertNext("two");
assertNext("");
assertAtEnd();
}
@ -100,4 +90,48 @@ public class TerminalHistoryTest {
assertAtBeginning();
}
@Test
public void currentLineIsUpdated() {
history.addLine("one");
history.updateCurrentLine("purple");
history.getPreviousLine();
assertNext("purple");
}
@Test
public void previousLineIsUpdated() {
history.addLine("one");
history.addLine("two");
history.getPreviousLine();
history.updateCurrentLine("purple");
history.getPreviousLine();
assertNext("purple");
}
@Test
public void goingPastFirstLine_KeepsReturningFirstLine() {
history.addLine("one");
history.addLine("two");
history.getPreviousLine();
history.getPreviousLine();
history.getPreviousLine();
history.getPreviousLine();
history.getPreviousLine();
assertPrevious("one");
}
@Test
public void goingPastLastLine_KeepsReturningLastLine() {
history.updateCurrentLine("current");
history.getNextLine();
history.getNextLine();
history.getNextLine();
history.getNextLine();
assertNext("current");
}
}