summaryrefslogtreecommitdiff
path: root/src/editfns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/editfns.c')
-rw-r--r--src/editfns.c203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/editfns.c b/src/editfns.c
index 43b17f9f116..da99c055b54 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -3105,6 +3105,207 @@ determines whether case is significant or ignored. */)
/* Same length too => they are equal. */
return make_number (0);
}
+
+
+/* Set up necessary definitions for diffseq.h; see comments in
+ diffseq.h for explanation. */
+
+#undef ELEMENT
+#undef EQUAL
+
+#define XVECREF_YVECREF_EQUAL(ctx, xoff, yoff) \
+ buffer_chars_equal ((ctx), (xoff), (yoff))
+
+#define OFFSET ptrdiff_t
+
+#define EXTRA_CONTEXT_FIELDS \
+ /* Buffers to compare. */ \
+ struct buffer *buffer_a; \
+ struct buffer *buffer_b; \
+ /* Bit vectors recording for each character whether it was deleted
+ or inserted. */ \
+ unsigned char *deletions; \
+ unsigned char *insertions;
+
+#define NOTE_DELETE(ctx, xoff) set_bit ((ctx)->deletions, (xoff))
+#define NOTE_INSERT(ctx, yoff) set_bit ((ctx)->insertions, (yoff))
+
+struct context;
+static void set_bit (unsigned char *, OFFSET);
+static bool bit_is_set (const unsigned char *, OFFSET);
+static bool buffer_chars_equal (struct context *, OFFSET, OFFSET);
+
+#include "minmax.h"
+#include "diffseq.h"
+
+DEFUN ("replace-buffer-contents", Freplace_buffer_contents,
+ Sreplace_buffer_contents, 1, 1, "bSource buffer: ",
+ doc: /* 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. */)
+ (Lisp_Object source)
+{
+ 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 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 Qnil;
+
+ if (a_empty)
+ return Finsert_buffer_substring (source, Qnil, Qnil);
+
+ if (b_empty)
+ {
+ del_range_both (BEGV, BEGV_BYTE, ZV, ZV_BYTE, true);
+ return Qnil;
+ }
+
+ /* 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. */
+
+ ptrdiff_t diags = size_a + size_b + 3;
+ ptrdiff_t *buffer;
+ USE_SAFE_ALLOCA;
+ SAFE_NALLOCA (buffer, 2, diags);
+ /* Micro-optimization: Casting to size_t generates much better
+ code. */
+ ptrdiff_t del_bytes = (size_t) size_a / CHAR_BIT + 1;
+ ptrdiff_t ins_bytes = (size_t) size_b / CHAR_BIT + 1;
+ struct context ctx = {
+ .buffer_a = a,
+ .buffer_b = b,
+ .deletions = SAFE_ALLOCA (del_bytes),
+ .insertions = SAFE_ALLOCA (ins_bytes),
+ .fdiag = buffer + size_b + 1,
+ .bdiag = buffer + diags + size_b + 1,
+ /* FIXME: Find a good number for .too_expensive. */
+ .too_expensive = 1000000,
+ };
+ memclear (ctx.deletions, del_bytes);
+ memclear (ctx.insertions, ins_bytes);
+ /* compareseq requires indices to be zero-based. We add BEGV back
+ later. */
+ bool early_abort = compareseq (0, size_a, 0, size_b, false, &ctx);
+ /* Since we didn’t define EARLY_ABORT, we should never abort
+ early. */
+ eassert (! early_abort);
+ SAFE_FREE ();
+
+ Fundo_boundary ();
+ ptrdiff_t count = SPECPDL_INDEX ();
+ record_unwind_protect (save_excursion_restore, save_excursion_save ());
+
+ 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)
+ {
+ /* 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 >= BEGV);
+ eassert (beg_b >= BUF_BEGV (b));
+ eassert (beg_a <= end_a);
+ eassert (beg_b <= end_b);
+ eassert (end_a <= ZV);
+ eassert (end_b <= BUF_ZV (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_natnum (beg_b),
+ make_natnum (end_b));
+ }
+ }
+ --i;
+ --j;
+ }
+
+ return unbind_to (count, Qnil);
+}
+
+static void
+set_bit (unsigned char *a, ptrdiff_t i)
+{
+ eassert (i >= 0);
+ /* Micro-optimization: Casting to size_t generates much better
+ code. */
+ size_t j = i;
+ a[j / CHAR_BIT] |= (1 << (j % CHAR_BIT));
+}
+
+static bool
+bit_is_set (const unsigned char *a, ptrdiff_t i)
+{
+ eassert (i >= 0);
+ /* Micro-optimization: Casting to size_t generates much better
+ code. */
+ size_t j = i;
+ return a[j / CHAR_BIT] & (1 << (j % CHAR_BIT));
+}
+
+/* Return true if the characters at position POS_A of buffer
+ CTX->buffer_a and at position POS_B of buffer CTX->buffer_b are
+ equal. POS_A and POS_B are zero-based. Text properties are
+ ignored. */
+
+static bool
+buffer_chars_equal (struct context *ctx,
+ ptrdiff_t pos_a, ptrdiff_t pos_b)
+{
+ eassert (pos_a >= 0);
+ pos_a += BUF_BEGV (ctx->buffer_a);
+ eassert (pos_a >= BUF_BEGV (ctx->buffer_a));
+ eassert (pos_a < BUF_ZV (ctx->buffer_a));
+
+ eassert (pos_b >= 0);
+ pos_b += BUF_BEGV (ctx->buffer_b);
+ eassert (pos_b >= BUF_BEGV (ctx->buffer_b));
+ eassert (pos_b < BUF_ZV (ctx->buffer_b));
+
+ return BUF_FETCH_CHAR_AS_MULTIBYTE (ctx->buffer_a, pos_a)
+ == BUF_FETCH_CHAR_AS_MULTIBYTE (ctx->buffer_b, pos_b);
+}
+
static void
subst_char_in_region_unwind (Lisp_Object arg)
@@ -3978,6 +4179,7 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
bool maybe_combine_byte;
bool arg_intervals = false;
USE_SAFE_ALLOCA;
+ sa_avail -= sizeof initial_buffer;
/* Information recorded for each format spec. */
struct info
@@ -5315,6 +5517,7 @@ functions if all the text being accessed has this property. */);
defsubr (&Sinsert_buffer_substring);
defsubr (&Scompare_buffer_substrings);
+ defsubr (&Sreplace_buffer_contents);
defsubr (&Ssubst_char_in_region);
defsubr (&Stranslate_region_internal);
defsubr (&Sdelete_region);