Skip to content
This repository has been archived by the owner on Dec 27, 2023. It is now read-only.

Commit

Permalink
Merge pull request #147 from bwangpj/command-history
Browse files Browse the repository at this point in the history
Allow switching between command history
  • Loading branch information
rayshawntan authored Nov 2, 2023
2 parents 1e65614 + eea8204 commit fa840db
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 3 deletions.
3 changes: 2 additions & 1 deletion docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ It is optimized for use via an in-app Command Line Interface (CLI), while still

It has useful features relevant to NUS SoC students:

- Tagging contacts by category: You could tag all your professors and classmates with custom tags such as "Professor", "Tutorial mate", "CS2103" etc., then filter by tag.
- Tagging contacts by category: You can tag your professors and classmates with custom tags such as "Professor", "Tutorial mate", "CS2103" etc., then filter by tag to view all contacts with a certain tag.
- Storing different ways to reach people: By adding alternate contact details, you could have local phone number, overseas phone number, Telegram, Discord etc. all in the same contact.
- Works like a usual CLI: You can use the up/down arrow keys to switch between previously-entered commands, making entering and repeating commands (e.g. adding many new contacts) easier.

If you can type fast, prefer typing, and are reasonably comfortable with CLI inputs, ConText can let you manage contacts faster than traditional GUI apps.

Expand Down
56 changes: 55 additions & 1 deletion src/main/java/swe/context/ui/CommandBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import swe.context.logic.commands.CommandResult;
import swe.context.logic.commands.exceptions.CommandException;
Expand All @@ -17,16 +18,18 @@ public class CommandBox extends UiPart<Region> {
private static final String FXML = "CommandBox.fxml";

private final CommandExecutor commandExecutor;
private final CommandBoxHistory commandBoxHistory;

@FXML
private TextField commandTextField;

/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
* Creates a {@code CommandBox} with the given {@code CommandExecutor} and empty {@code CommandBoxHistory}.
*/
public CommandBox(CommandExecutor commandExecutor) {
super(FXML);
this.commandExecutor = commandExecutor;
this.commandBoxHistory = new CommandBoxHistory();
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
}
Expand All @@ -43,12 +46,63 @@ private void handleCommandEntered() {

try {
commandExecutor.execute(commandText);
commandBoxHistory.add(commandText);
commandBoxHistory.resetPointer();
commandTextField.setText("");
} catch (CommandException | ParseException e) {
setStyleToIndicateCommandFailure();
}
}

/**
* Handles the up or down button pressed event.
*/
@FXML
private void handleKeyPress(KeyEvent keyEvent) {
switch (keyEvent.getCode()) {
case UP:
keyEvent.consume();
this.switchToPreviousCommand();
break;
case DOWN:
keyEvent.consume();
this.switchToNextCommand();
break;
default:
}
}

/**
* Switch to the previous command in {@code commandBoxHistory}, if there is such a command.
*/
private void switchToPreviousCommand() {
assert commandBoxHistory != null;
if (!commandBoxHistory.hasPrevious()) {
return;
}
this.replaceText(commandBoxHistory.previous());
}

/**
* Switch to the next command in {@code commandBoxHistory}, if there is such a command.
*/
private void switchToNextCommand() {
assert commandBoxHistory != null;
if (!commandBoxHistory.hasNext()) {
return;
}
this.replaceText(commandBoxHistory.next());
}

/**
* Sets {@code CommandBox}'s text field with {@code text} and
* positions the caret to the end of the {@code text}.
*/
private void replaceText(String text) {
commandTextField.setText(text);
commandTextField.positionCaret(commandTextField.getText().length());
}

/**
* Sets the command box style to use the default style.
*/
Expand Down
137 changes: 137 additions & 0 deletions src/main/java/swe/context/ui/CommandBoxHistory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package swe.context.ui;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

/**
* Stores the history of entered commands and a pointer indicating the current position in the list.
* The list always behaves externally as if its last element is the empty string.
*/
public class CommandBoxHistory {
private List<String> commandList;
private int commandIndex;

/**
* Constructs {@code CommandBoxHistory} with empty command history.
* The pointer is set to the last element in {@code commandList}.
*/
public CommandBoxHistory() {
this.commandList = new ArrayList<>();
this.commandIndex = this.commandList.size();
}

/**
* Constructs {@code CommandBoxHistory} defensively.
* The pointer is set to the last element in {@code commandList}.
*/
public CommandBoxHistory(List<String> list) {
this.commandList = new ArrayList<>(list);
this.commandIndex = this.commandList.size();
}

/**
* Appends {@code element} to the end of the commandList.
*/
public void add(String element) {
commandList.add(element);
}

/**
* Resets the command history pointer to the end of the list.
*/
public void resetPointer() {
this.commandIndex = this.commandList.size();
}

/**
* Returns true if calling {@code #next()} does not throw an {@code NoSuchElementException}.
*/
public boolean hasNext() {
int nextIndex = commandIndex + 1;
return isWithinBounds(nextIndex);
}

/**
* Returns true if calling {@code #previous()} does not throw an {@code NoSuchElementException}.
*/
public boolean hasPrevious() {
int previousIndex = commandIndex - 1;
return isWithinBounds(previousIndex);
}

/**
* Returns true if calling {@code #current()} does not throw an {@code NoSuchElementException}.
*/
public boolean hasCurrent() {
return isWithinBounds(commandIndex);
}

private boolean isWithinBounds(int index) {
return index >= 0 && index <= commandList.size();
}

/**
* Returns the next command in the commandList and advances the pointer position.
* @throws NoSuchElementException if there is no more next command in the commandList.
*/
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
commandIndex++;
if (commandIndex == commandList.size()) {
return "";
}
else {
return commandList.get(commandIndex);
}
}

/**
* Returns the previous command in the commandList and decrements the pointer position.
* @throws NoSuchElementException if there is no more previous command in the commandList.
*/
public String previous() {
if (!hasPrevious()) {
throw new NoSuchElementException();
}
commandIndex--;
if (commandIndex == commandList.size()) {
return "";
}
else {
return commandList.get(commandIndex);
}
}

/**
* Returns the current command in the commandList.
* @throws NoSuchElementException if the commandList is empty.
*/
public String current() {
if (!hasCurrent()) {
throw new NoSuchElementException();
}
if (commandIndex == commandList.size()) {
return "";
}
else {
return commandList.get(commandIndex);
}
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}

if (!(other instanceof CommandBoxHistory)) {
return false;
}

CommandBoxHistory iterator = (CommandBoxHistory) other;
return commandList.equals(iterator.commandList) && commandIndex == iterator.commandIndex;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/view/CommandBox.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
<?import javafx.scene.layout.StackPane?>

<StackPane styleClass="stack-pane" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<TextField fx:id="commandTextField" onAction="#handleCommandEntered" promptText="Enter command here..."/>
<TextField fx:id="commandTextField" onAction="#handleCommandEntered" onKeyPressed="#handleKeyPress" promptText="Enter command here..."/>
</StackPane>
Loading

0 comments on commit fa840db

Please sign in to comment.