Recently emacs has a feature undo-amalgamate-change-group which can be used to merge multiple actions into a single undo step.

How can this be used to make a with-undo-collapse macro similar to this one, which:

  • Handles errors.
  • Forwards the result of the body of the macro.

This is a basic version, however I'm not sure how it should work when there is an error in the body of the code which runs in this block.

(defmacro with-undo-collapse (&rest body)
  "Execute body, then collapse any resulting undo boundaries."
  (declare (indent 0))
  `(let ((cg (prepare-change-group)))
     (progn ,@body)
     (undo-amalgamate-change-group cg)))

2 Answers 2


Edit: emacs 29 supports (with-undo-amalgamate &rest BODY) which is very close to the answer below.

The command atomic-change-group fits all the requirements except for collapsing the undo history.

This is atomic-change-group with a one line change, adding undo-amalgamate-change-group.

(defmacro with-undo-collapse (&rest body)
  "Like `progn' but perform BODY with undo collapsed."
  (declare (indent 0) (debug t))
  (let ((handle (make-symbol "--change-group-handle--"))
        (success (make-symbol "--change-group-success--")))
    `(let ((,handle (prepare-change-group))
            ;; Don't truncate any undo data in the middle of this.
            (undo-outer-limit nil)
            (undo-limit most-positive-fixnum)
            (undo-strong-limit most-positive-fixnum)
            (,success nil))
           (activate-change-group ,handle)
           (prog1 ,(macroexp-progn body)
             (setq ,success t)))
         (if ,success
             (accept-change-group ,handle)
             (undo-amalgamate-change-group ,handle))
           (cancel-change-group ,handle))))))

I'm not entirely sure, but you are possibly trying to reimplement the atomic-change-group macro?

atomic-change-group is a Lisp macro in `subr.el'.

(atomic-change-group &rest BODY)

Perform BODY as an atomic change group.
This means that if BODY exits abnormally,
all of its changes to the current buffer are undone.
This works regardless of whether undo is enabled in the buffer.

This mechanism is transparent to ordinary use of undo;
if undo is enabled in the buffer and BODY succeeds, the
user can undo the change normally.

I'm not sure how it should work when there is an error in the body of the code which runs in this block.

I think you're looking for:

unwind-protect is a special form in `C source code'.

(unwind-protect BODYFORM UNWINDFORMS...)

Do BODYFORM, protecting with UNWINDFORMS.
If BODYFORM completes normally, its value is returned
after executing the UNWINDFORMS.
If BODYFORM exits nonlocally, the UNWINDFORMS are executed anyway.
  • 1
    atomic-change-group doesn't run undo-amalgamate-change-group, so actions must be undone one at a time afterwards. Although this function handles everything else, so I suppose it could be copy-pasted with an undo-amalgamate-change-group added near the end.
    – ideasman42
    Commented Dec 16, 2019 at 9:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.