about summary refs log blame commit diff stats
path: root/templates/c/shell_line_editor.sh
blob: 8d6833ade04e10f78c5113f568b7161a7b635e1b (plain) (tree)





















































































































































































































































                                                                                                   
#! /usr/bin/env sh
# Taken in verbatim from: https://unix.stackexchange.com/a/113450, and somewhat changed

LE_print_debug() {
    LE_debug="$1"
    LE_debug_msg="$2"
    [ -n "$LE_debug" ] && printf "\nDBG: (%s)\n" "$LE_debug_msg" >outfile.debug
}

LE() {
    # Shell Line Editor. Extremely slow and stupid code. However it
    # should work on ansi/vt100/linux derived terminals on POSIX
    # systems.
    # Understands some emacs key bindings: CTRL-(A,B,D,E,F,H,K,L)
    # plus the CTRL-W and CTRL-U normal killword and kill.
    # no Meta-X key, but handling of <Left>, <Right>, <Home>, <End>
    # <Suppr>.
    #
    # Args:
    #  [1]: prompt (\x sequences recognized, defaults to "")
    #  [2]: max input length (unlimited if < 0, (default))
    #  [3]: fill character when erasing (defaults to space)
    #  [4]: initial value.
    #  [5]: whether to output debugfiles (outfile.debug and outfile.raw.debug)
    # Returns:
    #  0: OK
    #  1: od(d) error or CTRL-C hit

    LE_prompt="$1"
    LE_max=${2--1}
    LE_fill=${3-" "}
    LE_debug="$5"

    LE_backward() {
        LE_substract="$1"
        while [ -n "$LE_substract" ]; do
            printf '\b%s' "$2"
            LE_substract=${LE_substract%?}
        done
    }

    LE_fill() {
        LE_substract="$1"
        while [ -n "$LE_substract" ]; do
            printf '%s' "$LE_fill"
            LE_substract=${LE_substract%?}
        done
    }

    # Used but not right now
    # shellcheck disable=2016
    LE_restore='stty "$LE_tty"
              LC_COLLATE='${LC_COLLATE-"; unset LC_COLLATE"}

    # LE_tty is used in the restore above
    # shellcheck disable=2034
    LE_ret=1 LE_tty=$(stty -g) LC_COLLATE=C

    # text on the right of the cursor
    LE_left=$4
    # text on the left of the cursor
    LE_right=''

    # Tell the terminal to show us every char inputted
    stty -icanon -echo -isig min 3 time 1 -istrip
    printf '%b%s' "$LE_prompt" "$LE_left"

    # clear the output
    [ -n "$LE_debug" ] && printf "" >outfile.debug
    [ -n "$LE_debug" ] && printf "" >outfile.raw.debug

    # The value needs to be split for it to work (and it's either way just numbers)
    # shellcheck disable=2046
    while set -- $(dd bs=3 count=1 2>/dev/null | od -vAn -to1); do
        while [ "$#" -gt 0 ]; do
            [ -n "$LE_debug" ] && printf "%b" "\0$1" >>outfile.debug
            [ -n "$LE_debug" ] && printf "%s " "$1" >>outfile.raw.debug
            LE_current_key=$1
            shift

            # 033 is ^[ (`printf "\\$1\n" | cat -v`)
            if [ "$LE_current_key" = 033 ]; then
                case "$1$2$3" in
                # [ C | O C -> ^F forward
                133103* | 117103*)
                    shift 2
                    LE_current_key=006
                    ;;
                # [ D | O D -> ^B backward
                133104* | 117104*)
                    shift 2
                    LE_current_key=002
                    ;;
                # [ H | O H -> ^A beginning of line
                133110* | 117110*)
                    shift 2
                    LE_current_key=001
                    ;;
                # [ P | O P -> ^D del char
                133120* | 117120*)
                    shift 2
                    LE_current_key=004
                    ;;
                # [ F | O F -> ^E end of line
                133106* | 117106*)
                    shift 2
                    LE_current_key=005
                    ;;
                # [ 1 ~ -> ^A beginning of line
                133061176)
                    shift 3
                    LE_current_key=001
                    ;;
                # [ 4 ~ -> ^E end of line
                133064176)
                    shift 3
                    LE_current_key=005
                    ;;
                # [ 3 ~ -> ^D del char
                133063176)
                    shift 3
                    LE_current_key=004
                    ;;
                # [ | O
                133* | 117*)
                    shift
                    # Is $1 in ge 0 AND le 9 OR eq ';'?
                    # These are control sequences for things like colors; Ignore them
                    while [ "0$1" -ge 060 ] && [ "0$1" -le 071 ] ||
                        [ "0$1" -eq 073 ]; do
                        shift
                    done
                    ;;
                esac
            fi

            case "$LE_current_key" in
            001) # ^A beginning of line
                LE_backward "$LE_left"
                LE_right="$LE_left$LE_right"
                LE_left=
                ;;
            002) # ^B backward
                if [ "$LE_left" = "" ]; then
                    # bell
                    printf '\a'
                    LE_print_debug "$LE_debug" "backward with empty left"
                else
                    printf '\b'
                    LE_tmp="${LE_left%?}"
                    LE_right="${LE_left#"$LE_tmp"}$LE_right"
                    LE_left="$LE_tmp"
                fi ;;
            003) # CTRL-C
                break 2 ;;
            004) # ^D del char
                if [ "$LE_right" = "" ]; then
                    # bell (tell the user that the line is empty)
                    printf '\a'
                    LE_print_debug "$LE_debug" "delete with empty right"
                else
                    LE_right="${LE_right#?}"
                    printf '%s\b' "$LE_right$LE_fill"
                    LE_backward "$LE_right"
                fi ;;
            012 | 015) # NL or CR
                LE_ret=0
                break 2
                ;;
            005) # ^E end of line
                printf '%s' "$LE_right"
                LE_left="$LE_left$LE_right"
                LE_right=
                ;;
            006) # ^F forward
                if [ "$LE_right" = "" ]; then
                    # bell (tell the user that the line is empty)
                    printf '\a'
                    LE_print_debug "$LE_debug" "forward with empty right"
                else
                    LE_tmp="${LE_right#?}"
                    LE_left="$LE_left${LE_right%"$LE_tmp"}"
                    printf %s "${LE_right%"$LE_tmp"}"
                    LE_right="$LE_tmp"
                fi ;;
            010 | 177) # backspace or del
                if [ "$LE_left" = "" ]; then
                    # bell
                    printf '\a'
                    LE_print_debug "$LE_debug" "backspace with empty left"
                else
                    printf '\b%s\b' "$LE_right$LE_fill"
                    LE_backward "$LE_right"
                    LE_left="${LE_left%?}"
                fi ;;
            013) # ^K kill to end of line
                LE_fill "$LE_right"
                LE_backward "$LE_right"
                LE_right=""
                ;;
            014) # ^L redraw
                printf '\r%b%s' "$LE_prompt" "$LE_left$LE_right"
                LE_backward "$LE_right"
                ;;
            025) # ^U kill line
                LE_backward "$LE_left"
                LE_fill "$LE_left$LE_right"
                LE_backward "$LE_left$LE_right"
                LE_left=""
                LE_right=""
                ;;
            027) # ^W kill word
                if [ "$LE_left" = "" ]; then
                    # bell
                    printf '\a'
                else
                    LE_tmp="${LE_left% *}"
                    LE_backward "${LE_left#"$LE_tmp"}"
                    LE_fill "${LE_left#"$LE_tmp"}"
                    LE_backward "${LE_left#"$LE_tmp"}"
                    LE_left="$LE_tmp"
                fi ;;
            # Print the received key, as it did not match a special key
            [02][4-7]? | [13]??) # 040 -> 177, 240 -> 377
                # was assuming iso8859-x at the time
                if [ "$LE_max" -gt 0 ] && LE_tmp="$LE_left$LE_right" &&
                    [ "${#LE_tmp}" -eq "$LE_max" ]; then
                    # bell, when the user is trying to cross the line limit
                    printf '\a'
                    LE_print_debug "$LE_debug" "max output reached"
                else
                    LE_left="$LE_left$(printf '%b' "\0$LE_current_key")"
                    printf '%b%s' "\0$LE_current_key" "$LE_right"
                    LE_backward "$LE_right"
                fi ;;
            *)
                LE_print_debug "$LE_debug" "key not recognized: $(printf "%b" "\0$LE_current_key")"
                printf '\a'
                ;;
            esac
        done
    done
    eval "$LE_restore"
    REPLY=$LE_left$LE_right
    echo
    return "$LE_ret"
}