mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 19:09:43 +00:00
300 lines
7.9 KiB
Bash
Executable File
300 lines
7.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Simple interactive menu selector with arrow key support
|
|
# No external dependencies, compatible with most bash versions
|
|
|
|
declare -a menu_options=()
|
|
declare -i selected=0
|
|
declare -i menu_size=0
|
|
|
|
# ANSI escape sequences
|
|
readonly ESC=$'\033'
|
|
readonly UP="${ESC}[A"
|
|
readonly DOWN="${ESC}[B"
|
|
readonly ENTER=$'\n'
|
|
readonly CLEAR_LINE="${ESC}[2K"
|
|
readonly HIDE_CURSOR="${ESC}[?25l"
|
|
readonly SHOW_CURSOR="${ESC}[?25h"
|
|
|
|
# Set terminal to raw mode for reading single characters
|
|
setup_terminal() {
|
|
stty -echo -icanon time 0 min 0
|
|
}
|
|
|
|
# Restore terminal to normal mode
|
|
restore_terminal() {
|
|
stty echo icanon
|
|
printf "%s" "$SHOW_CURSOR"
|
|
}
|
|
|
|
# Draw the menu
|
|
draw_menu() {
|
|
printf "%s" "$HIDE_CURSOR"
|
|
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
printf "\r%s" "$CLEAR_LINE"
|
|
|
|
if [[ $i -eq $selected ]]; then
|
|
printf "▶ \033[1;32m%s\033[0m\n" "${menu_options[i]}"
|
|
else
|
|
printf " %s\n" "${menu_options[i]}"
|
|
fi
|
|
done
|
|
|
|
# Move cursor back to the beginning
|
|
printf "${ESC}[%dA" $menu_size
|
|
}
|
|
|
|
# Read a single key
|
|
read_key() {
|
|
local key
|
|
IFS= read -r -n1 key 2>/dev/null
|
|
|
|
if [[ $key == $ESC ]]; then
|
|
# Handle escape sequences
|
|
IFS= read -r -n2 key 2>/dev/null
|
|
case "$key" in
|
|
'[A') echo "UP" ;;
|
|
'[B') echo "DOWN" ;;
|
|
*) echo "ESC" ;;
|
|
esac
|
|
elif [[ $key == "" ]]; then
|
|
echo "ENTER"
|
|
else
|
|
echo "$key"
|
|
fi
|
|
}
|
|
|
|
# Main menu function
|
|
# Usage: show_menu "Title" "option1" "option2" "option3" ...
|
|
show_menu() {
|
|
local title="$1"
|
|
shift
|
|
|
|
# Initialize menu options
|
|
menu_options=("$@")
|
|
menu_size=${#menu_options[@]}
|
|
selected=0
|
|
|
|
# Check if we have options
|
|
if [[ $menu_size -eq 0 ]]; then
|
|
echo "Error: No menu options provided" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Setup terminal
|
|
setup_terminal
|
|
trap restore_terminal EXIT INT TERM
|
|
|
|
# Display title
|
|
if [[ -n "$title" ]]; then
|
|
printf "\n\033[1;34m%s\033[0m\n\n" "$title"
|
|
fi
|
|
|
|
# Initial draw
|
|
draw_menu
|
|
|
|
# Main loop
|
|
while true; do
|
|
local key=$(read_key)
|
|
|
|
case "$key" in
|
|
"UP")
|
|
((selected--))
|
|
if [[ $selected -lt 0 ]]; then
|
|
selected=$((menu_size - 1))
|
|
fi
|
|
draw_menu
|
|
;;
|
|
"DOWN")
|
|
((selected++))
|
|
if [[ $selected -ge $menu_size ]]; then
|
|
selected=0
|
|
fi
|
|
draw_menu
|
|
;;
|
|
"ENTER")
|
|
# Clear the menu
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
printf "\r%s\n" "$CLEAR_LINE" >&2
|
|
done
|
|
printf "${ESC}[%dA" $menu_size >&2
|
|
|
|
# Show selection
|
|
printf "Selected: \033[1;32m%s\033[0m\n\n" "${menu_options[selected]}"
|
|
|
|
restore_terminal
|
|
return $selected
|
|
;;
|
|
"q"|"Q")
|
|
restore_terminal
|
|
echo "Cancelled." >&2
|
|
return 255
|
|
;;
|
|
[0-9])
|
|
# Jump to numbered option
|
|
local num=$((key - 1))
|
|
if [[ $num -ge 0 && $num -lt $menu_size ]]; then
|
|
selected=$num
|
|
draw_menu
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Multi-select menu function
|
|
# Usage: show_multi_menu "Title" "option1" "option2" "option3" ...
|
|
show_multi_menu() {
|
|
local title="$1"
|
|
shift
|
|
|
|
# Initialize menu options
|
|
menu_options=("$@")
|
|
menu_size=${#menu_options[@]}
|
|
selected=0
|
|
|
|
# Array to track selected items
|
|
declare -a selected_items=()
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
selected_items[i]=false
|
|
done
|
|
|
|
# Check if we have options
|
|
if [[ $menu_size -eq 0 ]]; then
|
|
echo "Error: No menu options provided" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Setup terminal
|
|
setup_terminal
|
|
trap restore_terminal EXIT INT TERM
|
|
|
|
# Display title
|
|
if [[ -n "$title" ]]; then
|
|
printf "\n\033[1;34m%s\033[0m\n" "$title" >&2
|
|
printf "\033[0;36mUse SPACE to select/deselect, ENTER to confirm, Q to quit\033[0m\n\n" >&2
|
|
fi
|
|
|
|
# Draw multi-select menu
|
|
draw_multi_menu() {
|
|
printf "%s" "$HIDE_CURSOR" >&2
|
|
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
printf "\r%s" "$CLEAR_LINE" >&2
|
|
|
|
local checkbox="☐"
|
|
if [[ ${selected_items[i]} == "true" ]]; then
|
|
checkbox="\033[1;32m☑\033[0m"
|
|
fi
|
|
|
|
if [[ $i -eq $selected ]]; then
|
|
printf "▶ %s \033[1;32m%s\033[0m\n" "$checkbox" "${menu_options[i]}" >&2
|
|
else
|
|
printf " %s %s\n" "$checkbox" "${menu_options[i]}" >&2
|
|
fi
|
|
done
|
|
|
|
# Move cursor back to the beginning
|
|
printf "${ESC}[%dA" $menu_size >&2
|
|
}
|
|
|
|
# Initial draw
|
|
draw_multi_menu
|
|
|
|
# Main loop
|
|
while true; do
|
|
local key=$(read_key)
|
|
|
|
case "$key" in
|
|
"UP")
|
|
((selected--))
|
|
if [[ $selected -lt 0 ]]; then
|
|
selected=$((menu_size - 1))
|
|
fi
|
|
draw_multi_menu
|
|
;;
|
|
"DOWN")
|
|
((selected++))
|
|
if [[ $selected -ge $menu_size ]]; then
|
|
selected=0
|
|
fi
|
|
draw_multi_menu
|
|
;;
|
|
" ")
|
|
# Toggle selection
|
|
if [[ ${selected_items[selected]} == "true" ]]; then
|
|
selected_items[selected]="false"
|
|
else
|
|
selected_items[selected]="true"
|
|
fi
|
|
draw_multi_menu
|
|
;;
|
|
"ENTER")
|
|
# Clear the menu
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
printf "\r%s\n" "$CLEAR_LINE" >&2
|
|
done
|
|
printf "${ESC}[%dA" $menu_size >&2
|
|
|
|
# Show selections to stderr so it doesn't interfere with return value
|
|
local has_selection=false
|
|
printf "Selected items:\n" >&2
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
if [[ ${selected_items[i]} == "true" ]]; then
|
|
printf " \033[1;32m%s\033[0m\n" "${menu_options[i]}" >&2
|
|
has_selection=true
|
|
fi
|
|
done
|
|
|
|
if [[ $has_selection == "false" ]]; then
|
|
printf " None\n" >&2
|
|
fi
|
|
printf "\n" >&2
|
|
|
|
restore_terminal
|
|
|
|
# Return selected indices as space-separated string
|
|
local result=""
|
|
for ((i = 0; i < menu_size; i++)); do
|
|
if [[ ${selected_items[i]} == "true" ]]; then
|
|
result="$result $i"
|
|
fi
|
|
done
|
|
echo "${result# }" # Remove leading space
|
|
return 0
|
|
;;
|
|
"q"|"Q")
|
|
restore_terminal
|
|
echo "Cancelled." >&2
|
|
return 255
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Example usage function
|
|
demo_menu() {
|
|
echo "=== Single Select Demo ==="
|
|
if show_menu "Choose an action:" "Install package" "Update system" "Clean cache" "Exit"; then
|
|
local choice=$?
|
|
echo "You selected option $choice"
|
|
fi
|
|
|
|
echo -e "\n=== Multi Select Demo ==="
|
|
local selections=$(show_multi_menu "Choose packages to install:" "git" "vim" "curl" "htop" "tree")
|
|
if [[ $? -eq 0 && -n "$selections" ]]; then
|
|
echo "Selected indices: $selections"
|
|
# Convert indices to actual values
|
|
local options=("git" "vim" "curl" "htop" "tree")
|
|
echo "Selected packages:"
|
|
for idx in $selections; do
|
|
echo " - ${options[idx]}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# If script is run directly, show demo
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
demo_menu
|
|
fi |