Skip to content

Commit f5b3831

Browse files
Pieter12345facchinm
authored andcommitted
Add serial monitor command history
The behavior is as follows: - Pressing the UP key will select older commands. - Pressing the DOWN key will select newer commands, restoring the last unexecuted command if available. - Pressing the ESC key will reset the input field to the latest unexecuted command and reset the traversal location. Pressing ESC while the latest unexecuted command is selected will clear the input field. This fixes #4891, with the difference that the proposed solution has a command history per Arduino session and this implementation has a command history per serial monitor start.
1 parent c88ff31 commit f5b3831

File tree

2 files changed

+158
-2
lines changed

2 files changed

+158
-2
lines changed
+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package processing.app;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
/**
7+
* Keeps track of command history in console-like applications.
8+
* @author P.J.S. Kools
9+
*/
10+
public class CommandHistory {
11+
12+
private List<String> commandHistory = new ArrayList<String>();
13+
private int selectedCommandIndex = 0;
14+
private final int maxHistorySize;
15+
16+
/**
17+
* Create a new {@link CommandHistory}.
18+
* @param maxHistorySize - The max command history size.
19+
*/
20+
public CommandHistory(int maxHistorySize) {
21+
this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
22+
this.commandHistory.add(""); // Current command placeholder.
23+
}
24+
25+
/**
26+
* Adds the given command to the history and resets the history traversal
27+
* position to the latest command. If the max history size is exceeded,
28+
* the oldest command will be removed from the history.
29+
* @param command - The command to add.
30+
*/
31+
public void addCommand(String command) {
32+
33+
// Remove the oldest command if the max history size is exceeded.
34+
if(this.commandHistory.size() >= this.maxHistorySize + 1) {
35+
this.commandHistory.remove(0);
36+
}
37+
38+
// Add the new command, reset the 'current' command and reset the index.
39+
this.commandHistory.set(this.commandHistory.size() - 1, command);
40+
this.commandHistory.add(""); // Current command placeholder.
41+
this.selectedCommandIndex = this.commandHistory.size() - 1;
42+
}
43+
44+
/**
45+
* Gets whether a next (more recent) command is available in the history.
46+
* @return {@code true} if a next command is available,
47+
* returns {@code false} otherwise.
48+
*/
49+
public boolean hasNextCommand() {
50+
return this.selectedCommandIndex + 1 < this.commandHistory.size();
51+
}
52+
53+
/**
54+
* Gets the next (more recent) command from the history.
55+
* @return The next command or {@code null} if no next command is available.
56+
*/
57+
public String getNextCommand() {
58+
return this.hasNextCommand()
59+
? this.commandHistory.get(++this.selectedCommandIndex) : null;
60+
}
61+
62+
/**
63+
* Gets whether a previous (older) command is available in the history.
64+
* @return {@code true} if a previous command is available,
65+
* returns {@code false} otherwise.
66+
*/
67+
public boolean hasPreviousCommand() {
68+
return this.selectedCommandIndex > 0;
69+
}
70+
71+
/**
72+
* Gets the previous (older) command from the history.
73+
* When this method is called while the most recent command in the history is
74+
* selected, this will store the current command as temporary latest command
75+
* so that {@link #getNextCommand()} will return it. This temporary latest
76+
* command gets reset when this case occurs again or when
77+
* {@link #addCommand(String)} is invoked.
78+
* @param currentCommand - The current unexecuted command.
79+
* @return The previous command or {@code null} if no previous command is
80+
* available.
81+
*/
82+
public String getPreviousCommand(String currentCommand) {
83+
84+
// Return null if there is no previous command available.
85+
if (!this.hasPreviousCommand()) {
86+
return null;
87+
}
88+
89+
// Store current unexecuted command if not traversing already.
90+
if (this.selectedCommandIndex == this.commandHistory.size() - 1) {
91+
this.commandHistory.set(this.commandHistory.size() - 1,
92+
(currentCommand == null ? "" : currentCommand));
93+
}
94+
95+
// Return the previous command.
96+
return this.commandHistory.get(--this.selectedCommandIndex);
97+
}
98+
99+
/**
100+
* Resets the history location to the most recent command.
101+
* @returns The latest unexecuted command as stored by
102+
* {@link #getPreviousCommand(String)} or an empty string if no such command
103+
* was set.
104+
*/
105+
public String resetHistoryLocation() {
106+
this.selectedCommandIndex = this.commandHistory.size() - 1;
107+
return this.commandHistory.set(this.commandHistory.size() - 1, "");
108+
}
109+
110+
/**
111+
* Clears the command history.
112+
*/
113+
public void clear() {
114+
this.commandHistory.clear();
115+
this.commandHistory.add(""); // Current command placeholder.
116+
this.selectedCommandIndex = 0;
117+
}
118+
}

app/src/processing/app/SerialMonitor.java

+40-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
import java.awt.Color;
2525
import java.awt.event.ActionEvent;
26+
import java.awt.event.ActionListener;
27+
import java.awt.event.KeyAdapter;
28+
import java.awt.event.KeyEvent;
2629

2730
import static processing.app.I18n.tr;
2831

@@ -32,6 +35,10 @@ public class SerialMonitor extends AbstractTextMonitor {
3235
private Serial serial;
3336
private int serialRate;
3437

38+
private static final int COMMAND_HISTORY_SIZE = 100;
39+
private final CommandHistory commandHistory =
40+
new CommandHistory(COMMAND_HISTORY_SIZE);
41+
3542
public SerialMonitor(BoardPort port) {
3643
super(port);
3744

@@ -54,11 +61,42 @@ public SerialMonitor(BoardPort port) {
5461
});
5562

5663
onSendCommand((ActionEvent event) -> {
57-
send(textField.getText());
64+
String command = textField.getText();
65+
send(command);
66+
commandHistory.addCommand(command);
5867
textField.setText("");
5968
});
60-
69+
6170
onClearCommand((ActionEvent event) -> textArea.setText(""));
71+
72+
// Add key listener to UP, DOWN, ESC keys for command history traversal.
73+
textField.addKeyListener(new KeyAdapter() {
74+
@Override
75+
public void keyPressed(KeyEvent e) {
76+
switch (e.getKeyCode()) {
77+
78+
// Select previous command.
79+
case KeyEvent.VK_UP:
80+
if (commandHistory.hasPreviousCommand()) {
81+
textField.setText(
82+
commandHistory.getPreviousCommand(textField.getText()));
83+
}
84+
break;
85+
86+
// Select next command.
87+
case KeyEvent.VK_DOWN:
88+
if (commandHistory.hasNextCommand()) {
89+
textField.setText(commandHistory.getNextCommand());
90+
}
91+
break;
92+
93+
// Reset history location, restoring the last unexecuted command.
94+
case KeyEvent.VK_ESCAPE:
95+
textField.setText(commandHistory.resetHistoryLocation());
96+
break;
97+
}
98+
}
99+
});
62100
}
63101

64102
private void send(String s) {

0 commit comments

Comments
 (0)