Add history feature to the terminal
The history is accessed through the up/down arrow keys. Resolves #8
This commit is contained in:
parent
243f8a83ac
commit
63fd890ac6
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(lineIndex);
|
||||
|
||||
return history.get(--currentLineIndex);
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue