Skip to content

Commit b07e678

Browse files
Merge pull request #106 from SauravBhowmick/main
Added search history for commands used.
2 parents 8a85b6b + 8a836d5 commit b07e678

5 files changed

Lines changed: 278 additions & 0 deletions

File tree

icons/mycmd.ico

-2.99 MB
Binary file not shown.

src/main/java/com/mycmd/App.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public static void main(String[] args) {
1616
System.out.println("(c) 2025 MyCMD Organization. All rights reserved.");
1717

1818
Scanner sc = new Scanner(System.in);
19+
context.setScanner(sc); // ← Added this line
1920

2021
while (true) {
2122
System.out.print(context.getCurrentDir().getAbsolutePath() + ">");
@@ -153,6 +154,8 @@ private CommandNames() {}
153154
private static final String WHOAMI = "whoami";
154155
private static final String WMIC = "wmic";
155156
private static final String XCOPY = "xcopy";
157+
private static final String SEARCHHISTORY = "searchhistory";
158+
private static final String ISEARCH = "isearch";
156159
}
157160

158161
private static void registerCommands(Map<String, Command> commands) {
@@ -230,5 +233,7 @@ private static void registerCommands(Map<String, Command> commands) {
230233
commands.put(CommandNames.WHOAMI, new WhoamiCommand());
231234
commands.put(CommandNames.WMIC, new WmicCommand());
232235
commands.put(CommandNames.XCOPY, new XcopyCommand());
236+
commands.put(CommandNames.SEARCHHISTORY, new SearchHistoryCommand());
237+
commands.put(CommandNames.ISEARCH, new InteractiveSearchCommand());
233238
}
234239
}

src/main/java/com/mycmd/ShellContext.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,47 @@ public class ShellContext {
2323

2424
private final Map<String, String> envVars = new HashMap<>();
2525

26+
private Scanner scanner;
27+
2628
public ShellContext() {
2729
this.currentDir = new File(System.getProperty("user.dir"));
2830
this.history = new ArrayList<>();
2931
this.aliases = new HashMap<>();
3032
this.commandHistory = new ArrayList<>();
3133
this.startTime = Instant.now();
34+
this.scanner = null; // Will be set by App.java
3235
loadAliases();
3336
}
3437

38+
// ==================== Scanner Management ====================
39+
40+
/**
41+
* Set the shared Scanner instance for all commands to use.
42+
* Should only be called once by App.java during initialization.
43+
*/
44+
public void setScanner(Scanner scanner) {
45+
if (this.scanner != null) {
46+
throw new IllegalStateException("Scanner already initialized");
47+
}
48+
if (scanner == null) {
49+
throw new IllegalArgumentException("Scanner cannot be null");
50+
}
51+
this.scanner = scanner;
52+
}
53+
54+
/**
55+
* Get the shared Scanner instance.
56+
* All commands should use this instead of creating their own Scanner.
57+
* @return the shared Scanner instance
58+
* @throws IllegalStateException if Scanner hasn't been initialized
59+
*/
60+
public Scanner getScanner() {
61+
if (scanner == null) {
62+
throw new IllegalStateException("Scanner not initialized in ShellContext");
63+
}
64+
return scanner;
65+
}
66+
3567
public void addToHistory(String command) {
3668
history.add(command);
3769
commandHistory.add(command);
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.mycmd.commands;
2+
3+
import com.mycmd.Command;
4+
import com.mycmd.ShellContext;
5+
import java.util.List;
6+
import java.util.Scanner;
7+
import java.util.stream.Collectors;
8+
9+
/**
10+
* Interactive history search command (similar to Ctrl+R in bash).
11+
* Usage: isearch
12+
*
13+
* Provides an interactive prompt where users can:
14+
* - Type to filter history in real-time
15+
* - Navigate through matches with numbers
16+
* - Execute a selected command
17+
*/
18+
public class InteractiveSearchCommand implements Command {
19+
20+
@Override
21+
public void execute(String[] args, ShellContext context) {
22+
List<String> history = context.getHistory();
23+
24+
if (history.isEmpty()) {
25+
System.out.println("No command history available.");
26+
return;
27+
}
28+
29+
// ✅ Use shared scanner from ShellContext instead of creating a new one
30+
Scanner scanner = context.getScanner();
31+
32+
System.out.println("=== Interactive History Search ===");
33+
System.out.println("Type to search, 'q' to quit");
34+
System.out.println();
35+
36+
while (true) {
37+
System.out.print("search> ");
38+
String searchTerm = scanner.nextLine().trim();
39+
40+
if (searchTerm.equalsIgnoreCase("q") || searchTerm.equalsIgnoreCase("quit")) {
41+
System.out.println("Search cancelled.");
42+
break;
43+
}
44+
45+
if (searchTerm.isEmpty()) {
46+
System.out.println("Enter a search term or 'q' to quit.");
47+
continue;
48+
}
49+
50+
// Search and display results
51+
List<String> matches = searchHistory(history, searchTerm);
52+
53+
if (matches.isEmpty()) {
54+
System.out.println("No matches found for: '" + searchTerm + "'");
55+
System.out.println();
56+
continue;
57+
}
58+
59+
// Display matches with numbers
60+
System.out.println("\nMatches for '" + searchTerm + "':");
61+
for (int i = 0; i < Math.min(matches.size(), 10); i++) {
62+
System.out.println(" " + (i + 1) + ". " + matches.get(i));
63+
}
64+
65+
if (matches.size() > 10) {
66+
System.out.println(" ... and " + (matches.size() - 10) + " more");
67+
}
68+
69+
// Ask user to select or refine
70+
System.out.println();
71+
System.out.print("Select number to copy (1-" + Math.min(matches.size(), 10) +
72+
"), or press Enter to search again: ");
73+
String selection = scanner.nextLine().trim();
74+
75+
if (selection.isEmpty()) {
76+
continue;
77+
}
78+
79+
try {
80+
int index = Integer.parseInt(selection) - 1;
81+
if (index >= 0 && index < Math.min(matches.size(), 10)) {
82+
String selectedCommand = matches.get(index);
83+
System.out.println("\nSelected command: " + selectedCommand);
84+
System.out.println("(You can now run this command by typing it at the prompt)");
85+
break;
86+
} else {
87+
System.out.println("Invalid selection. Try again.");
88+
}
89+
} catch (NumberFormatException e) {
90+
System.out.println("Invalid input. Enter a number or press Enter.");
91+
}
92+
93+
System.out.println();
94+
}
95+
}
96+
97+
/**
98+
* Search history for commands containing the search term.
99+
* Returns matches in reverse order (most recent first).
100+
*/
101+
private List<String> searchHistory(List<String> history, String searchTerm) {
102+
String lowerSearch = searchTerm.toLowerCase();
103+
104+
// Search in reverse order (most recent first)
105+
return history.stream()
106+
.filter(cmd -> cmd.toLowerCase().contains(lowerSearch))
107+
.collect(Collectors.collectingAndThen(
108+
Collectors.toList(),
109+
list -> {
110+
java.util.Collections.reverse(list);
111+
return list;
112+
}
113+
));
114+
}
115+
116+
@Override
117+
public String description() {
118+
return "Interactive history search (like Ctrl+R)";
119+
}
120+
121+
@Override
122+
public String usage() {
123+
return "isearch\n" +
124+
" Opens an interactive prompt to search command history.\n" +
125+
" Type your search term and select from matching commands.";
126+
}
127+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.mycmd.commands;
2+
3+
import com.mycmd.Command;
4+
import com.mycmd.ShellContext;
5+
import java.util.List;
6+
7+
/**
8+
* Command to search through command history.
9+
* Usage: searchhistory [search_term]
10+
*
11+
* If no search term is provided, shows all history.
12+
* If search term is provided, filters history to matching commands.
13+
*/
14+
public class SearchHistoryCommand implements Command {
15+
16+
@Override
17+
public void execute(String[] args, ShellContext context) {
18+
List<String> history = context.getHistory();
19+
20+
if (history.isEmpty()) {
21+
System.out.println("No command history available.");
22+
return;
23+
}
24+
25+
// If no search term provided, show all history
26+
if (args.length == 0) {
27+
System.out.println("Command History (use 'searchhistory <term>' to filter):");
28+
displayHistory(history, null);
29+
return;
30+
}
31+
32+
// Join all args as the search term (supports multi-word searches)
33+
String searchTerm = String.join(" ", args).toLowerCase();
34+
35+
System.out.println("Searching history for: '" + searchTerm + "'");
36+
System.out.println();
37+
38+
// Filter history
39+
List<String> matches = history.stream()
40+
.filter(cmd -> cmd.toLowerCase().contains(searchTerm))
41+
.toList();
42+
43+
if (matches.isEmpty()) {
44+
System.out.println("No matching commands found.");
45+
System.out.println("Tip: Search is case-insensitive and matches partial text.");
46+
} else {
47+
displayHistory(matches, searchTerm);
48+
System.out.println();
49+
System.out.println("Found " + matches.size() + " matching command(s).");
50+
}
51+
}
52+
53+
/**
54+
* Display history entries with line numbers.
55+
* Optionally highlights the search term if provided.
56+
*/
57+
private void displayHistory(List<String> commands, String searchTerm) {
58+
int maxDigits = String.valueOf(commands.size()).length();
59+
60+
for (int i = 0; i < commands.size(); i++) {
61+
String lineNum = String.format("%" + maxDigits + "d", i + 1);
62+
String command = commands.get(i);
63+
64+
// Highlight search term if provided (simple uppercase for visibility)
65+
if (searchTerm != null && !searchTerm.isEmpty()) {
66+
command = highlightTerm(command, searchTerm);
67+
}
68+
69+
System.out.println(" " + lineNum + " " + command);
70+
}
71+
}
72+
73+
/**
74+
* Simple highlighting by surrounding the search term with markers.
75+
* For terminal with color support, you could use ANSI codes instead.
76+
*/
77+
private String highlightTerm(String text, String term) {
78+
// Case-insensitive highlight
79+
int index = text.toLowerCase().indexOf(term.toLowerCase());
80+
if (index == -1) {
81+
return text;
82+
}
83+
84+
StringBuilder result = new StringBuilder();
85+
int lastIndex = 0;
86+
87+
while (index >= 0) {
88+
result.append(text, lastIndex, index);
89+
result.append("[");
90+
result.append(text, index, index + term.length());
91+
result.append("]");
92+
93+
lastIndex = index + term.length();
94+
index = text.toLowerCase().indexOf(term.toLowerCase(), lastIndex);
95+
}
96+
97+
result.append(text.substring(lastIndex));
98+
return result.toString();
99+
}
100+
101+
@Override
102+
public String description() {
103+
return "Search through command history";
104+
}
105+
106+
@Override
107+
public String usage() {
108+
return "searchhistory [search_term]\n" +
109+
" Examples:\n" +
110+
" searchhistory - Show all history\n" +
111+
" searchhistory dir - Find all 'dir' commands\n" +
112+
" searchhistory cd .. - Find all 'cd ..' commands";
113+
}
114+
}

0 commit comments

Comments
 (0)