import java.util.Collections;
import java.util.List;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
/**
* Prompts the user to select an option from a list using fuzzy matching and cursor keys.
* <p>
*/
public class Menu {
+ /**
+ * Presents an interactive menu; returns selection or null
+ */
public static String askSelection(String prompt, List<String> options) throws IOException {
if (options == null || options.isEmpty()) {
throw new IllegalArgumentException("Options list cannot be empty");
while (true) {
- List<String> filtered = filterOptions(options, filterString);
+ List<String> filteredOptions = filterOptions(options, filterString);
// Clamp selected to valid range
- selectedIndex = Math.max(0, Math.min(selectedIndex, filtered.size() - 1));
+ selectedIndex = max(0, min(selectedIndex, filteredOptions.size() - 1));
- updateDisplay(prompt, filtered, selectedIndex, filterString, lastTotalLines);
- lastTotalLines = getCurrentTotalLines(options, filterString);
+ lastTotalLines = updateDisplay(prompt, filteredOptions, selectedIndex, filterString, lastTotalLines);
- int c = System.in.read();
+ int c = System.in.read(); // read a single character from user input
// Handle Escape key or arrow keys
if (c == 27) { // ESC
// Handle Enter key
else if (c == 13 || c == 10) {
- if (filtered.isEmpty()) {
+ if (filteredOptions.isEmpty()) {
return null;
}
- return filtered.get(selectedIndex);
+ return filteredOptions.get(selectedIndex);
}
// Handle Backspace (8 or 127)
* @return filtered list of options that match the fuzzy pattern
*/
private static List<String> filterOptions(List<String> options, String filter) {
- if (filter == null || filter.isEmpty()) {
- return new ArrayList<>(options);
- }
+ if (filter == null || filter.isEmpty()) return new ArrayList<>(options);
- List<String> filtered = new ArrayList<>();
- for (String option : options) {
- if (matchesFuzzy(filter, option)) {
- filtered.add(option);
- }
- }
- return filtered;
+ List<String> result = new ArrayList<>();
+
+ for (String option : options)
+ if (matchesFuzzy(filter, option))
+ result.add(option);
+
+ return result;
}
/**
filter = filter.toLowerCase();
option = option.toLowerCase();
int filterIndex = 0;
- for (char c : option.toCharArray()) {
- if (filterIndex < filter.length() && c == filter.charAt(filterIndex)) {
+
+ for (char c : option.toCharArray())
+ if (filterIndex < filter.length() && c == filter.charAt(filterIndex))
filterIndex++;
- }
- }
+
return filterIndex == filter.length();
}
-
/**
- * Updates the display based on the current state and returns the adjusted selected index.
+ * Updates the display based on the current state.
+ * @return the total number of lines in the display
*/
- private static void updateDisplay(String prompt, List<String> filtered, int selected, String filter, int lastTotalLines) {
+ private static int updateDisplay(String prompt, List<String> filteredOptions, int selectedIndex, String filterString, int lastTotalLines) {
- if (filtered.isEmpty()) {
- filtered = Collections.singletonList("No matches found");
- }
+ if (filteredOptions.isEmpty()) filteredOptions = Collections.singletonList("No matches found");
- // ────────────────────────────────────────────────
- // 1. Position cursor at the START of our display area
- // ────────────────────────────────────────────────
- if (lastTotalLines > 0) {
+ // Position cursor at the START of the display area
+ if (lastTotalLines > 0)
System.out.print("\033[" + lastTotalLines + "A"); // move cursor up to prompt line
- }
+
System.out.print("\r"); // go to column 1 (just in case)
- // ────────────────────────────────────────────────
- // 2. Clear from here downward (removes old leftover lines perfectly)
- // ────────────────────────────────────────────────
+ // Clear from here downward (removes old leftover lines perfectly)
System.out.print("\033[J"); // ESC [ J = clear from cursor to end of screen
- // ────────────────────────────────────────────────
- // 3. Print fresh content
- // ────────────────────────────────────────────────
+ // Print fresh content:
// Prompt + current filter (on one line)
- System.out.println(prompt + ": " + filter);
+ System.out.println(prompt + ": " + filterString);
// Menu items
- for (int i = 0; i < filtered.size(); i++) {
- String line = (i == selected ? "> " : " ") + filtered.get(i);
+ for (int i = 0; i < filteredOptions.size(); i++) {
+ String line = (i == selectedIndex ? "> " : " ") + filteredOptions.get(i);
System.out.print("\r");
System.out.println(line);
}
System.out.print("\r");
System.out.flush(); // ensure immediate display
- }
- private static int getCurrentTotalLines(List<String> options, String filter) {
- List<String> filtered = filterOptions(options, filter);
- int menuLines = filtered.isEmpty() ? 1 : filtered.size();
- return 1 + menuLines; // 1 for prompt line + menu lines
+ return filteredOptions.size() + 1; // 1 for prompt line + menu lines
}
}