(Fset_window_hscroll, Fpos_visible_in_window_p):
[bpt/emacs.git] / src / window.c
index e7040e3..2b22716 100644 (file)
@@ -50,6 +50,7 @@ Boston, MA 02110-1301, USA.  */
 
 
 Lisp_Object Qwindowp, Qwindow_live_p, Qwindow_configuration_p;
+Lisp_Object Qscroll_up, Qscroll_down;
 Lisp_Object Qwindow_size_fixed;
 extern Lisp_Object Qleft_margin, Qright_margin;
 
@@ -62,7 +63,7 @@ static void window_scroll_pixel_based P_ ((Lisp_Object, int, int, int));
 static void window_scroll_line_based P_ ((Lisp_Object, int, int, int));
 static int window_min_size_1 P_ ((struct window *, int));
 static int window_min_size P_ ((struct window *, int, int, int *));
-static void size_window P_ ((Lisp_Object, int, int, int));
+static void size_window P_ ((Lisp_Object, int, int, int, int, int));
 static int freeze_window_start P_ ((struct window *, void *));
 static int window_fixed_size_p P_ ((struct window *, int, int));
 static void enlarge_window P_ ((Lisp_Object, int, int));
@@ -214,6 +215,10 @@ Lisp_Object Vscroll_preserve_screen_position;
 
 int window_deletion_count;
 
+/* Used by the function window_scroll_pixel_based */
+
+static int window_scroll_pixel_based_preserve_y;
+
 #if 0 /* This isn't used anywhere.  */
 /* Nonzero means we can split a frame even if it is "unsplittable".  */
 static int inhibit_frame_unsplittable;
@@ -328,13 +333,16 @@ Return nil if that position is scrolled vertically out of view.
 If a character is only partially visible, nil is returned, unless the
 optional argument PARTIALLY is non-nil.
 If POS is only out of view because of horizontal scrolling, return non-nil.
+If POS is t, it specifies the position of the last visible glyph in WINDOW.
 POS defaults to point in WINDOW; WINDOW defaults to the selected window.
 
 If POS is visible, return t if PARTIALLY is nil; if PARTIALLY is non-nil,
-return value is a list (X Y PARTIAL) where X and Y are the pixel coordinates
-relative to the top left corner of the window. PARTIAL is nil if the character
-after POS is fully visible; otherwise it is a cons (RTOP . RBOT) where RTOP
-and RBOT are the number of pixels invisible at the top and bottom of the row.  */)
+return value is a list of 2 or 6 elements (X Y [RTOP RBOT ROWH VPOS]),
+where X and Y are the pixel coordinates relative to the top left corner
+of the window.  The remaining elements are omitted if the character after
+POS is fully visible; otherwise, RTOP and RBOT are the number of pixels
+off-window at the top and bottom of the row, ROWH is the height of the
+display row, and VPOS is the row number (0-based) containing POS.  */)
      (pos, window, partially)
      Lisp_Object pos, window, partially;
 {
@@ -343,14 +351,16 @@ and RBOT are the number of pixels invisible at the top and bottom of the row.  *
   register struct buffer *buf;
   struct text_pos top;
   Lisp_Object in_window = Qnil;
-  int rtop, rbot, fully_p = 1;
+  int rtop, rbot, rowh, vpos, fully_p = 1;
   int x, y;
 
   w = decode_window (window);
   buf = XBUFFER (w->buffer);
   SET_TEXT_POS_FROM_MARKER (top, w->start);
 
-  if (!NILP (pos))
+  if (EQ (pos, Qt))
+    posint = -1;
+  else if (!NILP (pos))
     {
       CHECK_NUMBER_COERCE_MARKER (pos);
       posint = XINT (pos);
@@ -362,24 +372,138 @@ and RBOT are the number of pixels invisible at the top and bottom of the row.  *
 
   /* If position is above window start or outside buffer boundaries,
      or if window start is out of range, position is not visible.  */
-  if (posint >= CHARPOS (top)
-      && posint <= BUF_ZV (buf)
+  if ((EQ (pos, Qt)
+       || (posint >= CHARPOS (top) && posint <= BUF_ZV (buf)))
       && CHARPOS (top) >= BUF_BEGV (buf)
       && CHARPOS (top) <= BUF_ZV (buf)
-      && pos_visible_p (w, posint, &x, &y, &rtop, &rbot, NILP (partially))
+      && pos_visible_p (w, posint, &x, &y, &rtop, &rbot, &rowh, &vpos)
       && (fully_p = !rtop && !rbot, (!NILP (partially) || fully_p)))
     in_window = Qt;
 
   if (!NILP (in_window) && !NILP (partially))
-    in_window = Fcons (make_number (x),
-                      Fcons (make_number (y),
-                             Fcons ((fully_p ? Qnil
-                                    : Fcons (make_number (rtop),
-                                             make_number (rbot))),
-                                    Qnil)));
+    {
+      Lisp_Object part = Qnil;
+      if (!fully_p)
+       part = list4 (make_number (rtop), make_number (rbot),
+                       make_number (rowh), make_number (vpos));
+      in_window = Fcons (make_number (x),
+                        Fcons (make_number (y), part));
+    }
+
   return in_window;
 }
 
+DEFUN ("window-line-height", Fwindow_line_height,
+       Swindow_line_height, 0, 2, 0,
+       doc: /* Return height in pixels of text line LINE in window WINDOW.
+If WINDOW is nil or omitted, use selected window.
+
+Return height of current line if LINE is omitted or nil.  Return height of
+header or mode line if LINE is `header-line' and `mode-line'.
+Otherwise, LINE is a text line number starting from 0.  A negative number
+counts from the end of the window.
+
+Value is a list (HEIGHT VPOS YPOS OFFBOT), where HEIGHT is the height
+in pixels of the visible part of the line, VPOS and YPOS are the
+vertical position in lines and pixels of the row, relative to the top
+of the first text line, and OFFBOT is the number of off-window pixels at
+the bottom of the text row.  If there are off-window pixels at the top
+of the (first) text row, YPOS is negative.
+
+Return nil if window display is not up-to-date.  In that case, use
+`pos-visible-in-window-p' to obtain the information.  */)
+     (line, window)
+     Lisp_Object line, window;
+{
+  register struct window *w;
+  register struct buffer *b;
+  struct glyph_row *row, *end_row;
+  int max_y, crop, i, n;
+
+  w = decode_window (window);
+
+  if (noninteractive
+      || w->pseudo_window_p)
+    return Qnil;
+
+  CHECK_BUFFER (w->buffer);
+  b = XBUFFER (w->buffer);
+
+  /* Fail if current matrix is not up-to-date.  */
+  if (NILP (w->window_end_valid)
+      || current_buffer->clip_changed
+      || current_buffer->prevent_redisplay_optimizations_p
+      || XFASTINT (w->last_modified) < BUF_MODIFF (b)
+      || XFASTINT (w->last_overlay_modified) < BUF_OVERLAY_MODIFF (b))
+    return Qnil;
+
+  if (NILP (line))
+    {
+      i = w->cursor.vpos;
+      if (i < 0 || i >= w->current_matrix->nrows
+         || (row = MATRIX_ROW (w->current_matrix, i), !row->enabled_p))
+       return Qnil;
+      max_y = window_text_bottom_y (w);
+      goto found_row;
+    }
+
+  if (EQ (line, Qheader_line))
+    {
+      if (!WINDOW_WANTS_HEADER_LINE_P (w))
+       return Qnil;
+      row = MATRIX_HEADER_LINE_ROW (w->current_matrix);
+      if (!row->enabled_p)
+       return Qnil;
+      return list4 (make_number (row->height),
+                   make_number (0), make_number (0),
+                   make_number (0));
+    }
+
+  if (EQ (line, Qmode_line))
+    {
+      row = MATRIX_MODE_LINE_ROW (w->current_matrix);
+      if (!row->enabled_p)
+       return Qnil;
+      return list4 (make_number (row->height),
+                   make_number (0), /* not accurate */
+                   make_number (WINDOW_HEADER_LINE_HEIGHT (w)
+                                + window_text_bottom_y (w)),
+                   make_number (0));
+    }
+
+  CHECK_NUMBER (line);
+  n = XINT (line);
+
+  row = MATRIX_FIRST_TEXT_ROW (w->current_matrix);
+  end_row = MATRIX_BOTTOM_TEXT_ROW (w->current_matrix, w);
+  max_y = window_text_bottom_y (w);
+  i = 0;
+
+  while ((n < 0 || i < n)
+        && row <= end_row && row->enabled_p
+        && row->y + row->height < max_y)
+    row++, i++;
+
+  if (row > end_row || !row->enabled_p)
+    return Qnil;
+
+  if (++n < 0)
+    {
+      if (-n > i)
+       return Qnil;
+      row += n;
+      i += n;
+    }
+
+ found_row:
+  crop = max (0, (row->y + row->height) - max_y);
+  return list4 (make_number (row->height + min (0, row->y) - crop),
+               make_number (i),
+               make_number (row->y),
+               make_number (crop));
+}
+
+
 \f
 static struct window *
 decode_window (window)
@@ -444,7 +568,7 @@ DEFUN ("set-window-hscroll", Fset_window_hscroll, Sset_window_hscroll, 2, 2, 0,
 Return NCOL.  NCOL should be zero or positive.
 
 Note that if `automatic-hscrolling' is non-nil, you cannot scroll the
-window so that the location of point becomes invisible.  */)
+window so that the location of point moves off-window.  */)
      (window, ncol)
      Lisp_Object window, ncol;
 {
@@ -656,12 +780,24 @@ coordinates_in_window (w, x, y)
          || WINDOW_RIGHTMOST_P (w))
        {
          if (!WINDOW_LEFTMOST_P (w) && abs (*x - x0) < grabbable_width)
-           return ON_VERTICAL_BORDER;
+           {
+             /* Convert X and Y to window relative coordinates.
+                Vertical border is at the left edge of window.  */
+             *x = max (0, *x - x0);
+             *y -= top_y;
+             return ON_VERTICAL_BORDER;
+           }
        }
       else
        {
          if (abs (*x - x1) < grabbable_width)
-           return ON_VERTICAL_BORDER;
+           {
+             /* Convert X and Y to window relative coordinates.
+                Vertical border is at the right edge of window.  */
+             *x = min (x1, *x) - x0;
+             *y -= top_y;
+             return ON_VERTICAL_BORDER;
+           }
        }
 
       if (*x < x0 || *x >= x1)
@@ -703,7 +839,13 @@ coordinates_in_window (w, x, y)
          && !WINDOW_HAS_VERTICAL_SCROLL_BAR (w)
          && !WINDOW_RIGHTMOST_P (w)
          && (abs (*x - right_x) < grabbable_width))
-       return ON_VERTICAL_BORDER;
+       {
+         /* Convert X and Y to window relative coordinates.
+            Vertical border is at the right edge of window.  */
+         *x = min (right_x, *x) - left_x;
+         *y -= top_y;
+         return ON_VERTICAL_BORDER;
+       }
     }
   else
     {
@@ -715,6 +857,8 @@ coordinates_in_window (w, x, y)
        {
          /* On the border on the right side of the window?  Assume that
             this area begins at RIGHT_X minus a canonical char width.  */
+         *x = min (right_x, *x) - left_x;
+         *y -= top_y;
          return ON_VERTICAL_BORDER;
        }
     }
@@ -1021,9 +1165,11 @@ if it isn't already recorded.  */)
   Lisp_Object value;
   struct window *w = decode_window (window);
   Lisp_Object buf;
+  struct buffer *b;
 
   buf = w->buffer;
   CHECK_BUFFER (buf);
+  b = XBUFFER (buf);
 
 #if 0 /* This change broke some things.  We should make it later.  */
   /* If we don't know the end position, return nil.
@@ -1036,12 +1182,20 @@ if it isn't already recorded.  */)
 
   if (! NILP (update)
       && ! (! NILP (w->window_end_valid)
-           && XFASTINT (w->last_modified) >= MODIFF)
+           && XFASTINT (w->last_modified) >= BUF_MODIFF (b))
       && !noninteractive)
     {
       struct text_pos startp;
       struct it it;
-      struct buffer *old_buffer = NULL, *b = XBUFFER (buf);
+      struct buffer *old_buffer = NULL;
+
+      /* Cannot use Fvertical_motion because that function doesn't
+        cope with variable-height lines.  */
+      if (b != current_buffer)
+       {
+         old_buffer = current_buffer;
+         set_buffer_internal (b);
+       }
 
       /* In case W->start is out of the range, use something
          reasonable.  This situation occurred when loading a file with
@@ -1055,14 +1209,6 @@ if it isn't already recorded.  */)
       else
        SET_TEXT_POS_FROM_MARKER (startp, w->start);
 
-      /* Cannot use Fvertical_motion because that function doesn't
-        cope with variable-height lines.  */
-      if (b != current_buffer)
-       {
-         old_buffer = current_buffer;
-         set_buffer_internal (b);
-       }
-
       start_display (&it, w, startp);
       move_it_vertically (&it, window_box_height (w));
       if (it.current_y < it.last_visible_y)
@@ -1073,7 +1219,7 @@ if it isn't already recorded.  */)
        set_buffer_internal (old_buffer);
     }
   else
-    XSETINT (value, BUF_Z (XBUFFER (buf)) - XFASTINT (w->window_end_pos));
+    XSETINT (value, BUF_Z (b) - XFASTINT (w->window_end_pos));
 
   return value;
 }
@@ -1861,7 +2007,8 @@ MINIBUF neither nil nor t means never include the minibuffer window.  */)
      Lisp_Object frame, minibuf, window;
 {
   if (NILP (window))
-    window = selected_window;
+    window = FRAMEP (frame) ? XFRAME (frame)->selected_window : selected_window;
+  CHECK_WINDOW (window);
   if (NILP (frame))
     frame = selected_frame;
 
@@ -2007,7 +2154,7 @@ window_loop (type, obj, mini, frames)
               `obj & 1' means consider only full-width windows.
               `obj & 2' means consider also dedicated windows. */
            if (((XINT (obj) & 1) && !WINDOW_FULL_WIDTH_P (w))
-               || (!(XINT (obj) & 2) && EQ (w->dedicated, Qt))
+               || (!(XINT (obj) & 2) && !NILP (w->dedicated))
                /* Minibuffer windows are always ignored.  */
                || MINI_WINDOW_P (w))
              break;
@@ -2062,7 +2209,7 @@ window_loop (type, obj, mini, frames)
          case GET_LARGEST_WINDOW:
            { /* nil `obj' means to ignore dedicated windows.  */
              /* Ignore dedicated windows and minibuffers.  */
-             if (MINI_WINDOW_P (w) || (NILP (obj) && EQ (w->dedicated, Qt)))
+             if (MINI_WINDOW_P (w) || (NILP (obj) && !NILP (w->dedicated)))
                break;
 
              if (NILP (best_window))
@@ -2800,17 +2947,23 @@ shrink_windows (total, size, nchildren, shrinkable,
 
 /* Set WINDOW's height or width to SIZE.  WIDTH_P non-zero means set
    WINDOW's width.  Resize WINDOW's children, if any, so that they
-   keep their proportionate size relative to WINDOW.  Propagate
-   WINDOW's top or left edge position to children.  Delete windows
-   that become too small unless NODELETE_P is non-zero.
+   keep their proportionate size relative to WINDOW.
+
+   If FIRST_ONLY is 1, change only the first of WINDOW's children when
+   they are in series.  If LAST_ONLY is 1, change only the last of
+   WINDOW's children when they are in series.
+
+   Propagate WINDOW's top or left edge position to children.  Delete
+   windows that become too small unless NODELETE_P is non-zero.
 
    If NODELETE_P is 2, that means we do delete windows that are
    too small, even if they were too small before!  */
 
 static void
-size_window (window, size, width_p, nodelete_p)
+size_window (window, size, width_p, nodelete_p, first_only, last_only)
      Lisp_Object window;
      int size, width_p, nodelete_p;
+     int first_only, last_only;
 {
   struct window *w = XWINDOW (window);
   struct window *c;
@@ -2885,6 +3038,7 @@ size_window (window, size, width_p, nodelete_p)
 
   if (!NILP (*sideward))
     {
+      /* We have a chain of parallel siblings whose size should all change.  */
       for (child = *sideward; !NILP (child); child = c->next)
        {
          c = XWINDOW (child);
@@ -2892,9 +3046,45 @@ size_window (window, size, width_p, nodelete_p)
            c->left_col = w->left_col;
          else
            c->top_line = w->top_line;
-         size_window (child, size, width_p, nodelete_p);
+         size_window (child, size, width_p, nodelete_p,
+                      first_only, last_only);
        }
     }
+  else if (!NILP (*forward) && last_only)
+    {
+      /* Change the last in a series of siblings.  */
+      Lisp_Object last_child;
+      int child_size;
+
+      for (child = *forward; !NILP (child); child = c->next)
+       {
+         c = XWINDOW (child);
+         last_child = child;
+       }
+
+      child_size = XINT (width_p ? c->total_cols : c->total_lines);
+      size_window (last_child,
+                  size - old_size + child_size,
+                  width_p, nodelete_p, first_only, last_only);
+    }
+  else if (!NILP (*forward) && first_only)
+    {
+      /* Change the first in a series of siblings.  */
+      int child_size;
+
+      child = *forward;
+      c = XWINDOW (child);
+
+      if (width_p)
+       c->left_col = w->left_col;
+      else
+       c->top_line = w->top_line;
+
+      child_size = XINT (width_p ? c->total_cols : c->total_lines);
+      size_window (child,
+                  size - old_size + child_size,
+                  width_p, nodelete_p, first_only, last_only);
+    }
   else if (!NILP (*forward))
     {
       int fixed_size, each, extra, n;
@@ -2902,7 +3092,7 @@ size_window (window, size, width_p, nodelete_p)
       int last_pos, first_pos, nchildren, total;
       int *new_sizes = NULL;
 
-      /* Determine the fixed-size portion of the this window, and the
+      /* Determine the fixed-size portion of this window, and the
         number of child windows.  */
       fixed_size = nchildren = nfixed = total = 0;
       for (child = *forward; !NILP (child); child = c->next, ++nchildren)
@@ -2965,7 +3155,7 @@ size_window (window, size, width_p, nodelete_p)
          /* Set new height.  Note that size_window also propagates
             edge positions to children, so it's not a no-op if we
             didn't change the child's size.  */
-         size_window (child, new_size, width_p, 1);
+         size_window (child, new_size, width_p, 1, first_only, last_only);
 
          /* Remember the bottom/right edge position of this child; it
             will be used to set the top/left edge of the next child.  */
@@ -2984,7 +3174,7 @@ size_window (window, size, width_p, nodelete_p)
            int child_size;
            c = XWINDOW (child);
            child_size = width_p ? XINT (c->total_cols) : XINT (c->total_lines);
-           size_window (child, child_size, width_p, 2);
+           size_window (child, child_size, width_p, 2, first_only, last_only);
          }
     }
 }
@@ -3000,7 +3190,7 @@ set_window_height (window, height, nodelete)
      int height;
      int nodelete;
 {
-  size_window (window, height, 0, nodelete);
+  size_window (window, height, 0, nodelete, 0, 0);
 }
 
 
@@ -3015,7 +3205,7 @@ set_window_width (window, width, nodelete)
      int width;
      int nodelete;
 {
-  size_window (window, width, 1, nodelete);
+  size_window (window, width, 1, nodelete, 0, 0);
 }
 
 /* Change window heights in windows rooted in WINDOW by N lines.  */
@@ -3411,7 +3601,7 @@ displaying BUFFER, then simply raise that frame.
 The variables `special-display-buffer-names',
 `special-display-regexps', `same-window-buffer-names', and
 `same-window-regexps' customize how certain buffer names are handled.
-The latter two take effect only if NOT-THIS-WINDOW is t.
+The latter two take effect only if NOT-THIS-WINDOW is nil.
 
 If optional argument FRAME is `visible', search all visible frames.
 If FRAME is 0, search all visible and iconified frames.
@@ -3607,7 +3797,7 @@ displayed.  */)
 
 DEFUN ("force-window-update", Fforce_window_update, Sforce_window_update,
        0, 1, 0,
-       doc: /* Force redisplay of all windows.
+       doc: /* Force all windows to be updated on next redisplay.
 If optional arg OBJECT is a window, force redisplay of that window only.
 If OBJECT is a buffer or buffer name, force redisplay of all windows
 displaying that buffer.  */)
@@ -3752,7 +3942,9 @@ SIZE includes that window's scroll bar, or the divider column to its right.
 Interactively, all arguments are nil.
 
 Returns the newly created window (which is the lower or rightmost one).
-The upper or leftmost window is the original one and remains selected.
+The upper or leftmost window is the original one, and remains selected
+if it was selected before.
+
 See Info node `(elisp)Splitting Windows' for more details and examples.*/)
      (window, size, horflag)
      Lisp_Object window, size, horflag;
@@ -4249,16 +4441,48 @@ adjust_window_trailing_edge (window, delta, horiz_flag)
 
   while (1)
     {
+      Lisp_Object first_parallel = Qnil;
+
+      if (NILP (window))
+       {
+         /* This happens if WINDOW on the previous iteration was
+            at top level of the window tree.  */
+         Fset_window_configuration (old_config);
+         error ("Specified window edge is fixed");
+       }
+
       p = XWINDOW (window);
       parent = p->parent;
 
-      /* Make sure there is a following window.  */
-      if (NILP (parent)
-         && (horiz_flag ? 1
-             : NILP (XWINDOW (window)->next)))
+      /* See if this level has windows in parallel in the specified
+        direction.  If so, set FIRST_PARALLEL to the first one.  */
+      if (horiz_flag)
+       {
+         if (! NILP (parent) && !NILP (XWINDOW (parent)->vchild))
+           first_parallel = XWINDOW (parent)->vchild;
+         else if (NILP (parent) && !NILP (p->next))
+           {
+             /* Handle the vertical chain of main window and minibuffer
+                which has no parent.  */
+             first_parallel = window;
+             while (! NILP (XWINDOW (first_parallel)->prev))
+               first_parallel = XWINDOW (first_parallel)->prev;
+           }
+       }
+      else
+       {
+         if (! NILP (parent) && !NILP (XWINDOW (parent)->hchild))
+           first_parallel = XWINDOW (parent)->hchild;
+       }
+
+      /* If this level's succession is in the desired dimension,
+        and this window is the last one, and there is no higher level,
+        its trailing edge is fixed.  */
+      if (NILP (XWINDOW (window)->next) && NILP (first_parallel)
+         && NILP (parent))
        {
          Fset_window_configuration (old_config);
-         error ("No other window following this one");
+         error ("Specified window edge is fixed");
        }
 
       /* Don't make this window too small.  */
@@ -4278,14 +4502,13 @@ adjust_window_trailing_edge (window, delta, horiz_flag)
               XINT (CURSIZE (window)) + delta);
 
       /* If this window has following siblings in the desired dimension,
-        make them smaller.
+        make them smaller, and exit the loop.
+
         (If we reach the top of the tree and can never do this,
         we will fail and report an error, above.)  */
-      if (horiz_flag
-         ? !NILP (XWINDOW (parent)->hchild)
-         : !NILP (XWINDOW (parent)->vchild))
+      if (NILP (first_parallel))
        {
-         if (!NILP (XWINDOW (window)->next))
+         if (!NILP (p->next))
            {
               /* This may happen for the minibuffer.  In that case
                  the window_deletion_count check below does not work.  */
@@ -4298,21 +4521,19 @@ adjust_window_trailing_edge (window, delta, horiz_flag)
              XSETINT (CURBEG (p->next),
                       XINT (CURBEG (p->next)) + delta);
              size_window (p->next, XINT (CURSIZE (p->next)) - delta,
-                          horiz_flag, 0);
+                          horiz_flag, 0, 1, 0);
              break;
            }
        }
       else
        /* Here we have a chain of parallel siblings, in the other dimension.
           Change the size of the other siblings.  */
-       for (child = (horiz_flag
-                     ? XWINDOW (parent)->vchild
-                     : XWINDOW (parent)->hchild);
+       for (child = first_parallel;
             ! NILP (child);
             child = XWINDOW (child)->next)
          if (! EQ (child, window))
            size_window (child, XINT (CURSIZE (child)) + delta,
-                        horiz_flag, 0);
+                        horiz_flag, 0, 0, 1);
 
       window = parent;
     }
@@ -4718,11 +4939,10 @@ window_scroll_pixel_based (window, n, whole, noerror)
   struct it it;
   struct window *w = XWINDOW (window);
   struct text_pos start;
-  Lisp_Object tem;
   int this_scroll_margin;
-  int preserve_y;
   /* True if we fiddled the window vscroll field without really scrolling.   */
   int vscrolled = 0;
+  int x, y, rtop, rbot, rowh, vpos;
 
   SET_TEXT_POS_FROM_MARKER (start, w->start);
 
@@ -4730,8 +4950,8 @@ window_scroll_pixel_based (window, n, whole, noerror)
      the screen.  Allow PT to be partially visible, otherwise
      something like (scroll-down 1) with PT in the line before
      the partially visible one would recenter. */
-  tem = Fpos_visible_in_window_p (make_number (PT), window, Qt);
-  if (NILP (tem))
+
+  if (!pos_visible_p (w, PT, &x, &y, &rtop, &rbot, &rowh, &vpos))
     {
       /* Move backward half the height of the window.  Performance note:
         vmotion used here is about 10% faster, but would give wrong
@@ -4756,7 +4976,7 @@ window_scroll_pixel_based (window, n, whole, noerror)
     }
   else if (auto_window_vscroll_p)
     {
-      if (tem = XCAR (XCDR (XCDR (tem))), CONSP (tem))
+      if (rtop || rbot)                /* partially visible */
        {
          int px;
          int dy = WINDOW_FRAME_LINE_HEIGHT (w);
@@ -4766,19 +4986,52 @@ window_scroll_pixel_based (window, n, whole, noerror)
                      dy);
          dy *= n;
 
-         if (n < 0 && (px = XINT (XCAR (tem))) > 0)
+         if (n < 0)
            {
-             px = max (0, -w->vscroll - min (px, -dy));
-             Fset_window_vscroll (window, make_number (px), Qt);
-             return;
+             /* Only vscroll backwards if already vscrolled forwards.  */
+             if (w->vscroll < 0 && rtop > 0)
+               {
+                 px = max (0, -w->vscroll - min (rtop, -dy));
+                 Fset_window_vscroll (window, make_number (px), Qt);
+                 return;
+               }
            }
-         if (n > 0 && (px = XINT (XCDR (tem))) > 0)
+         if (n > 0)
            {
-             px = max (0, -w->vscroll + min (px, dy));
-             Fset_window_vscroll (window, make_number (px), Qt);
-             return;
+             /* Do vscroll if already vscrolled or only display line.  */
+             if (rbot > 0 && (w->vscroll < 0 || vpos == 0))
+               {
+                 px = max (0, -w->vscroll + min (rbot, dy));
+                 Fset_window_vscroll (window, make_number (px), Qt);
+                 return;
+               }
+
+             /* Maybe modify window start instead of scrolling.  */
+             if (rbot > 0 || w->vscroll < 0)
+               {
+                 int spos;
+
+                 Fset_window_vscroll (window, make_number (0), Qt);
+                 /* If there are other text lines above the current row,
+                    move window start to current row.  Else to next row. */
+                 if (rbot > 0)
+                   spos = XINT (Fline_beginning_position (Qnil));
+                 else
+                   spos = min (XINT (Fline_end_position (Qnil)) + 1, ZV);
+                 set_marker_restricted (w->start, make_number (spos),
+                                        w->buffer);
+                 w->start_at_line_beg = Qt;
+                 w->update_mode_line = Qt;
+                 XSETFASTINT (w->last_modified, 0);
+                 XSETFASTINT (w->last_overlay_modified, 0);
+                 /* Set force_start so that redisplay_window will run the
+                    window-scroll-functions.  */
+                 w->force_start = Qt;
+                 return;
+               }
            }
        }
+      /* Cancel previous vscroll.  */
       Fset_window_vscroll (window, make_number (0), Qt);
     }
 
@@ -4786,12 +5039,21 @@ window_scroll_pixel_based (window, n, whole, noerror)
      point in the same window line as it is now, so get that line.  */
   if (!NILP (Vscroll_preserve_screen_position))
     {
-      start_display (&it, w, start);
-      move_it_to (&it, PT, -1, -1, -1, MOVE_TO_POS);
-      preserve_y = it.current_y;
+      /* We preserve the goal pixel coordinate across consecutive
+        calls to scroll-up or scroll-down.  This avoids the
+        possibility of point becoming "stuck" on a tall line when
+        scrolling by one line.  */
+      if (window_scroll_pixel_based_preserve_y < 0
+         || (!EQ (current_kboard->Vlast_command, Qscroll_up)
+             && !EQ (current_kboard->Vlast_command, Qscroll_down)))
+       {
+         start_display (&it, w, start);
+         move_it_to (&it, PT, -1, -1, -1, MOVE_TO_POS);
+         window_scroll_pixel_based_preserve_y = it.current_y;
+       }
     }
   else
-    preserve_y = -1;
+    window_scroll_pixel_based_preserve_y = -1;
 
   /* Move iterator it from start the specified distance forward or
      backward.  The result is the new window start.  */
@@ -4810,7 +5072,7 @@ window_scroll_pixel_based (window, n, whole, noerror)
       if (dy <= 0)
        {
          move_it_vertically_backward (&it, -dy);
-         /* Ensure we actually does move, e.g. in case we are currently
+         /* Ensure we actually do move, e.g. in case we are currently
             looking at an image that is taller that the window height.  */
          while (start_pos == IT_CHARPOS (it)
                 && start_pos > BEGV)
@@ -4820,7 +5082,7 @@ window_scroll_pixel_based (window, n, whole, noerror)
        {
          move_it_to (&it, ZV, -1, it.current_y + dy, -1,
                      MOVE_TO_POS | MOVE_TO_Y);
-         /* Ensure we actually does move, e.g. in case we are currently
+         /* Ensure we actually do move, e.g. in case we are currently
             looking at an image that is taller that the window height.  */
          while (start_pos == IT_CHARPOS (it)
                 && start_pos < ZV)
@@ -4840,7 +5102,7 @@ window_scroll_pixel_based (window, n, whole, noerror)
        {
          if (it.current_y < it.last_visible_y
              && (it.current_y + it.max_ascent + it.max_descent
-                 >= it.last_visible_y))
+                 > it.last_visible_y))
            {
              /* The last line was only partially visible, make it fully
                 visible.  */
@@ -4850,8 +5112,10 @@ window_scroll_pixel_based (window, n, whole, noerror)
            }
          else if (noerror)
            return;
+         else if (n < 0)       /* could happen with empty buffers */
+           xsignal0 (Qbeginning_of_buffer);
          else
-           Fsignal (Qend_of_buffer, Qnil);
+           xsignal0 (Qend_of_buffer);
        }
       else
        {
@@ -4862,7 +5126,7 @@ window_scroll_pixel_based (window, n, whole, noerror)
          else if (noerror)
            return;
          else
-           Fsignal (Qbeginning_of_buffer, Qnil);
+           xsignal0 (Qbeginning_of_buffer);
        }
 
       /* If control gets here, then we vscrolled.  */
@@ -4921,14 +5185,14 @@ window_scroll_pixel_based (window, n, whole, noerror)
              || EQ (Vscroll_preserve_screen_position, Qt)))
        /* We found PT at a legitimate height.  Leave it alone.  */
        ;
-      else if (preserve_y >= 0)
+      else if (window_scroll_pixel_based_preserve_y >= 0)
        {
          /* If we have a header line, take account of it.
             This is necessary because we set it.current_y to 0, above.  */
-         if (WINDOW_WANTS_HEADER_LINE_P (w))
-           preserve_y -= CURRENT_HEADER_LINE_HEIGHT (w);
-
-         move_it_to (&it, -1, -1, preserve_y, -1, MOVE_TO_Y);
+         move_it_to (&it, -1, -1,
+                     window_scroll_pixel_based_preserve_y
+                     - (WINDOW_WANTS_HEADER_LINE_P (w) ? 1 : 0 ),
+                     -1, MOVE_TO_Y);
          SET_PT_BOTH (IT_CHARPOS (it), IT_BYTEPOS (it));
        }
       else
@@ -4948,7 +5212,8 @@ window_scroll_pixel_based (window, n, whole, noerror)
       int charpos, bytepos;
       int partial_p;
 
-      /* Save our position, for the preserve_y case.  */
+      /* Save our position, for the
+        window_scroll_pixel_based_preserve_y case.  */
       charpos = IT_CHARPOS (it);
       bytepos = IT_BYTEPOS (it);
 
@@ -4978,20 +5243,15 @@ window_scroll_pixel_based (window, n, whole, noerror)
              || EQ (Vscroll_preserve_screen_position, Qt)))
        /* We found PT before we found the display margin, so PT is ok.  */
        ;
-      else if (preserve_y >= 0)
+      else if (window_scroll_pixel_based_preserve_y >= 0)
        {
          SET_TEXT_POS_FROM_MARKER (start, w->start);
          start_display (&it, w, start);
-#if 0  /* It's wrong to subtract this here
-         because we called start_display again
-         and did not alter it.current_y this time.  */
-
-         /* If we have a header line, take account of it.  */
-         if (WINDOW_WANTS_HEADER_LINE_P (w))
-           preserve_y -= CURRENT_HEADER_LINE_HEIGHT (w);
-#endif
-
-         move_it_to (&it, -1, -1, preserve_y, -1, MOVE_TO_Y);
+         /* It would be wrong to subtract CURRENT_HEADER_LINE_HEIGHT
+            here because we called start_display again and did not
+            alter it.current_y this time.  */
+         move_it_to (&it, -1, -1, window_scroll_pixel_based_preserve_y, -1,
+                     MOVE_TO_Y);
          SET_PT_BOTH (IT_CHARPOS (it), IT_BYTEPOS (it));
        }
       else
@@ -5067,7 +5327,7 @@ window_scroll_line_based (window, n, whole, noerror)
       if (noerror)
        return;
       else
-       Fsignal (Qbeginning_of_buffer, Qnil);
+       xsignal0 (Qbeginning_of_buffer);
     }
 
   if (pos < ZV)
@@ -5153,7 +5413,7 @@ window_scroll_line_based (window, n, whole, noerror)
       if (noerror)
        return;
       else
-       Fsignal (Qend_of_buffer, Qnil);
+       xsignal0 (Qend_of_buffer);
     }
 }
 
@@ -5755,6 +6015,7 @@ struct saved_window
   Lisp_Object left_margin_cols, right_margin_cols;
   Lisp_Object left_fringe_width, right_fringe_width, fringes_outside_margins;
   Lisp_Object scroll_bar_width, vertical_scroll_bar_type;
+  Lisp_Object dedicated;
 };
 
 #define SAVED_WINDOW_N(swv,n) \
@@ -5765,9 +6026,7 @@ DEFUN ("window-configuration-p", Fwindow_configuration_p, Swindow_configuration_
      (object)
      Lisp_Object object;
 {
-  if (WINDOW_CONFIGURATIONP (object))
-    return Qt;
-  return Qnil;
+  return WINDOW_CONFIGURATIONP (object) ? Qt : Qnil;
 }
 
 DEFUN ("window-configuration-frame", Fwindow_configuration_frame, Swindow_configuration_frame, 1, 1, 0,
@@ -5778,8 +6037,7 @@ DEFUN ("window-configuration-frame", Fwindow_configuration_frame, Swindow_config
   register struct save_window_data *data;
   struct Lisp_Vector *saved_windows;
 
-  if (! WINDOW_CONFIGURATIONP (config))
-    wrong_type_argument (Qwindow_configuration_p, config);
+  CHECK_WINDOW_CONFIGURATION (config);
 
   data = (struct save_window_data *) XVECTOR (config);
   saved_windows = XVECTOR (data->saved_windows);
@@ -5804,8 +6062,7 @@ the return value is nil.  Otherwise the value is t.  */)
   FRAME_PTR f;
   int old_point = -1;
 
-  while (!WINDOW_CONFIGURATIONP (configuration))
-    wrong_type_argument (Qwindow_configuration_p, configuration);
+  CHECK_WINDOW_CONFIGURATION (configuration);
 
   data = (struct save_window_data *) XVECTOR (configuration);
   saved_windows = XVECTOR (data->saved_windows);
@@ -5989,6 +6246,7 @@ the return value is nil.  Otherwise the value is t.  */)
          w->fringes_outside_margins = p->fringes_outside_margins;
          w->scroll_bar_width = p->scroll_bar_width;
          w->vertical_scroll_bar_type = p->vertical_scroll_bar_type;
+         w->dedicated = p->dedicated;
          XSETFASTINT (w->last_modified, 0);
          XSETFASTINT (w->last_overlay_modified, 0);
 
@@ -6258,6 +6516,7 @@ save_window_save (window, vector, i)
       p->fringes_outside_margins = w->fringes_outside_margins;
       p->scroll_bar_width = w->scroll_bar_width;
       p->vertical_scroll_bar_type = w->vertical_scroll_bar_type;
+      p->dedicated = w->dedicated;
       if (!NILP (w->buffer))
        {
          /* Save w's value of point in the window configuration.
@@ -6552,9 +6811,11 @@ display marginal areas and the text area.  */)
   if (!NILP (right_width))
     CHECK_NATNUM (right_width);
 
-  if (!EQ (w->left_fringe_width, left_width)
-      || !EQ (w->right_fringe_width, right_width)
-      || !EQ (w->fringes_outside_margins, outside_margins))
+  /* Do nothing on a tty.  */
+  if (FRAME_WINDOW_P (WINDOW_XFRAME (w))
+      && (!EQ (w->left_fringe_width, left_width)
+         || !EQ (w->right_fringe_width, right_width)
+         || !EQ (w->fringes_outside_margins, outside_margins)))
     {
       w->left_fringe_width = left_width;
       w->right_fringe_width = right_width;
@@ -6582,10 +6843,11 @@ Value is a list of the form (LEFT-WIDTH RIGHT-WIDTH OUTSIDE-MARGINS).  */)
      Lisp_Object window;
 {
   struct window *w = decode_window (window);
+
   return Fcons (make_number (WINDOW_LEFT_FRINGE_WIDTH (w)),
                Fcons (make_number (WINDOW_RIGHT_FRINGE_WIDTH (w)),
-                      Fcons ((WINDOW_HAS_FRINGES_OUTSIDE_MARGINS (w) ?
-                              Qt : Qnil), Qnil)));
+                      Fcons ((WINDOW_HAS_FRINGES_OUTSIDE_MARGINS (w)
+                              Qt : Qnil), Qnil)));
 }
 
 
@@ -6757,7 +7019,9 @@ foreach_window (f, fn, user_data)
      int (* fn) P_ ((struct window *, void *));
      void *user_data;
 {
-  foreach_window_1 (XWINDOW (FRAME_ROOT_WINDOW (f)), fn, user_data);
+  /* Fdelete_frame may set FRAME_ROOT_WINDOW (f) to Qnil.  */
+  if (WINDOWP (FRAME_ROOT_WINDOW (f)))
+    foreach_window_1 (XWINDOW (FRAME_ROOT_WINDOW (f)), fn, user_data);
 }
 
 
@@ -6840,10 +7104,8 @@ compare_window_configurations (c1, c2, ignore_positions)
   struct Lisp_Vector *sw1, *sw2;
   int i;
 
-  if (!WINDOW_CONFIGURATIONP (c1))
-    wrong_type_argument (Qwindow_configuration_p, c1);
-  if (!WINDOW_CONFIGURATIONP (c2))
-    wrong_type_argument (Qwindow_configuration_p, c2);
+  CHECK_WINDOW_CONFIGURATION (c1);
+  CHECK_WINDOW_CONFIGURATION (c2);
 
   d1 = (struct save_window_data *) XVECTOR (c1);
   d2 = (struct save_window_data *) XVECTOR (c2);
@@ -6987,6 +7249,12 @@ init_window ()
 void
 syms_of_window ()
 {
+  Qscroll_up = intern ("scroll-up");
+  staticpro (&Qscroll_up);
+
+  Qscroll_down = intern ("scroll-down");
+  staticpro (&Qscroll_down);
+
   Qwindow_size_fixed = intern ("window-size-fixed");
   staticpro (&Qwindow_size_fixed);
   Fset (Qwindow_size_fixed, Qnil);
@@ -7012,6 +7280,8 @@ syms_of_window ()
   minibuf_selected_window = Qnil;
   staticpro (&minibuf_selected_window);
 
+  window_scroll_pixel_based_preserve_y = -1;
+
   DEFVAR_LISP ("temp-buffer-show-function", &Vtemp_buffer_show_function,
               doc: /* Non-nil means call as function to display a help buffer.
 The function is called with one argument, the buffer to be displayed.
@@ -7174,16 +7444,18 @@ See also `same-window-buffer-names'.  */);
   next_screen_context_lines = 2;
 
   DEFVAR_INT ("split-height-threshold", &split_height_threshold,
-             doc: /* *A window must be at least this tall to be eligible for splitting by `display-buffer'.
+             doc: /* *A window must be at least this tall to be eligible for splitting
+by `display-buffer'.  The value is in line units.
 If there is only one window, it is split regardless of this value.  */);
   split_height_threshold = 500;
 
   DEFVAR_INT ("window-min-height", &window_min_height,
-             doc: /* *Delete any window less than this tall (including its mode line).  */);
+             doc: /* *Delete any window less than this tall (including its mode line).
+The value is in line units. */);
   window_min_height = 4;
 
   DEFVAR_INT ("window-min-width", &window_min_width,
-             doc: /* *Delete any window less than this wide.  */);
+             doc: /* *Delete any window less than this wide (measured in characters).  */);
   window_min_width = 10;
 
   DEFVAR_LISP ("scroll-preserve-screen-position",
@@ -7209,6 +7481,7 @@ The selected frame is the one whose configuration has changed.  */);
   defsubr (&Swindowp);
   defsubr (&Swindow_live_p);
   defsubr (&Spos_visible_in_window_p);
+  defsubr (&Swindow_line_height);
   defsubr (&Swindow_buffer);
   defsubr (&Swindow_height);
   defsubr (&Swindow_width);