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 SafeOutputStream inputWriter;
|
||||||
private SafeInputStream outputReader;
|
private SafeInputStream outputReader;
|
||||||
private ControlSequenceHandler controlSequenceHandler;
|
private ControlSequenceHandler controlSequenceHandler;
|
||||||
|
private TerminalHistory history;
|
||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
private TerminalSize terminalSize;
|
private TerminalSize terminalSize;
|
||||||
private String inputLine;
|
private String inputLine;
|
||||||
|
@ -35,6 +36,7 @@ public class LispTerminal {
|
||||||
this.inputWriter = new SafeOutputStream(configuration.getInputWriter());
|
this.inputWriter = new SafeOutputStream(configuration.getInputWriter());
|
||||||
this.outputReader = new SafeInputStream(configuration.getOutputReader());
|
this.outputReader = new SafeInputStream(configuration.getOutputReader());
|
||||||
this.controlSequenceHandler = new ControlSequenceHandler();
|
this.controlSequenceHandler = new ControlSequenceHandler();
|
||||||
|
this.history = new TerminalHistory();
|
||||||
this.executorService = Executors.newFixedThreadPool(2);
|
this.executorService = Executors.newFixedThreadPool(2);
|
||||||
this.terminalSize = terminal.getTerminalSize();
|
this.terminalSize = terminal.getTerminalSize();
|
||||||
this.inputLine = "";
|
this.inputLine = "";
|
||||||
|
@ -141,7 +143,7 @@ public class LispTerminal {
|
||||||
doControlD();
|
doControlD();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doControlC() {
|
private synchronized void doControlC() {
|
||||||
moveCursorToEndOfInput();
|
moveCursorToEndOfInput();
|
||||||
terminal.putCharacter('\n');
|
terminal.putCharacter('\n');
|
||||||
inputLine = "";
|
inputLine = "";
|
||||||
|
@ -159,6 +161,10 @@ public class LispTerminal {
|
||||||
|
|
||||||
if (keyType == Enter)
|
if (keyType == Enter)
|
||||||
doEnter();
|
doEnter();
|
||||||
|
else if (keyType == ArrowUp)
|
||||||
|
doUpArrow();
|
||||||
|
else if (keyType == ArrowDown)
|
||||||
|
doDownArrow();
|
||||||
else if (keyType == ArrowLeft)
|
else if (keyType == ArrowLeft)
|
||||||
doLeftArrow();
|
doLeftArrow();
|
||||||
else if (keyType == ArrowRight)
|
else if (keyType == ArrowRight)
|
||||||
|
@ -173,12 +179,73 @@ public class LispTerminal {
|
||||||
|
|
||||||
private synchronized void doEnter() {
|
private synchronized void doEnter() {
|
||||||
moveCursorToEndOfInput();
|
moveCursorToEndOfInput();
|
||||||
|
history.addLine(inputLine);
|
||||||
terminal.putCharacter('\n');
|
terminal.putCharacter('\n');
|
||||||
|
writeInputLine();
|
||||||
|
setOriginToCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void writeInputLine() {
|
||||||
inputLine += "\n";
|
inputLine += "\n";
|
||||||
inputWriter.write(inputLine.getBytes());
|
inputWriter.write(inputLine.getBytes());
|
||||||
inputWriter.flush();
|
inputWriter.flush();
|
||||||
inputLine = "";
|
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() {
|
private synchronized void doLeftArrow() {
|
||||||
|
@ -390,7 +457,7 @@ public class LispTerminal {
|
||||||
return new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes()));
|
return new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyControlSequence(SafeInputStream input) {
|
private synchronized void applyControlSequence(SafeInputStream input) {
|
||||||
ControlSequence controlSequence = controlSequenceHandler.parse(input);
|
ControlSequence controlSequence = controlSequenceHandler.parse(input);
|
||||||
controlSequence.applyTo(terminal);
|
controlSequence.applyTo(terminal);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,48 @@ import java.util.*;
|
||||||
public class TerminalHistory {
|
public class TerminalHistory {
|
||||||
|
|
||||||
private List<String> history;
|
private List<String> history;
|
||||||
private int currentLineIndex;
|
private int lineIndex;
|
||||||
|
|
||||||
public TerminalHistory() {
|
public TerminalHistory() {
|
||||||
this.history = new ArrayList<>();
|
this.history = new ArrayList<>();
|
||||||
this.currentLineIndex = 0;
|
this.history.add("");
|
||||||
|
this.lineIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLine(String line) {
|
public void addLine(String line) {
|
||||||
history.add(line);
|
history.set(lastIndex(), line);
|
||||||
currentLineIndex = history.size();
|
history.add("");
|
||||||
|
lineIndex = lastIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastIndex() {
|
||||||
|
return history.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCurrentLine(String line) {
|
||||||
|
history.set(lineIndex, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPreviousLine() {
|
public String getPreviousLine() {
|
||||||
if (isBeginning())
|
if (isBeginning())
|
||||||
return null;
|
return history.get(lineIndex);
|
||||||
|
|
||||||
return history.get(--currentLineIndex);
|
return history.get(--lineIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBeginning() {
|
public boolean isBeginning() {
|
||||||
return currentLineIndex == 0;
|
return lineIndex == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNextLine() {
|
public String getNextLine() {
|
||||||
if (isEnd())
|
if (isEnd())
|
||||||
return null;
|
return history.get(lineIndex);
|
||||||
|
|
||||||
return history.get(++currentLineIndex);
|
return history.get(++lineIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEnd() {
|
public boolean isEnd() {
|
||||||
return currentLineIndex >= history.size() - 1;
|
return lineIndex == lastIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -434,4 +434,70 @@ public class LispTerminalTest {
|
||||||
terminal.assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } });
|
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 TerminalHistory history;
|
||||||
|
|
||||||
private void assertAtBeginning() {
|
private void assertAtBeginning() {
|
||||||
assertNull(history.getPreviousLine());
|
assertTrue(history.isBeginning());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAtEnd() {
|
private void assertAtEnd() {
|
||||||
assertNull(history.getNextLine());
|
assertTrue(history.isEnd());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertPrevious(String expected) {
|
private void assertPrevious(String expected) {
|
||||||
|
@ -44,7 +44,14 @@ public class TerminalHistoryTest {
|
||||||
|
|
||||||
assertPrevious("test line");
|
assertPrevious("test line");
|
||||||
assertAtBeginning();
|
assertAtBeginning();
|
||||||
assertAtEnd();
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void currentLineIsEmpty() {
|
||||||
|
history.addLine("one");
|
||||||
|
history.getPreviousLine();
|
||||||
|
|
||||||
|
assertNext("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -65,24 +72,7 @@ public class TerminalHistoryTest {
|
||||||
history.getPreviousLine();
|
history.getPreviousLine();
|
||||||
|
|
||||||
assertNext("two");
|
assertNext("two");
|
||||||
assertAtEnd();
|
assertNext("");
|
||||||
}
|
|
||||||
|
|
||||||
@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");
|
|
||||||
assertAtEnd();
|
assertAtEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,4 +90,48 @@ public class TerminalHistoryTest {
|
||||||
assertAtBeginning();
|
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