History Management
JLine provides sophisticated history management capabilities, allowing users to recall, search, and reuse previous commands.
Basic History Setup
To set up history in your JLine application:
HistorySetupExample.java
import org.jline.reader.History;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
import java.nio.file.Paths;
public class HistorySetupExample {
public static void main(String[] args) throws IOException {
// Create a terminal
Terminal terminal = TerminalBuilder.builder().build();
// Create a history instance
History history = new DefaultHistory();
// Create a line reader with history
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.history(history)
.variable(LineReader.HISTORY_FILE, Paths.get("history.txt"))
.build();
System.out.println("Type commands and use up/down arrows to navigate history");
// Now the user can navigate history with up/down arrows
String line = reader.readLine("prompt> ");
System.out.println("You entered: " + line);
}
}
Persistent History
JLine can save history to a file and load it when your application restarts:
PersistentHistoryExample.java
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
import java.nio.file.Paths;
public class PersistentHistoryExample {
public static void main(String[] args) throws IOException {
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.build();
// Set the history file
reader.setVariable(LineReader.HISTORY_FILE, Paths.get("~/.myapp_history"));
// Use the reader...
String line = reader.readLine("prompt> ");
// Save history explicitly (though it's usually done automatically)
((DefaultHistory) reader.getHistory()).save();
System.out.println("History saved to ~/.myapp_history");
}
}
History Size
You can control how many entries are kept in history:
HistorySizeExample.java
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
import java.nio.file.Paths;
public class HistorySizeExample {
public static void main(String[] args) throws IOException {
Terminal terminal = TerminalBuilder.builder().build();
// Configure history with size limits
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.variable(LineReader.HISTORY_FILE, Paths.get("~/.myapp_history"))
.variable(LineReader.HISTORY_SIZE, 1000) // Maximum entries in memory
.variable(LineReader.HISTORY_FILE_SIZE, 2000) // Maximum entries in file
.build();
System.out.println("History configured with size limits");
}
}
History Filtering
JLine provides options to filter what gets added to history:
HistoryFilteringExample.java
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
public class HistoryFilteringExample {
public static void main(String[] args) throws IOException {
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.build();
// Don't add duplicate entries
reader.setOption(LineReader.Option.HISTORY_IGNORE_DUPS, true);
// Don't add entries that start with space
reader.setOption(LineReader.Option.HISTORY_IGNORE_SPACE, true);
// Beep when trying to navigate past the end of history
reader.setOption(LineReader.Option.HISTORY_BEEP, true);
// Verify history expansion (like !!, !$, etc.)
reader.setOption(LineReader.Option.HISTORY_VERIFY, true);
System.out.println("History filtering configured");
}
}
History Navigation
Users can navigate history using:
- Up/Down arrows: Move through history entries
- Ctrl+R: Reverse incremental search
- Ctrl+S: Forward incremental search (if supported by terminal)
- Alt+<: Go to the first history entry
- Alt+>: Go to the last history entry
Programmatic History Access
You can access and manipulate history programmatically:
ProgrammaticHistoryAccessExample.java
import org.jline.reader.History;
import org.jline.reader.LineReader;
public class ProgrammaticHistoryAccessExample {
public void demonstrateHistoryAccess(LineReader reader) {
// Get the history
History history = reader.getHistory();
// Iterate through history entries
System.out.println("History entries:");
for (History.Entry entry : history) {
System.out.println(entry.index() + ": " + entry.line());
}
// Get a specific entry
if (history.size() > 0) {
String lastCommand = history.get(history.size() - 1);
System.out.println("Last command: " + lastCommand);
}
// Add an entry programmatically
history.add("manually added command");
System.out.println("Added command to history");
// Clear history (commented out to avoid actually clearing history)
// history.purge();
}
}
History Expansion
JLine supports history expansion similar to Bash:
HistoryExpansionExample.java
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
public class HistoryExpansionExample {
public static void main(String[] args) throws IOException {
Terminal terminal = TerminalBuilder.builder().build();
// Enable history expansion
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.option(LineReader.Option.HISTORY_EXPAND, true)
.build();
System.out.println("History expansion enabled. You can use:");
System.out.println("!! - repeat the last command");
System.out.println("!n - repeat command number n");
System.out.println("!-n - repeat nth previous command");
System.out.println("!string - repeat last command starting with string");
System.out.println("!?string - repeat last command containing string");
System.out.println("^string1^string2 - replace string1 with string2 in the last command");
String line = reader.readLine("prompt> ");
System.out.println("You entered: " + line);
}
}
Custom History Implementation
You can create your own history implementation by implementing the History
interface:
CustomHistory.java
import org.jline.reader.History;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CustomHistory implements History {
private final List<String> entries = new ArrayList<>();
@Override
public void add(String line) {
// Custom logic for adding entries
entries.add(line);
// Maybe save to a database or other storage
}
@Override
public String get(int index) {
return entries.get(index);
}
@Override
public int size() {
return entries.size();
}
@Override
public int index() {
return entries.size() - 1;
}
@Override
public Iterator<Entry> iterator() {
return new Iterator<Entry>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < entries.size();
}
@Override
public Entry next() {
final int currentIndex = index++;
return new Entry() {
@Override
public int index() {
return currentIndex;
}
@Override
public String line() {
return entries.get(currentIndex);
}
};
}
};
}
@Override
public void purge() {
entries.clear();
}
}
Advanced History Features
Timestamped History
You can create a history implementation that records timestamps:
TimestampedHistory.java
import org.jline.reader.impl.history.DefaultHistory;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
public class TimestampedHistory extends DefaultHistory {
private final Map<String, Instant> timestamps = new HashMap<>();
@Override
public void add(String line) {
super.add(line);
timestamps.put(line, Instant.now());
}
public Instant getTimestamp(String line) {
return timestamps.get(line);
}
public String getFormattedTimestamp(String line) {
Instant timestamp = timestamps.get(line);
if (timestamp != null) {
return timestamp.toString();
}
return "Unknown";
}
}
Searchable History
Implement custom search functionality:
HistorySearchExample.java
import org.jline.reader.History;
import org.jline.reader.LineReader;
import java.util.ArrayList;
import java.util.List;
public class HistorySearchExample {
public List<String> searchHistory(LineReader reader, String term) {
List<String> results = new ArrayList<>();
History history = reader.getHistory();
for (History.Entry entry : history) {
if (entry.line().contains(term)) {
results.add(entry.line());
}
}
return results;
}
public void demonstrateHistorySearch(LineReader reader) {
System.out.println("Searching history for 'git':");
List<String> gitCommands = searchHistory(reader, "git");
for (String command : gitCommands) {
System.out.println(" - " + command);
}
}
}
History Event Listeners
You can listen for history events:
HistoryListenerExample.java
import org.jline.reader.History;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import java.io.IOException;
public class HistoryListenerExample {
public static void main(String[] args) throws IOException {
// Create a history listener
History.Listener historyListener = new History.Listener() {
@Override
public void onAdd(History history, String line) {
System.out.println("Added to history: " + line);
}
@Override
public void onRemove(History history, String line) {
System.out.println("Removed from history: " + line);
}
};
// Add the listener to a DefaultHistory instance
DefaultHistory history = new DefaultHistory();
history.addListener(historyListener);
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.history(history)
.build();
System.out.println("Type commands to see history events:");
String line = reader.readLine("prompt> ");
System.out.println("You entered: " + line);
}
}
Best Practices
- Always set a history file for persistent history
- Configure appropriate history size limits
- Consider enabling HISTORY_IGNORE_DUPS to avoid clutter
- Provide clear documentation on history navigation for users
- Consider security implications of storing sensitive commands
- Implement history purging for sensitive operations
- Test history functionality with various input patterns