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
}