dd-ex: text handling functions

The editor only uses the "search and replace" functions. However, the "replace text given offset and size" is easy to write and we used this as an example in the documentation, so we might as well include it: all it does is to copy bits of the current line to temporary files and then assemble everything back.
  replace () {
    # copy the initial bit of the current line to $tmp.1
    $dd if=$ed.c of=$tmp.1 bs=1 "count=$1" 2>/dev/null

    # from the rest of the file, remove also the characters to be
    # replaced and save the result in $tmp.2
    ( $dd if=$ed.c "skip=$1" bs=1 | $dd of=$tmp.2 bs=1 "skip=$2" ) 2>/dev/null

    # remove the two numbers from the argument list
    shift
    shift

    # copy $tmp.1, the remaining arguments, $tmp.2 to $tmp.3
    ( $dd if=$tmp.1; Echo -n "$@"; $dd if=$tmp.2 ) > $tmp.3 2>/dev/null

    # save $tmp.3 as the new current line
    $dd if=$tmp.3 of=$ed.c 2>/dev/null
  }
To search for a string, we can use the shell's pattern matching. However, this won't tell us where the string is, unless we use a pattern which only finds it at the beginning of line.

If the string is not there, we try shifting a byte to a temporary file, and repeat until we either find the string of finish the bytes. In the latter case we return false, in the former we remove the search string, and copy the bytes shifted out, plus the replacement text, back at the beginning of the current line.

If we need to replace the n-th occurrence, all we need to do is to count the matches and only do the replacement after we have seen the required number of them.

We have an invisible cursor moving through the current line. The file $tmp.4 contains the text before the cursor, and $tmp.5 the text after it. The variable n contains the number of matches we need to find - it is decremented when we find a match, and, if it goes to 0, we do the replacement.

  rstring () {
    n="$1"
    shift;
    string="$1"
    shift

    # initially, $tmp.4 is empty and $tmp.5 is the whole current line
    $dd if=/dev/null of=$tmp.4 2>/dev/null
    $dd if=$ed.c of=$tmp.5 2>/dev/null

    # we exit when we find end of file or a match
    while true
    do
      # look at the start of $tmp.5
      case "`$dd if=$tmp.5 2>/dev/null`" in

	$string*)
	    # a match -- if $n < 2, we do the replacement
	    if lt $n 2
	    then
	      # save the replacement text to $tmp.2
	      # and the string to $tmp.1 - we need this to find out its length
	      Echo -n "$@" > $tmp.2
	      Echo -n "$string" > $tmp.1

	      # length of replacement string?
	      IFS="+"
	      set `$dd bs=1 if=$tmp.1 of=/dev/null 2>&1`
	      IFS="$saveIFS"
	      slen=$1
	      IFS="+"

	      # copy text from $tmp.4 (text before match), followed by
	      # $tmp.2 (replacement) and then $tmp.5 (text after match);
	      # from the latter, we skip $slen bytes to remove the text
	      # we matched
	      ( $dd if=$tmp.4; $dd if=$tmp.2; $dd if=$tmp.5 bs=1 skip=$slen ) \
		    2>/dev/null > $tmp

	      # and copy the resulting file back to the current line
	      $dd if=$tmp of=$ed.c 2>/dev/null

	      # return OK
	      return 0
	    else
	      # a match, but $n > 1: subtract 1, move the first byte of the
	      # match to $tmp.4 and continue
	      subtract n $n 1

	      # $tmp.4 followed by first byte of $tmp.5 goes to $tmp
	      ( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null

	      # and then back to $tmp.4
	      $dd if=$tmp of=$tmp.4 2>/dev/null

	      # remove first byte from $tmp.5
	      $dd if=$tmp.5 of=$tmp bs=1 skip=1 2>/dev/null
	      $dd if=$tmp of=$tmp.5 2>/dev/null
	    fi
	    ;;
	?*) # need to keep looking - move one byte from $tmp.5 to $tmp.4

	    # $tmp.4 followed by first byte of $tmp.5 goes to $tmp
	    ( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null

	    # and then back to $tmp.4
	    $dd if=$tmp of=$tmp.4 2>/dev/null

	    # remove first byte from $tmp.5
	    $dd if=$tmp.5 of=$tmp bs=1 skip=1 2>/dev/null
	    $dd if=$tmp of=$tmp.5 2>/dev/null
	    ;;
	*)  # end of file - return error
	    return 1
	    ;;
      esac
    done
  }