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