# !/bin/bash # MIT license, explained here: # http://www.tldrlegal.com/l/mit # Copyright (c) 2020 Cefan Daniel Rubin # # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # Latest: # https://github.com/cdrubin/bash_ui # internal variables _stdin="" _options="" _display_options="" _mode="single" _selected_line=1 _chosen="" _line_count=0 _top=0 _row=0 # ansi handling ESC="\0033[" SEP=";" ELLIPSIS="…" _sx=0; _sy=0 # shell _oldIFS=IFS IFS=$'\n' # read from stdin and don't block if empty if [ ! -t 0 ]; then read -d '' _stdin fi # default way of handling options _options=$_stdin # look for mode parameter if [ $# -gt 0 ]; then if [[ $1 =~ multi ]]; then # set mode and add multiple select line to options _mode=multiple _options=$_stdin$'\n'">" elif [[ $1 =~ clean ]]; then # leave less trace of ui in terminal _mode=clean fi fi # count number of lines now in _options _line_count=$(($(printf "%s\n" "$_options" | wc -l))) #echo $_line_count function gotoXY() { x=$1; y=$2 echo -en "${ESC}$((y))${SEP}$((x))H" } function setXY() { exec < /dev/tty local _oldstty=$(stty -g) stty raw -echo min 0 echo -en "\033[6n" > /dev/tty IFS=';' read -r -d R -a pos stty $_oldstty _sx=$((${pos[1]} - 1)) _sy=$((${pos[0]:2})) } function hideCursor() { echo -en "${ESC}?25l" } function showCursor() { echo -en "${ESC}?25h" } function set_row() { setXY _row=$((_sy - 1)) } function refresh() { # jump to top hideCursor gotoXY 0 $((_top + 1)) # disable wordwrap echo -ne "\033[?7l" _count=0 for _line in $_options; do _count=$((_count+1)) if [ $_count -eq $_selected_line ]; then echo -ne "\033[7m"; echo -n "$_line"; echo -e "\033[0m" else echo -ne "\033[K"; echo $_line fi done # enable wordwrap echo -ne "\033[?7h" showCursor } # print out the choices echo -ne "\x1b[?7l" # disable wordwrap printf "%s\n" "$_options" echo -ne "\x1b[?7h" # enable wordwrap # initialize set_row _top=$((_row - _line_count)) refresh _selected_line=1 while read -sn1 key; do if [ "$key" == $'\e' ]; then read -sn1 -t 1 k1; read -sn1 -t 1 k2; elif [ "$key" == "" ]; then if [ "$_mode" == "multiple" ]; then # if on the last option (multiple) then break if [ $_selected_line -eq $_line_count ]; then break # if on an option then add/remove it else _option_chosen=$( echo "${_options}" | sed "${_selected_line}q;d" ) # if _chosen contains _option_chosen then remove it if echo "$_chosen" | grep "^${_option_chosen}$" > /dev/null; then _chosen=$( echo "${_chosen}" | grep -v "^$_option_chosen\$" ) # otherwise add it else _chosen="$_chosen"$'\n'"$_option_chosen" # remove empty lines _chosen=$( echo "${_chosen}" | sed '/^$/d' ) fi _chosen_commas=$( echo "$_chosen" | awk -v ORS=', ' '1' | sed 's/, $//' ) _options=$_stdin$'\n'"> ${_chosen_commas}" refresh fi else _chosen=$( echo "${_options}" | sed "${_selected_line}q;d" ) break fi fi key+=${k1}${k2} case "$key" in $'\e[A'|$'\e0A') # up arrow if [ $_selected_line -gt 1 ]; then ((_selected_line--)); refresh; fi;; $'\e[B'|$'\e0B') # down arrow if [ $_selected_line -lt $_line_count ]; then ((_selected_line++)); refresh; fi;; esac done if [ "$_mode" == "clean" ]; then echo -e "\033[${_top};0H" # jump to beginning of line _top for i in $(seq ${_line_count}); do echo -e "\033[2K" # clear line done echo -e "\033[${_top};0H" # jump to beginning of line _top echo "$_chosen" fi echo "$_chosen" > /dev/stderr IFS=_oldIFS