From: Barry OReilly <gundaetiapo@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: 16818@debbugs.gnu.org
Subject: bug#16818: Acknowledgement (Undo in region after markers in undo history relocated)
Date: Mon, 17 Mar 2014 19:05:34 -0400 [thread overview]
Message-ID: <CAFM41H2fmREiVOm6Vh4KGeRLSLM99cMbsHKdHn_86p_dj=1yLw@mail.gmail.com> (raw)
In-Reply-To: <jwvlhwexjdk.fsf-monnier+emacsbugs@gnu.org>
[-- Attachment #1.1: Type: text/plain, Size: 206 bytes --]
> The primitive-undo fix should be safe enough for 24.4, so if you
> want to code this up and install it, feel free.
I have attached the patch for this, which I'll install if nothing
comes up from review.
[-- Attachment #1.2: Type: text/html, Size: 258 bytes --]
[-- Attachment #2: undo-skip-markers.diff --]
[-- Type: text/plain, Size: 16365 bytes --]
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index a1ee5bb..a53f5d6 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,13 @@
+2014-03-13 Barry O'Reilly <gundaetiapo@gmail.com>
+
+ * simple.el (primitive-undo): When adjusting a marker, check that
+ its position is still valid. (Bug#16818)
+ (undo-make-selective-list): Determine whether a marker adjustment
+ is in the region based on whether the deletion that recorded it in
+ undo history is in the region. Remove variable adjusted-markers,
+ which was unused and only non nil during undo-make-selective-list.
+ (undo-elt-in-region): New optional argument MARKER-VALIDITY-POS.
+
2014-03-13 Dmitry Gutov <dgutov@yandex.ru>
* progmodes/ruby-mode.el (ruby-font-lock-keywords): Fontify
diff --git a/lisp/simple.el b/lisp/simple.el
index 881a633..a72cf8b 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -2229,85 +2229,90 @@ Return what remains of the list."
(did-apply nil)
(next nil))
(while (> arg 0)
- (while (setq next (pop list)) ;Exit inner loop at undo boundary.
- ;; Handle an integer by setting point to that value.
- (pcase next
- ((pred integerp) (goto-char next))
- ;; Element (t . TIME) records previous modtime.
- ;; Preserve any flag of NONEXISTENT_MODTIME_NSECS or
- ;; UNKNOWN_MODTIME_NSECS.
- (`(t . ,time)
- ;; If this records an obsolete save
- ;; (not matching the actual disk file)
- ;; then don't mark unmodified.
- (when (or (equal time (visited-file-modtime))
- (and (consp time)
- (equal (list (car time) (cdr time))
- (visited-file-modtime))))
- (when (fboundp 'unlock-buffer)
- (unlock-buffer))
- (set-buffer-modified-p nil)))
- ;; Element (nil PROP VAL BEG . END) is property change.
- (`(nil . ,(or `(,prop ,val ,beg . ,end) pcase--dontcare))
- (when (or (> (point-min) beg) (< (point-max) end))
- (error "Changes to be undone are outside visible portion of buffer"))
- (put-text-property beg end prop val))
- ;; Element (BEG . END) means range was inserted.
- (`(,(and beg (pred integerp)) . ,(and end (pred integerp)))
- ;; (and `(,beg . ,end) `(,(pred integerp) . ,(pred integerp)))
- ;; Ideally: `(,(pred integerp beg) . ,(pred integerp end))
- (when (or (> (point-min) beg) (< (point-max) end))
- (error "Changes to be undone are outside visible portion of buffer"))
- ;; Set point first thing, so that undoing this undo
- ;; does not send point back to where it is now.
- (goto-char beg)
- (delete-region beg end))
- ;; Element (apply FUN . ARGS) means call FUN to undo.
- (`(apply . ,fun-args)
- (let ((currbuff (current-buffer)))
- (if (integerp (car fun-args))
- ;; Long format: (apply DELTA START END FUN . ARGS).
- (pcase-let* ((`(,delta ,start ,end ,fun . ,args) fun-args)
- (start-mark (copy-marker start nil))
- (end-mark (copy-marker end t)))
- (when (or (> (point-min) start) (< (point-max) end))
- (error "Changes to be undone are outside visible portion of buffer"))
- (apply fun args) ;; Use `save-current-buffer'?
- ;; Check that the function did what the entry
- ;; said it would do.
- (unless (and (= start start-mark)
- (= (+ delta end) end-mark))
- (error "Changes to be undone by function different than announced"))
- (set-marker start-mark nil)
- (set-marker end-mark nil))
- (apply fun-args))
- (unless (eq currbuff (current-buffer))
- (error "Undo function switched buffer"))
- (setq did-apply t)))
- ;; Element (STRING . POS) means STRING was deleted.
- (`(,(and string (pred stringp)) . ,(and pos (pred integerp)))
- (when (let ((apos (abs pos)))
- (or (< apos (point-min)) (> apos (point-max))))
- (error "Changes to be undone are outside visible portion of buffer"))
- (if (< pos 0)
- (progn
- (goto-char (- pos))
- (insert string))
- (goto-char pos)
- ;; Now that we record marker adjustments
- ;; (caused by deletion) for undo,
- ;; we should always insert after markers,
- ;; so that undoing the marker adjustments
- ;; put the markers back in the right place.
- (insert string)
- (goto-char pos)))
- ;; (MARKER . OFFSET) means a marker MARKER was adjusted by OFFSET.
- (`(,(and marker (pred markerp)) . ,(and offset (pred integerp)))
- (when (marker-buffer marker)
- (set-marker marker
- (- marker offset)
- (marker-buffer marker))))
- (_ (error "Unrecognized entry in undo list %S" next))))
+ (let (del-pos)
+ (while (setq next (pop list)) ;Exit inner loop at undo boundary.
+ ;; Handle an integer by setting point to that value.
+ (pcase next
+ ((pred integerp) (goto-char next))
+ ;; Element (t . TIME) records previous modtime.
+ ;; Preserve any flag of NONEXISTENT_MODTIME_NSECS or
+ ;; UNKNOWN_MODTIME_NSECS.
+ (`(t . ,time)
+ ;; If this records an obsolete save
+ ;; (not matching the actual disk file)
+ ;; then don't mark unmodified.
+ (when (or (equal time (visited-file-modtime))
+ (and (consp time)
+ (equal (list (car time) (cdr time))
+ (visited-file-modtime))))
+ (when (fboundp 'unlock-buffer)
+ (unlock-buffer))
+ (set-buffer-modified-p nil)))
+ ;; Element (nil PROP VAL BEG . END) is property change.
+ (`(nil . ,(or `(,prop ,val ,beg . ,end) pcase--dontcare))
+ (when (or (> (point-min) beg) (< (point-max) end))
+ (error "Changes to be undone are outside visible portion of buffer"))
+ (put-text-property beg end prop val))
+ ;; Element (BEG . END) means range was inserted.
+ (`(,(and beg (pred integerp)) . ,(and end (pred integerp)))
+ ;; (and `(,beg . ,end) `(,(pred integerp) . ,(pred integerp)))
+ ;; Ideally: `(,(pred integerp beg) . ,(pred integerp end))
+ (when (or (> (point-min) beg) (< (point-max) end))
+ (error "Changes to be undone are outside visible portion of buffer"))
+ ;; Set point first thing, so that undoing this undo
+ ;; does not send point back to where it is now.
+ (goto-char beg)
+ (delete-region beg end))
+ ;; Element (apply FUN . ARGS) means call FUN to undo.
+ (`(apply . ,fun-args)
+ (let ((currbuff (current-buffer)))
+ (if (integerp (car fun-args))
+ ;; Long format: (apply DELTA START END FUN . ARGS).
+ (pcase-let* ((`(,delta ,start ,end ,fun . ,args) fun-args)
+ (start-mark (copy-marker start nil))
+ (end-mark (copy-marker end t)))
+ (when (or (> (point-min) start) (< (point-max) end))
+ (error "Changes to be undone are outside visible portion of buffer"))
+ (apply fun args) ;; Use `save-current-buffer'?
+ ;; Check that the function did what the entry
+ ;; said it would do.
+ (unless (and (= start start-mark)
+ (= (+ delta end) end-mark))
+ (error "Changes to be undone by function different than announced"))
+ (set-marker start-mark nil)
+ (set-marker end-mark nil))
+ (apply fun-args))
+ (unless (eq currbuff (current-buffer))
+ (error "Undo function switched buffer"))
+ (setq did-apply t)))
+ ;; Element (STRING . POS) means STRING was deleted.
+ (`(,(and string (pred stringp)) . ,(and pos (pred integerp)))
+ (when (let ((apos (abs pos)))
+ (or (< apos (point-min)) (> apos (point-max))))
+ (error "Changes to be undone are outside visible portion of buffer"))
+ (setq del-pos pos)
+ (if (< pos 0)
+ (progn
+ (goto-char (- pos))
+ (insert string))
+ (goto-char pos)
+ ;; Now that we record marker adjustments
+ ;; (caused by deletion) for undo,
+ ;; we should always insert after markers,
+ ;; so that undoing the marker adjustments
+ ;; put the markers back in the right place.
+ (insert string)
+ (goto-char pos)))
+ ;; (MARKER . OFFSET) means a marker MARKER was adjusted by OFFSET.
+ (`(,(and marker (pred markerp)) . ,(and offset (pred integerp)))
+ (when (and del-pos
+ (integerp (marker-position marker))
+ (= del-pos marker)
+ (marker-buffer marker))
+ (set-marker marker
+ (- marker offset)
+ (marker-buffer marker))))
+ (_ (error "Unrecognized entry in undo list %S" next)))))
(setq arg (1- arg)))
;; Make sure an apply entry produces at least one undo entry,
;; so the test in `undo' for continuing an undo series
@@ -2341,8 +2346,6 @@ are ignored. If BEG and END are nil, all undo elements are used."
(undo-make-selective-list (min beg end) (max beg end))
buffer-undo-list)))
-(defvar undo-adjusted-markers)
-
(defun undo-make-selective-list (start end)
"Return a list of undo elements for the region START to END.
The elements come from `buffer-undo-list', but we keep only
@@ -2351,18 +2354,30 @@ If we find an element that crosses an edge of this region,
we stop and ignore all further elements."
(let ((undo-list-copy (undo-copy-list buffer-undo-list))
(undo-list (list nil))
- undo-adjusted-markers
+ ;; The position of a deletion record (TEXT . POSITION) of the
+ ;; current change group.
+ ;;
+ ;; This is used to check that marker adjustmenets are in the
+ ;; region. Bug 16818 describes why the marker's position is
+ ;; not suitable.
+ del-pos
some-rejected
undo-elt temp-undo-list delta)
(while undo-list-copy
(setq undo-elt (car undo-list-copy))
+ ;; Update del-pos
+ (if undo-elt
+ (when (and (consp undo-elt) (stringp (car undo-elt)))
+ (setq del-pos (cdr undo-elt)))
+ ;; Undo boundary means new change group, so unset del-pos
+ (setq del-pos nil))
(let ((keep-this
(cond ((and (consp undo-elt) (eq (car undo-elt) t))
;; This is a "was unmodified" element.
;; Keep it if we have kept everything thus far.
(not some-rejected))
(t
- (undo-elt-in-region undo-elt start end)))))
+ (undo-elt-in-region undo-elt start end del-pos)))))
(if keep-this
(progn
(setq end (+ end (cdr (undo-delta undo-elt))))
@@ -2415,9 +2430,13 @@ we stop and ignore all further elements."
(setq undo-list-copy (cdr undo-list-copy)))
(nreverse undo-list)))
-(defun undo-elt-in-region (undo-elt start end)
+(defun undo-elt-in-region (undo-elt start end &optional marker-validity-pos)
"Determine whether UNDO-ELT falls inside the region START ... END.
-If it crosses the edge, we return nil."
+If it crosses the edge, we return nil.
+
+If undo-elt is a (MARKER . ADJUSTMENT) record, either
+MARKER-VALIDITY-POS (if specified) or the marker's position is
+used to determine whether it is in the region."
(cond ((integerp undo-elt)
(and (>= undo-elt start)
(<= undo-elt end)))
@@ -2430,17 +2449,9 @@ If it crosses the edge, we return nil."
(and (>= (abs (cdr undo-elt)) start)
(<= (abs (cdr undo-elt)) end)))
((and (consp undo-elt) (markerp (car undo-elt)))
- ;; This is a marker-adjustment element (MARKER . ADJUSTMENT).
- ;; See if MARKER is inside the region.
- (let ((alist-elt (assq (car undo-elt) undo-adjusted-markers)))
- (unless alist-elt
- (setq alist-elt (cons (car undo-elt)
- (marker-position (car undo-elt))))
- (setq undo-adjusted-markers
- (cons alist-elt undo-adjusted-markers)))
- (and (cdr alist-elt)
- (>= (cdr alist-elt) start)
- (<= (cdr alist-elt) end))))
+ ;; (MARKER . ADJUSTMENT)
+ (let ((mpos (or marker-validity-pos (marker-position (car undo-elt)))))
+ (and (integerp mpos) (<= start mpos end))))
((null (car undo-elt))
;; (nil PROPERTY VALUE BEG . END)
(let ((tail (nthcdr 3 undo-elt)))
diff --git a/test/ChangeLog b/test/ChangeLog
index c87022c..e7ee14e 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,11 @@
+2014-03-13 Barry O'Reilly <gundaetiapo@gmail.com>
+
+ * undo-tests.el (undo-test-marker-adjustment-nominal): New test of
+ marker adjustments.
+ (undo-test-marker-adjustment-moved):
+ (undo-test-region-mark-adjustment): Two new test to demonstrate
+ bug#16818.
+
2014-03-07 Michael Albinus <michael.albinus@gmx.de>
* automated/tramp-tests.el (tramp-copy-size-limit): Declare.
diff --git a/test/automated/undo-tests.el b/test/automated/undo-tests.el
index 8a963f1..a348549 100644
--- a/test/automated/undo-tests.el
+++ b/test/automated/undo-tests.el
@@ -268,6 +268,80 @@
(should (string= (buffer-string)
"This sentence corrupted?aaa"))))
+(ert-deftest undo-test-marker-adjustment-nominal ()
+ "Test nominal behavior of marker adjustments."
+ (with-temp-buffer
+ (buffer-enable-undo)
+ (insert "abcdefg")
+ (undo-boundary)
+ (let ((m (make-marker)))
+ (set-marker m 2 (current-buffer))
+ (goto-char (point-min))
+ (delete-forward-char 3)
+ (undo-boundary)
+ (should (= (point-min) (marker-position m)))
+ (undo)
+ (undo-boundary)
+ (should (= 2 (marker-position m))))))
+
+(ert-deftest undo-test-marker-adjustment-moved ()
+ "Test marker adjustment behavior when the marker moves.
+Demonstrates bug 16818."
+ (with-temp-buffer
+ (buffer-enable-undo)
+ (insert "abcdefghijk")
+ (undo-boundary)
+ (let ((m (make-marker)))
+ (set-marker m 2 (current-buffer)) ; m at b
+ (goto-char (point-min))
+ (delete-forward-char 3) ; m at d
+ (undo-boundary)
+ (set-marker m 4) ; m at g
+ (undo)
+ (undo-boundary)
+ ;; m still at g, but shifted 3 because deletion undone
+ (should (= 7 (marker-position m))))))
+
+(ert-deftest undo-test-region-mark-adjustment ()
+ "Test that the mark's marker adjustment in undo history doesn't
+obstruct undo in region from finding the correct change group.
+Demonstrates bug 16818."
+ (with-temp-buffer
+ (buffer-enable-undo)
+ (transient-mark-mode 1)
+ (insert "First line\n")
+ (insert "Second line\n")
+ (undo-boundary)
+
+ (goto-char (point-min))
+ (insert "aaa")
+ (undo-boundary)
+
+ (undo)
+ (undo-boundary)
+
+ (goto-char (point-max))
+ (insert "bbb")
+ (undo-boundary)
+
+ (push-mark (point) t t)
+ (setq mark-active t)
+ (goto-char (- (point) 3))
+ (delete-forward-char 1)
+ (undo-boundary)
+
+ (insert "bbb")
+ (undo-boundary)
+
+ (goto-char (point-min))
+ (push-mark (point) t t)
+ (setq mark-active t)
+ (goto-char (+ (point) 3))
+ (undo)
+ (undo-boundary)
+
+ (should (string= (buffer-string) "aaaFirst line\nSecond line\nbbb"))))
+
(defun undo-test-all (&optional interactive)
"Run all tests for \\[undo]."
(interactive "p")
next prev parent reply other threads:[~2014-03-17 23:05 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-02-19 22:15 bug#16818: Undo in region after markers in undo history relocated Barry OReilly
[not found] ` <handler.16818.B.13928481719895.ack@debbugs.gnu.org>
2014-02-19 23:07 ` bug#16818: Acknowledgement (Undo in region after markers in undo history relocated) Barry OReilly
2014-02-20 4:53 ` Stefan Monnier
2014-02-20 14:38 ` Barry OReilly
2014-02-24 22:46 ` Barry OReilly
2014-02-25 21:43 ` Stefan Monnier
2014-02-26 15:18 ` Barry OReilly
2014-03-11 21:24 ` Barry OReilly
2014-03-12 23:24 ` Stefan Monnier
2014-03-13 1:59 ` Barry OReilly
2014-03-13 13:24 ` Stefan Monnier
2014-03-13 14:35 ` Stefan Monnier
2014-03-13 16:55 ` Barry OReilly
2014-03-17 23:05 ` Barry OReilly [this message]
2014-03-18 0:02 ` Stefan
2014-03-19 13:36 ` Barry OReilly
2014-03-19 18:52 ` Stefan
2014-03-19 13:45 ` Barry OReilly
2014-03-19 18:56 ` Stefan
2014-03-23 22:49 ` Barry OReilly
2014-03-24 13:03 ` Stefan
2014-03-24 22:10 ` Barry OReilly
2014-03-25 1:28 ` Stefan
2014-03-25 2:32 ` Barry OReilly
2020-09-09 12:25 ` Lars Ingebrigtsen
2020-12-06 15:57 ` bug#16818: Undo in region after markers in undo history relocated Lars Ingebrigtsen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAFM41H2fmREiVOm6Vh4KGeRLSLM99cMbsHKdHn_86p_dj=1yLw@mail.gmail.com' \
--to=gundaetiapo@gmail.com \
--cc=16818@debbugs.gnu.org \
--cc=monnier@iro.umontreal.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).