]> code.delx.au - gnu-emacs/commitdiff
Fix 'transpose-regions' when LEAVE-MARKERS arg is non-nil
authorEli Zaretskii <eliz@gnu.org>
Tue, 19 Jul 2016 15:59:41 +0000 (18:59 +0300)
committerEli Zaretskii <eliz@gnu.org>
Tue, 19 Jul 2016 15:59:41 +0000 (18:59 +0300)
* src/insdel.c (adjust_markers_bytepos): New function.
* src/lisp.h (adjust_markers_bytepos): Add prototype.
* src/insdel.c (replace_range, replace_range_2):
* src/editfns.c (Ftranspose_regions): Call
adjust_markers_bytepos.  (Bug#5131)

* test/src/editfns-tests.el (transpose-test-reverse-word)
(transpose-test-get-byte-positions): New functions.
(transpose-ascii-regions-test)
(transpose-nonascii-regions-test-1)
(transpose-nonascii-regions-test-2): New tests.

src/editfns.c
src/insdel.c
src/lisp.h
test/src/editfns-tests.el

index 4c8336b8c8209ab63e7cec430ae8285a8907e4c2..aed884ebe1c40d7b339eb9118d7b23c05e48f5d8 100644 (file)
@@ -5058,6 +5058,14 @@ Transposing beyond buffer boundaries is an error.  */)
                         start2_byte, start2_byte + len2_byte);
       fix_start_end_in_overlays (start1, end2);
     }
                         start2_byte, start2_byte + len2_byte);
       fix_start_end_in_overlays (start1, end2);
     }
+  else
+    {
+      /* The character positions of the markers remain intact, but we
+        still need to update their byte positions, because the
+        transposed regions might include multibyte sequences which
+        make some original byte positions of the markers invalid.  */
+      adjust_markers_bytepos (start1, start1_byte, end2, end2_byte, 0);
+    }
 
   signal_after_change (start1, end2 - start1, end2 - start1);
   return Qnil;
 
   signal_after_change (start1, end2 - start1, end2 - start1);
   return Qnil;
index 4ad1074f5f79bf94e7c47f72f92b5aa0132bcd5e..ec7bbb3e71599e81213cb00110ba86c97189740b 100644 (file)
@@ -364,6 +364,78 @@ adjust_markers_for_replace (ptrdiff_t from, ptrdiff_t from_byte,
   check_markers ();
 }
 
   check_markers ();
 }
 
+/* Starting at POS (BYTEPOS), find the byte position corresponding to
+   ENDPOS, which could be either before or after POS.  */
+static ptrdiff_t
+count_bytes (ptrdiff_t pos, ptrdiff_t bytepos, ptrdiff_t endpos)
+{
+  eassert (BEG_BYTE <= bytepos && bytepos <= Z_BYTE
+          && BEG <= endpos && endpos <= Z);
+
+  if (pos <= endpos)
+    for ( ; pos < endpos; pos++)
+      INC_POS (bytepos);
+  else
+    for ( ; pos > endpos; pos--)
+      DEC_POS (bytepos);
+
+  return bytepos;
+}
+
+/* Adjust byte positions of markers when their character positions
+   didn't change.  This is used in several places that replace text,
+   but keep the character positions of the markers unchanged -- the
+   byte positions could still change due to different numbers of bytes
+   in the new text.
+
+   FROM (FROM_BYTE) and TO (TO_BYTE) specify the region of text where
+   changes have been done.  TO_Z, if non-zero, means all the markers
+   whose positions are after TO should also be adjusted.  */
+void
+adjust_markers_bytepos (ptrdiff_t from, ptrdiff_t from_byte,
+                       ptrdiff_t to, ptrdiff_t to_byte, int to_z)
+{
+  register struct Lisp_Marker *m;
+  ptrdiff_t beg = from, begbyte = from_byte;
+
+  adjust_suspend_auto_hscroll (from, to);
+
+  if (Z == Z_BYTE || (!to_z && to == to_byte))
+    {
+      /* Make sure each affected marker's bytepos is equal to
+        its charpos.  */
+      for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+       {
+         if (m->bytepos > from_byte
+             && (to_z || m->bytepos <= to_byte))
+           m->bytepos = m->charpos;
+       }
+    }
+  else
+    {
+      for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+       {
+         /* Recompute each affected marker's bytepos.  */
+         if (m->bytepos > from_byte
+             && (to_z || m->bytepos <= to_byte))
+           {
+             if (m->charpos < beg
+                 && beg - m->charpos > m->charpos - from)
+               {
+                 beg = from;
+                 begbyte = from_byte;
+               }
+             m->bytepos = count_bytes (beg, begbyte, m->charpos);
+             beg = m->charpos;
+             begbyte = m->bytepos;
+           }
+       }
+    }
+
+  /* Make sure cached charpos/bytepos is invalid.  */
+  clear_charpos_cache (current_buffer);
+}
+
 \f
 void
 buffer_overflow (void)
 \f
 void
 buffer_overflow (void)
@@ -1397,6 +1469,16 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
   if (markers)
     adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
                                inschars, outgoing_insbytes);
   if (markers)
     adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
                                inschars, outgoing_insbytes);
+  else
+    {
+      /* The character positions of the markers remain intact, but we
+        still need to update their byte positions, because the
+        deleted and the inserted text might have multibyte sequences
+        which make the original byte positions of the markers
+        invalid.  */
+      adjust_markers_bytepos (from, from_byte, from + inschars,
+                             from_byte + outgoing_insbytes, 1);
+    }
 
   /* Adjust the overlay center as needed.  This must be done after
      adjusting the markers that bound the overlays.  */
 
   /* Adjust the overlay center as needed.  This must be done after
      adjusting the markers that bound the overlays.  */
@@ -1509,10 +1591,22 @@ replace_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
   eassert (GPT <= GPT_BYTE);
 
   /* Adjust markers for the deletion and the insertion.  */
   eassert (GPT <= GPT_BYTE);
 
   /* Adjust markers for the deletion and the insertion.  */
-  if (markers
-      && ! (nchars_del == 1 && inschars == 1 && nbytes_del == insbytes))
-    adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
-                               inschars, insbytes);
+  if (! (nchars_del == 1 && inschars == 1 && nbytes_del == insbytes))
+    {
+      if (markers)
+       adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
+                                   inschars, insbytes);
+      else
+       {
+         /* The character positions of the markers remain intact, but
+            we still need to update their byte positions, because the
+            deleted and the inserted text might have multibyte
+            sequences which make the original byte positions of the
+            markers invalid.  */
+         adjust_markers_bytepos (from, from_byte, from + inschars,
+                                 from_byte + insbytes, 1);
+       }
+    }
 
   /* Adjust the overlay center as needed.  This must be done after
      adjusting the markers that bound the overlays.  */
 
   /* Adjust the overlay center as needed.  This must be done after
      adjusting the markers that bound the overlays.  */
index e0eb52a84ea220103a4932414fe65ccbfd0b8d20..48c27281643f2f9bbd127ca2a099db2ba344ef9b 100644 (file)
@@ -3528,6 +3528,8 @@ extern void adjust_after_insert (ptrdiff_t, ptrdiff_t, ptrdiff_t,
                                 ptrdiff_t, ptrdiff_t);
 extern void adjust_markers_for_delete (ptrdiff_t, ptrdiff_t,
                                       ptrdiff_t, ptrdiff_t);
                                 ptrdiff_t, ptrdiff_t);
 extern void adjust_markers_for_delete (ptrdiff_t, ptrdiff_t,
                                       ptrdiff_t, ptrdiff_t);
+extern void adjust_markers_bytepos (ptrdiff_t, ptrdiff_t,
+                                   ptrdiff_t, ptrdiff_t, int);
 extern void replace_range (ptrdiff_t, ptrdiff_t, Lisp_Object, bool, bool, bool);
 extern void replace_range_2 (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t,
                             const char *, ptrdiff_t, ptrdiff_t, bool);
 extern void replace_range (ptrdiff_t, ptrdiff_t, Lisp_Object, bool, bool, bool);
 extern void replace_range_2 (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t,
                             const char *, ptrdiff_t, ptrdiff_t, bool);
index 507ceef2f7d13d19d644f2a73e65fbd0bb712e88..2f90d1e7495dbd7ee57687a2f53e71a868f1ba01 100644 (file)
                                    (propertize "23" 'face 'underline)
                                    (propertize "45" 'face 'italic)))
            #("012345    " 0 2 (face bold) 2 4 (face underline) 4 10 (face italic)))))
                                    (propertize "23" 'face 'underline)
                                    (propertize "45" 'face 'italic)))
            #("012345    " 0 2 (face bold) 2 4 (face underline) 4 10 (face italic)))))
+
+;; Tests for bug#5131.
+(defun transpose-test-reverse-word (start end)
+  "Reverse characters in a word by transposing pairs of characters."
+  (let ((begm (make-marker))
+        (endm (make-marker)))
+    (set-marker begm start)
+    (set-marker endm end)
+    (while (> endm begm)
+      (progn (transpose-regions begm (1+ begm) endm (1+ endm) t)
+             (set-marker begm (1+ begm))
+             (set-marker endm (1- endm))))))
+
+(defun transpose-test-get-byte-positions (len)
+  "Validate character position to byte position translation."
+  (let ((bytes '()))
+    (dotimes (pos len)
+      (setq bytes (add-to-list 'bytes (position-bytes (1+ pos)) t)))
+    bytes))
+
+(ert-deftest transpose-ascii-regions-test ()
+  (with-temp-buffer
+    (erase-buffer)
+    (insert "abcd")
+    (transpose-test-reverse-word 1 4)
+    (should (string= (buffer-string) "dcba"))
+    (should (equal (transpose-test-get-byte-positions 5) '(1 2 3 4 5)))))
+
+(ert-deftest transpose-nonascii-regions-test-1 ()
+  (with-temp-buffer
+    (erase-buffer)
+    (insert "÷bcd")
+    (transpose-test-reverse-word 1 4)
+    (should (string= (buffer-string) "dcb÷"))
+    (should (equal (transpose-test-get-byte-positions 5) '(1 2 3 4 6)))))
+
+(ert-deftest transpose-nonascii-regions-test-2 ()
+  (with-temp-buffer
+    (erase-buffer)
+    (insert "÷ab\"äé")
+    (transpose-test-reverse-word 1 6)
+    (should (string= (buffer-string) "éä\"ba÷"))
+    (should (equal (transpose-test-get-byte-positions 7) '(1 3 5 6 7 8 10)))))
+
+;;; editfns-tests.el ends here