You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
149 lines
4.2 KiB
149 lines
4.2 KiB
#!/usr/bin/env bash |
|
|
|
# This script generates a menu from the given list of items. Choosing an item runs the command for that item. |
|
# Usage: |
|
# change_my_password="Change my password%passwd" |
|
# list_users="List all users%cut -d: -f1 /etc/passwd" |
|
# exit="kill -2 $$" |
|
# menu "User management options:" "$change_my_password" "$list_users" "$exit" |
|
# Use up/down arrow keys to select a menu item, then press Enter. Or cancel with ESC. |
|
|
|
. colors |
|
|
|
if [ ${#} -lt 2 ]; then |
|
mud-menu |
|
exit 0 |
|
fi |
|
|
|
description=$1 |
|
shift |
|
|
|
# Parse args. Each arg is "name%command" |
|
declare -a items |
|
# declare -a descriptions # not displayed so presently disabled |
|
declare -a commands |
|
while [ ${#} -gt 0 ]; do |
|
IFS='%' read -r -a option <<< "$1" |
|
items+=("${option[0]}") |
|
#descriptions+=("$(option[1])") # not displayed so presently disabled |
|
commands+=("${option[1]}") |
|
shift |
|
done |
|
|
|
selected=0 |
|
displayed=0 |
|
|
|
# Calculate the position of the menu |
|
menu_x=$((0)) |
|
menu_y=$(fathom-cursor -y) |
|
|
|
# Calculate the number of rows that the menu will occupy |
|
num_rows=${#items[@]} |
|
|
|
# Calculate the maximum length of the item names |
|
# Todo: abstract this out into a max-length function in another script. I gave up on trying to find an elegant way to pass a list of strings with spaces. Even separating them with newlines didn't work. |
|
max_name_length=0 |
|
|
|
# Find the longest line |
|
for line in "${items[@]}"; do |
|
line_length=${#line} |
|
if [ $line_length -gt $max_name_length ]; then |
|
max_name_length=$line_length |
|
fi |
|
done |
|
|
|
# Make sure the menu items don't wrap, multiline menu items are not supported |
|
window_width=$(fathom-terminal -w) |
|
if [ $max_name_length -gt $window_width ]; then |
|
max_name_length=$window_width |
|
fi |
|
|
|
# Define the function to display the menu |
|
display_menu() { |
|
# Get the dimensions of the terminal window (again each time in case window is resized) |
|
window_width=$(fathom-terminal -w) |
|
window_height=$(fathom-terminal -h) |
|
|
|
# Recalculate the position of the menu |
|
menu_y=$(fathom-cursor -y) |
|
|
|
# Move the cursor to the correct position |
|
if [ $displayed -eq 0 ]; then |
|
# Don't move the cursor or clear the screen the first time |
|
displayed=1 |
|
else |
|
if [ $((menu_y + num_rows)) -ge $window_height ]; then |
|
menu_y=$((window_height)) |
|
fi |
|
move-cursor $menu_x $((menu_y - num_rows)) |
|
fi |
|
|
|
# Print the items and commands |
|
for i in "${!items[@]}"; do |
|
# Truncate the command to the width of the screen # Todo: come up with a better solution that allows the user to see the full command. |
|
max_command_length=$(( $window_width - $max_name_length - 3 )) # -3 is for the > and the space between item and command |
|
truncated_command=$(echo "${commands[$i]}" | cut -c -$max_command_length) |
|
if [ $i -eq $selected ]; then |
|
printf "${CYAN}> %-${max_name_length}s${RESET} ${GREY}%s${RESET}\n" "${items[$i]}" "$truncated_command" |
|
else |
|
# Print the menu item plus extra spaces to cover up its command, since it's not selected |
|
printf " %-${max_name_length}s %*s\n" "${items[$i]}" "${#truncated_command}" |
|
fi |
|
done |
|
} |
|
|
|
# Define the function to handle key presses |
|
handle_key() { |
|
key=$(await-keypress) |
|
case "$key" in |
|
up) |
|
selected=$((selected - 1)) |
|
if [ $selected -lt 0 ]; then |
|
selected=$((num_rows - 1)) |
|
fi |
|
;; |
|
down) |
|
selected=$((selected + 1)) |
|
if [ $selected -ge $num_rows ]; then |
|
selected=0 |
|
fi |
|
;; |
|
enter) |
|
cursor-blink on |
|
command="${commands[$selected]}" |
|
printf "\n" |
|
eval $command |
|
exit 0 |
|
;; |
|
escape) |
|
cursor-blink on |
|
echo ESC |
|
exit |
|
;; |
|
esac |
|
} |
|
|
|
# Catch Ctrl-C and restore the cursor blink, so the user doesn't get stuck with an invisible cursor |
|
handle_ctrl_c() { |
|
trap 'cursor-blink on; exit' INT |
|
} |
|
|
|
handle_ctrl_c |
|
|
|
test_menu() { |
|
assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3" -v)" "Please choose\n1) item1\n2) item2\n3) item3\n" "menu -v output differs" |
|
assert_equal "$(./menu "Please choose" "item1 item2 item3" "ls cat command3")" "1) item1\n2) item2\n3) item3\n" "menu output differs" |
|
} |
|
|
|
cursor-blink off |
|
|
|
# Display the menu's description |
|
if [ -n "$description" ]; then |
|
printf "$description\n" |
|
fi |
|
|
|
# Menu loop |
|
while true; do |
|
display_menu |
|
handle_key $key |
|
done |