(string_width): New function.
[bpt/emacs.git] / src / indent.c
index 6ce752b..fa757f9 100644 (file)
@@ -1,5 +1,5 @@
 /* Indentation functions.
-   Copyright (C) 1985,86,87,88,93,94 Free Software Foundation, Inc.
+   Copyright (C) 1985,86,87,88,93,94,95 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -28,6 +28,7 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include "termopts.h"
 #include "disptab.h"
 #include "intervals.h"
+#include "region-cache.h"
 
 /* Indentation can insert tabs if this is non-zero;
    otherwise always uses spaces */
@@ -56,15 +57,117 @@ buffer_display_table ()
   Lisp_Object thisbuf;
 
   thisbuf = current_buffer->display_table;
-  if (XTYPE (thisbuf) == Lisp_Vector
-      && XVECTOR (thisbuf)->size == DISP_TABLE_SIZE)
+  if (VECTORP (thisbuf) && XVECTOR (thisbuf)->size == DISP_TABLE_SIZE)
     return XVECTOR (thisbuf);
-  if (XTYPE (Vstandard_display_table) == Lisp_Vector
+  if (VECTORP (Vstandard_display_table)
       && XVECTOR (Vstandard_display_table)->size == DISP_TABLE_SIZE)
     return XVECTOR (Vstandard_display_table);
   return 0;
 }
 \f
+/* Width run cache considerations.  */
+
+/* Return the width of character C under display table DP.  */
+
+static int
+character_width (c, dp)
+     int c;
+     struct Lisp_Vector *dp;
+{
+  Lisp_Object elt;
+
+  /* These width computations were determined by examining the cases
+     in display_text_line.  */
+
+  /* Everything can be handled by the display table, if it's
+     present and the element is right.  */
+  if (dp && (elt = DISP_CHAR_VECTOR (dp, c), VECTORP (elt)))
+    return XVECTOR (elt)->size;
+
+  /* Some characters are special.  */
+  if (c == '\n' || c == '\t' || c == '\015')
+    return 0;
+
+  /* Printing characters have width 1.  */
+  else if (c >= 040 && c < 0177)
+    return 1;
+
+  /* Everybody else (control characters, metacharacters) has other
+     widths.  We could return their actual widths here, but they
+     depend on things like ctl_arrow and crud like that, and they're
+     not very common at all.  So we'll just claim we don't know their
+     widths.  */
+  else
+    return 0;
+}
+
+/* Return true iff the display table DISPTAB specifies the same widths
+   for characters as WIDTHTAB.  We use this to decide when to
+   invalidate the buffer's width_run_cache.  */
+int
+disptab_matches_widthtab (disptab, widthtab)
+     struct Lisp_Vector *disptab;
+     struct Lisp_Vector *widthtab;
+{
+  int i;
+
+  if (widthtab->size != 256)
+    abort ();
+
+  for (i = 0; i < 256; i++)
+    if (character_width (i, disptab)
+        != XFASTINT (widthtab->contents[i]))
+      return 0;
+
+  return 1;
+}
+
+/* Recompute BUF's width table, using the display table DISPTAB.  */
+void
+recompute_width_table (buf, disptab)
+     struct buffer *buf;
+     struct Lisp_Vector *disptab;
+{
+  int i;
+  struct Lisp_Vector *widthtab;
+
+  if (!VECTORP (buf->width_table))
+    buf->width_table = Fmake_vector (make_number (256), make_number (0));
+  widthtab = XVECTOR (buf->width_table);
+  if (widthtab->size != 256)
+    abort ();
+
+  for (i = 0; i < 256; i++)
+    XSETFASTINT (widthtab->contents[i], character_width (i, disptab));
+}
+
+/* Allocate or free the width run cache, as requested by the current
+   state of current_buffer's cache_long_line_scans variable.  */
+static void
+width_run_cache_on_off ()
+{
+  if (NILP (current_buffer->cache_long_line_scans))
+    {
+      /* It should be off.  */
+      if (current_buffer->width_run_cache)
+        {
+          free_region_cache (current_buffer->width_run_cache);
+          current_buffer->width_run_cache = 0;
+          current_buffer->width_table = Qnil;
+        }
+    }
+  else
+    {
+      /* It should be on.  */
+      if (current_buffer->width_run_cache == 0)
+        {
+          current_buffer->width_run_cache = new_region_cache ();
+          recompute_width_table (current_buffer, buffer_display_table ());
+        }
+    }
+}
+
+\f
 DEFUN ("current-column", Fcurrent_column, Scurrent_column, 0, 0, 0,
   "Return the horizontal position of point.  Beginning of line is column 0.\n\
 This is calculated by adding together the widths of all the displayed\n\
@@ -78,7 +181,7 @@ however, ^M is treated as end of line when `selective-display' is t.")
   ()
 {
   Lisp_Object temp;
-  XFASTINT (temp) = current_column ();
+  XSETFASTINT (temp, current_column ());
   return temp;
 }
 
@@ -137,11 +240,10 @@ current_column ()
        }
 
       c = *--ptr;
-      if (c >= 040 && c < 0177
-         && (dp == 0 || XTYPE (DISP_CHAR_VECTOR (dp, c)) != Lisp_Vector))
-       {
-         col++;
-       }
+      if (dp != 0 && VECTORP (DISP_CHAR_VECTOR (dp, c)))
+       col += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
+      else if (c >= 040 && c < 0177)
+       col++;
       else if (c == '\n')
        break;
       else if (c == '\r' && EQ (current_buffer->selective_display, Qt))
@@ -155,8 +257,6 @@ current_column ()
          col = 0;
          tab_seen = 1;
        }
-      else if (dp != 0 && XTYPE (DISP_CHAR_VECTOR (dp, c)) == Lisp_Vector)
-       col += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
       else
        col += (ctl_arrow && c < 0200) ? 2 : 4;
     }
@@ -174,7 +274,84 @@ current_column ()
   return col;
 }
 \f
+/* Return the width in columns of the part of STRING from BEG to END.
+   If BEG is nil, that stands for the beginning of STRING.
+   If END is nil, that stands for the end of STRING.  */
+
+static int
+string_width (string, beg, end)
+     Lisp_Object string, beg, end;
+{
+  register int col;
+  register unsigned char *ptr, *stop;
+  register int tab_seen;
+  int post_tab;
+  register int c;
+  register int tab_width = XINT (current_buffer->tab_width);
+  int ctl_arrow = !NILP (current_buffer->ctl_arrow);
+  register struct Lisp_Vector *dp = buffer_display_table ();
+  int b, e;
+
+  if (NILP (end))
+    e = XSTRING (string)->size;
+  else
+    {
+      CHECK_NUMBER (end, 0);
+      e = XINT (end);
+    }
+
+  if (NILP (beg))
+    b = 0;
+  else
+    {
+      CHECK_NUMBER (beg, 0);
+      b = XINT (beg);
+    }
+
+  /* Make a pointer for decrementing through the chars before point.  */
+  ptr = XSTRING (string)->data + e;
+  /* Make a pointer to where consecutive chars leave off,
+     going backwards from point.  */
+  stop = XSTRING (string)->data + b;
+
+  if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
+
+  col = 0, tab_seen = 0, post_tab = 0;
+
+  while (1)
+    {
+      if (ptr == stop)
+       break;
+
+      c = *--ptr;
+      if (dp != 0 && VECTORP (DISP_CHAR_VECTOR (dp, c)))
+       col += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
+      else if (c >= 040 && c < 0177)
+       col++;
+      else if (c == '\n')
+       break;
+      else if (c == '\t')
+       {
+         if (tab_seen)
+           col = ((col + tab_width) / tab_width) * tab_width;
 
+         post_tab += col;
+         col = 0;
+         tab_seen = 1;
+       }
+      else
+       col += (ctl_arrow && c < 0200) ? 2 : 4;
+    }
+
+  if (tab_seen)
+    {
+      col = ((col + tab_width) / tab_width) * tab_width;
+      col += post_tab;
+    }
+
+  return col;
+}
+\f
 DEFUN ("indent-to", Findent_to, Sindent_to, 1, 2, "NIndent to column: ",
   "Indent from point with tabs and spaces until COLUMN is reached.\n\
 Optional second argument MIN says always do at least MIN spaces\n\
@@ -188,7 +365,7 @@ even if that goes past COLUMN; by default, MIN is zero.")
 
   CHECK_NUMBER (col, 0);
   if (NILP (minimum))
-    XFASTINT (minimum) = 0;
+    XSETFASTINT (minimum, 0);
   CHECK_NUMBER (minimum, 1);
 
   fromcol = current_column ();
@@ -203,17 +380,17 @@ even if that goes past COLUMN; by default, MIN is zero.")
   if (indent_tabs_mode)
     {
       Lisp_Object n;
-      XFASTINT (n) = mincol / tab_width - fromcol / tab_width;
+      XSETFASTINT (n, mincol / tab_width - fromcol / tab_width);
       if (XFASTINT (n) != 0)
        {
-         Finsert_char (make_number ('\t'), n);
+         Finsert_char (make_number ('\t'), n, Qt);
 
          fromcol = (mincol / tab_width) * tab_width;
        }
     }
 
-  XFASTINT (col) = mincol - fromcol;
-  Finsert_char (make_number (' '), col);
+  XSETFASTINT (col, mincol - fromcol);
+  Finsert_char (make_number (' '), col, Qt);
 
   last_known_column = mincol;
   last_known_column_point = point;
@@ -222,6 +399,7 @@ even if that goes past COLUMN; by default, MIN is zero.")
   XSETINT (col, mincol);
   return col;
 }
+
 \f
 DEFUN ("current-indentation", Fcurrent_indentation, Scurrent_indentation,
   0, 0, 0,
@@ -232,7 +410,7 @@ following any initial whitespace.")
 {
   Lisp_Object val;
 
-  XFASTINT (val) = position_indentation (find_next_newline (point, -1));
+  XSETFASTINT (val, position_indentation (find_next_newline (point, -1)));
   return val;
 }
 
@@ -243,9 +421,9 @@ position_indentation (pos)
   register int tab_width = XINT (current_buffer->tab_width);
   register unsigned char *p;
   register unsigned char *stop;
-  
+
   if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
-  
+
   stop = &FETCH_CHAR (BUFFER_CEILING_OF (pos)) + 1;
   p = &FETCH_CHAR (pos);
   while (1)
@@ -280,9 +458,10 @@ indented_beyond_p (pos, column)
      int pos, column;
 {
   while (pos > BEGV && FETCH_CHAR (pos) == '\n')
-    pos = find_next_newline (pos - 1, -1);
+    pos = find_next_newline_no_quit (pos - 1, -1);
   return (position_indentation (pos) >= column);
 }
+
 \f
 DEFUN ("move-to-column", Fmove_to_column, Smove_to_column, 1, 2, 0,
   "Move point to column COLUMN in the current line.\n\
@@ -331,6 +510,12 @@ and if COLUMN is in the middle of a tab character, change it to spaces.")
   while (col < goal && pos < end)
     {
       c = FETCH_CHAR (pos);
+      if (dp != 0 && VECTORP (DISP_CHAR_VECTOR (dp, c)))
+       {
+         col += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
+         pos++;
+         break;
+       }
       if (c == '\n')
        break;
       if (c == '\r' && EQ (current_buffer->selective_display, Qt))
@@ -342,8 +527,6 @@ and if COLUMN is in the middle of a tab character, change it to spaces.")
          col += tab_width;
          col = col / tab_width * tab_width;
        }
-      else if (dp != 0 && XTYPE (DISP_CHAR_VECTOR (dp, c)) == Lisp_Vector)
-       col += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
       else if (ctl_arrow && (c < 040 || c == 0177))
         col += 2;
       else if (c < 040 || c >= 0177)
@@ -377,16 +560,21 @@ and if COLUMN is in the middle of a tab character, change it to spaces.")
   last_known_column_point = point;
   last_known_column_modified = MODIFF;
 
-  XFASTINT (val) = col;
+  XSETFASTINT (val, col);
   return val;
 }
+
 \f
+/* compute_motion: compute buffer posn given screen posn and vice versa */
+
 struct position val_compute_motion;
 
 /* Scan the current buffer forward from offset FROM, pretending that
    this is at line FROMVPOS, column FROMHPOS, until reaching buffer
    offset TO or line TOVPOS, column TOHPOS (whichever comes first),
-   and return the ending buffer position and screen location.
+   and return the ending buffer position and screen location.  If we
+   can't hit the requested column exactly (because of a tab or other
+   multi-column character), overshoot.
 
    WIDTH is the number of columns available to display text;
    compute_motion uses this to handle continuation lines and such.
@@ -398,8 +586,14 @@ struct position val_compute_motion;
 
    compute_motion returns a pointer to a struct position.  The bufpos
    member gives the buffer position at the end of the scan, and hpos
-   and vpos give its cartesian location.  I'm not clear on what the
-   other members are.
+   and vpos give its cartesian location.  prevhpos is the column at
+   which the character before bufpos started, and contin is non-zero
+   if we reached the current line by continuing the previous.
+
+   Note that FROMHPOS and TOHPOS should be expressed in real screen
+   columns, taking HSCROLL and the truncation glyph at the left margin
+   into account.  That is, beginning-of-line moves you to the hpos
+   -HSCROLL + (HSCROLL > 0).
 
    Note that FROMHPOS and TOHPOS should be expressed in real screen
    columns, taking HSCROLL and the truncation glyph at the left margin
@@ -411,13 +605,13 @@ struct position val_compute_motion;
    and the window's upper-left coordinates as FROMVPOS and FROMHPOS.
    Pass the buffer's ZV as TO, to limit the scan to the end of the
    visible section of the buffer, and pass LINE and COL as TOVPOS and
-   TOHPOS.  
+   TOHPOS.
 
    When displaying in window w, a typical formula for WIDTH is:
 
        window_width - 1
         - (has_vertical_scroll_bars
-           ? VERTICAL_SCROLL_BAR_WIDTH
+           ? FRAME_SCROLL_BAR_COLS (XFRAME (window->frame))
            : (window_width + window_left != frame_width))
 
        where
@@ -450,12 +644,12 @@ compute_motion (from, fromvpos, fromhpos, to, tovpos, tohpos, width, hscroll, ta
   register int ctl_arrow = !NILP (current_buffer->ctl_arrow);
   register struct Lisp_Vector *dp = window_display_table (win);
   int selective
-    = (XTYPE (current_buffer->selective_display) == Lisp_Int
+    = (INTEGERP (current_buffer->selective_display)
        ? XINT (current_buffer->selective_display)
        : !NILP (current_buffer->selective_display) ? -1 : 0);
-  int prev_vpos, prev_hpos = 0;
+  int prev_vpos = vpos, prev_hpos = 0;
   int selective_rlen
-    = (selective && dp && XTYPE (DISP_INVIS_VECTOR (dp)) == Lisp_Vector
+    = (selective && dp && VECTORP (DISP_INVIS_VECTOR (dp))
        ? XVECTOR (DISP_INVIS_VECTOR (dp))->size : 0);
 #ifdef USE_TEXT_PROPERTIES
   /* The next location where the `invisible' property changes */
@@ -463,8 +657,33 @@ compute_motion (from, fromvpos, fromhpos, to, tovpos, tohpos, width, hscroll, ta
   Lisp_Object prop, position;
 #endif
 
+  /* For computing runs of characters with similar widths.
+     Invariant: width_run_width is zero, or all the characters
+     from width_run_start to width_run_end have a fixed width of
+     width_run_width.  */
+  int width_run_start = from;
+  int width_run_end   = from;
+  int width_run_width = 0;
+  Lisp_Object *width_table;
+  Lisp_Object buffer;
+
+  /* The next buffer pos where we should consult the width run cache. */
+  int next_width_run = from;
+
+  XSETBUFFER (buffer, current_buffer);
+
+  width_run_cache_on_off ();
+  if (dp == buffer_display_table ())
+    width_table = (VECTORP (current_buffer->width_table)
+                   ? XVECTOR (current_buffer->width_table)->contents
+                   : 0);
+  else
+    /* If the window has its own display table, we can't use the width
+       run cache, because that's based on the buffer's display table.  */
+    width_table = 0;
+
   if (tab_width <= 0 || tab_width > 1000) tab_width = 8;
-  for (pos = from; pos < to; pos++)
+  for (pos = from; pos < to; )
     {
       /* Stop if past the target screen position.  */
       if (vpos > tovpos
@@ -479,111 +698,216 @@ compute_motion (from, fromvpos, fromhpos, to, tovpos, tohpos, width, hscroll, ta
         the next property change */
       while (pos == next_invisible && pos < to)
        {
-         XFASTINT (position) = pos;
+         XSETFASTINT (position, pos);
+
+         /* Give faster response for overlay lookup near POS.  */
+         recenter_overlay_lists (current_buffer, pos);
+
          prop = Fget_char_property (position,
                                     Qinvisible,
                                     Fcurrent_buffer ());
          {
-           Lisp_Object end, limit;
+           Lisp_Object end, limit, proplimit;
 
-           /* This is just an estimate to give reasonable
-              performance; nothing should go wrong if it is too small.  */
+           /* We must not advance farther than the next overlay change.
+              The overlay change might change the invisible property;
+              we have no way of telling.  */
            limit = Fnext_overlay_change (position);
-           if (XFASTINT (limit) > pos + 100)
-             XFASTINT (limit) = pos + 100;
-           end = Fnext_single_property_change (position, Qinvisible,
-                                               Fcurrent_buffer (), limit);
-           if (INTEGERP (end))
-             next_invisible = XINT (end);
+           /* As for text properties, this gives a lower bound
+              for where the invisible text property could change.  */
+           proplimit = Fnext_property_change (position, buffer, Qt);
+           if (XFASTINT (limit) < XFASTINT (proplimit))
+             proplimit = limit;
+           /* PROPLIMIT is now a lower bound for the next change
+              in invisible status.  If that is plenty far away,
+              use that lower bound.  */
+           if (XFASTINT (proplimit) > pos + 100 || XFASTINT (proplimit) >= to)
+             next_invisible = XINT (proplimit);
+           /* Otherwise, scan for the next `invisible' property change.  */
            else
-             next_invisible = to;
-           if (! NILP (prop))
+             {
+               /* Don't scan terribly far.  */
+               XSETFASTINT (proplimit, min (pos + 100, to));
+               /* No matter what. don't go past next overlay change.  */
+               if (XFASTINT (limit) < XFASTINT (proplimit))
+                 proplimit = limit;
+               end = Fnext_single_property_change (position, Qinvisible,
+                                                   buffer, proplimit);
+               if (INTEGERP (end) && XINT (end) < to)
+                 next_invisible = XINT (end);
+               else
+                 next_invisible = to;
+             }
+           if (TEXT_PROP_MEANS_INVISIBLE (prop))
              pos = next_invisible;
          }
        }
       if (pos >= to)
        break;
 #endif
-      c = FETCH_CHAR (pos);
-      if (c >= 040 && c < 0177
-         && (dp == 0 || XTYPE (DISP_CHAR_VECTOR (dp, c)) != Lisp_Vector))
-       hpos++;
-      else if (c == '\t')
-       {
-         hpos += tab_width - ((hpos + tab_offset + hscroll - (hscroll > 0)
-                               /* Add tab_width here to make sure positive.
-                                  hpos can be negative after continuation
-                                  but can't be less than -tab_width.  */
-                               + tab_width)
-                              % tab_width);
-       }
-      else if (c == '\n')
-       {
-         if (selective > 0 && indented_beyond_p (pos + 1, selective))
-           {
-             /* Skip any number of invisible lines all at once */
-             do
-               {
-                 while (++pos < to && FETCH_CHAR (pos) != '\n');
-               }
-             while (pos < to && indented_beyond_p (pos + 1, selective));
-             pos--;            /* Reread the newline on the next pass.  */
-             /* Allow for the " ..." that is displayed for them. */
-             if (selective_rlen)
-               {
-                 hpos += selective_rlen;
-                 if (hpos >= width)
-                   hpos = width;
-               }
-             /* We have skipped the invis text, but not the newline after.  */
-           }
-         else
-           {
-             /* A visible line.  */
-             vpos++;
-             hpos = 0;
-             hpos -= hscroll;
-             if (hscroll > 0) hpos++; /* Truncation glyph on column 0 */
-             tab_offset = 0;
-           }
-       }
-      else if (c == CR && selective < 0)
-       {
-         /* In selective display mode,
-            everything from a ^M to the end of the line is invisible */
-         while (pos < to && FETCH_CHAR (pos) != '\n') pos++;
-         /* Stop *before* the real newline.  */
-         pos--;
-         /* Allow for the " ..." that is displayed for them. */
-         if (selective_rlen)
-           {
-             hpos += selective_rlen;
-             if (hpos >= width)
-               hpos = width;
-           }
-       }
-      else if (dp != 0 && XTYPE (DISP_CHAR_VECTOR (dp, c)) == Lisp_Vector)
-       hpos += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
+
+      /* Consult the width run cache to see if we can avoid inspecting
+         the text character-by-character.  */
+      if (current_buffer->width_run_cache && pos >= next_width_run)
+        {
+          int run_end;
+          int common_width
+            = region_cache_forward (current_buffer,
+                                    current_buffer->width_run_cache,
+                                    pos, &run_end);
+
+          /* A width of zero means the character's width varies (like
+             a tab), is meaningless (like a newline), or we just don't
+             want to skip over it for some other reason.  */
+          if (common_width != 0)
+            {
+              int run_end_hpos;
+
+              /* Don't go past the final buffer posn the user
+                 requested.  */
+              if (run_end > to)
+                run_end = to;
+
+              run_end_hpos = hpos + (run_end - pos) * common_width;
+
+              /* Don't go past the final horizontal position the user
+                 requested.  */
+              if (vpos == tovpos && run_end_hpos > tohpos)
+                {
+                  run_end      = pos + (tohpos - hpos) / common_width;
+                  run_end_hpos = hpos + (run_end - pos) * common_width;
+                }
+
+              /* Don't go past the margin.  */
+              if (run_end_hpos >= width)
+                {
+                  run_end      = pos + (width  - hpos) / common_width;
+                  run_end_hpos = hpos + (run_end - pos) * common_width;
+                }
+
+              hpos = run_end_hpos;
+              if (run_end > pos)
+                prev_hpos = hpos - common_width;
+              pos = run_end;
+            }
+
+          next_width_run = run_end + 1;
+        }
+
+      /* We have to scan the text character-by-character.  */
       else
-       hpos += (ctl_arrow && c < 0200) ? 2 : 4;
+        {
+          c = FETCH_CHAR (pos);
+          pos++;
+
+          /* Perhaps add some info to the width_run_cache.  */
+          if (current_buffer->width_run_cache)
+            {
+              /* Is this character part of the current run?  If so, extend
+                 the run.  */
+              if (pos - 1 == width_run_end
+                  && width_table[c] == width_run_width)
+                width_run_end = pos;
+
+              /* The previous run is over, since this is a character at a
+                 different position, or a different width.  */
+              else
+                {
+                  /* Have we accumulated a run to put in the cache?
+                     (Currently, we only cache runs of width == 1).  */
+                  if (width_run_start < width_run_end
+                      && width_run_width == 1)
+                    know_region_cache (current_buffer,
+                                       current_buffer->width_run_cache,
+                                       width_run_start, width_run_end);
+
+                  /* Start recording a new width run.  */
+                  width_run_width = width_table[c];
+                  width_run_start = pos - 1;
+                  width_run_end = pos;
+                }
+            }
+
+          if (dp != 0 && VECTORP (DISP_CHAR_VECTOR (dp, c)))
+            hpos += XVECTOR (DISP_CHAR_VECTOR (dp, c))->size;
+          else if (c >= 040 && c < 0177)
+            hpos++;
+          else if (c == '\t')
+            {
+              hpos += tab_width - ((hpos + tab_offset + hscroll - (hscroll > 0)
+                                    /* Add tab_width here to make sure
+                                       positive.  hpos can be negative
+                                       after continuation but can't be
+                                       less than -tab_width.  */
+                                    + tab_width)
+                                   % tab_width);
+            }
+          else if (c == '\n')
+            {
+              if (selective > 0 && indented_beyond_p (pos, selective))
+                {
+                  /* Skip any number of invisible lines all at once */
+                  do
+                    pos = find_before_next_newline (pos, to, 1) + 1;
+                  while (pos < to
+                         && indented_beyond_p (pos, selective));
+                  /* Allow for the " ..." that is displayed for them. */
+                  if (selective_rlen)
+                    {
+                      hpos += selective_rlen;
+                      if (hpos >= width)
+                        hpos = width;
+                    }
+                 --pos;
+                  /* We have skipped the invis text, but not the
+                     newline after.  */
+                }
+              else
+                {
+                  /* A visible line.  */
+                  vpos++;
+                  hpos = 0;
+                  hpos -= hscroll;
+                  /* Count the truncation glyph on column 0 */
+                  if (hscroll > 0)
+                    hpos++;
+                  tab_offset = 0;
+                }
+            }
+          else if (c == CR && selective < 0)
+            {
+              /* In selective display mode,
+                 everything from a ^M to the end of the line is invisible.
+                 Stop *before* the real newline.  */
+              pos = find_before_next_newline (pos, to, 1);
+              /* Allow for the " ..." that is displayed for them. */
+              if (selective_rlen)
+                {
+                  hpos += selective_rlen;
+                  if (hpos >= width)
+                    hpos = width;
+                }
+            }
+          else
+            hpos += (ctl_arrow && c < 0200) ? 2 : 4;
+        }
 
       /* Handle right margin.  */
       if (hpos >= width
          && (hpos > width
-             || (pos < ZV - 1
-                 && FETCH_CHAR (pos + 1) != '\n')))
+             || (pos < ZV
+                 && FETCH_CHAR (pos) != '\n')))
        {
          if (vpos > tovpos
              || (vpos == tovpos && hpos >= tohpos))
            break;
          if (hscroll
              || (truncate_partial_width_windows
-                 && width + 1 < FRAME_WIDTH (selected_frame))
+                 && width + 1 < FRAME_WIDTH (XFRAME (WINDOW_FRAME (win))))
              || !NILP (current_buffer->truncate_lines))
            {
              /* Truncating: skip to newline.  */
-             while (pos < to && FETCH_CHAR (pos) != '\n') pos++;
-             pos--;
+              pos = find_before_next_newline (pos, to, 1);
              hpos = width;
            }
          else
@@ -597,6 +921,13 @@ compute_motion (from, fromvpos, fromhpos, to, tovpos, tohpos, width, hscroll, ta
        }
     }
 
+  /* Remember any final width run in the cache.  */
+  if (current_buffer->width_run_cache
+      && width_run_width == 1
+      && width_run_start < width_run_end)
+    know_region_cache (current_buffer, current_buffer->width_run_cache,
+                       width_run_start, width_run_end);
+
   val_compute_motion.bufpos = pos;
   val_compute_motion.hpos = hpos;
   val_compute_motion.vpos = vpos;
@@ -697,10 +1028,10 @@ DEFUN ("compute-motion", Fcompute_motion, Scompute_motion, 7, 7, 0,
                        XINT (width), hscroll, tab_offset,
                        XWINDOW (window));
 
-  XFASTINT (bufpos) = pos->bufpos;
-  XSET (hpos, Lisp_Int, pos->hpos);
-  XSET (vpos, Lisp_Int, pos->vpos);
-  XSET (prevhpos, Lisp_Int, pos->prevhpos);
+  XSETFASTINT (bufpos, pos->bufpos);
+  XSETINT (hpos, pos->hpos);
+  XSETINT (vpos, pos->vpos);
+  XSETINT (prevhpos, pos->prevhpos);
 
   return Fcons (bufpos,
                Fcons (hpos,
@@ -710,8 +1041,8 @@ DEFUN ("compute-motion", Fcompute_motion, Scompute_motion, 7, 7, 0,
 
 }
 \f
-/* Return the column of position POS in window W's buffer,
-   rounded down to a multiple of the internal width of W.
+/* Return the column of position POS in window W's buffer.
+   The result is rounded down to a multiple of the internal width of W.
    This is the amount of indentation of position POS
    that is not visible in its horizontal position in the window.  */
 
@@ -720,22 +1051,20 @@ pos_tab_offset (w, pos)
      struct window *w;
      register int pos;
 {
-  int opoint = point;
+  int opoint = PT;
   int col;
   int width = window_internal_width (w) - 1;
 
   if (pos == BEGV || FETCH_CHAR (pos - 1) == '\n')
     return 0;
-  SET_PT (pos);
+  TEMP_SET_PT (pos);
   col = current_column ();
-  SET_PT (opoint);
+  TEMP_SET_PT (opoint);
   return col - (col % width);
 }
 
-/* start_hpos is the hpos of the first character of the buffer:
-   zero except for the minibuffer window,
-   where it is the width of the prompt.  */
-
+\f
+/* Fvertical_motion and vmotion */
 struct position val_vmotion;
 
 struct position *
@@ -747,40 +1076,54 @@ vmotion (from, vtarget, width, hscroll, window)
   struct position pos;
   /* vpos is cumulative vertical position, changed as from is changed */
   register int vpos = 0;
-  register int prevline;
+  Lisp_Object prevline;
   register int first;
   int lmargin = hscroll > 0 ? 1 - hscroll : 0;
   int selective
-    = XTYPE (current_buffer->selective_display) == Lisp_Int
-      ? XINT (current_buffer->selective_display)
-       : !NILP (current_buffer->selective_display) ? -1 : 0;
+    = (INTEGERP (current_buffer->selective_display)
+       ? XINT (current_buffer->selective_display)
+       : !NILP (current_buffer->selective_display) ? -1 : 0);
   /* The omission of the clause
          && marker_position (XWINDOW (window)->start) == BEG
      here is deliberate; I think we want to measure from the prompt
      position even if the minibuffer window has scrolled.  */
-  int start_hpos = (EQ (window, minibuf_window) ? minibuf_prompt_width : 0);
+  int start_hpos = 0;
+
+  if (EQ (window, minibuf_window))
+    {
+      if (minibuf_prompt_width == 0)
+       minibuf_prompt_width = string_width (minibuf_prompt, Qnil, Qnil);
+
+      start_hpos = minibuf_prompt_width;
+    }
 
  retry:
   if (vtarget > vpos)
     {
-      /* Moving downward is simple, but must calculate from beg of line 
+      /* Moving downward is simple, but must calculate from beg of line
         to determine hpos of starting point */
       if (from > BEGV && FETCH_CHAR (from - 1) != '\n')
        {
-         prevline = find_next_newline (from, -1);
-         while (prevline > BEGV
+         Lisp_Object propval;
+
+         XSETFASTINT (prevline, find_next_newline_no_quit (from, -1));
+         while (XFASTINT (prevline) > BEGV
                 && ((selective > 0
-                     && indented_beyond_p (prevline, selective))
+                     && indented_beyond_p (XFASTINT (prevline), selective))
 #ifdef USE_TEXT_PROPERTIES
                     /* watch out for newlines with `invisible' property */
-                    || ! NILP (Fget_char_property (XFASTINT (prevline),
-                                                   Qinvisible,
-                                                   window))
+                    || (propval = Fget_char_property (prevline,
+                                                      Qinvisible,
+                                                      window),
+                        TEXT_PROP_MEANS_INVISIBLE (propval))
 #endif
                 ))
-           prevline = find_next_newline (prevline - 1, -1);
-         pos = *compute_motion (prevline, 0,
-                                lmargin + (prevline == 1 ? start_hpos : 0),
+           XSETFASTINT (prevline,
+                        find_next_newline_no_quit (XFASTINT (prevline) - 1,
+                                                   -1));
+         pos = *compute_motion (XFASTINT (prevline), 0,
+                                lmargin + (XFASTINT (prevline) == 1
+                                           ? start_hpos : 0),
                                 from, 1 << (INTBITS - 2), 0,
                                 width, hscroll, 0, XWINDOW (window));
        }
@@ -802,29 +1145,34 @@ vmotion (from, vtarget, width, hscroll, window)
 
   while ((vpos > vtarget || first) && from > BEGV)
     {
-      prevline = from;
+      XSETFASTINT (prevline, from);
       while (1)
        {
-         prevline = find_next_newline (prevline - 1, -1);
-         if (prevline == BEGV
+         Lisp_Object propval;
+
+         XSETFASTINT (prevline,
+                      find_next_newline_no_quit (XFASTINT (prevline) - 1,
+                                                 -1));
+         if (XFASTINT (prevline) == BEGV
              || ((selective <= 0
-                  || ! indented_beyond_p (prevline, selective))
+                  || ! indented_beyond_p (XFASTINT (prevline), selective))
 #ifdef USE_TEXT_PROPERTIES
                  /* watch out for newlines with `invisible' property */
-                 && NILP (Fget_char_property (XFASTINT (prevline),
-                                              Qinvisible,
-                                              window))
+                 && (propval = Fget_char_property (prevline, Qinvisible,
+                                                   window),
+                     ! TEXT_PROP_MEANS_INVISIBLE (propval))
 #endif
                  ))
            break;
        }
-      pos = *compute_motion (prevline, 0,
-                            lmargin + (prevline == 1 ? start_hpos : 0),
+      pos = *compute_motion (XFASTINT (prevline), 0,
+                            lmargin + (XFASTINT (prevline) == 1
+                                       ? start_hpos : 0),
                             from, 1 << (INTBITS - 2), 0,
                             width, hscroll, 0, XWINDOW (window));
       vpos -= pos.vpos;
       first = 0;
-      from = prevline;
+      from = XFASTINT (prevline);
     }
 
   /* If we made exactly the desired vertical distance,
@@ -839,7 +1187,7 @@ vmotion (from, vtarget, width, hscroll, window)
       val_vmotion.prevhpos = 0;
       return &val_vmotion;
     }
-  
+
   /* Otherwise find the correct spot by moving down */
   goto retry;
 }
@@ -868,7 +1216,7 @@ if beginning or end of buffer was reached.")
   if (! NILP (window))
     CHECK_WINDOW (window, 0);
   else
-    XSET (window, Lisp_Window, selected_window);
+    window = selected_window;
 
   w = XWINDOW (window);
 
@@ -880,6 +1228,8 @@ if beginning or end of buffer was reached.")
   return make_number (pos.vpos);
 }
 \f
+/* file's initialization.  */
+
 syms_of_indent ()
 {
   DEFVAR_BOOL ("indent-tabs-mode", &indent_tabs_mode,