scratch/undo-apply a34caecb046 3/3: Improve undo-in-region support for combine-change-calls

Helmut Eller <[email protected]> Sun, 26 Jul 2026 12:56:32 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: scratch/undo-apply
commit a34caecb046de0635891048cd0cc2305a64b56fb
Author: Helmut Eller <[email protected]>
Commit: Helmut Eller <[email protected]>

    Improve undo-in-region support for combine-change-calls
    
    * lisp/subr.el (combine-change-calls-1): Use the new 'apply entry
    variant for the buffer-undo-list.
    (undo--wrap-and-run-primitive-undo): Adjust the undo entries if the
    region has moved.
    * test/src/undo-tests.el (undo-test-combine-change-calls-4): New test.
---
 lisp/subr.el           | 22 ++++++++++++++++------
 test/src/undo-tests.el | 22 ++++++++++++++++++++++
 2 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/lisp/subr.el b/lisp/subr.el
index 60a57688f73..04ca6a042e9 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -5715,8 +5715,7 @@ the function `undo--wrap-and-run-primitive-undo'."
 	    ;; body-undo-list ...
 	    (push (list 'apply
 			(- end end-marker)
-			beg
-			(marker-position end-marker)
+			(cons beg (marker-position end-marker))
 			#'undo--wrap-and-run-primitive-undo
 			beg (marker-position end-marker)
 			body-undo-list)
@@ -5756,7 +5755,7 @@ the function `undo--wrap-and-run-primitive-undo'."
   (declare (debug (form form def-body)) (indent 2))
   `(combine-change-calls-1 ,beg ,end (lambda () ,@body)))
 
-(defun undo--wrap-and-run-primitive-undo (beg end list)
+(defun undo--wrap-and-run-primitive-undo (beg end orig-beg orig-end list)
   "Call `primitive-undo' on the undo elements in LIST.
 
 This function is intended to be called purely by `undo' as the
@@ -5771,9 +5770,20 @@ Additionally the fresh \"redo\" elements which are generated on
 
 Undo elements of this form are generated by the macro
 `combine-change-calls'."
-  (combine-change-calls beg end
-			(while list
-			  (setq list (primitive-undo 1 list)))))
+  (let* ((beg-delta (- orig-beg beg))
+         (end-delta (- orig-end end))
+         (list (cond ((/= beg-delta end-delta)
+                      (error "Not yet implemented"))
+                     ((= beg-delta 0)
+                      list)
+                     (t
+                      (let ((deltas (list (cons orig-beg beg-delta))))
+                        (mapcar (lambda (elt)
+                                  (undo-adjust-elt elt deltas))
+                                list))))))
+    (combine-change-calls beg end
+      (while list
+	(setq list (primitive-undo 1 list))))))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
diff --git a/test/src/undo-tests.el b/test/src/undo-tests.el
index 073b1f415fd..3722b913ebb 100644
--- a/test/src/undo-tests.el
+++ b/test/src/undo-tests.el
@@ -511,6 +511,28 @@ Case 3: a file-visiting buffer with `buffer-undo-list' nil and
         (replace-match "Z "))
       (should (= (length buffer-undo-list) 1)))))
 
+(ert-deftest undo-test-combine-change-calls-4 ()
+  "Test `combine-change-calls' in combination with undo-in-region."
+  (with-temp-buffer
+    (buffer-enable-undo)
+    (dolist (s '("A" "B" "C" "D"))
+      (insert s)
+      (undo-boundary))
+    (combine-change-calls (point-min) (point-max)
+      (goto-char (point-min))
+      (while (re-search-forward "[BD]" nil t)
+        (delete-char -1)
+        (insert "xy")))
+    (goto-char (point-min))
+    (insert "<")
+    (goto-char (point-max))
+    (insert ">")
+    (undo-boundary)
+    (should (equal (buffer-string) "<AxyCxy>"))
+    (undo-tests--mark-region 2 8)
+    (undo)
+    (should (equal (buffer-string) "<ABCD>"))))
+
 (defun undo-test-all (&optional interactive)
   "Run all tests for \\[undo]."
   (interactive "p")