Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/com/mycmd/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ private CommandNames() {}
private static final String WHOAMI = "whoami";
private static final String WMIC = "wmic";
private static final String XCOPY = "xcopy";
private static final String SEARCHHISTORY = "searchhistory";
private static final String ISEARCH = "isearch";
}

private static void registerCommands(Map<String, Command> commands) {
Expand Down Expand Up @@ -230,5 +232,7 @@ private static void registerCommands(Map<String, Command> commands) {
commands.put(CommandNames.WHOAMI, new WhoamiCommand());
commands.put(CommandNames.WMIC, new WmicCommand());
commands.put(CommandNames.XCOPY, new XcopyCommand());
commands.put(CommandNames.SEARCHHISTORY, new SearchHistoryCommand());
commands.put(CommandNames.ISEARCH, new InteractiveSearchCommand());
}
}
125 changes: 125 additions & 0 deletions src/main/java/com/mycmd/commands/InteractiveSearchCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.mycmd.commands;

import com.mycmd.Command;
import com.mycmd.ShellContext;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

/**
* Interactive history search command (similar to Ctrl+R in bash).
* Usage: isearch
*
* Provides an interactive prompt where users can:
* - Type to filter history in real-time
* - Navigate through matches with numbers
* - Execute a selected command
*/
public class InteractiveSearchCommand implements Command {

@Override
public void execute(String[] args, ShellContext context) {
List<String> history = context.getHistory();

if (history.isEmpty()) {
System.out.println("No command history available.");
return;
}

Scanner scanner = new Scanner(System.in);
System.out.println("=== Interactive History Search ===");
System.out.println("Type to search, 'q' to quit");
System.out.println();

while (true) {
System.out.print("search> ");
String searchTerm = scanner.nextLine().trim();

if (searchTerm.equalsIgnoreCase("q") || searchTerm.equalsIgnoreCase("quit")) {
System.out.println("Search cancelled.");
break;
}

if (searchTerm.isEmpty()) {
System.out.println("Enter a search term or 'q' to quit.");
continue;
}

// Search and display results
List<String> matches = searchHistory(history, searchTerm);

if (matches.isEmpty()) {
System.out.println("No matches found for: '" + searchTerm + "'");
System.out.println();
continue;
}

// Display matches with numbers
System.out.println("\nMatches for '" + searchTerm + "':");
for (int i = 0; i < Math.min(matches.size(), 10); i++) {
System.out.println(" " + (i + 1) + ". " + matches.get(i));
}

if (matches.size() > 10) {
System.out.println(" ... and " + (matches.size() - 10) + " more");
}

// Ask user to select or refine
System.out.println();
System.out.print("Select number to copy (1-" + Math.min(matches.size(), 10) +
"), or press Enter to search again: ");
String selection = scanner.nextLine().trim();

if (selection.isEmpty()) {
continue;
}

try {
int index = Integer.parseInt(selection) - 1;
if (index >= 0 && index < Math.min(matches.size(), 10)) {
String selectedCommand = matches.get(index);
System.out.println("\nSelected command: " + selectedCommand);
System.out.println("(You can now run this command by typing it at the prompt)");
break;
} else {
System.out.println("Invalid selection. Try again.");
}
} catch (NumberFormatException e) {
System.out.println("Invalid input. Enter a number or press Enter.");
}

System.out.println();
}
}

/**
* Search history for commands containing the search term.
* Returns matches in reverse order (most recent first).
*/
private List<String> searchHistory(List<String> history, String searchTerm) {
String lowerSearch = searchTerm.toLowerCase();

// Search in reverse order (most recent first)
return history.stream()
.filter(cmd -> cmd.toLowerCase().contains(lowerSearch))
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> {
java.util.Collections.reverse(list);
return list;
}
));
}

@Override
public String description() {
return "Interactive history search (like Ctrl+R)";
}

@Override
public String usage() {
return "isearch\n" +
" Opens an interactive prompt to search command history.\n" +
" Type your search term and select from matching commands.";
}
}
114 changes: 114 additions & 0 deletions src/main/java/com/mycmd/commands/SearchHistoryCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.mycmd.commands;

import com.mycmd.Command;
import com.mycmd.ShellContext;
import java.util.List;

/**
* Command to search through command history.
* Usage: searchhistory [search_term]
*
* If no search term is provided, shows all history.
* If search term is provided, filters history to matching commands.
*/
public class SearchHistoryCommand implements Command {

@Override
public void execute(String[] args, ShellContext context) {
List<String> history = context.getHistory();

if (history.isEmpty()) {
System.out.println("No command history available.");
return;
}

// If no search term provided, show all history
if (args.length == 0) {
System.out.println("Command History (use 'searchhistory <term>' to filter):");
displayHistory(history, null);
return;
}

// Join all args as the search term (supports multi-word searches)
String searchTerm = String.join(" ", args).toLowerCase();

System.out.println("Searching history for: '" + searchTerm + "'");
System.out.println();

// Filter history
List<String> matches = history.stream()
.filter(cmd -> cmd.toLowerCase().contains(searchTerm))
.toList();

if (matches.isEmpty()) {
System.out.println("No matching commands found.");
System.out.println("Tip: Search is case-insensitive and matches partial text.");
} else {
displayHistory(matches, searchTerm);
System.out.println();
System.out.println("Found " + matches.size() + " matching command(s).");
}
}

/**
* Display history entries with line numbers.
* Optionally highlights the search term if provided.
*/
private void displayHistory(List<String> commands, String searchTerm) {
int maxDigits = String.valueOf(commands.size()).length();

for (int i = 0; i < commands.size(); i++) {
String lineNum = String.format("%" + maxDigits + "d", i + 1);
String command = commands.get(i);

// Highlight search term if provided (simple uppercase for visibility)
if (searchTerm != null && !searchTerm.isEmpty()) {
command = highlightTerm(command, searchTerm);
}

System.out.println(" " + lineNum + " " + command);
}
}

/**
* Simple highlighting by surrounding the search term with markers.
* For terminal with color support, you could use ANSI codes instead.
*/
private String highlightTerm(String text, String term) {
// Case-insensitive highlight
int index = text.toLowerCase().indexOf(term.toLowerCase());
if (index == -1) {
return text;
}

StringBuilder result = new StringBuilder();
int lastIndex = 0;

while (index >= 0) {
result.append(text, lastIndex, index);
result.append("[");
result.append(text, index, index + term.length());
result.append("]");

lastIndex = index + term.length();
index = text.toLowerCase().indexOf(term.toLowerCase(), lastIndex);
}

result.append(text.substring(lastIndex));
return result.toString();
}

@Override
public String getDescription() {
return "Search through command history";
}

@Override
public String getUsage() {
return "searchhistory [search_term]\n" +
" Examples:\n" +
" searchhistory - Show all history\n" +
" searchhistory dir - Find all 'dir' commands\n" +
" searchhistory cd .. - Find all 'cd ..' commands";
}
}
Loading