diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6e963a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +.PHONY: test +test: + emacs -batch -l ert -l undo-propose.el -l undo-propose-test.el -f ert-run-tests-batch-and-exit diff --git a/Readme.org b/Readme.org index 688862e..63ef342 100644 --- a/Readme.org +++ b/Readme.org @@ -61,6 +61,12 @@ add [[https://www.emacswiki.org/emacs/RedoMode][redo]], call ~(undo-propose-wrap - ~undo-propose-done-hook~ is run after committing or squash committing an undo-propose +*** Markers + +Currently =undo-propose= does not correctly update markers in the +parent buffer after undo'ing. As a workaround, you can add markers to +=undo-propose-marker-list= to ensure they are updated after undo'ing. + *** Example configurations **** Simple configuration diff --git a/undo-propose-test.el b/undo-propose-test.el new file mode 100644 index 0000000..4519ae5 --- /dev/null +++ b/undo-propose-test.el @@ -0,0 +1,31 @@ +;;; undo-propose-test.el --- Tests for undo-propose + +(require 'undo-propose) + +(defmacro with-undoable-temp-buffer (&rest body) + "Like `with-temp-buffer', but doesn't disable `undo'." + `(let ((temp-buffer (generate-new-buffer + ;; this must NOT start with a space, otherwise + ;; undo won't work. See `get-buffer-create' + "*temp*"))) + (unwind-protect + (with-current-buffer temp-buffer + ,@body) + (kill-buffer temp-buffer)))) + +(ert-deftest undo-propose-test-org-clock () + (with-undoable-temp-buffer + (org-mode) + (insert "* test\n") + (undo-boundary) + (org-clock-in) + (undo-boundary) + (goto-char (point-max)) + (insert "\nfoobar") + (undo-boundary) + (undo-propose) + (call-interactively (command-remapping 'undo)) + (undo-propose-commit) + (org-clock-out))) + +;;; undo-propose-test.el ends here diff --git a/undo-propose.el b/undo-propose.el index ba43cb8..0dd2e8e 100644 --- a/undo-propose.el +++ b/undo-propose.el @@ -42,20 +42,28 @@ ;;; Code: +(require 'cl-lib) + (defgroup undo-propose nil "Simple and safe undo navigation" :group 'convenience) (defcustom undo-propose-done-hook nil - "Hook runs when initially entering the temporal buffer." + "Hook runs when leaving the temporal buffer." :type 'hook :group 'undo-propose) (defcustom undo-propose-entry-hook nil - "Hook runs when leaving the temporal buffer." + "Hook runs when entering the temporal buffer." :type 'hook :group 'undo-propose) +(defcustom undo-propose-marker-list + '(org-clock-marker org-clock-hd-marker) + "List of quoted markers to update after running undo-propose." + :type 'list + :group 'undo-propose) + (defvar undo-propose-parent nil "Parent buffer of ‘undo-propose’ buffer.") (defun undo-propose--message (content) @@ -98,6 +106,7 @@ If already inside an `undo-propose' buffer, this will simply call `undo'." (setq-local buffer-read-only t) (setq-local undo-propose-parent orig-buffer) (undo-propose-mode 1) + (undo-propose-copy-markers) (run-hooks 'undo-propose-entry-hook) (undo-propose--message "C-c C-c to commit, C-c C-s to squash commit, C-c C-k to cancel, C-c C-d to diff")))) @@ -131,6 +140,7 @@ If already inside an `undo-propose' buffer, this will simply call `undo'." (copy-to-buffer orig-buffer 1 (buffer-end 1)) (with-current-buffer orig-buffer (setq-local buffer-undo-list list-copy)) + (undo-propose-update-markers) (switch-to-buffer orig-buffer) (kill-buffer tmp-buffer) (goto-char pos) @@ -156,6 +166,7 @@ buffer contents are copied." (goto-char (point-max)) (insert-buffer-substring tmp-buffer first-diff tmp-end) (goto-char first-diff))) + (undo-propose-update-markers) (switch-to-buffer orig-buffer) (kill-buffer tmp-buffer) (undo-propose--message "squash commit")) @@ -181,6 +192,32 @@ buffer contents are copied." (interactive) (ediff-buffers undo-propose-parent (current-buffer))) +(defun undo-propose-copy-markers () + "Copy markers registered in `undo-propose-marker-list'." + (setq-local undo-propose-marker-map + (cl-loop for marker-symbol in undo-propose-marker-list + if (when (boundp marker-symbol) + (let ((orig-marker (symbol-value marker-symbol))) + (when (markerp orig-marker) + (eq (marker-buffer orig-marker) + undo-propose-parent)))) + collect + (let ((orig-marker (symbol-value marker-symbol)) + (new-marker (make-marker))) + (move-marker new-marker (marker-position orig-marker)) + (cons new-marker orig-marker))))) + +(defun undo-propose-update-markers () + "Update marker positions in parent buffer." + (cl-loop for association in undo-propose-marker-map do + (let ((new-marker (car association)) + (orig-marker (cdr association))) + ;; only update if orig-marker still exists + (when (and (markerp orig-marker) + (eq (marker-buffer orig-marker) undo-propose-parent)) + (move-marker orig-marker (marker-position new-marker) + undo-propose-parent))))) + (provide 'undo-propose) ;;; undo-propose.el ends here