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.

View in manual

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