Subject: bug#23632: 25.1.50; Gratuitous undo boundary in latex-insert-block

From: Chong Yidong <[email-masked]>
Date: Sat, 28 May 2016 16:22:43 +0800

> The attached patch, which gets rid of the undo boundary, seems to fix
> this:

Actually, the previous patch does not DTRT: if you switch back to the
original buffer from the minibuffer, and make further editing changes,
those changes would get lost because buffer-undo-list is temporarily
rebound.

Here is a different patch, which works by removing the undo boundary in
buffer-undo-list if there's one.  It also tweaks HTML mode and Texinfo
mode, which have similar issues.  It defines a new function
`undo-amalgamate', split off from `undo-auto-amalgamate', for
convenience.

diff --git a/lisp/simple.el b/lisp/simple.el
index e257062..decd737 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -2939,18 +2939,17 @@ undo-auto-amalgamate
           ;; Amalgamate all buffers that have changed.
           (dolist (b (cdr undo-auto--last-boundary-cause))
             (when (buffer-live-p b)
-              (with-current-buffer
-                  b
-                (when
-                    ;; The head of `buffer-undo-list' is nil.
-                    ;; `car-safe' doesn't work because
-                    ;; `buffer-undo-list' need not be a list!
-                    (and (listp buffer-undo-list)
-                         (not (car buffer-undo-list)))
-                  (setq buffer-undo-list
-                        (cdr buffer-undo-list))))))
+              (with-current-buffer b
+                (undo-amalgamate))))
         (setq undo-auto--last-boundary-cause 0)))))
 
+(defun undo-amalgamate ()
+  "Amalgamate undo in the current buffer."
+  ;; `car-safe' doesn't work as `buffer-undo-list' need not be a list!
+  (and (listp buffer-undo-list)
+       (null (car buffer-undo-list))
+       (pop buffer-undo-list)))
+
 (defun undo-auto--undoable-change ()
   "Called after every undoable buffer change."
   (add-to-list 'undo-auto--undoably-changed-buffers (current-buffer))
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index 990c09b..51b7241 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -700,11 +700,15 @@ sgml-tag
 `sgml-transformation-function' to `upcase'."
   (funcall (or skeleton-transformation-function 'identity)
            (setq sgml-tag-last
-		 (completing-read
-		  (if (> (length sgml-tag-last) 0)
-		      (format "Tag (default %s): " sgml-tag-last)
-		    "Tag: ")
-		  sgml-tag-alist nil nil nil 'sgml-tag-history sgml-tag-last)))
+                 ;; Avoid creating an undo boundary.
+                 (prog1
+                     (completing-read
+                      (if (> (length sgml-tag-last) 0)
+                          (format "Tag (default %s): " sgml-tag-last)
+                        "Tag: ")
+                      sgml-tag-alist nil nil nil
+                      'sgml-tag-history sgml-tag-last)
+                   (undo-amalgamate))))
   ?< str |
   (("") -1 '(undo-boundary) (identity "&lt;")) |	; see comment above
   `(("") '(setq v2 (sgml-attributes ,str t)) ?>
diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el
index b38b147..8b7d98f 100644
--- a/lisp/textmodes/tex-mode.el
+++ b/lisp/textmodes/tex-mode.el
@@ -1539,10 +1539,13 @@ 'tex-latex-block
 (define-skeleton latex-insert-block
   "Create a matching pair of lines \\begin{NAME} and \\end{NAME} at point.
 Puts point on a blank line between them."
-  (let ((choice (completing-read (format "LaTeX block name [%s]: "
-					 latex-block-default)
-                                 (latex-complete-envnames)
-				 nil nil nil nil latex-block-default)))
+  (let* ((buffer-undo-list t)
+         (choice (prog1
+                     (completing-read (format "LaTeX block name [%s]: "
+                                              latex-block-default)
+                                      (latex-complete-envnames)
+                                      nil nil nil nil latex-block-default)
+                   (undo-amalgamate))))
     (setq latex-block-default choice)
     (unless (or (member choice latex-standard-block-names)
 		(member choice latex-block-names))
diff --git a/lisp/textmodes/texinfo.el b/lisp/textmodes/texinfo.el
index ed6022f..fd411e2 100644
--- a/lisp/textmodes/texinfo.el
+++ b/lisp/textmodes/texinfo.el
@@ -653,9 +653,12 @@ texinfo-insert-block
   "Create a matching pair @<cmd> .. @end <cmd> at point.
 Puts point on a blank line between them."
   (setq texinfo-block-default
-	(completing-read (format "Block name [%s]: " texinfo-block-default)
-			 texinfo-environments
-			 nil nil nil nil texinfo-block-default))
+        (prog1
+            (completing-read (format "Block name [%s]: "
+                                     texinfo-block-default)
+                             texinfo-environments
+                             nil nil nil nil texinfo-block-default)
+          (undo-amalgamate)))
   \n "@" str
   ;; Blocks that take parameters: all the def* blocks take parameters,
   ;;  plus a few others.