]> code.delx.au - gnu-emacs/blobdiff - src/insdel.c
*** empty log message ***
[gnu-emacs] / src / insdel.c
index 843a76e3d8387dc639a4185d28cc8039624da8b4..b9d9574788e7106e33c3ed7d24d2c7d57d585016 100644 (file)
@@ -1,5 +1,6 @@
 /* Buffer insertion/deletion and gap motion for GNU Emacs.
-   Copyright (C) 1985, 86, 93, 94, 95, 97, 1998 Free Software Foundation, Inc.
+   Copyright (C) 1985, 1986, 1993, 1994, 1995, 1997, 1998, 1999, 2000, 2001,
+                 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -15,8 +16,8 @@ GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with GNU Emacs; see the file COPYING.  If not, write to
-the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.  */
+the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+Boston, MA 02110-1301, USA.  */
 
 
 #include <config.h>
@@ -26,20 +27,20 @@ Boston, MA 02111-1307, USA.  */
 #include "charset.h"
 #include "window.h"
 #include "blockinput.h"
+#include "region-cache.h"
 
 #ifndef NULL
 #define NULL 0
 #endif
 
-#define min(x, y) ((x) < (y) ? (x) : (y))
-
 static void insert_from_string_1 P_ ((Lisp_Object, int, int, int, int, int, int));
 static void insert_from_buffer_1 ();
 static void gap_left P_ ((int, int, int));
 static void gap_right P_ ((int, int));
 static void adjust_markers_gap_motion P_ ((int, int, int));
-static void adjust_markers_for_insert P_ ((int, int, int, int, int, int, int));
-static void adjust_markers_for_delete P_ ((int, int, int, int));
+static void adjust_markers_for_insert P_ ((int, int, int, int, int));
+void        adjust_markers_for_delete P_ ((int, int, int, int));
+static void adjust_markers_for_replace P_ ((int, int, int, int, int, int));
 static void adjust_point P_ ((int, int));
 
 Lisp_Object Fcombine_after_change_execute ();
@@ -62,6 +63,37 @@ Lisp_Object combine_after_change_list;
 
 /* Buffer which combine_after_change_list is about.  */
 Lisp_Object combine_after_change_buffer;
+
+Lisp_Object Qinhibit_modification_hooks;
+
+\f
+/* Check all markers in the current buffer, looking for something invalid.  */
+
+static int check_markers_debug_flag;
+
+#define CHECK_MARKERS()                                \
+  if (check_markers_debug_flag)                        \
+    check_markers ();                          \
+  else
+
+void
+check_markers ()
+{
+  register struct Lisp_Marker *tail;
+  int multibyte = ! NILP (current_buffer->enable_multibyte_characters);
+
+  for (tail = BUF_MARKERS (current_buffer); tail; tail = tail->next)
+    {
+      if (tail->buffer->text != current_buffer->text)
+       abort ();
+      if (tail->charpos > Z)
+       abort ();
+      if (tail->bytepos > Z_BYTE)
+       abort ();
+      if (multibyte && ! CHAR_HEAD_P (FETCH_BYTE (tail->bytepos)))
+       abort ();
+    }
+}
 \f
 /* Move gap to position CHARPOS.
    Note that this can quit!  */
@@ -101,21 +133,7 @@ gap_left (charpos, bytepos, newgap)
   int new_s1;
 
   if (!newgap)
-    {
-      if (unchanged_modified == MODIFF
-         && overlay_unchanged_modified == OVERLAY_MODIFF)
-       {
-         beg_unchanged = charpos - BEG;
-         end_unchanged = Z - charpos;
-       }
-      else
-       {
-         if (Z - GPT < end_unchanged)
-           end_unchanged = Z - GPT;
-         if (charpos < beg_unchanged)
-           beg_unchanged = charpos - BEG;
-       }
-    }
+    BUF_COMPUTE_UNCHANGED (current_buffer, charpos, GPT);
 
   i = GPT_BYTE;
   to = GAP_END_ADDR;
@@ -190,19 +208,7 @@ gap_right (charpos, bytepos)
   register int i;
   int new_s1;
 
-  if (unchanged_modified == MODIFF
-      && overlay_unchanged_modified == OVERLAY_MODIFF)
-    {
-      beg_unchanged = charpos - BEG;
-      end_unchanged = Z - charpos;
-    }
-  else
-    {
-      if (Z - charpos - 1 < end_unchanged)
-       end_unchanged = Z - charpos;
-      if (GPT - BEG < beg_unchanged)
-       beg_unchanged = GPT - BEG;
-    }
+  BUF_COMPUTE_UNCHANGED (current_buffer, charpos, GPT);
 
   i = GPT_BYTE;
   from = GAP_END_ADDR;
@@ -332,21 +338,16 @@ adjust_markers_gap_motion (from, to, amount)
    This function assumes that the gap is adjacent to
    or inside of the range being deleted.  */
 
-static void
+void
 adjust_markers_for_delete (from, from_byte, to, to_byte)
      register int from, from_byte, to, to_byte;
 {
   Lisp_Object marker;
   register struct Lisp_Marker *m;
   register int charpos;
-  /* This is what GAP_SIZE will be when this deletion is finished.  */
-  int coming_gap_size = GAP_SIZE + to_byte - from_byte;
-
-  marker = BUF_MARKERS (current_buffer);
 
-  while (!NILP (marker))
+  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
     {
-      m = XMARKER (marker);
       charpos = m->charpos;
 
       if (charpos > Z)
@@ -359,54 +360,63 @@ adjust_markers_for_delete (from, from_byte, to, to_byte)
          m->charpos -= to - from;
          m->bytepos -= to_byte - from_byte;
        }
-
       /* Here's the case where a marker is inside text being deleted.  */
       else if (charpos > from)
        {
-         record_marker_adjustment (marker, from - charpos);
+         if (! m->insertion_type)
+           { /* Normal markers will end up at the beginning of the
+              re-inserted text after undoing a deletion, and must be
+              adjusted to move them to the correct place.  */
+             XSETMISC (marker, m);
+           record_marker_adjustment (marker, from - charpos);
+           }
+         else if (charpos < to)
+           { /* Before-insertion markers will automatically move forward
+              upon re-inserting the deleted text, so we have to arrange
+              for them to move backward to the correct position.  */
+             XSETMISC (marker, m);
+           record_marker_adjustment (marker, charpos - to);
+           }
          m->charpos = from;
          m->bytepos = from_byte;
        }
-
-      marker = m->chain;
+      /* Here's the case where a before-insertion marker is immediately
+        before the deleted region.  */
+      else if (charpos == from && m->insertion_type)
+       {
+         /* Undoing the change uses normal insertion, which will
+            incorrectly make MARKER move forward, so we arrange for it
+            to then move backward to the correct place at the beginning
+            of the deleted region.  */
+         XSETMISC (marker, m);
+         record_marker_adjustment (marker, to - from);
+       }
     }
 }
+
 \f
 /* Adjust markers for an insertion that stretches from FROM / FROM_BYTE
    to TO / TO_BYTE.  We have to relocate the charpos of every marker
    that points after the insertion (but not their bytepos).
 
-   COMBINED_BEFORE_BYTES is the number of bytes at the start of the insertion
-   that combine into one character with the text before the insertion.
-   COMBINED_AFTER_BYTES is the number of bytes after the insertion
-   that combine into one character with the last inserted bytes.
-
    When a marker points at the insertion point,
    we advance it if either its insertion-type is t
    or BEFORE_MARKERS is true.  */
 
 static void
-adjust_markers_for_insert (from, from_byte, to, to_byte,
-                          combined_before_bytes, combined_after_bytes,
-                          before_markers)
+adjust_markers_for_insert (from, from_byte, to, to_byte, before_markers)
      register int from, from_byte, to, to_byte;
-     int combined_before_bytes, combined_after_bytes, before_markers;
+     int before_markers;
 {
-  Lisp_Object marker;
+  struct Lisp_Marker *m;
   int adjusted = 0;
   int nchars = to - from;
   int nbytes = to_byte - from_byte;
 
-  marker = BUF_MARKERS (current_buffer);
-
-  while (!NILP (marker))
+  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
     {
-      register struct Lisp_Marker *m = XMARKER (marker);
-
-      /* In a single-byte buffer, a marker's two positions must be equal.
-        (If this insertion is going to combine characters, Z will
-        become different from Z_BYTE, but they might be the same now.
-        If so, the two OLD positions of the marker should be equal.)  */
+      /* In a single-byte buffer, a marker's two positions must be
+        equal.  */
       if (Z == Z_BYTE)
        {
          if (m->charpos != m->bytepos)
@@ -417,54 +427,27 @@ adjust_markers_for_insert (from, from_byte, to, to_byte,
        {
          if (m->insertion_type || before_markers)
            {
-             m->bytepos += nbytes + combined_after_bytes;
-             m->charpos += nchars + !!combined_after_bytes;
-             /* Point the marker before the combined character,
-                so that undoing the insertion puts it back where it was.  */
-             if (combined_after_bytes)
-               DEC_BOTH (m->charpos, m->bytepos);
+             m->bytepos = to_byte;
+             m->charpos = to;
              if (m->insertion_type)
                adjusted = 1;
            }
-         else if (combined_before_bytes)
-           {
-             /* This marker doesn't "need relocation",
-                but don't leave it pointing in the middle of a character.
-                Point the marker after the combined character,
-                so that undoing the insertion puts it back where it was.  */
-
-             /* Here we depend on the fact that the gap is after
-                all of the combining bytes that we are going to skip over.  */
-             DEC_BOTH (m->charpos, m->bytepos);
-             INC_BOTH (m->charpos, m->bytepos);
-           }
-       }
-      /* If a marker was pointing into the combining bytes
-        after the insertion, don't leave it there
-        in the middle of a character.  */
-      else if (combined_after_bytes && m->bytepos >= from_byte
-              && m->bytepos < from_byte + combined_after_bytes)
-       {
-         /* Put it after the combining bytes.  */
-         m->bytepos = to_byte + combined_after_bytes;
-         m->charpos = to + 1;
-         /* Now move it back before the combined character,
-            so that undoing the insertion will put it where it was.  */
-         DEC_BOTH (m->charpos, m->bytepos);
        }
       else if (m->bytepos > from_byte)
        {
          m->bytepos += nbytes;
          m->charpos += nchars;
        }
-
-      marker = m->chain;
     }
 
   /* Adjusting only markers whose insertion-type is t may result in
-     disordered overlays in the slot `overlays_before'.  */
+     - disordered start and end in overlays, and 
+     - disordered overlays in the slot `overlays_before' of current_buffer.  */
   if (adjusted)
-    fix_overlays_before (current_buffer, from, to);
+    {
+      fix_start_end_in_overlays(from, to);
+      fix_overlays_before (current_buffer, from, to);
+    }
 }
 
 /* Adjust point for an insertion of NBYTES bytes, which are NCHARS characters.
@@ -489,14 +472,46 @@ adjust_point (nchars, nbytes)
       && PT != PT_BYTE)
     abort ();
 }
+\f
+/* Adjust markers for a replacement of a text at FROM (FROM_BYTE) of
+   length OLD_CHARS (OLD_BYTES) to a new text of length NEW_CHARS
+   (NEW_BYTES).  It is assumed that OLD_CHARS > 0, i.e., this is not
+   an insertion.  */
+
+static void
+adjust_markers_for_replace (from, from_byte, old_chars, old_bytes,
+                           new_chars, new_bytes)
+     int from, from_byte, old_chars, old_bytes, new_chars, new_bytes;
+{
+  register struct Lisp_Marker *m;
+  int prev_to_byte = from_byte + old_bytes;
+  int diff_chars = new_chars - old_chars;
+  int diff_bytes = new_bytes - old_bytes;
+
+  for (m = BUF_MARKERS (current_buffer); m; m = m->next)
+    {
+      if (m->bytepos >= prev_to_byte)
+       {
+         m->charpos += diff_chars;
+         m->bytepos += diff_bytes;
+       }
+      else if (m->bytepos > from_byte)
+       {
+         m->charpos = from;
+         m->bytepos = from_byte;
+       }
+    }
+
+  CHECK_MARKERS ();
+}
+
 \f
 /* Make the gap NBYTES_ADDED bytes longer.  */
 
 void
-make_gap (nbytes_added)
+make_gap_larger (nbytes_added)
      int nbytes_added;
 {
-  unsigned char *result;
   Lisp_Object tem;
   int real_gap_loc;
   int real_gap_loc_byte;
@@ -507,26 +522,16 @@ make_gap (nbytes_added)
 
   /* Don't allow a buffer size that won't fit in an int
      even if it will fit in a Lisp integer.
-     That won't work because so many places use `int'.  */
-     
-  if (Z_BYTE - BEG_BYTE + GAP_SIZE + nbytes_added
-      >= ((unsigned) 1 << (min (BITS_PER_INT, VALBITS) - 1)))
-    error ("Buffer exceeds maximum size");
+     That won't work because so many places use `int'.
 
-  BLOCK_INPUT;
-  /* We allocate extra 1-byte `\0' at the tail for anchoring a search.  */
-  result = BUFFER_REALLOC (BEG_ADDR, (Z_BYTE - BEG_BYTE
-                                     + GAP_SIZE + nbytes_added + 1));
+     Make sure we don't introduce overflows in the calculation.  */
 
-  if (result == 0)
-    {
-      UNBLOCK_INPUT;
-      memory_full ();
-    }
+  if (Z_BYTE - BEG_BYTE + GAP_SIZE
+      >= (((EMACS_INT) 1 << (min (VALBITS, BITS_PER_INT) - 1)) - 1
+         - nbytes_added))
+    error ("Buffer exceeds maximum size");
 
-  /* We can't unblock until the new address is properly stored.  */
-  BEG_ADDR = result;
-  UNBLOCK_INPUT;
+  enlarge_buffer_text (current_buffer, nbytes_added);
 
   /* Prevent quitting in move_gap.  */
   tem = Vinhibit_quit;
@@ -555,6 +560,78 @@ make_gap (nbytes_added)
 
   Vinhibit_quit = tem;
 }
+
+
+/* Make the gap NBYTES_REMOVED bytes shorter.  */
+
+void
+make_gap_smaller (nbytes_removed)
+     int nbytes_removed;
+{
+  Lisp_Object tem;
+  int real_gap_loc;
+  int real_gap_loc_byte;
+  int real_Z;
+  int real_Z_byte;
+  int real_beg_unchanged;
+  int new_gap_size;
+
+  /* Make sure the gap is at least 20 bytes.  */
+  if (GAP_SIZE - nbytes_removed < 20)
+    nbytes_removed = GAP_SIZE - 20;
+
+  /* Prevent quitting in move_gap.  */
+  tem = Vinhibit_quit;
+  Vinhibit_quit = Qt;
+
+  real_gap_loc = GPT;
+  real_gap_loc_byte = GPT_BYTE;
+  new_gap_size = GAP_SIZE - nbytes_removed;
+  real_Z = Z;
+  real_Z_byte = Z_BYTE;
+  real_beg_unchanged = BEG_UNCHANGED;
+
+  /* Pretend that the last unwanted part of the gap is the entire gap,
+     and that the first desired part of the gap is part of the buffer
+     text.  */
+  bzero (GPT_ADDR, new_gap_size);
+  GPT += new_gap_size;
+  GPT_BYTE += new_gap_size;
+  Z += new_gap_size;
+  Z_BYTE += new_gap_size;
+  GAP_SIZE = nbytes_removed;
+
+  /* Move the unwanted pretend gap to the end of the buffer.  This
+     adjusts the markers properly too.  */
+  gap_right (Z, Z_BYTE);
+
+  enlarge_buffer_text (current_buffer, -nbytes_removed);
+
+  /* Now restore the desired gap.  */
+  GAP_SIZE = new_gap_size;
+  GPT = real_gap_loc;
+  GPT_BYTE = real_gap_loc_byte;
+  Z = real_Z;
+  Z_BYTE = real_Z_byte;
+  BEG_UNCHANGED = real_beg_unchanged;
+
+  /* Put an anchor.  */
+  *(Z_ADDR) = 0;
+
+  Vinhibit_quit = tem;
+}
+
+void
+make_gap (nbytes_added)
+     int nbytes_added;
+{
+  if (nbytes_added >= 0)
+    make_gap_larger (nbytes_added);
+#if defined USE_MMAP_FOR_BUFFERS || defined REL_ALLOC || defined DOUG_LEA_MALLOC
+  else
+    make_gap_smaller (-nbytes_added);
+#endif
+}
 \f
 /* Copy NBYTES bytes of text from FROM_ADDR to TO_ADDR.
    FROM_MULTIBYTE says whether the incoming text is multibyte.
@@ -566,7 +643,7 @@ make_gap (nbytes_added)
 int
 copy_text (from_addr, to_addr, nbytes,
           from_multibyte, to_multibyte)
-     unsigned char *from_addr;
+     const unsigned char *from_addr;
      unsigned char *to_addr;
      int nbytes;
      int from_multibyte, to_multibyte;
@@ -580,15 +657,28 @@ copy_text (from_addr, to_addr, nbytes,
     {
       int nchars = 0;
       int bytes_left = nbytes;
+      Lisp_Object tbl = Qnil;
+
+      /* We set the variable tbl to the reverse table of
+         Vnonascii_translation_table in advance.  */
+      if (CHAR_TABLE_P (Vnonascii_translation_table))
+       {
+         tbl = Fchar_table_extra_slot (Vnonascii_translation_table,
+                                       make_number (0));
+         if (!CHAR_TABLE_P (tbl))
+           tbl = Qnil;
+       }
 
       /* Convert multibyte to single byte.  */
       while (bytes_left > 0)
        {
          int thislen, c;
          c = STRING_CHAR_AND_LENGTH (from_addr, bytes_left, thislen);
-         *to_addr++ = SINGLE_BYTE_CHAR_P (c) ? c : (c & 0177) + 0200;
+         if (!SINGLE_BYTE_CHAR_P (c))
+           c = multibyte_char_to_unibyte (c, tbl);
+         *to_addr++ = c;
          from_addr += thislen;
-         bytes_left--;
+         bytes_left -= thislen;
          nchars++;
        }
       return nchars;
@@ -601,15 +691,11 @@ copy_text (from_addr, to_addr, nbytes,
       while (nbytes > 0)
        {
          int c = *from_addr++;
-         unsigned char workbuf[4], *str;
-         int len;
 
-         if (c >= 0240 && c < 0400)
+         if (c >= 0200)
            {
              c = unibyte_char_to_multibyte (c);
-             len = CHAR_STRING (c, workbuf, str);
-             bcopy (str, to_addr, len);
-             to_addr += len;
+             to_addr += CHAR_STRING (c, to_addr);
              nbytes--;
            }
          else
@@ -626,7 +712,7 @@ copy_text (from_addr, to_addr, nbytes,
 
 int
 count_size_as_multibyte (ptr, nbytes)
-     unsigned char *ptr;
+     const unsigned char *ptr;
      int nbytes;
 {
   int i;
@@ -636,12 +722,12 @@ count_size_as_multibyte (ptr, nbytes)
     {
       unsigned int c = *ptr++;
 
-      if (c < 0240)
+      if (c < 0200)
        outgoing_nbytes++;
       else
        {
          c = unibyte_char_to_multibyte (c);
-         outgoing_nbytes += XINT (Fchar_bytes (make_number (c)));
+         outgoing_nbytes += CHAR_BYTES (c);
        }
     }
 
@@ -658,14 +744,16 @@ count_size_as_multibyte (ptr, nbytes)
 
 void
 insert (string, nbytes)
-     register unsigned char *string;
-     register nbytes;
+     register const unsigned char *string;
+     register int nbytes;
 {
   if (nbytes > 0)
     {
-      int opoint = PT;
-      insert_1 (string, nbytes, 0, 1, 0);
-      signal_after_change (opoint, 0, PT - opoint);
+      int len = chars_in_text (string, nbytes), opoint;
+      insert_1_both (string, len, nbytes, 0, 1, 0);
+      opoint = PT - len;
+      signal_after_change (opoint, 0, len);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -673,14 +761,16 @@ insert (string, nbytes)
 
 void
 insert_and_inherit (string, nbytes)
-     register unsigned char *string;
-     register nbytes;
+     register const unsigned char *string;
+     register int nbytes;
 {
   if (nbytes > 0)
     {
-      int opoint = PT;
-      insert_1 (string, nbytes, 1, 1, 0);
-      signal_after_change (opoint, 0, PT - opoint);
+      int len = chars_in_text (string, nbytes), opoint;
+      insert_1_both (string, len, nbytes, 1, 1, 0);
+      opoint = PT - len;
+      signal_after_change (opoint, 0, len);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -690,16 +780,15 @@ void
 insert_char (c)
      int c;
 {
-  unsigned char workbuf[4], *str;
+  unsigned char str[MAX_MULTIBYTE_LENGTH];
   int len;
 
   if (! NILP (current_buffer->enable_multibyte_characters))
-    len = CHAR_STRING (c, workbuf, str);
+    len = CHAR_STRING (c, str);
   else
     {
       len = 1;
-      workbuf[0] = c;
-      str = workbuf;
+      str[0] = c;
     }
 
   insert (str, len);
@@ -709,7 +798,7 @@ insert_char (c)
 
 void
 insert_string (s)
-     char *s;
+     const char *s;
 {
   insert (s, strlen (s));
 }
@@ -721,15 +810,16 @@ insert_string (s)
 
 void
 insert_before_markers (string, nbytes)
-     unsigned char *string;
+     const unsigned char *string;
      register int nbytes;
 {
   if (nbytes > 0)
     {
-      int opoint = PT;
-
-      insert_1 (string, nbytes, 0, 1, 1);
-      signal_after_change (opoint, 0, PT - opoint);
+      int len = chars_in_text (string, nbytes), opoint;
+      insert_1_both (string, len, nbytes, 0, 1, 1);
+      opoint = PT - len;
+      signal_after_change (opoint, 0, len);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -737,15 +827,16 @@ insert_before_markers (string, nbytes)
 
 void
 insert_before_markers_and_inherit (string, nbytes)
-     unsigned char *string;
+     const unsigned char *string;
      register int nbytes;
 {
   if (nbytes > 0)
     {
-      int opoint = PT;
-
-      insert_1 (string, nbytes, 1, 1, 1);
-      signal_after_change (opoint, 0, PT - opoint);
+      int len = chars_in_text (string, nbytes), opoint;
+      insert_1_both (string, len, nbytes, 1, 1, 1);
+      opoint = PT - len;
+      signal_after_change (opoint, 0, len);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -753,14 +844,17 @@ insert_before_markers_and_inherit (string, nbytes)
 
 void
 insert_1 (string, nbytes, inherit, prepare, before_markers)
-     register unsigned char *string;
+     register const unsigned char *string;
      register int nbytes;
      int inherit, prepare, before_markers;
 {
   insert_1_both (string, chars_in_text (string, nbytes), nbytes,
                 inherit, prepare, before_markers);
 }
+
 \f
+#ifdef BYTE_COMBINING_DEBUG
+
 /* See if the bytes before POS/POS_BYTE combine with bytes
    at the start of STRING to form a single character.
    If so, return the number of bytes at the start of STRING
@@ -768,34 +862,44 @@ insert_1 (string, nbytes, inherit, prepare, before_markers)
 
 int
 count_combining_before (string, length, pos, pos_byte)
-     unsigned char *string;
+     const unsigned char *string;
      int length;
      int pos, pos_byte;
 {
-  int opos = pos, opos_byte = pos_byte;
-  int c;
-  unsigned char *p = string;
+  int len, combining_bytes;
+  const unsigned char *p;
 
   if (NILP (current_buffer->enable_multibyte_characters))
     return 0;
-  if (length == 0 || CHAR_HEAD_P (*string))
+
+  /* At first, we can exclude the following cases:
+       (1) STRING[0] can't be a following byte of multibyte sequence.
+       (2) POS is the start of the current buffer.
+       (3) A character before POS is not a multibyte character.  */
+  if (length == 0 || CHAR_HEAD_P (*string)) /* case (1) */
     return 0;
-  if (pos == BEGV)
+  if (pos_byte == BEG_BYTE)    /* case (2) */
     return 0;
-  c = FETCH_BYTE (pos_byte - 1);
-  if (ASCII_BYTE_P (c))
+  len = 1;
+  p = BYTE_POS_ADDR (pos_byte - 1);
+  while (! CHAR_HEAD_P (*p)) p--, len++;
+  if (! BASE_LEADING_CODE_P (*p)) /* case (3) */
     return 0;
-  DEC_BOTH (pos, pos_byte);
-  c = FETCH_BYTE (pos_byte);
-  if (! BASE_LEADING_CODE_P (c))
+
+  combining_bytes = BYTES_BY_CHAR_HEAD (*p) - len;
+  if (combining_bytes <= 0)
+    /* The character preceding POS is, complete and no room for
+       combining bytes (combining_bytes == 0), or an independent 8-bit
+       character (combining_bytes < 0).  */
     return 0;
 
-  /* We have a combination situation.
-     Count the bytes at STRING that will combine.  */
+  /* We have a combination situation.  Count the bytes at STRING that
+     may combine.  */
+  p = string + 1;
   while (!CHAR_HEAD_P (*p) && p < string + length)
     p++;
 
-  return p - string;
+  return (combining_bytes < p - string ? combining_bytes : p - string);
 }
 
 /* See if the bytes after POS/POS_BYTE combine with bytes
@@ -805,69 +909,65 @@ count_combining_before (string, length, pos, pos_byte)
 
 int
 count_combining_after (string, length, pos, pos_byte)
-     unsigned char *string;
+     const unsigned char *string;
      int length;
      int pos, pos_byte;
 {
-  int opos = pos, opos_byte = pos_byte;
+  int opos_byte = pos_byte;
   int i;
-  int c;
+  int bytes;
+  unsigned char *bufp;
 
   if (NILP (current_buffer->enable_multibyte_characters))
     return 0;
-  if (length == 0 || ASCII_BYTE_P (string[length - 1]))
+
+  /* At first, we can exclude the following cases:
+       (1) The last byte of STRING is an ASCII.
+       (2) POS is the last of the current buffer.
+       (3) A character at POS can't be a following byte of multibyte
+           character.  */
+  if (length > 0 && ASCII_BYTE_P (string[length - 1])) /* case (1) */
+    return 0;
+  if (pos_byte == Z_BYTE)      /* case (2) */
     return 0;
+  bufp = BYTE_POS_ADDR (pos_byte);
+  if (CHAR_HEAD_P (*bufp))     /* case (3) */
+    return 0;
+
   i = length - 1;
-  while (i > 0 && ! CHAR_HEAD_P (string[i]))
+  while (i >= 0 && ! CHAR_HEAD_P (string[i]))
     {
       i--;
     }
-  if (! BASE_LEADING_CODE_P (string[i]))
-    return 0;
-
-  if (pos == ZV)
-    return 0;
-  c = FETCH_BYTE (pos_byte);
-  if (CHAR_HEAD_P (c))
-    return 0;
-  while (pos_byte < ZV_BYTE)
+  if (i < 0)
     {
-      c = FETCH_BYTE (pos_byte);
-      if (CHAR_HEAD_P (c))
-       break;
-      pos_byte++;
+      /* All characters in STRING are not character head.  We must
+        check also preceding bytes at POS.  We are sure that the gap
+        is at POS.  */
+      unsigned char *p = BEG_ADDR;
+      i = pos_byte - 2;
+      while (i >= 0 && ! CHAR_HEAD_P (p[i]))
+       i--;
+      if (i < 0 || !BASE_LEADING_CODE_P (p[i]))
+       return 0;
+
+      bytes = BYTES_BY_CHAR_HEAD (p[i]);
+      return (bytes <= pos_byte - 1 - i + length
+             ? 0
+             : bytes - (pos_byte - 1 - i + length));
     }
+  if (!BASE_LEADING_CODE_P (string[i]))
+    return 0;
 
-  return pos_byte - opos_byte;
-}
-
-/* Combine NBYTES stray trailing-codes, which were formerly separate
-   characters, with the preceding character.  These bytes
-   are located after position POS / POS_BYTE, and the preceding character
-   is located just before that position.  */
-
-static void
-combine_bytes (pos, pos_byte, nbytes)
-     int pos, pos_byte, nbytes;
-{
-  /* Adjust all markers.  */
-  adjust_markers_for_delete (pos, pos_byte, pos + nbytes, pos_byte);
+  bytes = BYTES_BY_CHAR_HEAD (string[i]) - (length - i);
+  bufp++, pos_byte++;
+  while (!CHAR_HEAD_P (*bufp)) bufp++, pos_byte++;
 
-  adjust_overlays_for_delete (pos, nbytes);
+  return (bytes <= pos_byte - opos_byte ? bytes : pos_byte - opos_byte);
+}
 
-  if (PT > pos)
-    BUF_PT (current_buffer) -= nbytes;
-  if (GPT > pos)
-    GPT -= nbytes;
-  if (Z > pos)
-    Z -= nbytes;
-  if (ZV > pos)
-    ZV -= nbytes;
+#endif
 
-  if (BUF_INTERVALS (current_buffer) != 0)
-    /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES.  */
-    offset_intervals (current_buffer, pos, - nbytes);
-}
 \f
 /* Insert a sequence of NCHARS chars which occupy NBYTES bytes
    starting at STRING.  INHERIT, PREPARE and BEFORE_MARKERS
@@ -875,51 +975,42 @@ combine_bytes (pos, pos_byte, nbytes)
 
 void
 insert_1_both (string, nchars, nbytes, inherit, prepare, before_markers)
-     register unsigned char *string;
+     register const unsigned char *string;
      register int nchars, nbytes;
      int inherit, prepare, before_markers;
 {
-  register Lisp_Object temp;
-  int combined_before_bytes, combined_after_bytes;
+  if (nchars == 0)
+    return;
 
   if (NILP (current_buffer->enable_multibyte_characters))
     nchars = nbytes;
 
+  if (prepare)
+    /* Do this before moving and increasing the gap,
+       because the before-change hooks might move the gap
+       or make it smaller.  */
+    prepare_to_modify_buffer (PT, PT, NULL);
+
   if (PT != GPT)
     move_gap_both (PT, PT_BYTE);
   if (GAP_SIZE < nbytes)
     make_gap (nbytes - GAP_SIZE);
 
-  if (prepare)
-    prepare_to_modify_buffer (PT, PT, NULL);
-
-  combined_before_bytes
-    = count_combining_before (string, nbytes, PT, PT_BYTE);
-  combined_after_bytes
-    = count_combining_after (string, nbytes, PT, PT_BYTE);
+#ifdef BYTE_COMBINING_DEBUG
+  if (count_combining_before (string, nbytes, PT, PT_BYTE)
+      || count_combining_after (string, nbytes, PT, PT_BYTE))
+    abort ();
+#endif
 
   /* Record deletion of the surrounding text that combines with
      the insertion.  This, together with recording the insertion,
-     will add up to the right stuff in the undo list.
-
-     But there is no need to actually delete the combining bytes
-     from the buffer and reinsert them.  */
-
-  if (combined_after_bytes)
-    record_delete (PT, combined_after_bytes);
-
-  if (combined_before_bytes)
-    record_delete (PT - 1, 1);
-
-  record_insert (PT - !!combined_before_bytes,
-                nchars - combined_before_bytes + !!combined_before_bytes);
+     will add up to the right stuff in the undo list.  */
+  record_insert (PT, nchars);
   MODIFF++;
 
   bcopy (string, GPT_ADDR, nbytes);
 
   GAP_SIZE -= nbytes;
-  /* When we have combining at the end of the insertion,
-     this is the character position before the combined character.  */
   GPT += nchars;
   ZV += nchars;
   Z += nchars;
@@ -928,41 +1019,28 @@ insert_1_both (string, nchars, nbytes, inherit, prepare, before_markers)
   Z_BYTE += nbytes;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
   if (GPT_BYTE < GPT)
     abort ();
 
+  /* The insert may have been in the unchanged region, so check again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
   adjust_overlays_for_insert (PT, nchars);
   adjust_markers_for_insert (PT, PT_BYTE,
                             PT + nchars, PT_BYTE + nbytes,
-                            combined_before_bytes, combined_after_bytes,
                             before_markers);
 
-#ifdef USE_TEXT_PROPERTIES
   if (BUF_INTERVALS (current_buffer) != 0)
-    /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES.  */
     offset_intervals (current_buffer, PT, nchars);
 
   if (!inherit && BUF_INTERVALS (current_buffer) != 0)
-    Fset_text_properties (make_number (PT), make_number (PT + nchars),
-                         Qnil, Qnil);
-#endif
-
-  {
-    int pos = PT, pos_byte = PT_BYTE;
-
-    adjust_point (nchars + combined_after_bytes,
-                 nbytes + combined_after_bytes);
+    set_text_properties (make_number (PT), make_number (PT + nchars),
+                        Qnil, Qnil, Qnil);
 
-    if (combined_after_bytes)
-      combine_bytes (pos + nchars, pos_byte + nbytes, combined_after_bytes);
+  adjust_point (nchars, nbytes);
 
-    if (combined_before_bytes)
-      combine_bytes (pos, pos_byte, combined_before_bytes);
-  }
+  CHECK_MARKERS ();
 }
 \f
 /* Insert the part of the text of STRING, a Lisp object assumed to be
@@ -980,13 +1058,15 @@ insert_from_string (string, pos, pos_byte, length, length_byte, inherit)
      register int pos, pos_byte, length, length_byte;
      int inherit;
 {
-  if (length > 0)
-    {
-      int opoint = PT;
-      insert_from_string_1 (string, pos, pos_byte, length, length_byte,
-                           inherit, 0);
-      signal_after_change (opoint, 0, PT - opoint);
-    }
+  int opoint = PT;
+
+  if (SCHARS (string) == 0)
+    return;
+
+  insert_from_string_1 (string, pos, pos_byte, length, length_byte,
+                       inherit, 0);
+  signal_after_change (opoint, 0, PT - opoint);
+  update_compositions (opoint, PT, CHECK_BORDER);
 }
 
 /* Like `insert_from_string' except that all markers pointing
@@ -999,13 +1079,15 @@ insert_from_string_before_markers (string, pos, pos_byte,
      register int pos, pos_byte, length, length_byte;
      int inherit;
 {
-  if (length > 0)
-    {
-      int opoint = PT;
-      insert_from_string_1 (string, pos, pos_byte, length, length_byte,
-                           inherit, 1);
-      signal_after_change (opoint, 0, PT - opoint);
-    }
+  int opoint = PT;
+
+  if (SCHARS (string) == 0)
+    return;
+
+  insert_from_string_1 (string, pos, pos_byte, length, length_byte,
+                       inherit, 1);
+  signal_after_change (opoint, 0, PT - opoint);
+  update_compositions (opoint, PT, CHECK_BORDER);
 }
 
 /* Subroutine of the insertion functions above.  */
@@ -1017,11 +1099,8 @@ insert_from_string_1 (string, pos, pos_byte, nchars, nbytes,
      register int pos, pos_byte, nchars, nbytes;
      int inherit, before_markers;
 {
-  register Lisp_Object temp;
   struct gcpro gcpro1;
   int outgoing_nbytes = nbytes;
-  int combined_before_bytes, combined_after_bytes;
-  int adjusted_nchars;
   INTERVAL intervals;
 
   /* Make OUTGOING_NBYTES describe the text
@@ -1029,61 +1108,41 @@ insert_from_string_1 (string, pos, pos_byte, nchars, nbytes,
 
   if (NILP (current_buffer->enable_multibyte_characters))
     outgoing_nbytes = nchars;
-  else if (nchars == nbytes)
+  else if (! STRING_MULTIBYTE (string))
     outgoing_nbytes
-      = count_size_as_multibyte (&XSTRING (string)->data[pos_byte],
+      = count_size_as_multibyte (SDATA (string) + pos_byte,
                                 nbytes);
 
-  /* Make sure point-max won't overflow after this insertion.  */
-  XSETINT (temp, outgoing_nbytes + Z);
-  if (outgoing_nbytes + Z != XINT (temp))
-    error ("Maximum buffer size exceeded");
-
   GCPRO1 (string);
+  /* Do this before moving and increasing the gap,
+     because the before-change hooks might move the gap
+     or make it smaller.  */
   prepare_to_modify_buffer (PT, PT, NULL);
 
   if (PT != GPT)
     move_gap_both (PT, PT_BYTE);
-  if (GAP_SIZE < nbytes)
+  if (GAP_SIZE < outgoing_nbytes)
     make_gap (outgoing_nbytes - GAP_SIZE);
   UNGCPRO;
 
   /* Copy the string text into the buffer, perhaps converting
      between single-byte and multibyte.  */
-  copy_text (XSTRING (string)->data + pos_byte, GPT_ADDR, nbytes,
-            /* If these are equal, it is a single-byte string.
-               Its chars are either ASCII, in which case copy_text
-               won't change it, or single-byte non-ASCII chars,
-               that need to be changed.  */
-            nchars != nbytes,
+  copy_text (SDATA (string) + pos_byte, GPT_ADDR, nbytes,
+            STRING_MULTIBYTE (string),
             ! NILP (current_buffer->enable_multibyte_characters));
 
+#ifdef BYTE_COMBINING_DEBUG
   /* We have copied text into the gap, but we have not altered
      PT or PT_BYTE yet.  So we can pass PT and PT_BYTE
      to these functions and get the same results as we would
      have got earlier on.  Meanwhile, PT_ADDR does point to
      the text that has been stored by copy_text.  */
+  if (count_combining_before (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE)
+      || count_combining_after (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE))
+    abort ();
+#endif
 
-  combined_before_bytes
-    = count_combining_before (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
-  combined_after_bytes
-    = count_combining_after (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
-
-  /* Record deletion of the surrounding text that combines with
-     the insertion.  This, together with recording the insertion,
-     will add up to the right stuff in the undo list.
-
-     But there is no need to actually delete the combining bytes
-     from the buffer and reinsert them.  */
-
-  if (combined_after_bytes)
-    record_delete (PT, combined_after_bytes);
-
-  if (combined_before_bytes)
-    record_delete (PT - 1, 1);
-
-  record_insert (PT - !!combined_before_bytes,
-                nchars - combined_before_bytes + !!combined_before_bytes);
+  record_insert (PT, nchars);
   MODIFF++;
 
   GAP_SIZE -= outgoing_nbytes;
@@ -1095,45 +1154,30 @@ insert_from_string_1 (string, pos, pos_byte, nchars, nbytes,
   Z_BYTE += outgoing_nbytes;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
   if (GPT_BYTE < GPT)
     abort ();
 
+  /* The insert may have been in the unchanged region, so check again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
   adjust_overlays_for_insert (PT, nchars);
   adjust_markers_for_insert (PT, PT_BYTE, PT + nchars,
                             PT_BYTE + outgoing_nbytes,
-                            combined_before_bytes, combined_after_bytes,
                             before_markers);
 
-  /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES */
   offset_intervals (current_buffer, PT, nchars);
 
-  intervals = XSTRING (string)->intervals;
-  /* Get the intervals for the part of the string we are inserting--
-     not including the combined-before bytes.  */
-  if (nbytes < XSTRING (string)->size_byte)
+  intervals = STRING_INTERVALS (string);
+  /* Get the intervals for the part of the string we are inserting.  */
+  if (nbytes < SBYTES (string))
     intervals = copy_intervals (intervals, pos, nchars);
-                              
+
   /* Insert those intervals.  */
   graft_intervals_into_buffer (intervals, PT, nchars,
                               current_buffer, inherit);
 
-  {
-    int pos = PT, pos_byte = PT_BYTE;
-
-    adjust_point (nchars + combined_after_bytes,
-                 outgoing_nbytes + combined_after_bytes);
-
-    if (combined_after_bytes)
-      combine_bytes (pos + nchars, pos_byte + outgoing_nbytes,
-                    combined_after_bytes);
-
-    if (combined_before_bytes)
-      combine_bytes (pos, pos_byte, combined_before_bytes);
-  }
+  adjust_point (nchars, outgoing_nbytes);
 }
 \f
 /* Insert text from BUF, NCHARS characters starting at CHARPOS, into the
@@ -1149,13 +1193,11 @@ insert_from_buffer (buf, charpos, nchars, inherit)
      int charpos, nchars;
      int inherit;
 {
-  if (nchars > 0)
-    {
-      int opoint = PT;
+  int opoint = PT;
 
-      insert_from_buffer_1 (buf, charpos, nchars, inherit);
-      signal_after_change (opoint, 0, PT - opoint);
-    }
+  insert_from_buffer_1 (buf, charpos, nchars, inherit);
+  signal_after_change (opoint, 0, PT - opoint);
+  update_compositions (opoint, PT, CHECK_BORDER);
 }
 
 static void
@@ -1165,13 +1207,11 @@ insert_from_buffer_1 (buf, from, nchars, inherit)
      int inherit;
 {
   register Lisp_Object temp;
-  int chunk;
+  int chunk, chunk_expanded;
   int from_byte = buf_charpos_to_bytepos (buf, from);
   int to_byte = buf_charpos_to_bytepos (buf, from + nchars);
   int incoming_nbytes = to_byte - from_byte;
   int outgoing_nbytes = incoming_nbytes;
-  int combined_before_bytes, combined_after_bytes;
-  int adjusted_nchars;
   INTERVAL intervals;
 
   /* Make OUTGOING_NBYTES describe the text
@@ -1180,15 +1220,39 @@ insert_from_buffer_1 (buf, from, nchars, inherit)
   if (NILP (current_buffer->enable_multibyte_characters))
     outgoing_nbytes = nchars;
   else if (NILP (buf->enable_multibyte_characters))
-    outgoing_nbytes
-      = count_size_as_multibyte (BUF_BYTE_ADDRESS (buf, from_byte),
-                                incoming_nbytes);
+    {
+      int outgoing_before_gap = 0;
+      int outgoing_after_gap = 0;
+
+      if (from < BUF_GPT (buf))
+       {
+         chunk =  BUF_GPT_BYTE (buf) - from_byte;
+         if (chunk > incoming_nbytes)
+           chunk = incoming_nbytes;
+         outgoing_before_gap
+           = count_size_as_multibyte (BUF_BYTE_ADDRESS (buf, from_byte),
+                                      chunk);
+       }
+      else
+       chunk = 0;
+
+      if (chunk < incoming_nbytes)
+       outgoing_after_gap
+         = count_size_as_multibyte (BUF_BYTE_ADDRESS (buf,
+                                                      from_byte + chunk),
+                                    incoming_nbytes - chunk);
+
+      outgoing_nbytes = outgoing_before_gap + outgoing_after_gap;
+    }
 
   /* Make sure point-max won't overflow after this insertion.  */
   XSETINT (temp, outgoing_nbytes + Z);
   if (outgoing_nbytes + Z != XINT (temp))
     error ("Maximum buffer size exceeded");
 
+  /* Do this before moving and increasing the gap,
+     because the before-change hooks might move the gap
+     or make it smaller.  */
   prepare_to_modify_buffer (PT, PT, NULL);
 
   if (PT != GPT)
@@ -1201,45 +1265,35 @@ insert_from_buffer_1 (buf, from, nchars, inherit)
       chunk = BUF_GPT_BYTE (buf) - from_byte;
       if (chunk > incoming_nbytes)
        chunk = incoming_nbytes;
-      copy_text (BUF_BYTE_ADDRESS (buf, from_byte),
-                GPT_ADDR, chunk,
-                ! NILP (buf->enable_multibyte_characters),
-                ! NILP (current_buffer->enable_multibyte_characters));
+      /* Record number of output bytes, so we know where
+        to put the output from the second copy_text.  */
+      chunk_expanded
+       = copy_text (BUF_BYTE_ADDRESS (buf, from_byte),
+                    GPT_ADDR, chunk,
+                    ! NILP (buf->enable_multibyte_characters),
+                    ! NILP (current_buffer->enable_multibyte_characters));
     }
   else
-    chunk = 0;
+    chunk_expanded = chunk = 0;
+
   if (chunk < incoming_nbytes)
     copy_text (BUF_BYTE_ADDRESS (buf, from_byte + chunk),
-              GPT_ADDR + chunk, incoming_nbytes - chunk,
+              GPT_ADDR + chunk_expanded, incoming_nbytes - chunk,
               ! NILP (buf->enable_multibyte_characters),
               ! NILP (current_buffer->enable_multibyte_characters));
 
+#ifdef BYTE_COMBINING_DEBUG
   /* We have copied text into the gap, but we have not altered
      PT or PT_BYTE yet.  So we can pass PT and PT_BYTE
      to these functions and get the same results as we would
      have got earlier on.  Meanwhile, GPT_ADDR does point to
      the text that has been stored by copy_text.  */
-  combined_before_bytes
-    = count_combining_before (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
-  combined_after_bytes
-    = count_combining_after (GPT_ADDR, outgoing_nbytes,
-                            PT, PT_BYTE);
-
-  /* Record deletion of the surrounding text that combines with
-     the insertion.  This, together with recording the insertion,
-     will add up to the right stuff in the undo list.
-
-     But there is no need to actually delete the combining bytes
-     from the buffer and reinsert them.  */
-
-  if (combined_after_bytes)
-    record_delete (PT, combined_after_bytes);
-
-  if (combined_before_bytes)
-    record_delete (PT - 1, 1);
+  if (count_combining_before (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE)
+      || count_combining_after (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE))
+    abort ();
+#endif
 
-  record_insert (PT - !!combined_before_bytes,
-                nchars - combined_before_bytes + !!combined_before_bytes);
+  record_insert (PT, nchars);
   MODIFF++;
 
   GAP_SIZE -= outgoing_nbytes;
@@ -1251,79 +1305,61 @@ insert_from_buffer_1 (buf, from, nchars, inherit)
   Z_BYTE += outgoing_nbytes;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
   if (GPT_BYTE < GPT)
     abort ();
 
+  /* The insert may have been in the unchanged region, so check again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
   adjust_overlays_for_insert (PT, nchars);
   adjust_markers_for_insert (PT, PT_BYTE, PT + nchars,
                             PT_BYTE + outgoing_nbytes,
-                            combined_before_bytes, combined_after_bytes, 0);
+                            0);
 
-#ifdef USE_TEXT_PROPERTIES
   if (BUF_INTERVALS (current_buffer) != 0)
     offset_intervals (current_buffer, PT, nchars);
-#endif
 
-  /* Get the intervals for the part of the string we are inserting--
-     not including the combined-before bytes.  */
+  /* Get the intervals for the part of the string we are inserting.  */
   intervals = BUF_INTERVALS (buf);
   if (outgoing_nbytes < BUF_Z_BYTE (buf) - BUF_BEG_BYTE (buf))
-    intervals = copy_intervals (intervals, from, nchars);
-                              
+    {
+      if (buf == current_buffer && PT <= from)
+       from += nchars;
+      intervals = copy_intervals (intervals, from, nchars);
+    }
+
   /* Insert those intervals.  */
   graft_intervals_into_buffer (intervals, PT, nchars, current_buffer, inherit);
 
-  {
-    int pos = PT, pos_byte = PT_BYTE;
-
-    adjust_point (nchars + combined_after_bytes,
-                 outgoing_nbytes + combined_after_bytes);
-
-    if (combined_after_bytes)
-      combine_bytes (pos + nchars, pos_byte + outgoing_nbytes,
-                    combined_after_bytes);
-
-    if (combined_before_bytes)
-      combine_bytes (pos, pos_byte, combined_before_bytes);
-  }
+  adjust_point (nchars, outgoing_nbytes);
 }
 \f
-/* This function should be called after moving gap to FROM and before
-   altering text between FROM and TO.  This adjusts various position
-   keepers and markers as if the text is deleted.  Don't forget to
-   call adjust_after_replace after you actually alter the text.  */
+/* Record undo information and adjust markers and position keepers for
+   a replacement of a text PREV_TEXT at FROM to a new text of LEN
+   chars (LEN_BYTE bytes) which resides in the gap just after
+   GPT_ADDR.
 
-void
-adjust_before_replace (from, from_byte, to, to_byte)
-     int from, from_byte, to, to_byte;
-{
-  adjust_markers_for_delete (from, from_byte, to, to_byte);
-  record_delete (from, to - from);
-  adjust_overlays_for_delete (from, to - from);
-}
-
-/* This function should be called after altering the text between FROM
-   and TO to a new text of LEN chars (LEN_BYTE bytes), but before
-   making the text a buffer contents.  It exists just after GPT_ADDR.  */
+   PREV_TEXT nil means the new text was just inserted.  */
 
 void
-adjust_after_replace (from, from_byte, to, to_byte, len, len_byte)
-     int from, from_byte, to, to_byte, len, len_byte;
+adjust_after_replace (from, from_byte, prev_text, len, len_byte)
+     int from, from_byte, len, len_byte;
+     Lisp_Object prev_text;
 {
-  int combined_before_bytes
-    = count_combining_before (GPT_ADDR, len_byte, from, from_byte);
-  int combined_after_bytes
-    = count_combining_after (GPT_ADDR, len_byte, from, from_byte);
+  int nchars_del = 0, nbytes_del = 0;
 
-  if (combined_after_bytes)
-    record_delete (from, combined_after_bytes);
+#ifdef BYTE_COMBINING_DEBUG
+  if (count_combining_before (GPT_ADDR, len_byte, from, from_byte)
+      || count_combining_after (GPT_ADDR, len_byte, from, from_byte))
+    abort ();
+#endif
 
-  if (combined_before_bytes)
-    record_delete (from - 1, 1);
+  if (STRINGP (prev_text))
+    {
+      nchars_del = SCHARS (prev_text);
+      nbytes_del = SBYTES (prev_text);
+    }
 
   /* Update various buffer positions for the new text.  */
   GAP_SIZE -= len_byte;
@@ -1332,42 +1368,113 @@ adjust_after_replace (from, from_byte, to, to_byte, len, len_byte)
   GPT += len; GPT_BYTE += len_byte;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor. */
 
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
-  record_insert (from - !!combined_before_bytes,
-                len - combined_before_bytes + !!combined_before_bytes);
-  adjust_overlays_for_insert (from, len);
-  adjust_markers_for_insert (from, from_byte,
-                            from + len, from_byte + len_byte,
-                            combined_before_bytes, combined_after_bytes, 0);
-#ifdef USE_TEXT_PROPERTIES
+  if (nchars_del > 0)
+    adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
+                               len, len_byte);
+  else
+    adjust_markers_for_insert (from, from_byte,
+                              from + len, from_byte + len_byte, 0);
+
+  if (! EQ (current_buffer->undo_list, Qt))
+    {
+      if (nchars_del > 0)
+       record_delete (from, prev_text);
+      record_insert (from, len);
+    }
+
+  if (len > nchars_del)
+    adjust_overlays_for_insert (from, len - nchars_del);
+  else if (len < nchars_del)
+    adjust_overlays_for_delete (from, nchars_del - len);
   if (BUF_INTERVALS (current_buffer) != 0)
-    offset_intervals (current_buffer, from, len - (to - from));
+    {
+      offset_intervals (current_buffer, from, len - nchars_del);
+    }
+
+  if (from < PT)
+    adjust_point (len - nchars_del, len_byte - nbytes_del);
+
+  /* As byte combining will decrease Z, we must check this again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
+  CHECK_MARKERS ();
+
+  if (len == 0)
+    evaporate_overlays (from);
+  MODIFF++;
+}
+
+/* Like adjust_after_replace, but doesn't require PREV_TEXT.
+   This is for use when undo is not enabled in the current buffer.  */
+
+void
+adjust_after_replace_noundo (from, from_byte, nchars_del, nbytes_del, len, len_byte)
+     int from, from_byte, nchars_del, nbytes_del, len, len_byte;
+{
+#ifdef BYTE_COMBINING_DEBUG
+  if (count_combining_before (GPT_ADDR, len_byte, from, from_byte)
+      || count_combining_after (GPT_ADDR, len_byte, from, from_byte))
+    abort ();
 #endif
 
-  {
-    int pos = PT, pos_byte = PT_BYTE;
+  /* Update various buffer positions for the new text.  */
+  GAP_SIZE -= len_byte;
+  ZV += len; Z+= len;
+  ZV_BYTE += len_byte; Z_BYTE += len_byte;
+  GPT += len; GPT_BYTE += len_byte;
+  if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor. */
+
+  if (nchars_del > 0)
+    adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
+                               len, len_byte);
+  else
+    adjust_markers_for_insert (from, from_byte,
+                              from + len, from_byte + len_byte, 0);
+
+  if (len > nchars_del)
+    adjust_overlays_for_insert (from, len - nchars_del);
+  else if (len < nchars_del)
+    adjust_overlays_for_delete (from, nchars_del - len);
+  if (BUF_INTERVALS (current_buffer) != 0)
+    {
+      offset_intervals (current_buffer, from, len - nchars_del);
+    }
 
-    if (from < PT)
-      adjust_point (len - (to - from) + combined_after_bytes,
-                   len_byte - (to_byte - from_byte) + combined_after_bytes);
-    else if (from == PT && combined_before_bytes)
-      adjust_point (0, combined_before_bytes);
+  if (from < PT)
+    adjust_point (len - nchars_del, len_byte - nbytes_del);
 
-    if (combined_after_bytes)
-      combine_bytes (from + len, from_byte + len_byte, combined_after_bytes);
+  /* As byte combining will decrease Z, we must check this again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
 
-    if (combined_before_bytes)
-      combine_bytes (from, from_byte, combined_before_bytes);
-  }
+  CHECK_MARKERS ();
 
   if (len == 0)
     evaporate_overlays (from);
   MODIFF++;
 }
 
+/* Record undo information, adjust markers and position keepers for an
+   insertion of a text from FROM (FROM_BYTE) to TO (TO_BYTE).  The
+   text already exists in the current buffer but character length (TO
+   - FROM) may be incorrect, the correct length is NEWLEN.  */
+
+void
+adjust_after_insert (from, from_byte, to, to_byte, newlen)
+     int from, from_byte, to, to_byte, newlen;
+{
+  int len = to - from, len_byte = to_byte - from_byte;
+
+  if (GPT != to)
+    move_gap_both (to, to_byte);
+  GAP_SIZE += len_byte;
+  GPT -= len; GPT_BYTE -= len_byte;
+  ZV -= len; ZV_BYTE -= len_byte;
+  Z -= len; Z_BYTE -= len_byte;
+  adjust_after_replace (from, from_byte, Qnil, newlen, len_byte);
+}
+\f
 /* Replace the text from character positions FROM to TO with NEW,
    If PREPARE is nonzero, call prepare_to_modify_buffer.
    If INHERIT, the newly inserted text should inherit text properties
@@ -1376,25 +1483,29 @@ adjust_after_replace (from, from_byte, to, to_byte, len, len_byte)
 /* Note that this does not yet handle markers quite right.
    Also it needs to record a single undo-entry that does a replacement
    rather than a separate delete and insert.
-   That way, undo will also handle markers properly.  */
+   That way, undo will also handle markers properly.
+
+   But if MARKERS is 0, don't relocate markers.  */
 
 void
-replace_range (from, to, new, prepare, inherit)
+replace_range (from, to, new, prepare, inherit, markers)
      Lisp_Object new;
-     int from, to, prepare, inherit;
+     int from, to, prepare, inherit, markers;
 {
-  int inschars = XSTRING (new)->size;
-  int insbytes = XSTRING (new)->size_byte;
+  int inschars = SCHARS (new);
+  int insbytes = SBYTES (new);
   int from_byte, to_byte;
   int nbytes_del, nchars_del;
   register Lisp_Object temp;
   struct gcpro gcpro1;
-  int combined_before_bytes, combined_after_bytes;
-  int adjusted_inschars;
   INTERVAL intervals;
   int outgoing_insbytes = insbytes;
+  Lisp_Object deletion;
+
+  CHECK_MARKERS ();
 
   GCPRO1 (new);
+  deletion = Qnil;
 
   if (prepare)
     {
@@ -1425,9 +1536,9 @@ replace_range (from, to, new, prepare, inherit)
 
   if (NILP (current_buffer->enable_multibyte_characters))
     outgoing_insbytes = inschars;
-  else if (inschars == insbytes)
+  else if (! STRING_MULTIBYTE (new))
     outgoing_insbytes
-      = count_size_as_multibyte (XSTRING (new)->data, insbytes);
+      = count_size_as_multibyte (SDATA (new), insbytes);
 
   /* Make sure point-max won't overflow after this insertion.  */
   XSETINT (temp, Z_BYTE - nbytes_del + insbytes);
@@ -1442,13 +1553,11 @@ replace_range (from, to, new, prepare, inherit)
   if (to < GPT)
     gap_left (to, to_byte, 0);
 
-  /* Relocate all markers pointing into the new, larger gap
-     to point at the end of the text before the gap.
-     Do this before recording the deletion,
-     so that undo handles this after reinserting the text.  */
-  adjust_markers_for_delete (from, from_byte, to, to_byte);
-
-  record_delete (from, nchars_del);
+  /* Even if we don't record for undo, we must keep the original text
+     because we may have to recover it because of inappropriate byte
+     combining.  */
+  if (! EQ (current_buffer->undo_list, Qt))
+    deletion = make_buffer_string_both (from, from_byte, to, to_byte, 1);
 
   GAP_SIZE += nbytes_del;
   ZV -= nchars_del;
@@ -1457,55 +1566,41 @@ replace_range (from, to, new, prepare, inherit)
   Z_BYTE -= nbytes_del;
   GPT = from;
   GPT_BYTE = from_byte;
-  *(GPT_ADDR) = 0;             /* Put an anchor.  */
+  if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
   if (GPT_BYTE < GPT)
     abort ();
 
-  if (GPT - BEG < beg_unchanged)
-    beg_unchanged = GPT - BEG;
-  if (Z - GPT < end_unchanged)
-    end_unchanged = Z - GPT;
+  if (GPT - BEG < BEG_UNCHANGED)
+    BEG_UNCHANGED = GPT - BEG;
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
 
   if (GAP_SIZE < insbytes)
     make_gap (insbytes - GAP_SIZE);
 
   /* Copy the string text into the buffer, perhaps converting
      between single-byte and multibyte.  */
-  copy_text (XSTRING (new)->data, GPT_ADDR, insbytes,
-            /* If these are equal, it is a single-byte string.
-               Its chars are either ASCII, in which case copy_text
-               won't change it, or single-byte non-ASCII chars,
-               that need to be changed.  */
-            inschars != insbytes,
+  copy_text (SDATA (new), GPT_ADDR, insbytes,
+            STRING_MULTIBYTE (new),
             ! NILP (current_buffer->enable_multibyte_characters));
 
-  /* We have copied text into the gap, but we have not altered
-     PT or PT_BYTE yet.  So we can pass PT and PT_BYTE
-     to these functions and get the same results as we would
-     have got earlier on.  Meanwhile, GPT_ADDR does point to
+#ifdef BYTE_COMBINING_DEBUG
+  /* We have copied text into the gap, but we have not marked
+     it as part of the buffer.  So we can use the old FROM and FROM_BYTE
+     here, for both the previous text and the following text.
+     Meanwhile, GPT_ADDR does point to
      the text that has been stored by copy_text.  */
+  if (count_combining_before (GPT_ADDR, outgoing_insbytes, from, from_byte)
+      || count_combining_after (GPT_ADDR, outgoing_insbytes, from, from_byte))
+    abort ();
+#endif
 
-  combined_before_bytes
-    = count_combining_before (GPT_ADDR, outgoing_insbytes, PT, PT_BYTE);
-  combined_after_bytes
-    = count_combining_after (GPT_ADDR, outgoing_insbytes, PT, PT_BYTE);
-
-  /* Record deletion of the surrounding text that combines with
-     the insertion.  This, together with recording the insertion,
-     will add up to the right stuff in the undo list.
-
-     But there is no need to actually delete the combining bytes
-     from the buffer and reinsert them.  */
-
-  if (combined_after_bytes)
-    record_delete (PT, combined_after_bytes);
-
-  if (combined_before_bytes)
-    record_delete (PT - 1, 1);
-
-  record_insert (PT - !!combined_before_bytes,
-                inschars - combined_before_bytes + !!combined_before_bytes);
+  if (! EQ (current_buffer->undo_list, Qt))
+    {
+      record_delete (from, deletion);
+      record_insert (from, inschars);
+    }
 
   GAP_SIZE -= outgoing_insbytes;
   GPT += inschars;
@@ -1516,10 +1611,6 @@ replace_range (from, to, new, prepare, inherit)
   Z_BYTE += outgoing_insbytes;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
   if (GPT_BYTE < GPT)
     abort ();
 
@@ -1527,43 +1618,157 @@ replace_range (from, to, new, prepare, inherit)
      adjusting the markers that bound the overlays.  */
   adjust_overlays_for_delete (from, nchars_del);
   adjust_overlays_for_insert (from, inschars);
-  adjust_markers_for_insert (from, from_byte,
-                            from + inschars, from_byte + outgoing_insbytes,
-                            combined_before_bytes, combined_after_bytes, 0);
 
-#ifdef USE_TEXT_PROPERTIES
-  offset_intervals (current_buffer, PT, inschars - nchars_del);
+  /* Adjust markers for the deletion and the insertion.  */
+  if (markers)
+    adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
+                               inschars, outgoing_insbytes);
+
+  offset_intervals (current_buffer, from, inschars - nchars_del);
 
   /* Get the intervals for the part of the string we are inserting--
      not including the combined-before bytes.  */
-  intervals = XSTRING (new)->intervals;
+  intervals = STRING_INTERVALS (new);
   /* Insert those intervals.  */
   graft_intervals_into_buffer (intervals, from, inschars,
                               current_buffer, inherit);
-#endif
 
   /* Relocate point as if it were a marker.  */
   if (from < PT)
-    adjust_point ((from + inschars - (PT < to ? PT : to)
-                  + combined_after_bytes),
+    adjust_point ((from + inschars - (PT < to ? PT : to)),
                  (from_byte + outgoing_insbytes
-                  - (PT_BYTE < to_byte ? PT_BYTE : to_byte)
-                  + combined_after_bytes));
-
-  if (combined_after_bytes)
-    combine_bytes (from + inschars, from_byte + outgoing_insbytes,
-                  combined_after_bytes);
-
-  if (combined_before_bytes)
-    combine_bytes (from, from_byte, combined_before_bytes);
+                  - (PT_BYTE < to_byte ? PT_BYTE : to_byte)));
 
   if (outgoing_insbytes == 0)
     evaporate_overlays (from);
 
+  CHECK_MARKERS ();
+
   MODIFF++;
   UNGCPRO;
 
-  signal_after_change (from, nchars_del, PT - from);
+  signal_after_change (from, nchars_del, GPT - from);
+  update_compositions (from, GPT, CHECK_BORDER);
+}
+\f
+/* Replace the text from character positions FROM to TO with
+   the text in INS of length INSCHARS.
+   Keep the text properties that applied to the old characters
+   (extending them to all the new chars if there are more new chars).
+
+   Note that this does not yet handle markers quite right.
+
+   If MARKERS is nonzero, relocate markers.
+
+   Unlike most functions at this level, never call
+   prepare_to_modify_buffer and never call signal_after_change.  */
+
+void
+replace_range_2 (from, from_byte, to, to_byte, ins, inschars, insbytes, markers)
+     int from, from_byte, to, to_byte;
+     char *ins;
+     int inschars, insbytes, markers;
+{
+  int nbytes_del, nchars_del;
+  Lisp_Object temp;
+
+  CHECK_MARKERS ();
+
+  nchars_del = to - from;
+  nbytes_del = to_byte - from_byte;
+
+  if (nbytes_del <= 0 && insbytes == 0)
+    return;
+
+  /* Make sure point-max won't overflow after this insertion.  */
+  XSETINT (temp, Z_BYTE - nbytes_del + insbytes);
+  if (Z_BYTE - nbytes_del + insbytes != XINT (temp))
+    error ("Maximum buffer size exceeded");
+
+  /* Make sure the gap is somewhere in or next to what we are deleting.  */
+  if (from > GPT)
+    gap_right (from, from_byte);
+  if (to < GPT)
+    gap_left (to, to_byte, 0);
+
+  GAP_SIZE += nbytes_del;
+  ZV -= nchars_del;
+  Z -= nchars_del;
+  ZV_BYTE -= nbytes_del;
+  Z_BYTE -= nbytes_del;
+  GPT = from;
+  GPT_BYTE = from_byte;
+  if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
+
+  if (GPT_BYTE < GPT)
+    abort ();
+
+  if (GPT - BEG < BEG_UNCHANGED)
+    BEG_UNCHANGED = GPT - BEG;
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
+  if (GAP_SIZE < insbytes)
+    make_gap (insbytes - GAP_SIZE);
+
+  /* Copy the replacement text into the buffer.  */
+  bcopy (ins, GPT_ADDR, insbytes);
+
+#ifdef BYTE_COMBINING_DEBUG
+  /* We have copied text into the gap, but we have not marked
+     it as part of the buffer.  So we can use the old FROM and FROM_BYTE
+     here, for both the previous text and the following text.
+     Meanwhile, GPT_ADDR does point to
+     the text that has been stored by copy_text.  */
+  if (count_combining_before (GPT_ADDR, insbytes, from, from_byte)
+      || count_combining_after (GPT_ADDR, insbytes, from, from_byte))
+    abort ();
+#endif
+
+  GAP_SIZE -= insbytes;
+  GPT += inschars;
+  ZV += inschars;
+  Z += inschars;
+  GPT_BYTE += insbytes;
+  ZV_BYTE += insbytes;
+  Z_BYTE += insbytes;
+  if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
+
+  if (GPT_BYTE < GPT)
+    abort ();
+
+  /* Adjust the overlay center as needed.  This must be done after
+     adjusting the markers that bound the overlays.  */
+  if (nchars_del != inschars)
+    {
+      adjust_overlays_for_insert (from, inschars);
+      adjust_overlays_for_delete (from + inschars, nchars_del);
+    }
+
+  /* Adjust markers for the deletion and the insertion.  */
+  if (markers
+      && ! (nchars_del == 1 && inschars == 1 && nbytes_del == insbytes))
+    adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
+                               inschars, insbytes);
+
+  offset_intervals (current_buffer, from, inschars - nchars_del);
+
+  /* Relocate point as if it were a marker.  */
+  if (from < PT && (nchars_del != inschars || nbytes_del != insbytes))
+    {
+      if (PT < to)
+       /* PT was within the deleted text.  Move it to FROM.  */
+       adjust_point (from - PT, from_byte - PT_BYTE);
+      else
+       adjust_point (inschars - nchars_del, insbytes - nbytes_del);
+    }
+
+  if (insbytes == 0)
+    evaporate_overlays (from);
+
+  CHECK_MARKERS ();
+
+  MODIFF++;
 }
 \f
 /* Delete characters in current buffer
@@ -1574,16 +1779,19 @@ void
 del_range (from, to)
      register int from, to;
 {
-  del_range_1 (from, to, 1);
+  del_range_1 (from, to, 1, 0);
 }
 
-/* Like del_range; PREPARE says whether to call prepare_to_modify_buffer.  */
+/* Like del_range; PREPARE says whether to call prepare_to_modify_buffer.
+   RET_STRING says to return the deleted text. */
 
-void
-del_range_1 (from, to, prepare)
-     int from, to, prepare;
+Lisp_Object
+del_range_1 (from, to, prepare, ret_string)
+     int from, to, prepare, ret_string;
 {
   int from_byte, to_byte;
+  Lisp_Object deletion;
+  struct gcpro gcpro1;
 
   /* Make args be valid */
   if (from < BEGV)
@@ -1592,19 +1800,24 @@ del_range_1 (from, to, prepare)
     to = ZV;
 
   if (to <= from)
-    return;
+    return Qnil;
 
   if (prepare)
     {
       int range_length = to - from;
       prepare_to_modify_buffer (from, to, &from);
-      to = from + range_length;
+      to = min (ZV, from + range_length);
     }
 
   from_byte = CHAR_TO_BYTE (from);
   to_byte = CHAR_TO_BYTE (to);
 
-  del_range_2 (from, from_byte, to, to_byte);
+  deletion = del_range_2 (from, from_byte, to, to_byte, ret_string);
+  GCPRO1(deletion);
+  signal_after_change (from, to - from, 0);
+  update_compositions (from, from, CHECK_HEAD);
+  UNGCPRO;
+  return deletion;
 }
 
 /* Like del_range_1 but args are byte positions, not char positions.  */
@@ -1636,11 +1849,18 @@ del_range_byte (from_byte, to_byte, prepare)
 
       if (old_from != from)
        from_byte = CHAR_TO_BYTE (from);
-      if (old_to == Z - to)
+      if (to > ZV)
+       {
+         to = ZV;
+         to_byte = ZV_BYTE;
+       }
+      else if (old_to == Z - to)
        to_byte = CHAR_TO_BYTE (to);
     }
 
-  del_range_2 (from, from_byte, to, to_byte);
+  del_range_2 (from, from_byte, to, to_byte, 0);
+  signal_after_change (from, to - from, 0);
+  update_compositions (from, from, CHECK_HEAD);
 }
 
 /* Like del_range_1, but positions are specified both as charpos
@@ -1673,23 +1893,33 @@ del_range_both (from, from_byte, to, to_byte, prepare)
 
       if (old_from != from)
        from_byte = CHAR_TO_BYTE (from);
-      if (old_to == Z - to)
+      if (to > ZV)
+       {
+         to = ZV;
+         to_byte = ZV_BYTE;
+       }
+      else if (old_to == Z - to)
        to_byte = CHAR_TO_BYTE (to);
     }
 
-  del_range_2 (from, from_byte, to, to_byte);
+  del_range_2 (from, from_byte, to, to_byte, 0);
+  signal_after_change (from, to - from, 0);
+  update_compositions (from, from, CHECK_HEAD);
 }
 
 /* Delete a range of text, specified both as character positions
    and byte positions.  FROM and TO are character positions,
-   while FROM_BYTE and TO_BYTE are byte positions.  */
+   while FROM_BYTE and TO_BYTE are byte positions.
+   If RET_STRING is true, the deleted area is returned as a string. */
 
-void
-del_range_2 (from, from_byte, to, to_byte)
-     int from, from_byte, to, to_byte;
+Lisp_Object
+del_range_2 (from, from_byte, to, to_byte, ret_string)
+     int from, from_byte, to, to_byte, ret_string;
 {
   register int nbytes_del, nchars_del;
-  int combined_after_bytes;
+  Lisp_Object deletion;
+
+  CHECK_MARKERS ();
 
   nchars_del = to - from;
   nbytes_del = to_byte - from_byte;
@@ -1700,8 +1930,16 @@ del_range_2 (from, from_byte, to, to_byte)
   if (to < GPT)
     gap_left (to, to_byte, 0);
 
-  combined_after_bytes
-    = count_combining_before (GAP_END_ADDR, ZV_BYTE - GPT_BYTE, PT, PT_BYTE);
+#ifdef BYTE_COMBINING_DEBUG
+  if (count_combining_before (BUF_BYTE_ADDRESS (current_buffer, to_byte),
+                             Z_BYTE - to_byte, from, from_byte))
+    abort ();
+#endif
+
+  if (ret_string || ! EQ (current_buffer->undo_list, Qt))
+    deletion = make_buffer_string_both (from, from_byte, to, to_byte, 1);
+  else
+    deletion = Qnil;
 
   /* Relocate all markers pointing into the new, larger gap
      to point at the end of the text before the gap.
@@ -1709,8 +1947,8 @@ del_range_2 (from, from_byte, to, to_byte)
      so that undo handles this after reinserting the text.  */
   adjust_markers_for_delete (from, from_byte, to, to_byte);
 
-  record_delete (from - !!combined_after_bytes,
-                nchars_del + combined_after_bytes + !!combined_after_bytes);
+  if (! EQ (current_buffer->undo_list, Qt))
+    record_delete (from, deletion);
   MODIFF++;
 
   /* Relocate point as if it were a marker.  */
@@ -1718,12 +1956,11 @@ del_range_2 (from, from_byte, to, to_byte)
     adjust_point (from - (PT < to ? PT : to),
                  from_byte - (PT_BYTE < to_byte ? PT_BYTE : to_byte));
 
-  /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES */
   offset_intervals (current_buffer, from, - nchars_del);
 
   /* Adjust the overlay center as needed.  This must be done after
      adjusting the markers that bound the overlays.  */
-  adjust_overlays_for_delete (from_byte, nchars_del);
+  adjust_overlays_for_delete (from, nchars_del);
 
   GAP_SIZE += nbytes_del;
   ZV_BYTE -= nbytes_del;
@@ -1732,28 +1969,21 @@ del_range_2 (from, from_byte, to, to_byte)
   Z -= nchars_del;
   GPT = from;
   GPT_BYTE = from_byte;
-  *(GPT_ADDR) = 0;             /* Put an anchor.  */
-
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
+  if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor.  */
 
   if (GPT_BYTE < GPT)
     abort ();
 
-  if (GPT - BEG < beg_unchanged)
-    beg_unchanged = GPT - BEG;
-  if (Z - GPT < end_unchanged)
-    end_unchanged = Z - GPT;
-
-  if (combined_after_bytes)
-    combine_bytes (PT, PT_BYTE, combined_after_bytes);
+  if (GPT - BEG < BEG_UNCHANGED)
+    BEG_UNCHANGED = GPT - BEG;
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
 
-  if (combined_after_bytes)
-    record_insert (GPT - 1, 1);
+  CHECK_MARKERS ();
 
   evaporate_overlays (from);
-  signal_after_change (from, nchars_del, 0);
+
+  return deletion;
 }
 \f
 /* Call this if you're about to change the region of BUFFER from
@@ -1774,14 +2004,7 @@ modify_region (buffer, start, end)
 
   prepare_to_modify_buffer (start, end, NULL);
 
-  if (start - 1 < beg_unchanged
-      || (unchanged_modified == MODIFF
-         && overlay_unchanged_modified == OVERLAY_MODIFF))
-    beg_unchanged = start - 1;
-  if (Z - end < end_unchanged
-      || (unchanged_modified == MODIFF
-         && overlay_unchanged_modified == OVERLAY_MODIFF))
-    end_unchanged = Z - end;
+  BUF_COMPUTE_UNCHANGED (buffer, start - 1, end);
 
   if (MODIFF <= SAVE_MODIFF)
     record_first_change ();
@@ -1811,7 +2034,11 @@ prepare_to_modify_buffer (start, end, preserve_ptr)
   if (!NILP (current_buffer->read_only))
     Fbarf_if_buffer_read_only ();
 
-  /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES */
+  /* Let redisplay consider other windows than selected_window
+     if modifying another buffer.  */
+  if (XBUFFER (XWINDOW (selected_window)->buffer) != current_buffer)
+    ++windows_or_buffers_changed;
+
   if (BUF_INTERVALS (current_buffer) != 0)
     {
       if (preserve_ptr)
@@ -1822,7 +2049,7 @@ prepare_to_modify_buffer (start, end, preserve_ptr)
          GCPRO1 (preserve_marker);
          verify_interval_modification (current_buffer, start, end);
          *preserve_ptr = marker_position (preserve_marker);
-         unchain_marker (preserve_marker);
+         unchain_marker (XMARKER (preserve_marker));
          UNGCPRO;
        }
       else
@@ -1870,7 +2097,7 @@ prepare_to_modify_buffer (start, end, preserve_ptr)
   if (! NILP (preserve_marker))                                        \
     {                                                          \
       *preserve_ptr = marker_position (preserve_marker);       \
-      unchain_marker (preserve_marker);                                \
+      unchain_marker (XMARKER (preserve_marker));              \
     }
 
 #define PRESERVE_START_END                     \
@@ -1901,6 +2128,9 @@ signal_before_change (start_int, end_int, preserve_ptr)
   Lisp_Object preserve_marker;
   struct gcpro gcpro1, gcpro2, gcpro3;
 
+  if (inhibit_modification_hooks)
+    return;
+
   start = make_number (start_int);
   end = make_number (end_int);
   preserve_marker = Qnil;
@@ -1918,16 +2148,6 @@ signal_before_change (start_int, end_int, preserve_ptr)
       call1 (Vrun_hooks, Qfirst_change_hook);
     }
 
-  /* Run the before-change-function if any.
-     We don't bother "binding" this variable to nil
-     because it is obsolete anyway and new code should not use it.  */
-  if (!NILP (Vbefore_change_function))
-    {
-      PRESERVE_VALUE;
-      PRESERVE_START_END;
-      call2 (Vbefore_change_function, FETCH_START, FETCH_END);
-    }
-
   /* Now run the before-change-functions if any.  */
   if (!NILP (Vbefore_change_functions))
     {
@@ -1935,6 +2155,8 @@ signal_before_change (start_int, end_int, preserve_ptr)
       Lisp_Object before_change_functions;
       Lisp_Object after_change_functions;
       struct gcpro gcpro1, gcpro2;
+      struct buffer *old = current_buffer;
+      struct buffer *new;
 
       PRESERVE_VALUE;
       PRESERVE_START_END;
@@ -1954,14 +2176,25 @@ signal_before_change (start_int, end_int, preserve_ptr)
       args[2] = FETCH_END;
       run_hook_list_with_args (before_change_functions, 3, args);
 
-      /* "Unbind" the variables we "bound" to nil.  */
-      Vbefore_change_functions = before_change_functions;
-      Vafter_change_functions = after_change_functions;
+      /* "Unbind" the variables we "bound" to nil.  Beware a
+        buffer-local hook which changes the buffer when run (e.g. W3).  */
+      if (old != current_buffer)
+       {
+         new = current_buffer;
+         set_buffer_internal (old);
+         Vbefore_change_functions = before_change_functions;
+         Vafter_change_functions = after_change_functions;
+         set_buffer_internal (new);
+       }
+      else
+       {
+         Vbefore_change_functions = before_change_functions;
+         Vafter_change_functions = after_change_functions;
+       }
       UNGCPRO;
     }
 
-  if (!NILP (current_buffer->overlays_before)
-      || !NILP (current_buffer->overlays_after))
+  if (current_buffer->overlays_before || current_buffer->overlays_after)
     {
       PRESERVE_VALUE;
       report_overlay_modification (FETCH_START, FETCH_END, 0,
@@ -1987,13 +2220,16 @@ void
 signal_after_change (charpos, lendel, lenins)
      int charpos, lendel, lenins;
 {
+  if (inhibit_modification_hooks)
+    return;
+
   /* If we are deferring calls to the after-change functions
      and there are no before-change functions,
      just record the args that we were going to use.  */
   if (! NILP (Vcombine_after_change_calls)
-      && NILP (Vbefore_change_function) && NILP (Vbefore_change_functions)
-      && NILP (current_buffer->overlays_before)
-      && NILP (current_buffer->overlays_after))
+      && NILP (Vbefore_change_functions)
+      && !current_buffer->overlays_before
+      && !current_buffer->overlays_after)
     {
       Lisp_Object elt;
 
@@ -2011,22 +2247,16 @@ signal_after_change (charpos, lendel, lenins)
       return;
     }
 
-  if (!NILP (combine_after_change_list)) 
+  if (!NILP (combine_after_change_list))
     Fcombine_after_change_execute ();
 
-  /* Run the after-change-function if any.
-     We don't bother "binding" this variable to nil
-     because it is obsolete anyway and new code should not use it.  */
-  if (!NILP (Vafter_change_function))
-    call3 (Vafter_change_function,
-          make_number (charpos), make_number (charpos + lenins),
-          make_number (lendel));
-
   if (!NILP (Vafter_change_functions))
     {
       Lisp_Object args[4];
       Lisp_Object before_change_functions;
       Lisp_Object after_change_functions;
+      struct buffer *old = current_buffer;
+      struct buffer *new;
       struct gcpro gcpro1, gcpro2;
 
       /* "Bind" before-change-functions and after-change-functions
@@ -2046,14 +2276,25 @@ signal_after_change (charpos, lendel, lenins)
       run_hook_list_with_args (after_change_functions,
                               4, args);
 
-      /* "Unbind" the variables we "bound" to nil.  */
-      Vbefore_change_functions = before_change_functions;
-      Vafter_change_functions = after_change_functions;
+      /* "Unbind" the variables we "bound" to nil.  Beware a
+        buffer-local hook which changes the buffer when run (e.g. W3).  */
+      if (old != current_buffer)
+       {
+         new = current_buffer;
+         set_buffer_internal (old);
+         Vbefore_change_functions = before_change_functions;
+         Vafter_change_functions = after_change_functions;
+         set_buffer_internal (new);
+       }
+      else
+       {
+         Vbefore_change_functions = before_change_functions;
+         Vafter_change_functions = after_change_functions;
+       }
       UNGCPRO;
     }
 
-  if (!NILP (current_buffer->overlays_before)
-      || !NILP (current_buffer->overlays_after))
+  if (current_buffer->overlays_before || current_buffer->overlays_after)
     report_overlay_modification (make_number (charpos),
                                 make_number (charpos + lenins),
                                 1,
@@ -2064,7 +2305,8 @@ signal_after_change (charpos, lendel, lenins)
   /* After an insertion, call the text properties
      insert-behind-hooks or insert-in-front-hooks.  */
   if (lendel == 0)
-    report_interval_modification (charpos, charpos + lenins);
+    report_interval_modification (make_number (charpos),
+                                 make_number (charpos + lenins));
 }
 
 Lisp_Object
@@ -2076,16 +2318,18 @@ Fcombine_after_change_execute_1 (val)
 }
 
 DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
-  Scombine_after_change_execute, 0, 0, 0,
-  "This function is for use internally in `combine-after-change-calls'.")
-  ()
+       Scombine_after_change_execute, 0, 0, 0,
+       doc: /* This function is for use internally in `combine-after-change-calls'.  */)
+     ()
 {
-  register Lisp_Object val;
-  int count = specpdl_ptr - specpdl;
+  int count = SPECPDL_INDEX ();
   int beg, end, change;
   int begpos, endpos;
   Lisp_Object tail;
 
+  if (NILP (combine_after_change_list))
+    return Qnil;
+
   record_unwind_protect (Fset_buffer, Fcurrent_buffer ());
 
   Fset_buffer (combine_after_change_buffer);
@@ -2100,26 +2344,26 @@ DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
   /* Scan the various individual changes,
      accumulating the range info in BEG, END and CHANGE.  */
   for (tail = combine_after_change_list; CONSP (tail);
-       tail = XCONS (tail)->cdr)
+       tail = XCDR (tail))
     {
       Lisp_Object elt;
       int thisbeg, thisend, thischange;
 
       /* Extract the info from the next element.  */
-      elt = XCONS (tail)->car;
+      elt = XCAR (tail);
       if (! CONSP (elt))
        continue;
-      thisbeg = XINT (XCONS (elt)->car);
+      thisbeg = XINT (XCAR (elt));
 
-      elt = XCONS (elt)->cdr;
+      elt = XCDR (elt);
       if (! CONSP (elt))
        continue;
-      thisend = XINT (XCONS (elt)->car);
+      thisend = XINT (XCAR (elt));
 
-      elt = XCONS (elt)->cdr;
+      elt = XCDR (elt);
       if (! CONSP (elt))
        continue;
-      thischange = XINT (XCONS (elt)->car);
+      thischange = XINT (XCAR (elt));
 
       /* Merge this range into the accumulated range.  */
       change += thischange;
@@ -2133,7 +2377,7 @@ DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
      that was changed.  */
   begpos = BEG + beg;
   endpos = Z - end;
-  
+
   /* We are about to handle these, so discard them.  */
   combine_after_change_list = Qnil;
 
@@ -2142,18 +2386,36 @@ DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
   record_unwind_protect (Fcombine_after_change_execute_1,
                         Vcombine_after_change_calls);
   signal_after_change (begpos, endpos - begpos - change, endpos - begpos);
+  update_compositions (begpos, endpos, CHECK_ALL);
 
-  return unbind_to (count, val);
+  return unbind_to (count, Qnil);
 }
 \f
+void
 syms_of_insdel ()
 {
   staticpro (&combine_after_change_list);
+  staticpro (&combine_after_change_buffer);
   combine_after_change_list = Qnil;
+  combine_after_change_buffer = Qnil;
 
+  DEFVAR_BOOL ("check-markers-debug-flag", &check_markers_debug_flag,
+              doc: /* Non-nil means enable debugging checks for invalid marker positions.  */);
+  check_markers_debug_flag = 0;
   DEFVAR_LISP ("combine-after-change-calls", &Vcombine_after_change_calls,
-     "Used internally by the `combine-after-change-calls' macro.");
+              doc: /* Used internally by the `combine-after-change-calls' macro.  */);
   Vcombine_after_change_calls = Qnil;
 
+  DEFVAR_BOOL ("inhibit-modification-hooks", &inhibit_modification_hooks,
+              doc: /* Non-nil means don't run any of the hooks that respond to buffer changes.
+This affects `before-change-functions' and `after-change-functions',
+as well as hooks attached to text properties and overlays.  */);
+  inhibit_modification_hooks = 0;
+  Qinhibit_modification_hooks = intern ("inhibit-modification-hooks");
+  staticpro (&Qinhibit_modification_hooks);
+
   defsubr (&Scombine_after_change_execute);
 }
+
+/* arch-tag: 9b34b886-47d7-465e-a234-299af411b23d
+   (do not change this comment) */