1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 16:14:44 +00:00
Files
Mole/lib/better_menu.sh
2025-09-25 20:22:51 +08:00

248 lines
6.6 KiB
Bash
Executable File

#!/bin/bash
# Better menu system with proper terminal handling
# Uses tried-and-true approach for better compatibility
# Terminal state management
save_terminal() {
stty -g 2>/dev/null || true
}
restore_terminal() {
stty "$(save_terminal)" 2>/dev/null || true
printf '\033[?25h' >&2 # Show cursor
printf '\033[0m' >&2 # Reset colors
}
# Read a single key (handles arrow keys properly)
read_key() {
local key
read -rsn1 key
case "$key" in
$'\033') # ESC sequence
read -rsn2 key 2>/dev/null || key=""
case "$key" in
'[A') echo "UP" ;;
'[B') echo "DOWN" ;;
*) echo "ESC" ;;
esac
;;
' ') echo "SPACE" ;;
'') echo "ENTER" ;;
'q'|'Q') echo "QUIT" ;;
*) echo "OTHER" ;;
esac
}
# Multi-select menu with proper pagination
multi_select_menu() {
local title="$1"
shift
local -a items=("$@")
if [[ ${#items[@]} -eq 0 ]]; then
echo "Error: No items provided" >&2
return 1
fi
local -a selected=()
local current=0
local page_size=10
local total=${#items[@]}
# Initialize selection array
for ((i = 0; i < total; i++)); do
selected[i]=false
done
# Save terminal state
local saved_state=""
saved_state=$(save_terminal)
trap 'test -n "$saved_state" && stty "$saved_state" 2>/dev/null; restore_terminal' EXIT INT TERM
while true; do
# Calculate pagination
local start_page=$((current / page_size))
local start_idx=$((start_page * page_size))
local end_idx=$((start_idx + page_size - 1))
if [[ $end_idx -ge $total ]]; then
end_idx=$((total - 1))
fi
# Clear screen and show header
printf '\033[2J\033[H' >&2
echo "┌─── $title ───┐" >&2
echo "│ Found $total items (Page $((start_page + 1)) of $(((total + page_size - 1) / page_size))) │" >&2
echo "└─────────────────────────────────────────────┘" >&2
echo "" >&2
# Show items for current page
for ((i = start_idx; i <= end_idx; i++)); do
local marker=" "
local checkbox="☐"
if [[ $i -eq $current ]]; then
marker="▶ "
fi
if [[ ${selected[i]} == "true" ]]; then
checkbox="☑"
fi
printf "%s%s %s\n" "$marker" "$checkbox" "${items[i]}" >&2
done
echo "" >&2
echo "Controls: ↑/↓=Navigate Space=Select/Deselect Enter=Confirm Q=Quit" >&2
# Show selection summary
local count=0
for ((i = 0; i < total; i++)); do
if [[ ${selected[i]} == "true" ]]; then
((count++))
fi
done
echo "Selected: $count items" >&2
echo "" >&2
# Read key
local key
key=$(read_key)
case "$key" in
"UP")
((current--))
if [[ $current -lt 0 ]]; then
current=$((total - 1))
fi
;;
"DOWN")
((current++))
if [[ $current -ge $total ]]; then
current=0
fi
;;
"SPACE")
if [[ ${selected[current]} == "true" ]]; then
selected[current]=false
else
selected[current]=true
fi
;;
"ENTER")
# Build result string
local result=""
for ((i = 0; i < total; i++)); do
if [[ ${selected[i]} == "true" ]]; then
result="$result $i"
fi
done
# Clean up and return
restore_terminal
echo "${result# }" # Remove leading space
return 0
;;
"QUIT"|"ESC")
restore_terminal
return 1
;;
esac
done
}
# Simple single-select menu
single_select_menu() {
local title="$1"
shift
local -a items=("$@")
if [[ ${#items[@]} -eq 0 ]]; then
echo "Error: No items provided" >&2
return 1
fi
local current=0
local total=${#items[@]}
# Save terminal state
local saved_state=""
saved_state=$(save_terminal)
trap 'test -n "$saved_state" && stty "$saved_state" 2>/dev/null; restore_terminal' EXIT INT TERM
while true; do
# Clear screen and show header
printf '\033[2J\033[H' >&2
echo "┌─── $title ───┐" >&2
echo "│ Choose one of $total items │" >&2
echo "└────────────────────────────┘" >&2
echo "" >&2
# Show all items
for ((i = 0; i < total; i++)); do
local marker=" "
if [[ $i -eq $current ]]; then
marker="▶ "
fi
printf "%s%s\n" "$marker" "${items[i]}" >&2
done
echo "" >&2
echo "Controls: ↑/↓=Navigate Enter=Select Q=Quit" >&2
echo "" >&2
# Read key
local key
key=$(read_key)
case "$key" in
"UP")
((current--))
if [[ $current -lt 0 ]]; then
current=$((total - 1))
fi
;;
"DOWN")
((current++))
if [[ $current -ge $total ]]; then
current=0
fi
;;
"ENTER")
restore_terminal
echo "$current"
return 0
;;
"QUIT"|"ESC")
restore_terminal
return 1
;;
esac
done
}
# Demo function for testing
demo() {
echo "=== Multi-select Demo ===" >&2
local result
result=$(multi_select_menu "Test Multi-Select" "Option 1" "Option 2" "Option 3" "Option 4" "Option 5")
if [[ $? -eq 0 ]]; then
echo "You selected indices: $result" >&2
else
echo "Selection cancelled" >&2
fi
echo "" >&2
echo "=== Single-select Demo ===" >&2
result=$(single_select_menu "Test Single-Select" "Choice A" "Choice B" "Choice C")
if [[ $? -eq 0 ]]; then
echo "You selected index: $result" >&2
else
echo "Selection cancelled" >&2
fi
}
# Run demo if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
demo
fi