Fix 'transpose-regions' when LEAVE-MARKERS arg is non-nil

* 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.
This commit is contained in:
Eli Zaretskii 2016-07-19 18:59:41 +03:00
parent 439f3c3e56
commit 00b6647651
4 changed files with 153 additions and 4 deletions

View 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);
}
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;

View file

@ -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. */

View 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);
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);

View file

@ -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