summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/editfns.c8
-rw-r--r--src/insdel.c102
-rw-r--r--src/lisp.h2
-rw-r--r--test/src/editfns-tests.el45
4 files changed, 153 insertions, 4 deletions
diff --git a/src/editfns.c b/src/editfns.c
index 4c8336b8c82..aed884ebe1c 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -5058,6 +5058,14 @@ Transposing beyond buffer boundaries is an error. */)
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;
diff --git a/src/insdel.c b/src/insdel.c
index 4ad1074f5f7..ec7bbb3e715 100644
--- a/src/insdel.c
+++ b/src/insdel.c
@@ -364,6 +364,78 @@ adjust_markers_for_replace (ptrdiff_t from, ptrdiff_t from_byte,
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);
+}
+
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);
+ 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. */
@@ -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. */
- 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. */
diff --git a/src/lisp.h b/src/lisp.h
index e0eb52a84ea..48c27281643 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -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);
+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);
diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el
index 507ceef2f7d..2f90d1e7495 100644
--- a/test/src/editfns-tests.el
+++ b/test/src/editfns-tests.el
@@ -89,3 +89,48 @@
(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