Next / Previous / Contents / Shipman's homepage

5.13. ibp-ditto-once: Duplicate one field

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 ->
      else if point is not at end of line, or beyond all tail fields ->
      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 ibp-line-object named prev. The current line's layout is described by an ibp-line-object named 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-beg and dup-end bracket the portion of the previous line that needs to be copied to the current line. Variable dup-string will contain the actual text to be copied.

First we open a let scope and define the local variables. These variables are as illustrated above.

  (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 stored in 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 null.

    ;; [ 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 returns null.

    ;; [ 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 prev 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 prev between dup-beg and dup-end. If 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)))