diff --git a/commands/ask b/commands/ask index e8d959f82..bad01e279 100755 --- a/commands/ask +++ b/commands/ask @@ -101,7 +101,7 @@ function ask_() ( } # process - local item args=() option_question='' option_default='' option_password='no' option_required='no' option_confirm='no' option_timeout='' + local item args=() option_question='' option_default='' option_timeout='' option_password='no' option_required='no' option_linger='no' option_confirm_default='yes' option_confirm_input='no' while test "$#" -ne 0; do item="$1" shift @@ -116,8 +116,14 @@ function ask_() ( '--no-required'* | '--required'*) option_required="$(get-flag-value --affirmative --fallback="$option_required" -- "$item")" ;; - '--no-confirm'* | '--confirm'*) - option_confirm="$(get-flag-value --affirmative --fallback="$option_confirm" -- "$item")" + '--no-linger'* | '--linger'*) + option_linger="$(get-flag-value --affirmative --fallback="$option_linger" -- "$item")" + ;; + '--no-confirm-default'* | '--confirm-default'*) + option_confirm_default="$(get-flag-value --affirmative --fallback="$option_confirm_default" -- "$item")" + ;; + '--no-confirm-input'* | '--confirm-input'*) + option_confirm_input="$(get-flag-value --affirmative --fallback="$option_confirm_input" -- "$item")" ;; '--') args+=("$@") @@ -133,46 +139,94 @@ function ask_() ( # Action # prepare - local RESULT ASKED='no' tty_target + # @todo implement cursor move fallback + local tty_target tty_target="$(is-tty --fallback)" + + # adjust result + local RESULT if test -n "$option_default"; then RESULT="$option_default" else RESULT='' fi + # adjust timeout to one minute if we have a default value, or if optional + if test -z "$option_timeout" && (is-value -- "$RESULT" || test "$option_required" = 'no'); then + option_timeout=60 + fi + + # adjust question + local question_render prompt + prompt='> ' + question_render="$(echo-style --bold="$option_question")" + + # adjust tty + local size_columns bin_gfold bin_gwc + size_columns="$(tput cols)" + if is-mac; then + bin_gfold="$(type -P 'gfold' 2>/dev/null || :)" + bin_gwc="$(type -P 'gwc' 2>/dev/null || :)" + else + bin_gfold="$(type -P 'fold' 2>/dev/null || :)" + bin_gwc="$(type -P 'wc' 2>/dev/null || :)" + fi + # helpers + local ASKED='no' + function send_result { + if test "$option_linger" = 'yes'; then + print_line "$question_render"$'\n'"$RESULT" >"$tty_target" + fi + print_line "$RESULT" + } function on_timeout { if is-value -- "$RESULT"; then - echo-style --notice="Ask timed out, using fallback value: " --code="$RESULT" >/dev/stderr + echo-style --notice="Ask timed out. Using the fallback value: " --code="$RESULT" >/dev/stderr sleep 5 - print_line "$RESULT" + send_result return 0 elif test "$option_required" = 'no'; then - echo-style --notice='Ask timed out, as the field was optional will use no value.' >/dev/stderr + echo-style --notice='Ask timed out. Had no fallback value, this is fine as the field was optional.' >/dev/stderr sleep 5 return 0 else - echo-style --warning='Ask timed out, with no fallback.' >/dev/stderr + echo-style --warning='Ask timed out. Had no fallback value... the field was required.' >/dev/stderr sleep 5 return 60 # ETIMEDOUT 60 Operation timed out fi } - function do_ask { # has sideffects: RESULT, ASKED - local __read_status - tty_auto + function do_prompt { # has sideffects: RESULT, ASKED + local __read_status render render_rows + + # tty_auto ASKED='yes' # not local - if test -n "${1-}"; then - print_line "$1" >"$tty_target" + if test -n "$question_render"; then + print_line "$question_render" >"$tty_target" fi while true; do - __read_status=0 && read -r -t 300 -r -p '> ' RESULT || __read_status=$? + # -i requires -e + __read_status=0 && read -r -t 300 -ei "$RESULT" -p "$prompt" RESULT || __read_status=$? + + # \n at the end to factor in the enter key + render="$(echo-trim-colors -- "$question_render"$'\n'"$prompt$RESULT")" + render="$("$bin_gfold" -w "$size_columns" <<<"$render")" + render_rows="$("$bin_gwc" -l <<<"$render")" + + # move these lines up and erase + printf '\e[%sF\e[G\e[J' "$render_rows" >"$tty_target" + if test "$__read_status" -eq 142; then return 60 # ETIMEDOUT 60 Operation timed out fi if is-value -- "$RESULT"; then + # we have a value, proceed break - elif test "$option_required" = 'no'; then + elif test "$option_required" = 'yes'; then + # ask again + continue + else + # no result, optional, set value to empty, and proceed RESULT='' break fi @@ -180,89 +234,82 @@ function ask_() ( do_validate } function do_validate { - local choose_status ask_status choice choices=() - if is-value -- "$RESULT"; then - # we have a value, so go for it - if test "$option_confirm" != 'yes'; then - print_line "$RESULT" + local choose_status prompt_status choice choices=() + + # have we prompted? + if test "$ASKED" = 'no'; then + # do we want to confirm the default value + if is-value -- "$RESULT" && test "$option_confirm_default" = 'no'; then + send_result return 0 fi - # proceed with confirm - if test "$ASKED" = 'yes'; then - if test "$option_password" = 'yes'; then - choices+=('existing' 'use the entered password') - else - choices+=('existing' "use the entered value: [$RESULT]") - fi + else + # we have asked, do we want to confirm the input value + if test "$option_confirm_input" = 'no'; then + send_result + return 0 + fi + + # redo choices, has to be redone each time due to result + if test "$option_password" = 'yes'; then + choices+=('existing' 'use the entered password') else - if test "$option_password" = 'yes'; then - choices+=('existing' 'use the preconfigured password') - else - choices+=('existing' "use the preconfigured value: [$RESULT]") - fi + choices+=('existing' "use the entered value: [$RESULT]") fi - fi - if test "$ASKED" = 'yes'; then choices+=('custom' 'redo the entered value') - else - choices+=('custom' 'enter a value') - fi - if test "$option_required" = 'no'; then - choices+=('none' 'use no value') - fi + if test "$option_required" = 'no'; then + choices+=('none' 'use no value') + fi - # as need to confirm, adjust the timeout - if test -z "$option_timeout" && (is-value -- "$RESULT" || test "$option_required" = 'no'); then - # timeout of one minute for confirms of existing values, or optional values - option_timeout=60 + # we want to confirm + eval_capture --statusvar=choose_status --stdoutvar=choice -- \ + choose-option \ + --timeout="$option_timeout" \ + --question="$option_question" \ + --label -- "${choices[@]}" + + # check the confirmation + if test "$choose_status" -eq 60; then + echo-style --error="Choose timed out: $choose_status" >/dev/stderr + on_timeout + return + elif test "$choose_status" -ne 0; then + echo-style --error="Choose failed: $choose_status" >/dev/stderr + sleep 3 + return "$choose_status" + fi + + # proceess the confirmation + if test "$choice" = 'existing'; then + # done, sucess + send_result + return 0 + elif test "$choice" = 'custom'; then + : # proceed with prompt + elif test "$choice" = 'none'; then + # done, sucess + echo + return 0 + else + # unknown error + echo-style --error="Invalid choice: $choice" >/dev/stderr + sleep 3 + return 14 # EFAULT 14 Bad address + fi fi - # ask - eval_capture --statusvar=choose_status --stdoutvar=choice -- \ - choose-option \ - --timeout="$option_timeout" \ - --question="$option_question" \ - --label -- "${choices[@]}" + # prompt + eval_capture --statusvar=prompt_status -- do_prompt - # check - if test "$choose_status" -eq 60; then - echo-style --error="Choose timed out: $choose_status" >/dev/stderr + # check for failure + if test "$prompt_status" -ne 0; then + # timeout probably on_timeout return - elif test "$choose_status" -ne 0; then - echo-style --error="Choose failed: $choose_status" >/dev/stderr - sleep 3 - return "$choose_status" fi - # handle - if test "$choice" = 'existing'; then - # done, sucess - print_line "$RESULT" - return 0 - elif test "$choice" = 'custom'; then - # ask - eval_capture --statusvar=ask_status -- do_ask "$option_question" - - # check for failure - if test "$ask_status" -ne 0; then - # timeout probably - on_timeout - return - fi - - # done, success - return 0 - elif test "$choice" = 'none'; then - # done, sucess - echo - return 0 - else - # unknown error - echo-style --error="Invalid choice: $choice" >/dev/stderr - sleep 3 - return 14 # EFAULT 14 Bad address - fi + # done, success + return 0 } # act diff --git a/commands/choose-menu b/commands/choose-menu index 2c7d3fc05..dfb200d6c 100755 --- a/commands/choose-menu +++ b/commands/choose-menu @@ -365,14 +365,14 @@ function choose_menu() ( if test "$size_rows" -ne "$size_rows_prior" -o "$size_columns" -ne "$size_columns_prior"; then size_content="$((size_columns - 5))" # recalculate for new size - menu_header_shrunk="$(echo-trim-colors "$menu_header" | "$bin_gfold" -w "$size_columns")" + menu_header_shrunk="$(echo-trim-colors -- "$menu_header" | "$bin_gfold" -w "$size_columns")" menu_header_size="$("$bin_gwc" -l <<<"${menu_header_shrunk}")" menu_hint="${menu_hint_standard}${menu_hint_extras}" - menu_hint_shrunk="$(echo-trim-colors "$menu_hint" | "$bin_gfold" -w "$size_columns")" + menu_hint_shrunk="$(echo-trim-colors -- "$menu_hint" | "$bin_gfold" -w "$size_columns")" menu_hint_size="$("$bin_gwc" -l <<<"${menu_hint_shrunk}")" if test "$menu_hint_size" -gt 1; then menu_hint="${menu_hint_standard}" - menu_hint_shrunk="$(echo-trim-colors "$menu_hint" | "$bin_gfold" -w "$size_columns")" + menu_hint_shrunk="$(echo-trim-colors -- "$menu_hint" | "$bin_gfold" -w "$size_columns")" menu_hint_size="$("$bin_gwc" -l <<<"${menu_hint_shrunk}")" fi # move start index to current item, as otherwise it could be out of range diff --git a/commands/confirm b/commands/confirm index 7f902a7a1..708162515 100755 --- a/commands/confirm +++ b/commands/confirm @@ -258,17 +258,16 @@ function confirm_() ( CURSOR_COLUMN='' print_string "$prompt " >"$tty_target" # send an ansi query to fetch the cursor row and column, returns [^[[24;80R] where 24 is row, 80 is column - # use _ to discard, the first read var is garbage, the second read var is the column, the final read var is the column + # use _ to discard, the first read var is garbage, the second read var is the row, the final read var is the column # use a 2 second timeout, as otherwise [confirm --test] on macos sonoma will wait forever # shorter timeouts aren't suitable as slower machines take a while for the response # we are already in a TTY, so can usually guarantee an answer, and the read will complete immediately upon a response thanks to [-d R] which completes reading when the R is read, which is the final character of the response query local _ IFS='[;' read -t 2 -srd R -p $'\e[6n' _ _ CURSOR_COLUMN <"$tty_target" || : - # output the body if it exists + # output the body on a newline if it exists if test -n "$body"; then - echo >"$tty_target" - print_string "$body" >"$tty_target" + print_string $'\n'"$body" >"$tty_target" # move these lines up if test "$body_lines" -ne 0; then @@ -308,7 +307,7 @@ function confirm_() ( fi finished=yes - # make sure to erase question, as ctrl+c buggers everything + # erase from start of current row to end of screen, as ctrl+c buggers everything printf '\e[G\e[J' >"$tty_target" # output the finale