Skip to content

Commit

Permalink
Fix evil-with-undo/evil-undo-pop with undo-tree
Browse files Browse the repository at this point in the history
Addresses emacs-evil#1074

- evil-with-undo:
  nconc'ing onto front of buffer-undo-list here can corrupt buffer-undo-list
  when in undo-tree-mode in rare circumstances (see issue emacs-evil#1074). Leave
  standard undo machinery to work as usual when undo is enabled. Deal with
  disabled undo by temporarily enabling then disabling undo, and transferring
  any undo changes to evil-temporary-undo.

- evil-undo-pop:
  This function called `undo' directly from Elisp, which is wrong when in
  undo-tree-mode. Fix this by calling undo-tree-undo instead when in
  undo-tree-mode.

Co-authored-by: Axel Forsman <axelsfor@gmail.com>
  • Loading branch information
tsc25 and axelf4 committed Jan 5, 2023
1 parent f5ab7ff commit aff02d3
Showing 1 changed file with 37 additions and 41 deletions.
78 changes: 37 additions & 41 deletions evil-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -3524,60 +3524,56 @@ make the entries undoable as a single action. See
"Execute BODY with enabled undo.
If undo is disabled in the current buffer, the undo information
is stored in `evil-temporary-undo' instead of `buffer-undo-list'."
(declare (indent defun)
(debug t))
`(unwind-protect
(let (buffer-undo-list)
(unwind-protect
(progn ,@body)
(setq evil-temporary-undo buffer-undo-list)
;; ensure evil-temporary-undo starts with exactly one undo
;; boundary marker, i.e. nil
(unless (null (car-safe evil-temporary-undo))
(push nil evil-temporary-undo))))
(unless (eq buffer-undo-list t)
;; undo is enabled, so update the global buffer undo list
(setq buffer-undo-list
;; prepend new undos (if there are any)
(if (cdr evil-temporary-undo)
(nconc evil-temporary-undo buffer-undo-list)
buffer-undo-list)
evil-temporary-undo nil))))
(declare (debug t))
(let ((undo-list (make-symbol "undo-list")))
`(let ((,undo-list buffer-undo-list) evil-undo-system)
(when (eq ,undo-list t) (setq buffer-undo-list nil))
(unwind-protect
(progn ,@body)
;; ensure any new undo changes we've accumulated start with
;; exactly one undo boundary marker, i.e. nil
(when (car-safe buffer-undo-list) (push nil buffer-undo-list))
(if (eq ,undo-list t)
;; undo is disabled, so store undo information in
;; evil-temporary-undo
(setq evil-temporary-undo buffer-undo-list
buffer-undo-list t)
(setq evil-temporary-undo nil))))))

(defmacro evil-with-single-undo (&rest body)
"Execute BODY as a single undo step."
(declare (indent defun)
(debug t))
(declare (debug t))
`(let (evil-undo-list-pointer)
(evil-with-undo
(evil-start-undo-step)
(unwind-protect
(progn
(evil-start-undo-step)
(let ((evil-in-single-undo t))
,@body))
(let ((evil-in-single-undo t)) ,@body)
(evil-end-undo-step)))))

(defun evil-undo-pop ()
"Undo the last buffer change.
Removes the last undo information from `buffer-undo-list'.
If undo is disabled in the current buffer, use the information
in `evil-temporary-undo' instead."
(let ((paste-undo (list nil)))
(let ((undo-list (if (eq buffer-undo-list t)
evil-temporary-undo
buffer-undo-list)))
(when (or (not undo-list) (car undo-list))
(user-error "Can't undo previous change"))
(while (and undo-list (null (car undo-list)))
(pop undo-list)) ; remove nil
(while (and undo-list (car undo-list))
(push (pop undo-list) paste-undo))
(let ((buffer-undo-list (nreverse paste-undo)))
(evil-save-echo-area
(undo)))
(if (eq buffer-undo-list t)
(setq evil-temporary-undo nil)
(setq buffer-undo-list undo-list)))))
(if (and (eq evil-undo-system 'undo-tree)
(not (eq buffer-undo-list t)))
(undo-tree-undo)
(let ((paste-undo (list nil)))
(let ((undo-list (if (eq buffer-undo-list t)
evil-temporary-undo
buffer-undo-list)))
(when (or (not undo-list) (car undo-list))
(user-error "Can't undo previous change"))
(while (and undo-list (null (car undo-list)))
(pop undo-list)) ; remove nil
(while (and undo-list (car undo-list))
(push (pop undo-list) paste-undo))
(let ((buffer-undo-list (nreverse paste-undo)))
(evil-save-echo-area
(undo)))
(if (eq buffer-undo-list t)
(setq evil-temporary-undo nil)
(setq buffer-undo-list undo-list))))))

;;; Search
(defun evil-transform-regexp (regexp replacements-alist)
Expand Down

0 comments on commit aff02d3

Please sign in to comment.