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.
Probably introduced at or before Emacs version 26.1.
Key Bindings
Aliases
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;
}