Function: replace-region-contents

replace-region-contents is an interactive function defined in editfns.c.

Signature

(replace-region-contents BEG END SOURCE &optional MAX-SECS MAX-COSTS INHERIT)

Documentation

Replace the region between BEG and END with that of SOURCE.

SOURCE can be a buffer, a string, or a vector [SBUF SBEG SEND] denoting the substring SBEG..SEND of buffer SBUF.

If optional argument INHERIT is non-nil, the inserted text will inherit properties from adjoining text.

As far as possible the replacement is non-destructive, i.e. existing buffer contents, markers, point, properties, and overlays in the current buffer stay intact. Point is treated like an "insert before" marker: if point starts at END, it will always be at the end of the replacement when this function returns, whereas if point starts at BEG it will remain at BEG only if the replaced text is not empty.

Because this function can be very slow if there is a large number of differences between the two buffers, there are two optional arguments mitigating this issue.

The MAX-SECS argument, if given, defines a hard limit on the time used for comparing the buffers. If it takes longer than MAX-SECS, the function falls back to a plain delete-region and insert-buffer-substring. (Note that the checks are not performed too evenly over time, so in some cases it may run a bit longer than allowed). In particular, passing zero as the value of MAX-SECS disables the comparison step, so this function immediately falls back to a plain delete/insert method.

The optional argument MAX-COSTS defines the quality of the difference computation. If the actual costs exceed this limit, heuristics are used to provide a faster but suboptimal solution. The default value is 1000000.

Note: If the replacement is a string, it'll usually be placed internally in a temporary buffer. Therefore, all else being equal, it is preferable to pass a buffer rather than a string as SOURCE argument.

This function returns t if a non-destructive replacement could be performed. Otherwise, i.e., if MAX-SECS was exceeded, it returns nil.

SOURCE can also be a function that will be called with no arguments and with current buffer narrowed to BEG..END, and should return a buffer or a string. But this is deprecated.

View in manual

Probably introduced at or before Emacs version 27.1.

Key Bindings

Aliases

org-replace-region-contents

Source Code

// Defined in /usr/src/emacs/src/editfns.c
// Skipping highlighting due to helpful-max-highlight.
{
  validate_region (&beg, &end);
  ptrdiff_t min_a = XFIXNUM (beg);
  ptrdiff_t size_a = XFIXNUM (end) - min_a;
  eassume (size_a >= 0);
  bool a_empty = size_a == 0;
  bool inh = !NILP (inherit);

  if (FUNCTIONP (source))
    {
      specpdl_ref count = SPECPDL_INDEX ();
      record_unwind_protect_excursion ();
      record_unwind_protect (save_restriction_restore,
			     save_restriction_save ());
      Fnarrow_to_region (beg, end);
      source = calln (source);
      unbind_to (count, Qnil);
    }
  ptrdiff_t min_b, size_b;
  struct buffer *b;
  if (STRINGP (source))
    {
      b = NULL;
      min_b = BEG;		/* Assuming we'll copy it into a buffer.  */
      /* Like 'size_b = SCHARS (source);', except inline to pacify -Wclobbered
	 with gcc 14.2.1 20250110 (Red Hat 14.2.1-7) x86-64 -O2; see
	 <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21161>.  */
      size_b = XSTRING (source)->u.s.size;
    }
  else if (BUFFERP (source))
    {
      b = XBUFFER (source);
      min_b = BUF_BEGV (b);
      size_b = BUF_ZV (b) - min_b;
    }
  else
    {
      CHECK_TYPE (VECTORP (source),
		  list (Qor, Qstring, Qbuffer, Qvector), source);
      /* Let Faref signal an error if SOURCE is too small.  */
      Lisp_Object send = Faref (source, make_fixnum (2));
      Lisp_Object sbeg = AREF (source, 1);
      CHECK_BUFFER (AREF (source, 0));
      b = XBUFFER (AREF (source, 0));
      specpdl_ref count = SPECPDL_INDEX ();
      record_unwind_current_buffer ();
      set_buffer_internal (b);
      validate_region (&sbeg, &send);
      unbind_to (count, Qnil);
      min_b = XFIXNUM (sbeg);
      size_b = XFIXNUM (send) - min_b;
    }
  eassume (0 <= size_b);
  bool b_empty = size_b == 0;
  if (b && !BUFFER_LIVE_P (b))
    error ("Selecting deleted buffer");

  /* Handle trivial cases where at least one accessible portion is
     empty.  */

  if (a_empty && b_empty)
    return Qt;
  else if (a_empty || b_empty
	   || EQ (max_secs, make_fixnum (0))
	   || EQ (max_costs, make_fixnum (0)))
    {
      replace_range (min_a, min_a + size_a, source, true, false, inh);
      return Qt;
    }

  struct buffer *a = current_buffer;
  if (a == b)
    error ("Cannot replace a buffer with itself");

  ptrdiff_t too_expensive;
  if (NILP (max_costs))
    too_expensive = 1000000;
  else if (FIXNUMP (max_costs))
    too_expensive = clip_to_bounds (0, XFIXNUM (max_costs), PTRDIFF_MAX);
  else
    {
      CHECK_INTEGER (max_costs);
      too_expensive = NILP (Fnatnump (max_costs)) ? 0 : PTRDIFF_MAX;
    }

  struct timespec time_limit = make_timespec (0, -1);
  if (!NILP (max_secs))
    {
      struct timespec
	tlim = timespec_add (current_timespec (),
			     lisp_time_argument (max_secs)),
	tmax = make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
      if (timespec_cmp (tlim, tmax) < 0)
	time_limit = tlim;
    }

  specpdl_ref count = SPECPDL_INDEX ();

  ptrdiff_t diags = size_a + size_b + 3;
  ptrdiff_t del_bytes = size_a / CHAR_BIT + 1;
  ptrdiff_t ins_bytes = size_b / CHAR_BIT + 1;
  ptrdiff_t *buffer;
  ptrdiff_t bytes_needed;
  if (ckd_mul (&bytes_needed, diags, 2 * sizeof *buffer)
      || ckd_add (&bytes_needed, bytes_needed, del_bytes + ins_bytes))
    memory_full (SIZE_MAX);
  USE_SAFE_ALLOCA;
  buffer = SAFE_ALLOCA (bytes_needed);
  unsigned char *deletions_insertions = memset (buffer + 2 * diags, 0,
						del_bytes + ins_bytes);

  /* The rest of the code is not prepared to handle a string SOURCE.  */
  if (!b)
    {
      Lisp_Object workbuf
	= code_conversion_save (true, STRING_MULTIBYTE (source));
      b = XBUFFER (workbuf);
      set_buffer_internal (b);
      CALLN (Finsert, source);
      set_buffer_internal (a);
    }
  Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike);

  /* FIXME: It is not documented how to initialize the contents of the
     context structure.  This code cargo-cults from the existing
     caller in src/analyze.c of GNU Diffutils, which appears to
     work.  */
  struct context ctx = {
    .buffer_a = a,
    .buffer_b = b,
    .beg_a = min_a,
    .beg_b = min_b,
    .a_unibyte = BUF_ZV (a) == BUF_ZV_BYTE (a),
    .b_unibyte = BUF_ZV (b) == BUF_ZV_BYTE (b),
    .deletions = deletions_insertions,
    .insertions = deletions_insertions + del_bytes,
    .fdiag = buffer + size_b + 1,
    .bdiag = buffer + diags + size_b + 1,
    .heuristic = true,
    .too_expensive = too_expensive,
    .time_limit = time_limit,
  };

  /* compareseq requires indices to be zero-based.  We add BEGV back
     later.  */
  bool early_abort;
  if (! sys_setjmp (ctx.jmp))
    early_abort = compareseq (0, size_a, 0, size_b, false, &ctx);
  else
    early_abort = true;

  if (early_abort)
    {
      Lisp_Object src = CALLN (Fvector, source_buffer,
			       make_fixnum (BUF_BEGV (b)),
			       make_fixnum (BUF_ZV (b)));
      replace_range (min_a, min_a + size_a, src, true, false, inh);
      SAFE_FREE_UNBIND_TO (count, Qnil);
      return Qnil;
    }

  Fundo_boundary ();
  bool modification_hooks_inhibited = false;

  /* We are going to make a lot of small modifications, and having the
     modification hooks called for each of them will slow us down.
     Instead, we announce a single modification for the entire
     modified region.  But don't do that if the caller inhibited
     modification hooks, because then they don't want that.  */
  if (!inhibit_modification_hooks)
    {
      prepare_to_modify_buffer (min_a, min_a + size_a, NULL);
      specbind (Qinhibit_modification_hooks, Qt);
      modification_hooks_inhibited = true;
    }

  ptrdiff_t i = size_a;
  ptrdiff_t j = size_b;
  Lisp_Object src = CALLN (Fvector, source_buffer, Qnil, Qnil);
  /* Walk backwards through the lists of changes.  This was also
     cargo-culted from src/analyze.c in GNU Diffutils.  Because we
     walk backwards, we don't have to keep the positions in sync.  */
  while (i >= 0 || j >= 0)
    {
      rarely_quit (++ctx.quitcounter);

      /* Check whether there is a change (insertion or deletion)
         before the current position.  */
      if ((i > 0 && bit_is_set (ctx.deletions, i - 1))
	  || (j > 0 && bit_is_set (ctx.insertions, j - 1)))
	{
          ptrdiff_t end_a = min_a + i;
          ptrdiff_t end_b = min_b + j;
          /* Find the beginning of the current change run.  */
	  while (i > 0 && bit_is_set (ctx.deletions, i - 1))
            --i;
	  while (j > 0 && bit_is_set (ctx.insertions, j - 1))
            --j;

          ptrdiff_t beg_a = min_a + i;
          ptrdiff_t beg_b = min_b + j;
          eassert (beg_a <= end_a);
          eassert (beg_b <= end_b);
          eassert (beg_a < end_a || beg_b < end_b);
          ASET (src, 1, make_fixed_natnum (beg_b));
          ASET (src, 2, make_fixed_natnum (end_b));
          replace_range (beg_a, end_a, src, true, false, inh);
	}
      --i;
      --j;
    }

  SAFE_FREE_UNBIND_TO (count, Qnil);

  if (modification_hooks_inhibited)
    {
      signal_after_change (min_a, size_a, size_b);
      update_compositions (min_a, min_a + size_b, CHECK_INSIDE);
      /* We've locked the buffer's file above in
	 prepare_to_modify_buffer; if the buffer is unchanged at this
	 point, i.e. no insertions or deletions have been made, unlock
	 the file now.  */
      if (SAVE_MODIFF == MODIFF
	  && STRINGP (BVAR (a, file_truename)))
	Funlock_file (BVAR (a, file_truename));
    }

  return Qt;
}