]> code.delx.au - gnu-emacs/blobdiff - src/insdel.c
Add description of +LINE:COLUMN.
[gnu-emacs] / src / insdel.c
index fa40da761606763f1b440c24066b2cc95be048fe..99576218b4b35383a11e38f665d860ddb224c1e7 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, 86,93,94,95,97,98, 1999, 2000, 2001
+   Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -26,20 +27,23 @@ 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))
+#define max(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 ();
@@ -63,6 +67,38 @@ Lisp_Object combine_after_change_list;
 /* Buffer which combine_after_change_list is about.  */
 Lisp_Object combine_after_change_buffer;
 \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 Lisp_Object tail;
+  int multibyte = ! NILP (current_buffer->enable_multibyte_characters);
+
+  tail = BUF_MARKERS (current_buffer);
+
+  while (! NILP (tail))
+    {
+      if (XMARKER (tail)->buffer->text != current_buffer->text)
+       abort ();
+      if (XMARKER (tail)->charpos > Z)
+       abort ();
+      if (XMARKER (tail)->bytepos > Z_BYTE)
+       abort ();
+      if (multibyte && ! CHAR_HEAD_P (FETCH_BYTE (XMARKER (tail)->bytepos)))
+       abort ();
+
+      tail = XMARKER (tail)->chain;
+    }
+}
+\f
 /* Move gap to position CHARPOS.
    Note that this can quit!  */
 
@@ -101,21 +137,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 +212,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,15 +342,13 @@ 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);
 
@@ -359,38 +367,51 @@ 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.  */ 
+           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.  */
+           record_marker_adjustment (marker, charpos - to);
+
          m->charpos = from;
          m->bytepos = from_byte;
        }
+      /* 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.  */
+         record_marker_adjustment (marker, to - from);
+       }
 
       marker = m->chain;
     }
 }
+
 \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;
   int adjusted = 0;
@@ -403,10 +424,8 @@ adjust_markers_for_insert (from, from_byte, to, to_byte,
     {
       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,40 +436,11 @@ 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)
        {
@@ -489,6 +479,43 @@ 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;
+{
+  Lisp_Object marker = BUF_MARKERS (current_buffer);
+  int prev_to_byte = from_byte + old_bytes;
+  int diff_chars = new_chars - old_chars;
+  int diff_bytes = new_bytes - old_bytes;
+
+  while (!NILP (marker))
+    {
+      register struct Lisp_Marker *m = XMARKER (marker);
+
+      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;
+       }
+
+      marker = m->chain;
+    }
+
+  CHECK_MARKERS ();
+}
+
 \f
 /* Make the gap NBYTES_ADDED bytes longer.  */
 
@@ -496,7 +523,6 @@ void
 make_gap (nbytes_added)
      int nbytes_added;
 {
-  unsigned char *result;
   Lisp_Object tem;
   int real_gap_loc;
   int real_gap_loc_byte;
@@ -513,20 +539,7 @@ make_gap (nbytes_added)
       >= ((unsigned) 1 << (min (BITS_PER_INT, VALBITS) - 1)))
     error ("Buffer exceeds maximum size");
 
-  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));
-
-  if (result == 0)
-    {
-      UNBLOCK_INPUT;
-      memory_full ();
-    }
-
-  /* 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;
@@ -580,15 +593,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 +627,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
@@ -636,12 +658,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);
        }
     }
 
@@ -659,13 +681,14 @@ count_size_as_multibyte (ptr, nbytes)
 void
 insert (string, nbytes)
      register unsigned char *string;
-     register nbytes;
+     register int nbytes;
 {
   if (nbytes > 0)
     {
       int opoint = PT;
       insert_1 (string, nbytes, 0, 1, 0);
       signal_after_change (opoint, 0, PT - opoint);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -674,13 +697,14 @@ insert (string, nbytes)
 void
 insert_and_inherit (string, nbytes)
      register unsigned char *string;
-     register nbytes;
+     register int nbytes;
 {
   if (nbytes > 0)
     {
       int opoint = PT;
       insert_1 (string, nbytes, 1, 1, 0);
       signal_after_change (opoint, 0, PT - opoint);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -690,16 +714,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);
@@ -730,6 +753,7 @@ insert_before_markers (string, nbytes)
 
       insert_1 (string, nbytes, 0, 1, 1);
       signal_after_change (opoint, 0, PT - opoint);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -746,6 +770,7 @@ insert_before_markers_and_inherit (string, nbytes)
 
       insert_1 (string, nbytes, 1, 1, 1);
       signal_after_change (opoint, 0, PT - opoint);
+      update_compositions (opoint, PT, CHECK_BORDER);
     }
 }
 
@@ -760,7 +785,10 @@ insert_1 (string, nbytes, 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
@@ -772,30 +800,40 @@ count_combining_before (string, length, pos, pos_byte)
      int length;
      int pos, pos_byte;
 {
-  int opos = pos, opos_byte = pos_byte;
-  int c;
-  unsigned char *p = string;
+  int len, combining_bytes;
+  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
@@ -809,79 +847,61 @@ count_combining_after (string, length, pos, pos_byte)
      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;
-}
-
-/* Adjust the position TARGET/TARGET_BYTE for the combining of NBYTES
-   following the position POS/POS_BYTE to the character preceding POS.
-   If TARGET is after POS+NBYTES, we only have to adjust the character
-   position TARGET, else, if TARGET is after POS, we have to adjust
-   both the character position TARGET and the byte position
-   TARGET_BYTE, else we don't have to do any adjustment.  */
-
-#define ADJUST_CHAR_POS(target, target_byte)   \
-  do {                                         \
-    if (target > pos + nbytes)                 \
-      target -= nbytes;                                \
-    else if (target >= pos)                    \
-      {                                                \
-       target = pos;                           \
-       target_byte = pos_byte + nbytes;        \
-      }                                                \
-  } while (0)
-
-/* 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.  */
+  bytes = BYTES_BY_CHAR_HEAD (string[i]) - (length - i);
+  bufp++, pos_byte++;
+  while (!CHAR_HEAD_P (*bufp)) bufp++, pos_byte++;
 
-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);
-
-  adjust_overlays_for_delete (pos, nbytes);
+  return (bytes <= pos_byte - opos_byte ? bytes : pos_byte - opos_byte);
+}
 
-  ADJUST_CHAR_POS (BUF_PT (current_buffer), BUF_PT_BYTE (current_buffer));
-  ADJUST_CHAR_POS (GPT, GPT_BYTE);
-  ADJUST_CHAR_POS (Z, Z_BYTE);
-  ADJUST_CHAR_POS (ZV, ZV_BYTE);
+#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
@@ -893,47 +913,35 @@ insert_1_both (string, nchars, nbytes, inherit, prepare, before_markers)
      register int nchars, nbytes;
      int inherit, prepare, before_markers;
 {
-  register Lisp_Object temp;
-  int combined_before_bytes, combined_after_bytes;
-
   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;
@@ -942,41 +950,24 @@ 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 ();
 
   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
+    set_text_properties (make_number (PT), make_number (PT + nchars),
+                        Qnil, Qnil, Qnil);
 
-  {
-    int pos = PT, pos_byte = PT_BYTE;
+  adjust_point (nchars, nbytes);
 
-    adjust_point (nchars + combined_after_bytes,
-                 nbytes + combined_after_bytes);
-
-    if (combined_after_bytes)
-      combine_bytes (pos + nchars, pos_byte + nbytes, combined_after_bytes);
-
-    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
@@ -994,13 +985,11 @@ 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;
+  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
@@ -1013,13 +1002,11 @@ 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;
+  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.  */
@@ -1031,11 +1018,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
@@ -1043,61 +1027,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],
                                 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,
+            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;
@@ -1109,45 +1073,26 @@ 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 ();
 
   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)
+  /* Get the intervals for the part of the string we are inserting.  */
+  if (nbytes < STRING_BYTES (XSTRING (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
@@ -1163,13 +1108,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
@@ -1179,13 +1122,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
@@ -1194,15 +1135,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)
@@ -1215,45 +1180,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;
@@ -1265,79 +1220,57 @@ 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 ();
 
   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, replace)
-     int from, from_byte, to, to_byte, len, len_byte, replace;
+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 = XSTRING (prev_text)->size;
+      nbytes_del = STRING_BYTES (XSTRING (prev_text));
+    }
 
   /* Update various buffer positions for the new text.  */
   GAP_SIZE -= len_byte;
@@ -1346,46 +1279,63 @@ adjust_after_replace (from, from_byte, to, to_byte, len, len_byte, replace)
   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 (BUF_INTERVALS (current_buffer) != 0)
-    /* REPLACE zero means that we have not yet adjusted the interval
-       tree for the text between FROM and TO, thus, we must treat the
-       new text as a newly inserted text, not as a replacement of
-       something.  */
-    offset_intervals (current_buffer, from, len - (replace ? to - from : 0));
-#endif
+  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);
 
-  {
-    int pos = PT, pos_byte = PT_BYTE;
+  if (! EQ (current_buffer->undo_list, Qt))
+    {
+      if (nchars_del > 0)
+       record_delete (from, prev_text);
+      record_insert (from, len);
+    }
 
-    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 (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 (combined_after_bytes)
-      combine_bytes (from + len, from_byte + len_byte, combined_after_bytes);
+  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;
 
-    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);
+}
+
 /* 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
@@ -1394,25 +1344,29 @@ adjust_after_replace (from, from_byte, to, to_byte, len, len_byte, replace)
 /* 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 insbytes = STRING_BYTES (XSTRING (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)
     {
@@ -1443,7 +1397,7 @@ 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);
 
@@ -1460,13 +1414,18 @@ 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);
+  /* 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);
 
-  record_delete (from, nchars_del);
+  if (markers)
+    /* 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);
 
   GAP_SIZE += nbytes_del;
   ZV -= nchars_del;
@@ -1480,10 +1439,10 @@ replace_range (from, to, new, prepare, inherit)
   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);
@@ -1491,39 +1450,25 @@ replace_range (from, to, new, prepare, inherit)
   /* 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,
+            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;
@@ -1534,10 +1479,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 ();
 
@@ -1545,12 +1486,12 @@ 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);
+  if (markers)
+    adjust_markers_for_insert (from, from_byte,
+                              from + inschars, from_byte + outgoing_insbytes,
+                              0);
 
-#ifdef USE_TEXT_PROPERTIES
-  offset_intervals (current_buffer, PT, inschars - nchars_del);
+  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.  */
@@ -1558,30 +1499,23 @@ replace_range (from, to, new, prepare, inherit)
   /* 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
 /* Delete characters in current buffer
@@ -1592,16 +1526,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)
@@ -1610,19 +1547,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.  */
@@ -1654,11 +1596,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
@@ -1691,23 +1640,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;
@@ -1718,9 +1677,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 (BUF_BYTE_ADDRESS (current_buffer, to_byte),
-                             ZV_BYTE - to_byte, from, from_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.
@@ -1728,13 +1694,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 (combined_after_bytes)
-    /* COMBINED_AFTER_BYTES nonzero means that the above record_delete
-       moved the gap by calling Fbuffer_substring.  We must move the
-       gap again to a proper place.  */
-    move_gap_both (from, from_byte);
+  if (! EQ (current_buffer->undo_list, Qt))
+    record_delete (from, deletion);
   MODIFF++;
 
   /* Relocate point as if it were a marker.  */
@@ -1742,7 +1703,6 @@ 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
@@ -1756,29 +1716,21 @@ del_range_2 (from, from_byte, to, to_byte)
   Z -= nchars_del;
   GPT = from;
   GPT_BYTE = from_byte;
-
-  if (combined_after_bytes)
-    move_gap_both (GPT + combined_after_bytes,
-                  GPT_BYTE + combined_after_bytes);
-
   *(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 (combined_after_bytes)
-    combine_bytes (from, from_byte, combined_after_bytes);
-
-  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
@@ -1799,14 +1751,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 ();
@@ -1836,7 +1781,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)
@@ -1926,6 +1875,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;
@@ -1943,16 +1895,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))
     {
@@ -1960,6 +1902,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;
@@ -1979,9 +1923,21 @@ 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;
     }
 
@@ -2012,11 +1968,14 @@ 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 (Vbefore_change_functions)
       && NILP (current_buffer->overlays_before)
       && NILP (current_buffer->overlays_after))
     {
@@ -2039,19 +1998,13 @@ signal_after_change (charpos, lendel, lenins)
   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
@@ -2071,9 +2024,21 @@ 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;
     }
 
@@ -2089,7 +2054,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
@@ -2105,12 +2071,14 @@ DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
   "This function is for use internally in `combine-after-change-calls'.")
   ()
 {
-  register Lisp_Object val;
   int count = specpdl_ptr - specpdl;
   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);
@@ -2125,26 +2093,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;
@@ -2167,18 +2135,30 @@ 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);
   combine_after_change_list = Qnil;
+  combine_after_change_buffer = Qnil;
 
+  DEFVAR_BOOL ("check-markers-debug-flag", &check_markers_debug_flag,
+    "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.");
+    "Used internally by the `combine-after-change-calls' macro.");
   Vcombine_after_change_calls = Qnil;
 
+  DEFVAR_BOOL ("inhibit-modification-hooks", &inhibit_modification_hooks,
+    "Non-nil means don't run any of the hooks that respond to buffer changes.\n\
+This affects `before-change-functions' and `after-change-functions',\n\
+as well as hooks attached to text properties and overlays.");
+  inhibit_modification_hooks = 0;
+
   defsubr (&Scombine_after_change_execute);
 }