Created
January 27, 2023 16:26
-
-
Save am-kantox/c604803c4a507ba742111554197c6e98 to your computer and use it in GitHub Desktop.
Implementation for single and multiple selects in bash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function multiselect { | |
# credits: https://unix.stackexchange.com/a/673436/55106 (altered with single-choice by me) | |
# little helpers for terminal print control and key input | |
ESC=$( printf "\033") | |
cursor_blink_on() { printf "$ESC[?25h"; } | |
cursor_blink_off() { printf "$ESC[?25l"; } | |
cursor_to() { printf "$ESC[$1;${2:-1}H"; } | |
print_inactive() { printf "$2 $1 "; } | |
print_active() { printf "$2 $ESC[7m $1 $ESC[27m"; } | |
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; } | |
local return_value=$1 | |
local -n options=$2 | |
local -n defaults=$3 | |
local single_choice=$4 | |
local selected=() | |
for ((i=0; i<${#options[@]}; i++)); do | |
if [[ ${defaults[i]} = "true" ]]; then | |
selected+=("true") | |
else | |
selected+=("false") | |
fi | |
printf "\n" | |
done | |
# determine current screen position for overwriting the options | |
local lastrow=`get_cursor_row` | |
local startrow=$(($lastrow - ${#options[@]})) | |
# ensure cursor and input echoing back on upon a ctrl+c during read -s | |
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2 | |
cursor_blink_off | |
key_input() { | |
local key | |
IFS= read -rsn1 key 2>/dev/null >&2 | |
if [[ $key = "" ]]; then echo enter; fi; | |
if [[ $key = $'\x20' ]]; then echo space; fi; | |
if [[ $key = "k" ]]; then echo up; fi; | |
if [[ $key = "j" ]]; then echo down; fi; | |
if [[ $key = $'\x1b' ]]; then | |
read -rsn2 key | |
if [[ $key = [A || $key = k ]]; then echo up; fi; | |
if [[ $key = [B || $key = j ]]; then echo down; fi; | |
fi | |
} | |
toggle_option() { | |
local option=$1 | |
if [[ ${single_choice} == true ]]; then | |
for ((idx=0; idx<${#options[@]}; idx++)); do | |
selected[idx]=false | |
done | |
selected[option]=true | |
else | |
if [[ ${selected[option]} == true ]]; then | |
selected[option]=false | |
else | |
selected[option]=true | |
fi | |
fi | |
} | |
print_options() { | |
# print options by overwriting the last lines | |
local idx=0 | |
local lb='[' | |
local rb=']' | |
if [[ ${single_choice} == true ]]; then lb='('; rb=')'; fi | |
for option in "${options[@]}"; do | |
local prefix="$lb\e[1;31m✗\e[0m$rb" | |
if [[ ${selected[idx]} == true ]]; then | |
prefix="$lb\e[38;5;46m✔\e[0m$rb" | |
fi | |
cursor_to $(($startrow + $idx)) | |
if [ $idx -eq $1 ]; then | |
print_active "$option" "$prefix" | |
else | |
print_inactive "$option" "$prefix" | |
fi | |
((idx++)) | |
done | |
} | |
local active | |
if [[ ${single_choice} == true ]]; then | |
for ((idx=0; idx<${#options[@]}; idx++)); do | |
if [[ ${selected[$idx]} == true ]]; then active=$idx; fi | |
done | |
else | |
active=0 | |
fi | |
while true; do | |
print_options $active | |
# user key control | |
case `key_input` in | |
space) toggle_option $active;; | |
enter) if [[ ${single_choice} == true ]]; then toggle_option $active; fi; | |
print_options -1; break;; | |
up) ((active--)); | |
if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi;; | |
down) ((active++)); | |
if [ $active -ge ${#options[@]} ]; then active=0; fi;; | |
esac | |
done | |
# cursor position back to normal | |
cursor_to $lastrow | |
printf "\n" | |
cursor_blink_on | |
local option | |
if [[ ${single_choice} == true ]]; then | |
for ((idx=0; idx<${#options[@]}; idx++)); do | |
if [[ ${selected[$idx]} == true ]]; then option=${options[$idx]}; fi | |
done | |
eval $return_value='("${option}")' | |
else | |
eval $return_value='("${selected[@]}")' | |
fi | |
} | |
# Usage: multiselect | |
# my_options=( "Option 1" "Option 2" "Option 3" ) | |
# preselection=( "true" "true" "false" ) | |
# multiselect result my_options preselection false | |
# Usage: soloselect | |
# my_options=( "Option 1" "Option 2" "Option 3" ) | |
# preselection=( "false" "true" "false" ) | |
# multiselect result my_options preselection true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment