This function first figures out which field we're in on the current line, and then it tries to find the last previous encounter line that has that same field, and duplicates that value onto the current line.
;; - - - i b p - d i t t o - o n c e - - - (defun ibp-ditto-once () "Duplicate a field from the corresponding field of the previous tail. [ if there are no previous lines with tails -> error/exit else if point is not at end of line, or beyond all tail fields -> error/exit else -> buffer := buffer with text appended after point copied from the corresponding tail position of the last previous line with a tail point := point advanced to the end of the field containing point ] ----------------------------------------------------------------"
Here is an illustration of the process.
The previous line is the top box in this figure, and its
layout is described in an
prev. The current line's layout is
described by an
line. Note that the previous line's head
portion may be a different size than the head of the
current line, e.g., an N-type encounter line can ditto a
field from a G-type line.
The hatched portion represents content in the current
field already entered, if any. Variables
dup-end bracket the
portion of the previous line that needs to be copied to
the current line. Variable
will contain the actual text to be copied.
First we open a
let scope and define the
local variables. These variables are as illustrated
(let (line ;; ibp-line-object for the line containing point prev ;; ibp-line-object for last prev. line with a tail field ;; ibp-field-object for the field containing point dup-beg ;; Start position of string to be duplicated dup-end ;; End position of string to be duplicated dup-len ;; Length of string to be duplicated dup-string ;; String to be duplicated tail-off) ;; Offset relative to tail of point
First we find the parts of the current line using
Section 5.6, “
ibp-analyze-line: Where are the parts of
the current line?”.
;; [ line := a ibp-line-object representing the line ;; containing point ] (setq line (ibp-analyze-line))
Duplication is valid only at the end of the line. It is not valid except on encounter lines.
;; [ if (line.kind is 'non-trans) ;; or (point is not at end of line) -> ;; error/exit ;; else -> I ] (if (or (eq (ibp-line-kind line) 'non-trans) (/= (point) (ibp-line-end line))) (error "Duplication is valid only at the end of a line."))
Next we need to find the line from which we are copying.
One nice feature of this process is that it searches back
through the buffer until it finds a transaction line; a
ibp-line-object representing that line is
prev. This searching is done by
Section 5.14, “
ibp-find-prev-trans: Find the last
preceding line with a tail”; this could
fail, in which case that function returns
;; [ if there is at least one line with a tail preceding the line ;; containing point -> ;; prev := an ibp-line-object representing that line ;; else -> error/exit ] (setq prev (ibp-find-prev-trans)) (if (null prev) (error "No previous line to duplicate."))
Next we need to find the field in the current line
containing the cursor, and set
field to an
ibp-field-object describing that field.
This is done by Section 5.9, “
ibp-bracket-field: What field contains a
given position?”; if
the cursor is beyond all the tail fields, that function
;; [ if point is in the head part of line -> ;; field := an ibp-field-object representing the head, ;; with nil filler ;; if point is in a tail field -> ;; field := an ibp-field-object representing that field ;; if point is beyond all tail fields -> ;; error/exit ] (setq field (ibp-bracket-field line (point))) (if (null field) (error "You are beyond the fields we know."))
Now we want to find the part of
corresponding to the field we are duplicating, and set
dup-string to the part to be copied.
First we compute
tail-off, the distance
into the tail where we want to start copying. Then we
bracket the corresponding field in
dup-end is past the end of the
previous line, that's an error.
;; [ if prev does not have characters corresponding to [line.end: ;; field.end] -> ;; error/exit ;; else -> ;; dup-string := characters from prev whose position relative to ;; prev's tail correspond to characters [line.end: ;; field.end] ] (setq tail-off (- (ibp-line-end line) (ibp-line-tail line))) (setq dup-beg (+ (ibp-line-tail prev) tail-off)) (setq dup-len (- (ibp-field-end field) (ibp-line-end line))) (setq dup-end (+ dup-beg dup-len)) (if (> dup-end (ibp-line-end prev)) (error "Previous line is too short to duplicate.")) (setq dup-string (buffer-substring dup-beg dup-end))
All that remains is to insert the duplicated text before the cursor. The cursor is left at the end of the insertion.
;; [ buffer := buffer with dup-string inserted before point ] (insert dup-string)))