* bug#39680: 27.0.60; electric-pair-mode broken by undo
@ 2020-02-19 18:34 Kévin Le Gouguec
2020-02-25 19:34 ` Alan Mackenzie
0 siblings, 1 reply; 10+ messages in thread
From: Kévin Le Gouguec @ 2020-02-19 18:34 UTC (permalink / raw)
To: 39680; +Cc: Stefan Monnier
Hello,
Commit e66d5a1c45 (2019-02-19T00:00:44Z!monnier@iro.umontreal.ca) might
have introduced a bug. From emacs -Q:
1. C-x b foo RET
2. M-x electric-pair-mode RET
3. (
- A closing parenthesis has been inserted.
4. C-b C-f
- This is to break undo grouping.
5. a
6. C-_
7. (
In Emacs 26.3, buffer foo contains "(())" and point is after the
innermost opening bracket.
In Emacs 27, buffer foo contains "()" and point is after the closing
bracket. The *Messages* buffer shows:
> cancel-change-group: Undoing to some unrelated state
NB: this can be reproduced with other electric-pair characters, e.g. ".
I found the bug in a Python buffer trying to write a tuple of strings: I
opened a parenthesis, forgot to add quotes, wrote a string, hit undo,
tried to insert a single quote… and got moved past the end parenthesis
instead.
I tried to write a non-regression test; unfortunately what I came up
with does not catch this bug reliably:
(ert-deftest electric-pair-undo-unrelated-state ()
(with-temp-buffer
(buffer-enable-undo)
(electric-pair-mode)
(let ((last-command-event ?\())
(self-insert-command 1))
(undo-boundary)
(insert "hi there")
(undo)
(let ((last-command-event ?\())
(self-insert-command 1))))
C-x C-e'ing the (with-temp-buffer …) form only triggers the error once
every two evaluations, for some reason.
Thank you for your time.
In GNU Emacs 28.0.50 (build 5, x86_64-pc-linux-gnu, GTK+ Version 3.24.13, cairo version 1.16.0)
of 2020-02-19 built on my-little-tumbleweed
Repository revision: e1e1bd8f85c53fea9f61b6ec99b461ddd93461b9
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12007000
System Description: openSUSE Tumbleweed
Configured using:
'configure --with-xwidgets --with-cairo'
^ permalink raw reply [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-02-19 18:34 bug#39680: 27.0.60; electric-pair-mode broken by undo Kévin Le Gouguec @ 2020-02-25 19:34 ` Alan Mackenzie 2020-03-09 18:26 ` Stefan Monnier 0 siblings, 1 reply; 10+ messages in thread From: Alan Mackenzie @ 2020-02-25 19:34 UTC (permalink / raw) To: Kévin Le Gouguec, Stefan Monnier; +Cc: 39680 Hello, Kévin and Stefan. On Wed, Feb 19, 2020 at 19:34:37 +0100, Kévin Le Gouguec wrote: > Hello, > Commit e66d5a1c45 (2019-02-19T00:00:44Z!monnier@iro.umontreal.ca) might > have introduced a bug. From emacs -Q: > 1. C-x b foo RET > 2. M-x electric-pair-mode RET > 3. ( > - A closing parenthesis has been inserted. > 4. C-b C-f > - This is to break undo grouping. > 5. a > 6. C-_ > 7. ( > In Emacs 26.3, buffer foo contains "(())" and point is after the > innermost opening bracket. > In Emacs 27, buffer foo contains "()" and point is after the closing > bracket. The *Messages* buffer shows: > > cancel-change-group: Undoing to some unrelated state The cause of this bug is a bug in cancel-change-group, part of the atomic-change-group group of functions. The commit you (Kévin) refer to above substitutes atomic-change-group for a simple insertion and deletion of a character (for a good reason). cancel-change-group looks like this: (defun cancel-change-group (handle) "Finish a change group made with `prepare-change-group' (which see). This finishes the change group by reverting all of its changes." (dolist (elt handle) (with-current-buffer (car elt) (setq elt (cdr elt)) (save-restriction ;; Widen buffer temporarily so if the buffer was narrowed within ;; the body of `atomic-change-group' all changes can be undone. (widen) (let ((old-car (car-safe elt)) (old-cdr (cdr-safe elt))) (unwind-protect (progn ;; Temporarily truncate the undo log at ELT. (when (consp elt) (setcar elt nil) (setcdr elt nil)) (unless (eq last-command 'undo) (undo-start)) <======= ;; Make sure there's no confusion. ============> (when (and (consp elt) (not (eq elt (last pending-undo-list)))) (error "Undoing to some unrelated state")) ;; Undo it all. (save-excursion (while (listp pending-undo-list) (undo-more 1))) ;; Revert the undo info to what it was when we grabbed ;; the state. (setq buffer-undo-list elt)) ;; Reset the modified cons cell ELT to its original content. (when (consp elt) (setcar elt old-car) (setcdr elt old-cdr)))))))) On entry to this function, HANDLE has the value: ((#<buffer asdf> (2 . 3) nil ("a" . 2) (#<marker at 2 in asdf> . -1) nil (2 . 3))) . At the first indicated spot above, last-command is indeed 'undo, so undo-start is not invoked. Since the undo which undid "a" emptied pending-undo-list, pending-undo-list has been set to t. So when the eq is done in the second indicated spot, pending-undo-list is not ELT (the first cons cell from (cdr handle)). The function thus spuriously signals the error "Undoing to some unrelated state". ######################################################################### I admit I don't fully understand the mechanism of atomic-change-group, but I see the problem as the EQ comparison on the <======= line. If pending-undo-list has been replaced by t (after being exhausted by the previous 'undo operation), there is no point EQing it with the cons from HANDLE. So, as a first approximation to a fix, I added a (consp pending-undo-list) into this test, as follow: diff --git a/lisp/subr.el b/lisp/subr.el index b5ec0de156..8b7d9b5451 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -2975,7 +2975,9 @@ cancel-change-group ;; Temporarily truncate the undo log at ELT. (when (consp elt) (setcar elt nil) (setcdr elt nil)) - (unless (eq last-command 'undo) (undo-start)) + (unless (and (eq last-command 'undo) + (consp pending-undo-list)) + (undo-start)) ;; Make sure there's no confusion. (when (and (consp elt) (not (eq elt (last pending-undo-list)))) (error "Undoing to some unrelated state")) Stefan, what is your view on this attempted patch? Is it sound? [ .... ] > Thank you for your time. Thank you for a good bug report, conveniently reduced to a minimum test case. > In GNU Emacs 28.0.50 (build 5, x86_64-pc-linux-gnu, GTK+ Version 3.24.13, cairo version 1.16.0) > of 2020-02-19 built on my-little-tumbleweed > Repository revision: e1e1bd8f85c53fea9f61b6ec99b461ddd93461b9 > Repository branch: master > Windowing system distributor 'The X.Org Foundation', version 11.0.12007000 > System Description: openSUSE Tumbleweed > Configured using: > 'configure --with-xwidgets --with-cairo' -- Alan Mackenzie (Nuremberg, Germany). ^ permalink raw reply related [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-02-25 19:34 ` Alan Mackenzie @ 2020-03-09 18:26 ` Stefan Monnier 2020-03-10 6:45 ` Kévin Le Gouguec 0 siblings, 1 reply; 10+ messages in thread From: Stefan Monnier @ 2020-03-09 18:26 UTC (permalink / raw) To: Alan Mackenzie; +Cc: 39680, Kévin Le Gouguec > The cause of this bug is a bug in cancel-change-group, Indeed. Thanks Alan for the nice analysis (and sorry I didn't get to it earlier). > (unless (eq last-command 'undo) (undo-start)) <======= > ;; Make sure there's no confusion. > ============> (when (and (consp elt) (not (eq elt (last pending-undo-list)))) > . At the first indicated spot above, last-command is indeed 'undo, so > undo-start is not invoked. I think there's a bug right here: the fact that the previous command was `undo` shouldn't really matter. Worse, we've made changes to the buffer since that last `undo` so it's plain wrong to pass that old `pending-undo-list` to `undo-more`. > Stefan, what is your view on this attempted patch? Is it sound? I think we need something like the patch below (not really tested yet). WDYT? >> Thank you for your time. > Thank you for a good bug report, conveniently reduced to a minimum test > case. Indeed. This is pretty delicate code, so a concise and easy to reproduce test case is very welcome. Stefan diff --git a/lisp/subr.el b/lisp/subr.el index 13515ca7da..ebc8e320dc 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -2972,13 +2972,14 @@ cancel-change-group ;; the body of `atomic-change-group' all changes can be undone. (widen) (let ((old-car (car-safe elt)) - (old-cdr (cdr-safe elt))) + (old-cdr (cdr-safe elt)) + (start-pul pending-undo-list)) (unwind-protect (progn ;; Temporarily truncate the undo log at ELT. (when (consp elt) (setcar elt nil) (setcdr elt nil)) - (unless (eq last-command 'undo) (undo-start)) + (setq pending-undo-list buffer-undo-list) ;; Make sure there's no confusion. (when (and (consp elt) (not (eq elt (last pending-undo-list)))) (error "Undoing to some unrelated state")) @@ -2991,7 +2992,13 @@ cancel-change-group ;; Reset the modified cons cell ELT to its original content. (when (consp elt) (setcar elt old-car) - (setcdr elt old-cdr)))))))) + (setcdr elt old-cdr))) + ;; Let's not break a sequence of undos just because we + ;; tried to make a change and then undid it: preserve + ;; the original `pending-undo-list' if it's still valid. + (if (eq (undo--last-change-was-undo-p buffer-undo-list) + start-pul) + (setq pending-undo-list start-pul))))))) \f ;;;; Display-related functions. ^ permalink raw reply related [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-03-09 18:26 ` Stefan Monnier @ 2020-03-10 6:45 ` Kévin Le Gouguec 2020-03-11 15:31 ` Stefan Monnier [not found] ` <87d078ajzs.fsf@gmail.com> 0 siblings, 2 replies; 10+ messages in thread From: Kévin Le Gouguec @ 2020-03-10 6:45 UTC (permalink / raw) To: Stefan Monnier; +Cc: Alan Mackenzie, 39680 Stefan Monnier <monnier@iro.umontreal.ca> writes: >> Stefan, what is your view on this attempted patch? Is it sound? > > I think we need something like the patch below (not really tested yet). > WDYT? FWIW, I applied it and AFAICT I can't reproduce the issue. >>> Thank you for your time. >> Thank you for a good bug report, conveniently reduced to a minimum test >> case. > > Indeed. This is pretty delicate code, so a concise and easy to reproduce > test case is very welcome. Happy to help. Though I wonder why my attempted ERT test only fails half the time. Reproduced here for context: (ert-deftest electric-pair-undo-unrelated-state () (with-temp-buffer (buffer-enable-undo) (electric-pair-mode) (let ((last-command-event ?\()) (self-insert-command 1)) (undo-boundary) (insert "hi there") (undo) (let ((last-command-event ?\()) (self-insert-command 1)))) With your patch, C-x C-e'ing the inner with-temp-buffer form once triggers no error (an "Undo" shows up in *Messages*); C-x C-e'ing a second time without moving point causes a "No further undo information" user error. At any rate, thanks for the fix! ^ permalink raw reply [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-03-10 6:45 ` Kévin Le Gouguec @ 2020-03-11 15:31 ` Stefan Monnier 2020-03-11 16:20 ` Eli Zaretskii [not found] ` <87d078ajzs.fsf@gmail.com> 1 sibling, 1 reply; 10+ messages in thread From: Stefan Monnier @ 2020-03-11 15:31 UTC (permalink / raw) To: Kévin Le Gouguec; +Cc: Alan Mackenzie, 39680 >> I think we need something like the patch below (not really tested yet). >> WDYT? > > FWIW, I applied it and AFAICT I can't reproduce the issue. Thanks. I think the patch wasn't quite right and was overly complicated: `undo` is already responsible for dealing with the freshness of `pending-undo-list`. So I think the better patch is the one below. Eli, is the patch below OK for `emacs-27`? Stefan [ BTW, I accidentally installed the previous patch on `master`, and I'm waiting for "the right solution" to clean up. ] diff --git a/lisp/subr.el b/lisp/subr.el index 5b94343e499..4c69c9dd044 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -2964,13 +2964,18 @@ cancel-change-group ;; the body of `atomic-change-group' all changes can be undone. (widen) (let ((old-car (car-safe elt)) - (old-cdr (cdr-safe elt))) + (old-cdr (cdr-safe elt)) + ;; Use `pending-undo-list' temporarily since `undo-more' needs + ;; it, but restore it afterwards so as not to mess with an + ;; ongoing sequence of `undo's. + (pending-undo-list + ;; Use `buffer-undo-list' unconditionally (bug#39680). + buffer-undo-list)) (unwind-protect (progn ;; Temporarily truncate the undo log at ELT. (when (consp elt) (setcar elt nil) (setcdr elt nil)) - (unless (eq last-command 'undo) (undo-start)) ;; Make sure there's no confusion. (when (and (consp elt) (not (eq elt (last pending-undo-list)))) (error "Undoing to some unrelated state")) ^ permalink raw reply related [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-03-11 15:31 ` Stefan Monnier @ 2020-03-11 16:20 ` Eli Zaretskii 2020-03-12 14:04 ` Stefan Monnier 0 siblings, 1 reply; 10+ messages in thread From: Eli Zaretskii @ 2020-03-11 16:20 UTC (permalink / raw) To: Stefan Monnier; +Cc: acm, kevin.legouguec, 39680 > From: Stefan Monnier <monnier@iro.umontreal.ca> > Date: Wed, 11 Mar 2020 11:31:45 -0400 > Cc: Alan Mackenzie <acm@muc.de>, 39680@debbugs.gnu.org > > Eli, is the patch below OK for `emacs-27`? Yes, thanks. ^ permalink raw reply [flat|nested] 10+ messages in thread
* bug#39680: 27.0.60; electric-pair-mode broken by undo 2020-03-11 16:20 ` Eli Zaretskii @ 2020-03-12 14:04 ` Stefan Monnier 0 siblings, 0 replies; 10+ messages in thread From: Stefan Monnier @ 2020-03-12 14:04 UTC (permalink / raw) To: Eli Zaretskii; +Cc: acm, 39680-done, kevin.legouguec >> Eli, is the patch below OK for `emacs-27`? > Yes, thanks. Thanks, installed, Stefan ^ permalink raw reply [flat|nested] 10+ messages in thread
[parent not found: <87d078ajzs.fsf@gmail.com>]
[parent not found: <87blmstg0v.fsf@gmail.com>]
[parent not found: <87blms6ww9.fsf@gmail.com>]
[parent not found: <877dxgtae9.fsf@gmail.com>]
* bug#39680: Test case [not found] ` <877dxgtae9.fsf@gmail.com> @ 2020-05-19 22:05 ` Kévin Le Gouguec 2020-05-19 22:26 ` Kévin Le Gouguec 2020-05-19 23:17 ` João Távora 0 siblings, 2 replies; 10+ messages in thread From: Kévin Le Gouguec @ 2020-05-19 22:05 UTC (permalink / raw) To: João Távora; +Cc: Alan Mackenzie, control, Stefan Monnier, 39680 [-- Attachment #1: Type: text/plain, Size: 209 bytes --] Finally nailed it, after finding out about ert-simulate-command. The following patch adds a test that passes on master and emacs-27, and "successfully fails" when reverting Stefan's fix (commit c1ce9fa7f2). [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Add-test-for-bug-39680.patch --] [-- Type: text/x-patch, Size: 1414 bytes --] From c83fbd50f0f0999814f09706fc908df5c22c5a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Le=20Gouguec?= <kevin.legouguec@gmail.com> Date: Tue, 19 May 2020 23:17:04 +0200 Subject: [PATCH] Add test for bug#39680 * test/lisp/electric-tests.el (electric-pair-undo-unrelated-state): New test. --- test/lisp/electric-tests.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el index 56d1bdb110..67f474cbd5 100644 --- a/test/lisp/electric-tests.el +++ b/test/lisp/electric-tests.el @@ -546,6 +546,24 @@ js-mode-braces-with-layout-and-indent (electric-pair-delete-pair 1) (should (equal "" (buffer-string)))))) +\f +;;; Undoing +(ert-deftest electric-pair-undo-unrelated-state () + "Make sure `electric-pair-mode' does not confuse `undo' (bug#39680)." + (with-temp-buffer + (buffer-enable-undo) + (electric-pair-local-mode) + (let ((last-command-event ?\()) + (ert-simulate-command '(self-insert-command 1))) + (undo-boundary) + (let ((last-command-event ?a)) + (ert-simulate-command '(self-insert-command 1))) + (undo-boundary) + (ert-simulate-command '(undo)) + (let ((last-command-event ?\()) + (ert-simulate-command '(self-insert-command 1))) + (should (string= (buffer-string) "(())")))) + \f ;;; Electric newlines between pairs ;;; TODO: better tests -- 2.26.2 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* bug#39680: Test case 2020-05-19 22:05 ` bug#39680: Test case Kévin Le Gouguec @ 2020-05-19 22:26 ` Kévin Le Gouguec 2020-05-19 23:17 ` João Távora 1 sibling, 0 replies; 10+ messages in thread From: Kévin Le Gouguec @ 2020-05-19 22:26 UTC (permalink / raw) To: João Távora; +Cc: Alan Mackenzie, Stefan Monnier, 39680 (Removing control@debbugs.gnu.org from the CC list; sorry for this blunder.) Kévin Le Gouguec <kevin.legouguec@gmail.com> writes: > Finally nailed it, after finding out about ert-simulate-command. > > The following patch adds a test that passes on master and emacs-27, and > "successfully fails" when reverting Stefan's fix (commit c1ce9fa7f2). ^ permalink raw reply [flat|nested] 10+ messages in thread
* bug#39680: Test case 2020-05-19 22:05 ` bug#39680: Test case Kévin Le Gouguec 2020-05-19 22:26 ` Kévin Le Gouguec @ 2020-05-19 23:17 ` João Távora 1 sibling, 0 replies; 10+ messages in thread From: João Távora @ 2020-05-19 23:17 UTC (permalink / raw) To: Kévin Le Gouguec; +Cc: Alan Mackenzie, control, Stefan Monnier, 39680 Kévin Le Gouguec <kevin.legouguec@gmail.com> writes: > Finally nailed it, after finding out about ert-simulate-command. > > The following patch adds a test that passes on master and emacs-27, and > "successfully fails" when reverting Stefan's fix (commit c1ce9fa7f2). Thanks! pushed to master. João ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2020-05-19 23:17 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2020-02-19 18:34 bug#39680: 27.0.60; electric-pair-mode broken by undo Kévin Le Gouguec 2020-02-25 19:34 ` Alan Mackenzie 2020-03-09 18:26 ` Stefan Monnier 2020-03-10 6:45 ` Kévin Le Gouguec 2020-03-11 15:31 ` Stefan Monnier 2020-03-11 16:20 ` Eli Zaretskii 2020-03-12 14:04 ` Stefan Monnier [not found] ` <87d078ajzs.fsf@gmail.com> [not found] ` <87blmstg0v.fsf@gmail.com> [not found] ` <87blms6ww9.fsf@gmail.com> [not found] ` <877dxgtae9.fsf@gmail.com> 2020-05-19 22:05 ` bug#39680: Test case Kévin Le Gouguec 2020-05-19 22:26 ` Kévin Le Gouguec 2020-05-19 23:17 ` João Távora
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).