Function: replace-buffer-contents

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

Signature

(replace-buffer-contents SOURCE &optional MAX-SECS MAX-COSTS)

Documentation

Replace accessible portion of current buffer with that of SOURCE.

SOURCE can be a buffer or a string that names a buffer. Interactively, prompt for SOURCE.

As far as possible the replacement is non-destructive, i.e. existing buffer contents, markers, properties, and overlays in the current buffer stay intact.

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).

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.

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

View in manual

Probably introduced at or before Emacs version 26.1.

Key Bindings

Aliases

org-replace-buffer-contents

Source Code

// Defined in /usr/src/emacs/src/editfns.c
// Skipping highlighting due to helpful-max-highlight.
{
  struct buffer *a = current_buffer;
  Lisp_Object source_buffer = Fget_buffer (source);
  if (NILP (source_buffer))
    nsberror (source);
  struct buffer *b = XBUFFER (source_buffer);
  if (! BUFFER_LIVE_P (b))
    error ("Selecting deleted 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;
    }

  ptrdiff_t min_a = BEGV;
  ptrdiff_t min_b = BUF_BEGV (b);
  ptrdiff_t size_a = ZV - min_a;
  ptrdiff_t size_b = BUF_ZV (b) - min_b;
  eassume (size_a >= 0);
  eassume (size_b >= 0);
  bool a_empty = size_a == 0;
  bool b_empty = size_b == 0;

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

  if (a_empty && b_empty)
    return Qt;

  if (a_empty)
    {
      Finsert_buffer_substring (source, Qnil, Qnil);
      return Qt;
    }

  if (b_empty)
    {
      del_range_both (BEGV, BEGV_BYTE, ZV, ZV_BYTE, true);
      return Qt;
    }

  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 (INT_MULTIPLY_WRAPV (diags, 2 * sizeof *buffer, &bytes_needed)
      || INT_ADD_WRAPV (del_bytes + ins_bytes, bytes_needed, &bytes_needed))
    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);

  /* 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)
    {
      del_range (min_a, ZV);
      Finsert_buffer_substring (source, Qnil,Qnil);
      SAFE_FREE_UNBIND_TO (count, Qnil);
      return Qnil;
    }

  Fundo_boundary ();
  bool modification_hooks_inhibited = false;
  record_unwind_protect_excursion ();

  /* 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 (BEGV, ZV, NULL);
      specbind (Qinhibit_modification_hooks, Qt);
      modification_hooks_inhibited = true;
    }

  ptrdiff_t i = size_a;
  ptrdiff_t j = size_b;
  /* 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);
          if (beg_a < end_a)
            del_range (beg_a, end_a);
          if (beg_b < end_b)
            {
              SET_PT (beg_a);
              Finsert_buffer_substring (source, make_fixed_natnum (beg_b),
                                        make_fixed_natnum (end_b));
            }
	}
      --i;
      --j;
    }

  SAFE_FREE_UNBIND_TO (count, Qnil);

  if (modification_hooks_inhibited)
    {
      signal_after_change (BEGV, size_a, ZV - BEGV);
      update_compositions (BEGV, ZV, 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;
}