Function: transpose-regions
transpose-regions is an interactive function defined in editfns.c.
Signature
(transpose-regions STARTR1 ENDR1 STARTR2 ENDR2 &optional LEAVE-MARKERS)
Documentation
Transpose region STARTR1 to ENDR1 with STARTR2 to ENDR2.
The regions should not be overlapping, because the size of the buffer is never changed in a transposition.
Optional fifth arg LEAVE-MARKERS, if non-nil, means don't update any markers that happen to be located in the regions.
Transposing beyond buffer boundaries is an error.
Interactively, STARTR1 and ENDR1 are point and mark; STARTR2 and ENDR2 are the last two marks pushed to the mark ring; LEAVE-MARKERS is nil. If a prefix argument N is given, STARTR2 and ENDR2 are the two successive marks N entries back in the mark ring. A negative prefix argument instead counts forward from the oldest mark in the mark ring.
Probably introduced at or before Emacs version 19.23.
Key Bindings
Source Code
// Defined in /usr/src/emacs/src/editfns.c
// Skipping highlighting due to helpful-max-highlight.
{
register ptrdiff_t start1, end1, start2, end2;
ptrdiff_t start1_byte, end1_byte, start2_byte, end2_byte;
ptrdiff_t gap, len1, len_mid, len2;
ptrdiff_t len1_byte, len_mid_byte, len2_byte;
unsigned char *start1_addr, *start2_addr, *temp;
INTERVAL cur_intv, tmp_interval1, tmp_interval2;
INTERVAL tmp_interval_mid, tmp_interval3;
Lisp_Object buf;
XSETBUFFER (buf, current_buffer);
cur_intv = buffer_intervals (current_buffer);
validate_region (&startr1, &endr1);
validate_region (&startr2, &endr2);
start1 = XFIXNAT (startr1);
end1 = XFIXNAT (endr1);
start2 = XFIXNAT (startr2);
end2 = XFIXNAT (endr2);
gap = GPT;
/* Swap the regions if they're reversed. We do not swap the
corresponding Lisp objects as well, since we reference these only
to clear text properties in both regions. */
if (start2 < end1)
{
register ptrdiff_t glumph = start1;
start1 = start2;
start2 = glumph;
glumph = end1;
end1 = end2;
end2 = glumph;
}
len1 = end1 - start1;
len2 = end2 - start2;
if (start2 < end1)
error ("Transposed regions overlap");
/* Nothing to change for adjacent regions with one being empty */
else if ((start1 == end1 || start2 == end2) && end1 == start2)
return Qnil;
start1_byte = CHAR_TO_BYTE (start1);
end2_byte = CHAR_TO_BYTE (end2);
#ifdef HAVE_TREE_SITTER
struct ts_linecol start_linecol
= treesit_linecol_maybe (start1, start1_byte,
BUF_TS_LINECOL_POINT (current_buffer));
struct ts_linecol old_end_linecol
= treesit_linecol_maybe (end2, end2_byte,
BUF_TS_LINECOL_POINT (current_buffer));
#endif
/* Run the before-change-functions *before* we move the gap. */
modify_text (start1, end2);
/* It must be pointed out that the really studly thing to do would
be not to move the gap at all, but to leave it in place and work
around it if necessary. This would be extremely efficient,
especially considering that people are likely to do
transpositions near where they are working interactively, which
is exactly where the gap would be found. However, such code
would be much harder to write and to read. So, if you are
reading this comment and are feeling squirrely, by all means have
a go! I just didn't feel like doing it, so I will simply move
the gap the minimum distance to get it out of the way, and then
deal with an unbroken array. */
/* Hmmm... how about checking to see if the gap is large
enough to use as the temporary storage? That would avoid an
allocation... interesting. Later, don't fool with it now. */
/* Make sure the gap won't interfere, by moving it out of the text
we will operate on. */
if (start1 < gap && gap < end2)
{
if (gap - start1 < end2 - gap)
move_gap_both (start1, start1_byte);
else
move_gap_both (end2, end2_byte);
}
start2_byte = CHAR_TO_BYTE (start2);
end1_byte = CHAR_TO_BYTE (end1);
len1_byte = end1_byte - start1_byte;
len2_byte = end2_byte - start2_byte;
#ifdef BYTE_COMBINING_DEBUG
if (end1 == start2)
{
if (count_combining_before (BYTE_POS_ADDR (start2_byte),
len2_byte, start1, start1_byte)
|| count_combining_before (BYTE_POS_ADDR (start1_byte),
len1_byte, end2, start2_byte + len2_byte)
|| count_combining_after (BYTE_POS_ADDR (start1_byte),
len1_byte, end2, start2_byte + len2_byte))
emacs_abort ();
}
else
{
if (count_combining_before (BYTE_POS_ADDR (start2_byte),
len2_byte, start1, start1_byte)
|| count_combining_before (BYTE_POS_ADDR (start1_byte),
len1_byte, start2, start2_byte)
|| count_combining_after (BYTE_POS_ADDR (start2_byte),
len2_byte, end1, start1_byte + len1_byte)
|| count_combining_after (BYTE_POS_ADDR (start1_byte),
len1_byte, end2, start2_byte + len2_byte))
emacs_abort ();
}
#endif
/* The possibilities are:
1. Regions of equal size, possibly even adjacent (contiguous).
2. Regions of unequal size.
In case 1. we can leave the "mid", that is, the region between the
two regions untouched.
The worst case is usually No. 2. It means that (aside from
potential need for getting the gap out of the way), there also
needs to be a shifting of the text between the two regions. So
if they are spread far apart, we are that much slower... sigh. */
/* As an additional difficulty, we have to carefully consider byte vs.
character semantics: Maintaining undo and text properties needs to
be done in terms of characters, swapping text in memory needs to be
done in terms of bytes.
Handling case 1. mentioned above in a special way is beneficial
both for undo/text properties and for memory swapping, only we have
to consider case 1. for the character-related bits (len1 == len2)
and case 1. for the byte-related bits (len1_byte == len2_byte)
separately. */
tmp_interval1 = copy_intervals (cur_intv, start1, len1);
tmp_interval2 = copy_intervals (cur_intv, start2, len2);
len_mid = start2 - end1;
len_mid_byte = start2_byte - end1_byte;
if (len1 == len2)
{
if (end1 == start2) /* Merge the two parts into a single one. */
record_change (start1, (end2 - start1));
else
{
record_change (start1, len1);
record_change (start2, len2);
}
tmp_interval3 = validate_interval_range (buf, &startr1, &endr1, 0);
if (tmp_interval3)
set_text_properties_1 (startr1, endr1, Qnil, buf, tmp_interval3);
tmp_interval3 = validate_interval_range (buf, &startr2, &endr2, 0);
if (tmp_interval3)
set_text_properties_1 (startr2, endr2, Qnil, buf, tmp_interval3);
}
else
/* Regions have different length, character-wise. Handle undo and
text properties for both regions as one long piece of text
spanning both regions and the mid. But while doing so, save the
intervals of the mid to later restore them in their new
position. */
{
record_change (start1, (end2 - start1));
tmp_interval_mid = copy_intervals (cur_intv, end1, len_mid);
tmp_interval3 = validate_interval_range (buf, &startr1, &endr2, 0);
if (tmp_interval3)
set_text_properties_1 (startr1, endr2, Qnil, buf, tmp_interval3);
}
USE_SAFE_ALLOCA;
if (len1_byte == len2_byte)
{
temp = SAFE_ALLOCA (len1_byte);
start1_addr = BYTE_POS_ADDR (start1_byte);
start2_addr = BYTE_POS_ADDR (start2_byte);
memcpy (temp, start1_addr, len1_byte);
memcpy (start1_addr, start2_addr, len2_byte);
memcpy (start2_addr, temp, len1_byte);
}
else if (len1_byte < len2_byte) /* Second region larger than first */
{
/* holds region 2 */
temp = SAFE_ALLOCA (len2_byte);
start1_addr = BYTE_POS_ADDR (start1_byte);
start2_addr = BYTE_POS_ADDR (start2_byte);
memcpy (temp, start2_addr, len2_byte);
memcpy (start1_addr + len_mid_byte + len2_byte, start1_addr, len1_byte);
memmove (start1_addr + len2_byte, start1_addr + len1_byte, len_mid_byte);
memcpy (start1_addr, temp, len2_byte);
}
else
/* Second region smaller than first. */
{
/* holds region 1 */
temp = SAFE_ALLOCA (len1_byte);
start1_addr = BYTE_POS_ADDR (start1_byte);
start2_addr = BYTE_POS_ADDR (start2_byte);
memcpy (temp, start1_addr, len1_byte);
memcpy (start1_addr, start2_addr, len2_byte);
memmove (start1_addr + len2_byte, start1_addr + len1_byte, len_mid_byte);
memcpy (start1_addr + len2_byte + len_mid_byte, temp, len1_byte);
}
SAFE_FREE ();
if (len1 != len2)
/* Restore intervals of the mid. */
{
graft_intervals_into_buffer (tmp_interval_mid, start1 + len2,
len_mid, current_buffer, 0);
}
graft_intervals_into_buffer (tmp_interval1, end2 - len1,
len1, current_buffer, 0);
graft_intervals_into_buffer (tmp_interval2, start1,
len2, current_buffer, 0);
update_compositions (start1, start1 + len2, CHECK_BORDER);
update_compositions (end2 - len1, end2, CHECK_BORDER);
/* When doing multiple transpositions, it might be nice
to optimize this. Perhaps the markers in any one buffer
should be organized in some sorted data tree. */
if (NILP (leave_markers))
{
/* FIXME: Since the undo info doesn't record the transposition as its own
operation, we won't enjoy 'transpose_markers' during undo :-( */
transpose_markers (start1, end1, start2, end2,
start1_byte, start1_byte + len1_byte,
start2_byte, start2_byte + len2_byte);
}
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);
}
#ifdef HAVE_TREE_SITTER
treesit_record_change (start1_byte, end2_byte, end2_byte,
start_linecol, old_end_linecol, end2);
#endif
signal_after_change (start1, end2 - start1, end2 - start1);
return Qnil;
}