bse

#!/bin/sh
# bourne shell editor – text editor written in bourne shell
# © 2013 Nils Dagsson Moskopp (erlehmann) – license: GPLv3+
if [ ! -n "$1" ]; then printf "Usage: $0 [FILE]\n"; exit 1; fi
if [ ! -e "$1" ]; then touch "$1"; fi
BUFFER="$1"; STDOUT=$(mktemp /tmp/ee-stdout-$(whoami).XXXXXX)
CURSOR=0; START=0; END=0
DEL=$(printf "\177"); ESC=$(printf "\033"); SO=$(printf "\016")
ETB=$(printf "\027"); EM=$(printf "\031")
ddhead() { awk '{printf "%s", substr($0,0,'"$1"')}'; }  # { head - -c+$1 }
ddtail() { awk '{printf "%s", substr($0,'"$1"',length)}'; }  # { tail - -c+$1 }
strip_newline() { awk '{printf $0}'; }
delete_selection() {
    tr '\n' '\0' | awk '{ printf "%s", substr($0,0,'"$START"'); printf "%s", substr($0,'"$(expr $END - 1)"',length) }' | tr '\0' '\n'
    CURSOR=$START
}
selection() { ddtail $(expr $START + 1) | ddhead c$(expr $END - $START); }
deselect() { START=$CURSOR; END=$CURSOR; }
dispatch() {
  case "$1" in
    ("$SO") true ;;
    ("^[")
          if [ $START -lt $END ]; then
              delete_selection; deselect
          else
              END=$CURSOR; false
          fi
          ;;
    ("^[w") selection | xsel -i; false ;;
    ("$EM") ddhead $CURSOR; xsel -o; cat; CURSOR=$(expr $CURSOR + $(xsel -o | wc -c)) ;;
    ("^[[C")
          EOT=$(wc -c)
          [ $CURSOR -lt $EOT ] && CURSOR=$(expr $CURSOR + 1)
          deselect; false
          ;;
    ("^[[D")
          [ $CURSOR -gt 0 ] && CURSOR=$(expr $CURSOR - 1)
          deselect; false
          ;;
    ("$DEL")
          if [ $START -lt $END ]; then
              delete_selection; deselect
          elif [ $CURSOR -gt 0 ]; then
              tr '\n' '\0' | awk '{ printf "%s", substr($0,0,'$CURSOR'-1); printf "%s", substr($0,'$CURSOR'+1,length) }' | \
              tr '\0' '\n' && CURSOR=$(expr $CURSOR - 1)
          else
              false
          fi
          ;;
    ("^[[3~")
          if [ $START -lt $END ]; then
              delete_selection
          fi
          tr '\n' '\0' | awk '{ printf "%s", substr($0,0,'$CURSOR'); printf "%s", substr($0,'$CURSOR'+2,length) }' | \
              tr '\0' '\n'
          deselect
          ;;
    ("^[[1;2C")
          EOT=$(wc -c)
          if [ $CURSOR -lt $EOT ]; then
            if [ $START -eq $END ]; then
              START=$CURSOR; CURSOR=$(expr $CURSOR + 1); END=$CURSOR
            elif [ $START -lt $END ]; then
              if [ $CURSOR = $END ]; then
                CURSOR=$(expr $CURSOR + 1); END=$CURSOR
              elif [ $CURSOR = $START ]; then
                CURSOR=$(expr $CURSOR + 1); START=$CURSOR
              fi
            fi
          fi
          false
          ;;
    ("^[[1;2D")
          EOT=$(wc -c)
          if [ $CURSOR -gt 0 ]; then
            if [ $START -eq $END ]; then
              END=$CURSOR; CURSOR=$(expr $CURSOR - 1); START=$CURSOR
            elif [ $START -lt $END ]; then
              if [ $CURSOR = $START ]; then
                CURSOR=$(expr $CURSOR - 1); START=$CURSOR
              elif [ $CURSOR = $END ]; then
                CURSOR=$(expr $CURSOR - 1); END=$CURSOR
              fi
            fi
          fi
          false
          ;;
    (*)
         if [ "$(printf "$1" | head -c2)" = "^[" ]; then
             false  # do not insert escape codes
         elif [ $END -gt $START ]; then
             CURSOR=$START
             tr '\n' '\0' | awk '{ printf "%s", substr($0,0,'$START'); printf "%s", substr($0,'$(expr $END+1)',length) }' | \
                 (awk '{ printf "%s", substr($0,0,'$CURSOR'); printf "%s", "'"$1"'"; printf "%s", substr($0,'$CURSOR'+1,length) }' | tr '\0' '\n')
             deselect
         else
              tr '\n' '\0' | awk '{ printf "%s", substr($0,0,'$CURSOR'); printf "%s", "'"$1"'"; printf "%s", substr($0,'$CURSOR'+1,length) }' | tr '\0' '\n'
             CURSOR=$(expr $CURSOR + 1)
         fi
         ;;
  esac
}
stty -echo -icanon time 0 min 0
readbyte() { dd if=/dev/tty bs=1 count=1 2>/dev/null; }
render() {
  tput clear
  tr '\n' '\0' < "$1" | awk '{printf "%s", substr($0,0,'"$CURSOR"')}' | \
    tr '\0' '\n'
  tput sc
  tput clear
    tr '\n' '\0' < "$1" | awk '{printf "%s", substr($0,0,'"$START"')}' | \
      tr '\0' '\n'
  tput rev
  tr '\n' '\0' < "$1" | ddtail $(expr $START + 1) | \
    awk '{printf "%s", substr($0,0,'"$(expr $END - $START)"')}' | tr '\0' '\n'
  tput sgr0
  tr '\n' '\0' < "$1" | ddtail $(expr $END + 1) | tr '\0' '\n'
  tput cup $(expr $LINES - 3) 0
  printf "\n\$BUFFER=\"$BUFFER\"\n"
  printf "\$CURSOR=$CURSOR\t\$START=$START\t\$END=$END\tKEY=\""
  printf "$(printf "$KEY" | od -a | head -n2 | cut -b8- | tr -d ' ')"
  printf "\""
  tput rc
}
init() { LINES=$(tput lines); render "$BUFFER" "" "$CURSOR"; }
init
KEY=""
while :; do
  trap "sleep 0.1; init $BUFFER $CURSOR $START $END" 28
  BYTE=$(readbyte; printf x)
  BYTE=${BYTE%x}
  case "$BYTE" in
    ("")
      if ([ "$KEY" ]) then
          dispatch "$KEY" < "$BUFFER" > "$STDOUT"
          if [ "$?" -eq "0" ]; then
            cat "$STDOUT" > "$BUFFER"
          fi
          render "$BUFFER" "$CURSOR" "$START" "$END"
          KEY=""
      fi
    ;;
    (*)
      KEY=$KEY$(printf -- "$BYTE"x | sed -e "s/$ESC/^[/")
      KEY=${KEY%x}
    ;;
  esac
done
stty sane