Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMPROVMENTS/WIP] Spitballing Readability & Visual Improvmenets + Refactor Ideas #25

Open
Nsomnia opened this issue Nov 26, 2024 · 0 comments

Comments

@Nsomnia
Copy link

Nsomnia commented Nov 26, 2024

The refactor is much easier to read! I didn't even notice it existed and have been scouring git for repository to splice together that are neither too complex, nor too simple for my needs seeing as multisekect is rare in a bash/posix menu system.

Guaranteed to be errors to fix, shellcheck pukes when I import the original isn't vscodium that I'll have to take care of, and way more to do, but a rough draft.

Updates as life allows and perhaps I'll fork/pr when I feel it's useable for my scripting needs.

Hopefully of nothing else refactoring for readability with a English speaking daddy world and simple comments is a help for now. Copy pasta your friend!

I came across this new repo I was the first star in that's very interesting a bash script "generator/constructor/boilerplate'r" named vesta or something, should be in my stars, that's very interesting yet multiselect is rare. Who wants dialog or whip?! His way of key press reads, as well as the selector repos implimentstion, is clean and modular for near ant key press.

Show this off to Chris Titus tech at some point in the future for some actual pull requests. wink

(Edits are done on mobile in bed and to my needs with residuals still in here. Didnt finish)

#!/usr/bin/env bash
#===============================================================================
# SOURCE: https://github.com/pedro-hs/checkbox.sh
#
# DESCRIPTION: Create customizable checkbox for the terminal.
#              Check out how to use on README.md at <https://github.com/pedro-hs/checkbox.sh>
#===============================================================================

## Constants (readonly)
# ☆◇♡+÷#<>•°|《》▪︎¤○●■ as your TERM supports. linux-term compatible ligs÷icos in bashtop
declare -r SELECTED="✔"  UNSELECTED="[ ]"
declare -r ANSI_ESC="\033" BLACK_FG="${ANSI_ESC}[30m" 
GRAY_BG="${ANSI_ESC}[47m" WHITE="${ANSI_ESC}[37m" BLUE="${ANSI_ESC}[34m" RED="${ANSI_ESC}[36m" GREEN="${ANSI_ESC}[32m"
# is double too for active/header/footer plus tees
declare -r BOX_TOP_L="┌" BOX_TOP_R="┐" BOX_BOT_L="└" BOX_BOT_R="┘" BOX_HOR="─" BOX_VER="│"

## Variables
# ints (strings)
declare cursor=0 options_length=0 terminal_width=0 start_page=0 end_page=0
# bool
declare has_multiple_options=false has_index_result=false show_copy_message=false show_help=false
# arrays
declare -a options=("${DEFAULT_OPTIONS[@]}") selected_options=() checkbox_output=()
# strings 
declare content="" message="" separator="" options_input=""
declare color=$WHITE

# Array Utilities
array_remove_value() {
    local value="$1" && shift
    local new_array=()
 
   for item in "$@"; do
        [[ "$item" != "$value" ]] && new_array+=("$item")
    done

    echo "${new_array[@]}"   # TODO printf 》echo
}

array_contains() {
    local element="$1" && shift
    local elements=("$@")
    for e in "${elements[@]}"; do
        [[ "$e" == "$element" ]] && return 0
    done
    return 1
}

# Help Page
display_help() {
    cat <<EOF
# Keybinds:
    [UP/DOWN ARROWS], [HOME/END], [PAGE UP/DOWN] or k/j, g/G, u/d: Move cursor
    o or [ENTER]: Close and return selected options
    x or [SPACE]: Select current option
    q or [ESC]: Exit
    r: Refresh renderization
                (press q to quit)
EOF
    while true; do
        local key=$(get_pressed_key)
        case $key in
            _esc|q) return ;;
        esac
    done
}

# Checkbox Rendering
render_options() {
    declare content && content=""
    local start=$start_page end=$end_page
    
    for (( index = start; index <= end; index++ )); do
        if [[ $index -ge 0 && $index < $options_length ]]; then   # TODO RH quotes shellcheck
            local option="${options[index]}"
            if [[ $index -eq $cursor ]]; then
                set_line_color
            fi
            render_option "$index" "$option" && color=$WHITE
        fi
    done
}

render_option() {
    local index="$1" option="$2"
    if array_contains "$index" "${selected_options[@]}"; then
        content+="$color    $SELECTED $option\n"    # TODO content of what, selectable itsms? Descriptor 
    else
        content+="$color    $UNSELECTED $option\n"
    fi
}

set_line_color() {
    if [[ "$has_multiple_options" = true ]]; then
        if [[ "$select_mode_on" = true ]]; then    # TODO I personally won't need these, or yellow+grey?
            color=$GREEN!!
        elif [[ "$unselect_mode_on" = true ]]; then
            color=$RED
        else
            color=$BLUE
        fi
    fi
}

# Option Handling
set_options() {
    if [[ -n "$options_input" ]]; then
        local escaped_options=$(escape_options_ascii_signs)
        IFS="|" read -ra options <<< "$escaped_options"
    fi
}

escape_options_ascii_signs() {
# TODO micro function required used once?
    local options_str="${options_input#*=}"
    options_str=$(echo "$options_str" | sed 's/\\[a-zA-Z]//g')
    echo "$options_str"
}

# Terminal and Interface Management
validate_terminal_size() {
    if [[ $terminal_width -lt 8 ]]; then
        printf "Resize the terminal to at least 8 lines and press r to refresh. Current width: $terminal_width lines.\n"
    fi
}

construct_footer() {
    local footer="$(( $cursor + 1 ))/$options_length"
    if [[ "$has_multiple_options" = true ]]; then
        footer+="  |  ${#selected_options[@]} selected"
    fi
# TODO I won't need this copy pasta in tty isn't Chad 
    if [[ "$show_copy_message" = true ]]; then
        footer+="  |  current line copied"
        show_copy_message=false
    fi
    # TODO arrays should have {}? (All over)
    echo "$footer"
}

render_checkbox() {
    # TODO use tput to rellace ansi in other places?
    terminal_width=$(tput lines)
    render_options

    local output && output=""
    if [[ -n "$message" ]]; then
        local header=$(center_text " $message ")
        output+="${BLACK_FG}${GRAY_BG}${BOX_TOP_L}$(printf '%0.s${BOX_HORI}' {1..$((${#header} - 2))})${BOX_TOP_R}${ANSI_ESC}[0m\n"
        output+="${BLACK_FG}${GRAY_BACKGROUND}${BOX_VERT} $message ${BOX_VERT}${ANSI_ESC}[0m\n"
        output+="${BLACK_FG}${GRAY_BG}${BOX_BOTTOM_L}$(printf '%0.s${BOX_HORI}' {1..$((${#header} - 2))})${BOX_BOTTOM_R}${ANSI_ESC}[0m\n"
    fi
    output+="$content"
    local footer=$(construct_footer)
    output+="${BLACK_FG}${GRAY_BG}${BOX_TOP_L}$(printf '%0.s${BOX_HORI}' {1..$((${#footer} - 2))})${BOX_TOP_R}${ANSI_ESC}[0m\n"
    output+="${BLACK_FG}${GRAY_BG}${BOX_VERT} $footer ${BOX_VERT}${ANSI_ESC}[0m\n"
    output+="${BLACK_FOREGROUND}${GRAY_BACKGROUND}${BOX_BOTTOM_LEFT}$(printf '%0.s${BOX_HORIZONTAL}' {1..$((${#footer} - 2))})${BOX_BOTTOM_RIGHT}${ANSI_ESCAPE}[0m\n"

    printf "$output"
}

clear_checkbox() {
    local header_footer_lines=3
    local checkbox_lines=$(($options_length + $header_footer_lines))
    local delete_lines_above="${ANSI_ESCAPE}[${checkbox_lines}A"
    printf "$delete_lines_above"
}

render_result() {
    for option in "${checkbox_output[@]}"; do
        echo "$option"
    done
}

# Key Press Handlers
toggle_select_mode() {
    if [[ "$has_multiple_options" = true ]]; then
        unselect_mode_on=false
        if [[ "$select_mode_on" = true ]]; then
            select_mode_on=false
        else
            select_mode_on=true
            if ! array_contains "$cursor" "${selected_options[@]}"; then
                selected_options+=("$cursor")
            fi
        fi
    fi
}

toggle_unselect_mode() {
    if [[ "$has_multiple_options" = true ]]; then
        select_mode_on=false
        if [[ "$unselect_mode_on" = true ]]; then
            unselect_mode_on=false
        else
            unselect_mode_on=true
            selected_options=($(array_remove_value "$cursor" "${selected_options[@]}"))
        fi
    fi
}

# Utility Functions
get_pressed_key() {
    IFS= read -sn1 key 2>/dev/null >&2
    read -sn1 -t 0.02
1 key
    case $key in
        '') key=_enter ;;
        ' ') key=_space ;;
        $'\x1b') key=_esc ;;
        $'\e[F') key=_end ;;
        $'\e[H') key=_home ;;
        $'\x7f') key=_backspace ;;
        $'\x1b\x5b\x32\x7e') key=_insert ;;
        $'\x1b\x5b\x41') key=_up ;;
        $'\x1b\x5b\x42') key=_down ;;
        $'\x1b\x5b\x35\x7e') key=_pgup ;;
        $'\x1b\x5b\x36\x7e') key=_pgdown ;;
    esac

    echo "$key"
}

parse_arguments() {
    while [[ $# -gt 0 ]]; do
        opt=$1
        shift
        case $opt in
            --index) has_index_result=true ;;
            --multiple) has_multiple_options=true ;;
            --message=*) message="${opt#*=}" ;;
            --options=*) options_input="$opt" ;;
            --help) show_help=true ;;
            *) show_help=true ;;
        esac
    done
}

initialize() {
    set_options
    options_length=${#options[@]}
    terminal_width=$(tput lines)
    start_page=0
    end_page=$(( $start_page + $terminal_width - 4 ))
    [[ $end_page -ge $options_length ]] && end_page=$(( $options_length - 1 ))

    if [[ ${#message} -gt 40 ]]; then
        message_length=$(( ${#message} + 10 ))
    else
        message_length=50
    fi
    separator=$(perl -E "say '-' x $message_length")
}

# Center text function
center_text() {
    local text="$1"
    local width=$(tput cols)
    local padding=$(( (width - ${#text}) / 2 ))
    printf "%*s" $padding "" | tr ' ' "$BOX_HORIZONTAL"
    printf "%s" "$text"
    printf "%*s" $padding "" | tr ' ' "$BOX_HORIZONTAL"
}

# Main Function
main() {
    parse_arguments "$@"

    if [[ "$show_help" = true ]]; then
        display_help
        printf "\n"
        exit
    fi

    initialize
    render_checkbox

    while true; do
        validate_terminal_size
        local key=$(get_pressed_key)

        case $key in
            _up|k) move_cursor_up ;;
            _down|j) move_cursor_down ;;
            _home|g) move_to_home ;;
            _end|G) move_to_end ;;
            _pgup|u) page_up ;;
            _pgdown|d) page_down ;;
            _esc|q) break ;;
            _enter|o) confirm_selection && break ;;
            _space|x) select_current_option ;;
            _insert|v) toggle_select_mode ;;
            _backspace|V) toggle_unselect_mode ;;
            c|y) copy_current_option ;;
            r) refresh_render ;;
            a) select_all ;;
            A) unselect_all ;;
        esac

        clear_checkbox
        render_checkbox
    done

    render_result
}

main "$@"

shrug

@Nsomnia Nsomnia changed the title [IMPROVMENTS/WIP] Spitballing Readability & Visual Improvmenets [IMPROVMENTS/WIP] Spitballing Readability & Visual Improvmenets + Refactor Ideas Nov 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant