dd-ex: current line movement

We include the functions to insert and delete lines here because they actually move the current line around.

Remembering how open was implemented, skipping to the next line is actually easy. First we append the current line to the text which precedes it; then we need to split the text following the current line so the first line of it is made "current" - but this is exactly what open does, so we can call it (after saving the temporary buffers soomewhere else).

  next () {
    # open resets lineno to 1, so we save lineno+1 to l
    add l $lineno 1

    # copy the text up to the current line, followed by the current
    # line itself, to a temporary place
    ( $dd if=$ed.a; $dd if=$ed.c ) 2>/dev/null > $tmp.3

    # copy the text after the current line to another temporary place
    $dd if=$ed.b of=$tmp.4 2>/dev/null

    # open the last file - this sets up $ed.c and $ed.b as required,
    # but leaves us with an empty $ed.a
    open $tmp.4

    # copy the first temporary file to $ed.a and reset line number
    $dd if=$tmp.3 of=$ed.a 2>/dev/null
    lineno=$l
  }
Going to the previous line is slightly more complicated. First we check if the line number is greater than one - if it is not, there is nothing to do.

After subtracting 1 from the line number, we need to find the beginning of the previous line (last line in /tmp/ed.$$.a - we do that by looking for a newlines starting at end of file - but skipping the very last byte, since that is the newline terminating the last line.

Once we have the size of the last line, it's just a matter of copying text around.

  prev () {
    case "$lineno" in
      1) ;;
      *) subtract lineno $lineno 1
	 # read last line of $ed.a
	 IFS='+'
	 set `$dd if=$ed.a of=/dev/null bs=1 2>&1`
	 IFS="$saveIFS"
	 size=$1
	 # if the file is empty, there is nothing to do
	 # (this shouldn't actually happen)
	 case "$size" in
	   0) return ;;
	 esac
	 subtract size $size 1
	 # skip final newline
	 case "$size" in
	   0) ;;
	   *) subtract size1 $size 1
	      case "`$dd if=$ed.a bs=1 skip=$size count=1 2>/dev/null`" in
		?*) ;;
		*) size=$size1 ;;
	      esac
	      ;;
	 esac

	 # look for a newline starting from the byte just before the final
	 # newline
	 go=true
	 while $go
	 do
	   case "$size" in
	     0) go=false ;;
	     *) case "`$dd if=$ed.a bs=1 skip=$size count=1 2>/dev/null`" in
		  ?*)  go=true; subtract size $size 1 ;;
		  *)   go=false; add size $size 1 ;;
		esac
		;;
	   esac
	 done

	 # now $size is the size of the first n-1 lines

	 # copy $ed.c to the beginning of $ed.b
	 ( $dd if=$ed.c; $dd if=$ed.b ) 2>/dev/null > $tmp.5
	 $dd if=$tmp.5 of=$ed.b 2>/dev/null

	 # move line to ed.c
	 case "$size" in
	   0) $dd if=$ed.a of=$ed.c 2>/dev/null
	      $dd if=/dev/null of=$tmp.5 2>/dev/null
	      ;;
	   *) $dd if=$ed.a of=$ed.c bs=1 skip=$size 2>/dev/null
	      $dd if=$ed.a of=$tmp.5 bs=1 count=$size 2>/dev/null
	      ;;
	 esac

	 # move rest to ed.a
	 $dd if=$tmp.5 of=$ed.a 2>/dev/null
      ;;
    esac
  }
Deleting the current line is similar to skipping to the next one - except that we don't save the current line, so it's automatically removed
  delete () {
    l=$lineno

    # copy $ed.a to temporary file
    $dd if=$ed.a 2>/dev/null > $tmp.1

    # copy #ed.b to temporary file
    $dd if=$ed.b of=$tmp.2 2>/dev/null

    # open it - this overwrites $ed.c with the new current line
    #           and clears $ed.a
    open $tmp.2

    # restore $ed.a and line number
    $dd if=$tmp.1 of=$ed.a 2>/dev/null
    lineno=$l
  }
Inserting a line before the current is just a matter of appending the new line to the end of /tmp/ed.$$.a. If you want to make the new line current, you'll have to call prev.
  insert () {
    # append the new text at the end of $ed.a
    ( $dd if=$ed.a; Echo "$@" ) 2>/dev/null > $tmp.1
    $dd if=$tmp.1 of=$ed.a 2>/dev/null

    # and remember that it has one more line
    add lineno $lineno 1
  }
To skip to a line given its number we can just call next or prev as needed. The function compare is useful here, as the three outcomes correspond to "call next", "call prev", or "do nothing". After the initial comparison, the loop condition can use gt or lt, which are slightly faster.
  goto () {
    rl="$1"

    # do we need to go up or down?
    compare bla "$rl" $lineno
    case "$bla" in
      eq) return
	  ;;
      gt) while gt "$rl" $lineno
	  do
	    next
	  done
	  ;;
      lt) while lt "$rl" $lineno
	  do
	    prev
	  done
	  ;;
    esac
  }