diff --git a/shell/select-option.sh b/shell/select-option.sh index 6f518ea..e74fdbc 100755 --- a/shell/select-option.sh +++ b/shell/select-option.sh @@ -1,22 +1,16 @@ #!/usr/bin/env bash -# Renders a text based list of options that can be selected by the -# user using up, down and enter keys and returns the chosen option. -# -# Arguments : list of options, maximum of 256 -# "opt1" "opt2" ... -# Return value: selected index (0 for opt1, 1 for opt2 ...) - - function select_option { local nformat local optcount=$# printf -v nformat "%%%dd. " ${#optcount} local max_opt=0 + local multiselected=() for opt; do if [[ "${#opt}" -gt $max_opt ]]; then max_opt=${#opt} fi + multiselected+=(0) done # little helpers for terminal print control and key input @@ -25,11 +19,13 @@ function select_option { cursor_blink_off() { printf "$ESC[?25l"; } cursor_to() { printf "$ESC[$1;${2:-1}H"; } if [[ $SELECT_MONOCHROME -eq 1 ]]; then - print_option() { printf " $1 "; } - print_selected() { printf " [$ESC[1m$1$ESC[0m] "; } + print_option() { printf " $ESC[30m%s $ESC[0m %s $ESC[30m%s$ESC[0m " "$2" "$1" "$3"; } + print_selected() { printf " $ESC[30m%s$ESC[0m{ $ESC[1m%s$ESC[0m }$ESC[30m%s$ESC[0m " "$2" "$1" "$3"; } + print_multiselected() { printf "$ESC[30;1m*$ESC[0m"; } else - print_option() { printf " $ESC[33m $1 $ESC[0m "; } - print_selected() { printf " [$ESC[37;1m$1$ESC[0m] "; } + print_option() { printf " $ESC[30m%s $ESC[33m %s $ESC[0m $ESC[30m%s$ESC[0m " "$2" "$1" "$3"; } + print_selected() { printf " $ESC[30m%s$ESC[35;1m{$ESC[37;1m %s $ESC[0;35;1m}$ESC[0;30m%s$ESC[0m " "$2" "$1" "$3"; } + print_multiselected() { printf "$ESC[32;1m*$ESC[0m"; } fi get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; } key_input() { read -s -n1 key1 2>/dev/null >&2 @@ -39,7 +35,11 @@ function select_option { fi if [[ $key1$key2 = $ESC[A ]]; then echo up; return; fi if [[ $key1$key2 = $ESC[B ]]; then echo down; return; fi + if [[ $key1$key2 = $ESC[C ]]; then echo left; return; fi + if [[ $key1$key2 = $ESC[D ]]; then echo right; return; fi if [[ $key1 = $'\e' ]]; then echo esc; return; fi + if [[ $key1 = x ]]; then echo multiselect; return; fi + if [[ $key1 = q ]]; then echo esc; return; fi if [[ $key1 = "" ]]; then echo enter; return; fi } reset_display() { @@ -47,22 +47,35 @@ function select_option { } # initially print empty new lines (scroll down if at bottom of screen) for opt; do printf "\n"; done + # Extras for top and bottom + printf "\n\n" # determine current screen position for overwriting the options local lastrow=`get_cursor_row` - local startrow=$(($lastrow - $#)) + local startrow=$(( $lastrow - $# - 2 )) + local toprow="+~~--" + local bottomrow="--~~+" + local lborderchars="|:'" + local rborderchars="|:." # ensure cursor and input echoing back on upon a ctrl+c during read -s trap "cursor_blink_on; stty echo; printf '\n'; exit 1" 2 cursor_blink_off local selected=0 + while true; do # print options by overwriting the last lines local idx=0 local nidx + cursor_to $startrow + printf " $ESC[30m%s$ESC[0m" "$toprow" for opt; do + local lborder="${lborderchars:$idx:1}" + if [ -z "$lborder" ]; then lborder=" "; fi + local rborder="${rborderchars:$(( $optcount - $idx - 1)):1}" + if [ -z "$rborder" ]; then rborder=" "; fi if [[ $SELECT_UNDERSCORES -eq 1 ]]; then opt=${opt//_/ } fi @@ -70,14 +83,21 @@ function select_option { printf -v nidx "$nformat" $(( idx + 1 )) fi printf -v padopt "%-${max_opt}s" "$opt" - cursor_to $(($startrow + $idx)) + cursor_to $(($startrow + $idx + 1)) if [ $idx -eq $selected ]; then - print_selected "$nidx$padopt" + print_selected "$nidx$padopt" "$lborder" "$rborder" else - print_option "$nidx$padopt" + print_option "$nidx$padopt" "${lborder}" "$rborder" + fi + if (( ${multiselected[$idx]} )); then + cursor_to $(($startrow + $idx + 1)) 5 + print_multiselected fi ((idx++)) done + cursor_to $(($startrow + $idx + 1)) + local bottomlength=$(( 8 + $max_opt + ${#nidx} )) + printf "$ESC[30m%${bottomlength}s$ESC[0m" "$bottomrow" # user key control local user_input=`key_input` @@ -88,6 +108,7 @@ function select_option { if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;; down) ((selected++)); if [ $selected -ge $# ]; then selected=0; fi;; + multiselect) if (( $SELECT_MULTI )); then multiselected[$selected]=$(( 1 - ${multiselected[$selected]} )); fi;; [0-9]) selected=$(( $user_input - 1 )) ;; esac done @@ -95,37 +116,77 @@ function select_option { # cursor position back to normal cursor_to $lastrow reset_display - if [[ -n "$SELECT_TOFILE" ]]; then - local idx=0 + local returnvalue=0 + local idx=0 + if (( $SELECT_MULTI )); then + if [[ -n "$SELECT_TOFILE" ]]; then printf "" > "$SELECT_TOFILE"; fi for opt; do - if [ $idx -eq $selected ]; then - echo "$opt" > "$SELECT_TOFILE" + if (( ${multiselected[$idx]} )); then + if [[ -n "$SELECT_TOFILE" ]]; then echo "$opt" >> "$SELECT_TOFILE"; fi + returnvalue=$(( $returnvalue + 2**$idx )) fi ((idx++)) done + else + # Single choice + returnvalue=$selected + if [[ -n "$SELECT_TOFILE" ]]; then + for opt; do + if [ $idx -eq $selected ]; then + echo "$opt" > "$SELECT_TOFILE" + fi + ((idx++)) + done + fi fi - return $(( 10 + $selected )) + + return $(( $returnvalue + 10 )) } [[ $_ == $0 ]] && { _help() { SELF=$( basename "$0" ) - echo "Select Option + echo "Select Option [Pure BASH] + Adapted from: https://unix.stackexchange.com/questions/146570/arrow-key-enter-menu Usage: $SELF [-n] [--nc] [-o File] [-_] list of options - -n Number list + -n Numbered list + -m Multiple choice selection. Note that only 7 options can be encoded + reliably in return value. Use File output for more options. --nc No colors - -o File Save the choice in a file - -_ Print spaces instead of underscores + -o File Save the choice(s) in a file. + -_ Print spaces instead of underscores, workaround for + shell arguments with spaces. - Returns - (10 + index) of selection (first option = 10) - 1 if ctrl-c or ESC pressed + User interface: + Enter Select option + Esc / Ctrl-c Exit + x Multi-selection + + Returns: + Single selection: + (10 + index) of selection (first option = 10) + 1 if ctrl-c or ESC pressed + If output file is defined, choice is printed there. + Multiple selection: + (10 + 2**index) encoded multiple values (no options = 10). See example. + 1 if ctrl-c or ESC pressed + All choices separately in the output File. Example: - $SELF -n one two three - EC=\$? - [[ \$EC -lt 10 ]] && { echo Exited..; } || { echo You selected \$(( \$EC - 9 )); } + Single choice: + $SELF -n one two three + EC=\$? + [[ \$EC -lt 10 ]] && { echo Exited..; } || { echo You selected \$(( \$EC - 9 )); } + + Multi choice: + $SELF -m one two three four five six seven -o choice.txt + C=\$?; C=\$(( \$C - 10 )) + if (( \$C == 0 )); then printf 'No selection'; else printf 'Selections: '; fi + for b in 7 6 5 4 3 2 1 0; do + if (( \$C >= 2**\$b )); then printf '%d, ' \$(( \$b + 1 )); C=\$(( \$C - 2**\$b )); fi + done; printf '\\n' + " exit } @@ -133,6 +194,7 @@ _help() { SELECT_MONOCHROME=0 SELECT_NUMBERS=0 SELECT_UNDERSCORES=0 +SELECT_MULTI=0 SELECT_TOFILE="" for (( i=1; i<=$#; i++ )); do value=${!i} @@ -140,6 +202,7 @@ for (( i=1; i<=$#; i++ )); do [[ "$value" = "-h" ]] && _help [[ "$value" = "--help" ]] && _help [[ "$value" = "--nc" ]] && { SELECT_MONOCHROME=1; continue; } + [[ "$value" = "-m" ]] && { SELECT_MULTI=1; continue; } [[ "$value" = "-n" ]] && { SELECT_NUMBERS=1; continue; } [[ "$value" = "-_" ]] && { SELECT_UNDERSCORES=1; continue; } [[ "$value" = "-o" ]] && { SELECT_TOFILE="${!j}"; ((i++)); continue; }