#! /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 , , , # . # # 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" }