I just noticed that modifying another buffer creates an undo barrier in the current.
For example:
(defun my-test-undo ()
(interactive)
(insert "A")
(with-current-buffer (get-buffer-create "FOO")
(insert "abc"))
(insert "B"))
The command will print AB
. When hitting undo, only the B
is removed. A second undo is needed to remove the A
.
This version logs buffer-undo-list
:
(defvar my-test-undo-log nil)
(defun my-test-undo-with-log ()
(interactive)
(setq buffer-undo-list '())
(let ((before buffer-undo-list))
(insert "A")
(with-current-buffer (get-buffer-create "FOO")
(insert "abc"))
(let ((mid buffer-undo-list))
(insert "B")
(setq my-test-undo-log (list before mid buffer-undo-list)))))
After running this my-test-undo-log
contains three versions of the undo information. In the last a nil
has been inserted, indicating an undo barrier.
I came up with the following to manually modifying the undo list, but it feels like a hack. (Of course, it can be slimlined into a macro.)
(defun my-delete-nil-until (list sublist)
;; Strip of leading nil:s
(while (and (not (eq list sublist))
(null (car list)))
(setq list (cdr list)))
;; Destructively remove nil:s
(let ((next list))
(while (not (eq (cdr next) sublist))
(if (car (cdr next))
(setq next (cdr next))
(setcdr next (cdr (cdr next))))))
list)
(defun my-test-undo-with-workaround ()
(interactive)
(let ((before buffer-undo-list))
(insert "A")
(with-current-buffer (get-buffer-create "FOO")
(insert "abc"))
(insert "B")
(setq buffer-undo-list (my-delete-nil-until buffer-undo-list before))))
Is there any rational reasons why Emacs should behave this way, or is it a bug?
Are there better ways to handle this, like a built-in macro I've missed?