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 #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 #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 7, 2023
1 parent cc59e44 commit a7e2c75
Showing 1 changed file with 40 additions and 29 deletions.
69 changes: 40 additions & 29 deletions evil-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -3629,6 +3629,14 @@ If no description is available, return the empty string."

;;; Undo

(defvar buffer-undo-tree)
(declare-function undo-tree-current "ext:undo-tree")
(declare-function undo-tree-node-next "ext:undo-tree")
(declare-function undo-tree-node-branch "ext:undo-tree")
(declare-function undo-tree-node-branch "ext:undo-tree")
(declare-function undo-tree-undo "ext:undo-tree")
(declare-function undo-tree-snip-node "ext:undo-tree")

(defun evil-start-undo-step (&optional continue)
"Start a undo step.
All following buffer modifications are grouped together as a
Expand Down Expand Up @@ -3667,46 +3675,49 @@ 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 evil-undo-system))
(when (eq ,undo-list t) (setq buffer-undo-list nil
evil-undo-system 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'.
"Undo and forget the last buffer change.
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)
(if (and (eq evil-undo-system 'undo-tree)
(not (eq buffer-undo-list t)))
(let (current)
(undo-tree-undo)
(setq current (undo-tree-current buffer-undo-tree)
current (nth (undo-tree-node-branch current)
(undo-tree-node-next current)))
;; Remove only if leaf to not have to adjust child buffer positions
(unless (undo-tree-node-next current) (undo-tree-snip-node current)))
(let ((paste-undo (list nil))
(undo-list (if (eq buffer-undo-list t)
evil-temporary-undo
buffer-undo-list)))
(when (or (not undo-list) (car undo-list))
Expand Down

0 comments on commit a7e2c75

Please sign in to comment.