Add support for large files, 64-bit Solaris, system locale codings.
[bpt/emacs.git] / src / insdel.c
index c8d4de6..6a4aa46 100644 (file)
@@ -1,5 +1,5 @@
 /* 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 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -33,6 +33,7 @@ Boston, MA 02111-1307, USA.  */
 #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 ();
@@ -77,7 +78,8 @@ static int check_markers_debug_flag;
 void
 check_markers ()
 {
-  register Lisp_Object tail, prev, next;
+  register Lisp_Object tail;
+  int multibyte = ! NILP (current_buffer->enable_multibyte_characters);
 
   tail = BUF_MARKERS (current_buffer);
 
@@ -89,6 +91,8 @@ check_markers ()
        abort ();
       if (XMARKER (tail)->bytepos > Z_BYTE)
        abort ();
+      if (multibyte && ! CHAR_HEAD_P (FETCH_BYTE (XMARKER (tail)->bytepos)))
+       abort ();
 
       tail = XMARKER (tail)->chain;
     }
@@ -132,21 +136,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;
@@ -221,19 +211,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;
@@ -478,8 +456,8 @@ 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;
+             m->bytepos = to_byte + combined_after_bytes;
+             m->charpos = to - combined_before_bytes;
              /* Point the marker before the combined character,
                 so that undoing the insertion puts it back where it was.  */
              if (combined_after_bytes)
@@ -493,11 +471,11 @@ adjust_markers_for_insert (from, from_byte, to, to_byte,
                 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);
+             m->bytepos += combined_before_bytes;
+             if (combined_before_bytes == nbytes)
+               /* All new bytes plus combined_after_bytes (if any)
+                  are combined.  */
+               m->bytepos += combined_after_bytes;
            }
        }
       /* If a marker was pointing into the combining bytes
@@ -508,7 +486,7 @@ adjust_markers_for_insert (from, from_byte, to, to_byte,
        {
          /* Put it after the combining bytes.  */
          m->bytepos = to_byte + combined_after_bytes;
-         m->charpos = to + 1;
+         m->charpos = to - combined_before_bytes;
          /* 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);
@@ -516,7 +494,7 @@ adjust_markers_for_insert (from, from_byte, to, to_byte,
       else if (m->bytepos > from_byte)
        {
          m->bytepos += nbytes;
-         m->charpos += nchars;
+         m->charpos += nchars - combined_after_bytes - combined_before_bytes;
        }
 
       marker = m->chain;
@@ -575,7 +553,12 @@ adjust_markers_for_replace (from, from_byte, old_chars, old_bytes,
     {
       register struct Lisp_Marker *m = XMARKER (marker);
 
-      if (m->bytepos >= prev_to_byte)
+      if (m->bytepos >= prev_to_byte
+         && (old_bytes != 0
+             /* If this is an insertion (replacing 0 chars),
+                reject the case of a marker that is at the
+                insertion point and should stay before the insertion.  */
+             || m->bytepos > from_byte || m->insertion_type))
        {
          if (m->bytepos < prev_to_byte + combined_after_bytes)
            {
@@ -697,7 +680,7 @@ copy_text (from_addr, to_addr, nbytes,
     {
       int nchars = 0;
       int bytes_left = nbytes;
-      Lisp_Object tbl = Qnil, temp;
+      Lisp_Object tbl = Qnil;
 
       /* We set the variable tbl to the reverse table of
          Vnonascii_translation_table in advance.  */
@@ -734,7 +717,9 @@ copy_text (from_addr, to_addr, nbytes,
          unsigned char workbuf[4], *str;
          int len;
 
-         if ((c >= 0240 || !NILP (Vnonascii_translation_table)) && c < 0400)
+         if (c < 0400
+             && (c >= 0240
+                 || (c >= 0200 && !NILP (Vnonascii_translation_table))))
            {
              c = unibyte_char_to_multibyte (c);
              len = CHAR_STRING (c, workbuf, str);
@@ -766,7 +751,7 @@ count_size_as_multibyte (ptr, nbytes)
     {
       unsigned int c = *ptr++;
 
-      if (c < 0240 && NILP (Vnonascii_translation_table))
+      if (c < 0200 || (c < 0240 && NILP (Vnonascii_translation_table)))
        outgoing_nbytes++;
       else
        {
@@ -891,6 +876,33 @@ insert_1 (string, nbytes, inherit, prepare, before_markers)
                 inherit, prepare, before_markers);
 }
 \f
+/* See if the byte sequence at STR1 of length LEN1 combine with the
+   byte sequence at STR2 of length LEN2 to form a single composite
+   character.  If so, return the number of bytes at the start of STR2
+   which combine in this way.  Otherwise, return 0.  If STR3 is not
+   NULL, it is a byte sequence of length LEN3 to be appended to STR1
+   before checking the combining.  */
+int
+count_combining_composition (str1, len1, str2, len2, str3, len3)
+     unsigned char *str1, *str2, *str3;
+     int len1, len2, len3;
+{
+  int len = len1 + len2 + len3;
+  unsigned char *buf = (unsigned char *) alloca (len + 1);
+  int bytes;
+
+  bcopy (str1, buf, len1);
+  if (str3)
+    {
+      bcopy (str3, buf + len1, len3);
+      len1 += len3;
+    }
+  bcopy (str2, buf + len1 , len2);
+  buf[len] = 0;
+  PARSE_MULTIBYTE_SEQ (buf, len, bytes);
+  return (bytes <= len1 ? 0 : bytes - len1);
+}
+
 /* 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
@@ -902,30 +914,44 @@ 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))
+
+  /* A sequence of a composite character requires a special handling.  */
+  if (*p == LEADING_CODE_COMPOSITION)
+    return count_combining_composition (p, len, string, length, NULL, 0);
+
+  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
@@ -939,14 +965,27 @@ 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]))
     {
@@ -954,33 +993,37 @@ count_combining_after (string, length, pos, pos_byte)
     }
   if (i < 0)
     {
-      /* 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.  */
-      string = BEG_ADDR;
+      /* 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 (string[i]))
+      while (i >= 0 && ! CHAR_HEAD_P (p[i]))
        i--;
-      if (i < 0 || !BASE_LEADING_CODE_P (string[i]))
+      if (i < 0 || !BASE_LEADING_CODE_P (p[i]))
        return 0;
+      /* A sequence of a composite character requires a special handling.  */
+      if (p[i] == LEADING_CODE_COMPOSITION)
+       return count_combining_composition (p + i, pos_byte - 1 - i,
+                                           bufp, Z_BYTE - pos_byte,
+                                           string, length);
+      bytes = BYTES_BY_CHAR_HEAD (p[i]);
+      return (bytes <= pos_byte - 1 - i + length
+             ? 0
+             : bytes - (pos_byte - 1 - i + length));
     }
-  else if (!BASE_LEADING_CODE_P (string[i]))
+  if (!BASE_LEADING_CODE_P (string[i]))
     return 0;
+  /* A sequence of a composite character requires a special handling.  */
+  if (string[i] == LEADING_CODE_COMPOSITION)
+    return count_combining_composition (string + i, length - i,
+                                       bufp, Z_BYTE - pos_byte, NULL, 0);
 
-  if (pos == ZV)
-    return 0;
-  c = FETCH_BYTE (pos_byte);
-  if (CHAR_HEAD_P (c))
-    return 0;
-  while (pos_byte < ZV_BYTE)
-    {
-      c = FETCH_BYTE (pos_byte);
-      if (CHAR_HEAD_P (c))
-       break;
-      pos_byte++;
-    }
+  bytes = BYTES_BY_CHAR_HEAD (string[i]) - (length - i);
+  bufp++, pos_byte++;
+  while (!CHAR_HEAD_P (*bufp)) bufp++, pos_byte++;
 
-  return pos_byte - opos_byte;
+  return (bytes <= pos_byte - opos_byte ? bytes : pos_byte - opos_byte);
 }
 
 /* Adjust the position TARGET/TARGET_BYTE for the combining of NBYTES
@@ -1008,8 +1051,7 @@ count_combining_after (string, length, pos, pos_byte)
 
    This function does not adjust markers for byte combining.  That
    should be done in advance by the functions
-   adjust_markers_for_insert, adjust_markers_for_delete, or
-   adjust_markers_for_replace.  */
+   adjust_markers_for_insert or adjust_markers_for_replace.  */
 
 static void
 combine_bytes (pos, pos_byte, nbytes)
@@ -1026,6 +1068,22 @@ combine_bytes (pos, pos_byte, nbytes)
     /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES.  */
     offset_intervals (current_buffer, pos, - nbytes);
 }
+
+void
+byte_combining_error ()
+{
+  error ("Byte combining across boundary of accessible buffer text inhibitted");
+}
+
+/* If we are going to combine bytes at POS which is at a narrowed
+   region boundary, signal an error.  */
+#define CHECK_BYTE_COMBINING_FOR_INSERT(pos)                           \
+  do {                                                                 \
+    if ((combined_before_bytes && pos == BEGV)                         \
+       || (combined_after_bytes && pos == ZV))                         \
+      byte_combining_error (); \
+  } while (0)
+
 \f
 /* Insert a sequence of NCHARS chars which occupy NBYTES bytes
    starting at STRING.  INHERIT, PREPARE and BEFORE_MARKERS
@@ -1037,7 +1095,6 @@ 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))
@@ -1058,6 +1115,7 @@ insert_1_both (string, nchars, nbytes, inherit, prepare, before_markers)
     = count_combining_before (string, nbytes, PT, PT_BYTE);
   combined_after_bytes
     = count_combining_after (string, nbytes, PT, PT_BYTE);
+  CHECK_BYTE_COMBINING_FOR_INSERT (PT);
 
   /* Record deletion of the surrounding text that combines with
      the insertion.  This, together with recording the insertion,
@@ -1149,6 +1207,8 @@ insert_1_both (string, nchars, nbytes, inherit, prepare, before_markers)
     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
@@ -1197,11 +1257,9 @@ 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
@@ -1222,7 +1280,7 @@ insert_from_string_1 (string, pos, pos_byte, nchars, nbytes,
 
   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;
 
@@ -1242,6 +1300,12 @@ insert_from_string_1 (string, pos, pos_byte, nchars, nbytes,
     = count_combining_before (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
   combined_after_bytes
     = count_combining_after (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
+  {
+    unsigned char save = *(GPT_ADDR);
+    *(GPT_ADDR) = 0;
+    CHECK_BYTE_COMBINING_FOR_INSERT (PT);
+    *(GPT_ADDR) = save;
+  }
 
   /* Record deletion of the surrounding text that combines with
      the insertion.  This, together with recording the insertion,
@@ -1361,13 +1425,12 @@ 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
@@ -1376,10 +1439,31 @@ 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))
@@ -1400,16 +1484,20 @@ 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));
 
@@ -1421,8 +1509,13 @@ insert_from_buffer_1 (buf, from, nchars, inherit)
   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);
+    = count_combining_after (GPT_ADDR, outgoing_nbytes, PT, PT_BYTE);
+  {
+    unsigned char save = *(GPT_ADDR);
+    *(GPT_ADDR) = 0;
+    CHECK_BYTE_COMBINING_FOR_INSERT (PT);
+    *(GPT_ADDR) = save;
+  }
 
   /* Record deletion of the surrounding text that combines with
      the insertion.  This, together with recording the insertion,
@@ -1556,8 +1649,29 @@ adjust_after_replace (from, from_byte, prev_text, len, len_byte)
     = count_combining_before (GPT_ADDR, len_byte, from, from_byte);
   int combined_after_bytes
     = count_combining_after (GPT_ADDR, len_byte, from, from_byte);
+  /* This flag tells if we combine some bytes with a character before
+     FROM.  This happens even if combined_before_bytes is zero.  */
+  int combine_before = (combined_before_bytes
+                       || (len == 0 && combined_after_bytes));
+
   int nchars_del = 0, nbytes_del = 0;
 
+  if (STRINGP (prev_text))
+    {
+      nchars_del = XSTRING (prev_text)->size;
+      nbytes_del = STRING_BYTES (XSTRING (prev_text));
+    }
+
+  if ((combine_before && from == BEGV)
+      || (combined_after_bytes && from == ZV))
+    {
+      /* We can't combine bytes nor signal an error here.  So, let's
+        pretend that the new text is just a single space.  */
+      len = len_byte = 1;
+      combined_before_bytes = combined_after_bytes = 0;
+      *(GPT_ADDR) = ' ';
+    }
+
   if (combined_after_bytes)
     {
       Lisp_Object deletion;
@@ -1574,10 +1688,11 @@ adjust_after_replace (from, from_byte, prev_text, len, len_byte)
                                        from_byte + combined_after_bytes);
 
       if (! EQ (current_buffer->undo_list, Qt))
-       record_delete (from + len, deletion);
+       record_delete (from + nchars_del, deletion);
     }
 
-  if (combined_before_bytes)
+  if (combined_before_bytes
+      || (len_byte == 0 && combined_after_bytes > 0))
     {
       Lisp_Object deletion;
       deletion = Qnil;
@@ -1598,22 +1713,23 @@ adjust_after_replace (from, from_byte, prev_text, len, len_byte)
   GPT += len; GPT_BYTE += len_byte;
   if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor. */
 
+  /* The gap should be at character boundary.  */
   if (combined_after_bytes)
     move_gap_both (GPT + combined_after_bytes,
                   GPT_BYTE + combined_after_bytes);
 
-  if (STRINGP (prev_text))
-    {
-      nchars_del = XSTRING (prev_text)->size;
-      nbytes_del = STRING_BYTES (XSTRING (prev_text));
-    }
   adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
                              len, len_byte,
                              combined_before_bytes, combined_after_bytes);
-  if (STRINGP (prev_text))
-    record_delete (from - !!combined_before_bytes, prev_text);
-  record_insert (from - !!combined_before_bytes,
-                len - combined_before_bytes + !!combined_before_bytes);
+  if (! EQ (current_buffer->undo_list, Qt))
+    {
+      if (nchars_del > 0)
+       record_delete (from - combine_before, prev_text);
+      if (combine_before)
+       record_insert (from - 1, len - combined_before_bytes + 1);
+      else
+       record_insert (from, len);
+    }
 
   if (len > nchars_del)
     adjust_overlays_for_insert (from, len - nchars_del);
@@ -1627,24 +1743,26 @@ adjust_after_replace (from, from_byte, prev_text, len, len_byte)
 #endif
 
   {
-    int pos = PT, pos_byte = PT_BYTE;
-
     if (from < PT)
       adjust_point (len - nchars_del, len_byte - nbytes_del);
 
     if (combined_after_bytes)
       {
-       if (combined_before_bytes)
+       if (combined_before_bytes == len_byte)
+         /* This is the case that all new bytes are combined.  */
          combined_before_bytes += combined_after_bytes;
        else
          combine_bytes (from + len, from_byte + len_byte,
                         combined_after_bytes);
       }
-
     if (combined_before_bytes)
       combine_bytes (from, from_byte, combined_before_bytes);
   }
 
+  /* 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)
@@ -1696,7 +1814,6 @@ replace_range (from, to, new, prepare, inherit, markers)
   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;
@@ -1751,10 +1868,10 @@ replace_range (from, to, new, prepare, inherit, markers)
   if (to < GPT)
     gap_left (to, to_byte, 0);
 
-  deletion = Qnil;
-
-  if (! EQ (current_buffer->undo_list, Qt))
-    deletion = make_buffer_string_both (from, from_byte, to, to_byte, 1);
+  /* 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.  */
+  deletion = make_buffer_string_both (from, from_byte, to, to_byte, 1);
 
   if (markers)
     /* Relocate all markers pointing into the new, larger gap
@@ -1775,10 +1892,10 @@ replace_range (from, to, new, prepare, inherit, markers)
   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);
@@ -1800,6 +1917,28 @@ replace_range (from, to, new, prepare, inherit, markers)
   combined_after_bytes
     = count_combining_after (GPT_ADDR, outgoing_insbytes, from, from_byte);
 
+  if ((combined_before_bytes && from == BEGV)
+      || (combined_after_bytes && from == ZV))
+    {
+      /* Bytes are being combined across the region boundary.  We
+         should avoid it.  We recover the original contents before
+         signaling an error.  */
+      bcopy (XSTRING (deletion)->data, GPT_ADDR, nbytes_del);
+      GAP_SIZE -= nbytes_del;
+      ZV += nchars_del;
+      Z += nchars_del;
+      ZV_BYTE += nbytes_del;
+      Z_BYTE += nbytes_del;
+      GPT = from + nchars_del;
+      GPT_BYTE = from_byte + nbytes_del;
+      *(GPT_ADDR) = 0;         /* Put an anchor.  */
+      if (markers)
+       adjust_markers_for_insert (from, from_byte, to, to_byte, 0, 0, 0);
+      UNGCPRO;
+      byte_combining_error ();
+      GCPRO1 (new);
+    }
+
   /* 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.
@@ -1815,13 +1954,14 @@ replace_range (from, to, new, prepare, inherit, markers)
       if (! EQ (current_buffer->undo_list, Qt))
        deletion = make_buffer_string_both (from, from_byte,
                                            from + combined_after_bytes,
-                                           from_byte + combined_after_bytes, 1);
+                                           from_byte + combined_after_bytes,
+                                           1);
 
       adjust_markers_for_record_delete (from, from_byte,
                                        from + combined_after_bytes,
                                        from_byte + combined_after_bytes);
       if (! EQ (current_buffer->undo_list, Qt))
-       record_delete (from + inschars, deletion);
+       record_delete (from + nchars_del, deletion);
     }
 
   if (combined_before_bytes)
@@ -1890,7 +2030,8 @@ replace_range (from, to, new, prepare, inherit, markers)
 
   if (combined_after_bytes)
     {
-      if (combined_before_bytes)
+      if (combined_before_bytes == outgoing_insbytes)
+       /* This is the case that all new bytes are combined.  */
        combined_before_bytes += combined_after_bytes;
       else
        combine_bytes (from + inschars, from_byte + outgoing_insbytes,
@@ -1899,6 +2040,10 @@ replace_range (from, to, new, prepare, inherit, markers)
   if (combined_before_bytes)
     combine_bytes (from, from_byte, combined_before_bytes);
 
+  /* As byte combining will decrease Z, we must check this again. */
+  if (Z - GPT < END_UNCHANGED)
+    END_UNCHANGED = Z - GPT;
+
   if (outgoing_insbytes == 0)
     evaporate_overlays (from);
 
@@ -1949,6 +2094,7 @@ del_range_1 (from, to, prepare)
   to_byte = CHAR_TO_BYTE (to);
 
   del_range_2 (from, from_byte, to, to_byte);
+  signal_after_change (from, to - from, 0);
 }
 
 /* Like del_range_1 but args are byte positions, not char positions.  */
@@ -1985,6 +2131,7 @@ del_range_byte (from_byte, to_byte, prepare)
     }
 
   del_range_2 (from, from_byte, to, to_byte);
+  signal_after_change (from, to - from, 0);
 }
 
 /* Like del_range_1, but positions are specified both as charpos
@@ -2022,6 +2169,7 @@ del_range_both (from, from_byte, to, to_byte, prepare)
     }
 
   del_range_2 (from, from_byte, to, to_byte);
+  signal_after_change (from, to - from, 0);
 }
 
 /* Delete a range of text, specified both as character positions
@@ -2050,9 +2198,11 @@ del_range_2 (from, from_byte, to, to_byte)
 
   combined_after_bytes
     = count_combining_before (BUF_BYTE_ADDRESS (current_buffer, to_byte),
-                             ZV_BYTE - to_byte, from, from_byte);
+                             Z_BYTE - to_byte, from, from_byte);
   if (combined_after_bytes)
     {
+      if (from == BEGV || to == ZV)
+       byte_combining_error ();
       from_byte_1 = from_byte;
       DEC_POS (from_byte_1);
     }
@@ -2124,22 +2274,29 @@ del_range_2 (from, from_byte, to, to_byte)
   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)
     {
+      /* Adjust markers for byte combining.  As we have already
+         adjuted markers without concerning byte combining, here we
+         must concern only byte combining.  */
+      adjust_markers_for_replace (from, from_byte, 0, 0, 0, 0,
+                                 0, combined_after_bytes);
       combine_bytes (from, from_byte, combined_after_bytes);
 
       record_insert (GPT - 1, 1);
+
+      if (Z - GPT < END_UNCHANGED)
+       END_UNCHANGED = Z - GPT;
     }
 
   CHECK_MARKERS ();
 
   evaporate_overlays (from);
-  signal_after_change (from, nchars_del, 0);
 }
 \f
 /* Call this if you're about to change the region of BUFFER from
@@ -2160,14 +2317,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 ();
@@ -2197,6 +2347,11 @@ prepare_to_modify_buffer (start, end, preserve_ptr)
   if (!NILP (current_buffer->read_only))
     Fbarf_if_buffer_read_only ();
 
+  /* Let redisplay consider other windows than selected_window
+     if modifying another buffer.  */
+  if (XBUFFER (XWINDOW (selected_window)->buffer) != current_buffer)
+    ++windows_or_buffers_changed;
+
   /* Only defined if Emacs is compiled with USE_TEXT_PROPERTIES */
   if (BUF_INTERVALS (current_buffer) != 0)
     {
@@ -2473,12 +2628,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);
@@ -2493,26 +2650,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;
@@ -2536,7 +2693,7 @@ DEFUN ("combine-after-change-execute", Fcombine_after_change_execute,
                         Vcombine_after_change_calls);
   signal_after_change (begpos, endpos - begpos - change, endpos - begpos);
 
-  return unbind_to (count, val);
+  return unbind_to (count, Qnil);
 }
 \f
 void
@@ -2544,6 +2701,7 @@ 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.");
@@ -2552,5 +2710,11 @@ syms_of_insdel ()
     "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);
 }