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.
142 lines
4.2 KiB
142 lines
4.2 KiB
#!/usr/bin/env sh |
|
|
|
# This script generates a menu from the given list of items. Choosing an item runs the command for that item. |
|
# Usage: menu "Please choose" "choice one,choice two with\, a comma,item three" "ls -al,cat \"This string\, has a comma\",command3" |
|
# Use up/down arrow keys to select a menu item, then press Enter. Or cancel with ESC. |
|
# Todo: the menu only works with one-word items and commands. maybe this is a problem with loading the arrays? it should work with multi-word items and commands. |
|
|
|
. colors |
|
|
|
description=$1 |
|
#shift 1 |
|
#parse items array |
|
IFS=',' read -r -a items <<< "$2" |
|
items=("${items[@]//\\,/,}") |
|
declare -a items |
|
#parse commands array |
|
IFS=',' read -r -a commands <<< "$3" |
|
commands=("${commands[@]//\\,/,}") |
|
declare -a commands |
|
|
|
#items=("$@") |
|
#commands=("${items[@]:3}") |
|
#items=("$2") |
|
#commands=("$3") |
|
#read -r -a items <<< "$2" |
|
#read -r -a commands <<< "$3" |
|
|
|
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 |
|
echo $description |
|
|
|
# Menu loop |
|
while true; do |
|
display_menu |
|
handle_key $key |
|
done |