diff --git a/src/buffer.c b/src/buffer.c
index 98b61c3..ba37cbc 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -5073,6 +5073,7 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, display_table), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, syntax_table), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cache_long_scans), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, window_start_end_hook), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, category_table), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, bidi_display_reordering), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, bidi_paragraph_direction), idx); ++idx;
@@ -5166,6 +5167,7 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_defaults, fill_column), 70);
   XSETFASTINT (BVAR (&buffer_defaults, left_margin), 0);
   bset_cache_long_scans (&buffer_defaults, Qt);
+  bset_window_start_end_hook (&buffer_defaults, Qnil);
   bset_file_truename (&buffer_defaults, Qnil);
   XSETFASTINT (BVAR (&buffer_defaults, display_count), 0);
   XSETFASTINT (BVAR (&buffer_defaults, left_margin_cols), 0);
@@ -5379,6 +5381,8 @@ syms_of_buffer (void)
   staticpro (&QSFundamental);
   staticpro (&Vbuffer_alist);
 
+  DEFSYM (Qwindow_start_end_hook, "window-start-end-hook");
+
   DEFSYM (Qchoice, "choice");
   DEFSYM (Qleft, "left");
   DEFSYM (Qright, "right");
@@ -6148,6 +6152,11 @@ maintained internally by the Emacs primitives.  Enabling or disabling
 the cache should not affect the behavior of any of the motion
 functions; it should only affect their performance.  */);
 
+  DEFVAR_PER_BUFFER ("window-start-end-hook", &BVAR (current_buffer, window_start_end_hook), Qnil,
+    doc: /* A bufer-local hook used to obtain new `window-start` and `window-end`
+during redisplay.  The function attached to this hook has the following arguments:
+WINDOW, WINDOW-START, WINDOW-END, PBOL-START, PEOL-END, FULLY-P.  */);
+
   DEFVAR_PER_BUFFER ("point-before-scroll", &BVAR (current_buffer, point_before_scroll), Qnil,
 		     doc: /* Value of point before the last series of scroll operations, or nil.  */);
 
diff --git a/src/buffer.h b/src/buffer.h
index 5783bfb..0e13247 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -643,6 +643,9 @@ struct buffer
      cache are enabled.  See search.c, indent.c and bidi.c for details.  */
   Lisp_Object cache_long_scans_;
 
+  /* The name of the hook.  */
+  Lisp_Object window_start_end_hook_;
+
   /* If the width run cache is enabled, this table contains the
      character widths width_run_cache (see above) assumes.  When we
      do a thorough redisplay, we compare this against the buffer's
@@ -895,6 +898,11 @@ bset_cache_long_scans (struct buffer *b, Lisp_Object val)
   b->cache_long_scans_ = val;
 }
 INLINE void
+bset_window_start_end_hook (struct buffer *b, Lisp_Object val)
+{
+  b->window_start_end_hook_ = val;
+}
+INLINE void
 bset_case_canon_table (struct buffer *b, Lisp_Object val)
 {
   b->case_canon_table_ = val;
diff --git a/src/keyboard.c b/src/keyboard.c
index 3431cd8..3d8d54f 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1233,6 +1233,15 @@ static int read_key_sequence (Lisp_Object *, int, Lisp_Object,
                               bool, bool, bool, bool);
 static void adjust_point_for_property (ptrdiff_t, bool);
 
+static void
+set_window_start_end_hook (void)
+{
+  Lisp_Object window = (selected_window);
+  struct window *w = decode_live_window (window);
+  w->window_start_end_hook_force = true;
+  w->window_start_end_hook_pending = true;
+}
+
 Lisp_Object
 command_loop_1 (void)
 {
@@ -1258,6 +1267,8 @@ command_loop_1 (void)
       if (!NILP (Vpost_command_hook) && !NILP (Vrun_hooks))
 	safe_run_hooks (Qpost_command_hook);
 
+      set_window_start_end_hook ();
+
       /* If displaying a message, resize the echo area window to fit
 	 that message's size exactly.  */
       if (!NILP (echo_area_buffer[0]))
@@ -1474,6 +1485,8 @@ command_loop_1 (void)
 
       safe_run_hooks (Qpost_command_hook);
 
+      set_window_start_end_hook ();
+
       /* If displaying a message, resize the echo area window to fit
 	 that message's size exactly.  */
       if (!NILP (echo_area_buffer[0]))
diff --git a/src/window.h b/src/window.h
index c29207d..3156927 100644
--- a/src/window.h
+++ b/src/window.h
@@ -348,6 +348,12 @@ struct window
        Vwindow_scroll_functions; also by Frecenter with argument.  */
     bool_bf optional_new_start : 1;
 
+    /* True means force a call to Vwindow_start_end_hook.  */
+    bool_bf window_start_end_hook_force : 1;
+
+    /* True means the hook has not yet run with point fully visible.  */
+    bool_bf window_start_end_hook_pending : 1;
+
     /* True means the cursor is currently displayed.  This can be
        set to zero by functions overpainting the cursor image.  */
     bool_bf phys_cursor_on_p : 1;
diff --git a/src/xdisp.c b/src/xdisp.c
index fed5879..e38e8dc 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -13417,6 +13417,83 @@ do { if (! polling_stopped_here) stop_polling ();	\
 do { if (polling_stopped_here) start_polling ();	\
        polling_stopped_here = false; } while (false)
 
+static void
+run_window_start_end_hook (Lisp_Object window, struct text_pos startp, struct it it, char *string)
+{
+  struct window *w = decode_live_window (window);
+  Lisp_Object buf;
+  struct buffer *b;
+  buf = w->contents;
+  CHECK_BUFFER (buf);
+  b = XBUFFER (buf);
+  struct buffer *old_buffer = NULL;
+  void *itdata = NULL;
+  bool fully_p = false;
+  EMACS_INT posint = PT;
+  int x, y, rtop, rbot, rowh, vpos, startp_integer, endp, pbol_startp, peol_endp;
+  startp_integer = CHARPOS (startp);
+  if (b != current_buffer)
+    {
+      old_buffer = current_buffer;
+      set_buffer_internal (b);
+    }
+  if (!NILP (BVAR (current_buffer, window_start_end_hook))
+      && w->window_start_end_hook_pending)
+    {
+      if ((posint >= CHARPOS (startp) && posint <= BUF_ZV (b))
+          && CHARPOS (startp) >= BUF_BEGV (b)
+          && CHARPOS (startp) <= BUF_ZV (b)
+          && pos_visible_p (w, posint, &x, &y, &rtop, &rbot, &rowh, &vpos))
+        fully_p = !rtop && !rbot;
+      if (fully_p)
+        {
+          CLIP_TEXT_POS_FROM_MARKER (startp, w->start);
+          itdata = bidi_shelve_cache ();
+          start_display (&it, w, startp);
+          move_it_vertically (&it, window_box_height (w));
+          if (it.current_y < it.last_visible_y)
+            move_it_past_eol (&it);
+          endp = (IT_CHARPOS (it));
+          bidi_unshelve_cache (itdata, false);
+          SET_PT (startp_integer);
+          pbol_startp = XINT (Fline_beginning_position (Qnil));
+          SET_PT (endp);
+          peol_endp = XINT (Fline_end_position (Qnil));
+          SET_PT (posint);
+#ifdef GLYPH_DEBUG
+          /* See the doc-string for `pos-visible-in-window-p'.  */
+          debug_method_add (w, "%s -- x: %d | y: %d | startp: %d | endp: %d | pbol_startp: %d | peol_endp: %d",
+                               string, x, y, startp_integer, endp, pbol_startp, peol_endp);
+#endif
+          /*  Reset the flag _before_ calling Lisp, not after, so that
+              if Lisp does signal an error, the flag will be reset, and
+              won't cause the hook to be called ad nauseam.  */
+          w->window_start_end_hook_pending = false;
+          /*  When calling Lisp from redisplay, always use safe_call, to be
+              protected against errors signaled by that Lisp.  */
+          /* fn, arg1, arg2, arg3, arg4, arg5, arg6, arg7  */
+          safe_call (8,
+              Qrun_hook_with_args,
+              Qwindow_start_end_hook,
+              window,
+              make_number (startp_integer),
+              make_number (endp),
+              make_number (pbol_startp),
+              make_number (peol_endp),
+              fully_p ? Qt : Qnil);
+        }
+        else
+          {
+#ifdef GLYPH_DEBUG
+            /* See the doc-string for `pos-visible-in-window-p'.  */
+            debug_method_add (w, "%s -- x: %d | y: %d | rtop: %d | rbot %d | rowh: %d | vpos: %d",
+                                 string, x, y, rtop, rbot, rowh, vpos);
+#endif
+          }
+    }
+  if (old_buffer)
+    set_buffer_internal (old_buffer);
+}
 
 /* Perhaps in the future avoid recentering windows if it
    is not necessary; currently that causes some problems.  */
@@ -13714,6 +13791,7 @@ redisplay_internal (void)
       && match_p
       && !w->force_start
       && !w->optional_new_start
+      && !w->window_start_end_hook_force
       /* Point must be on the line that we have info recorded about.  */
       && PT >= CHARPOS (tlbufpos)
       && PT <= Z - CHARPOS (tlendpos)
@@ -15418,6 +15496,8 @@ try_scrolling (Lisp_Object window, bool just_this_one_p,
   /* Run window scroll functions.  */
   startp = run_window_scroll_functions (window, startp);
 
+  run_window_start_end_hook (window, startp, it, "try_scrolling");
+
   /* Display the window.  Give up if new fonts are loaded, or if point
      doesn't appear.  */
   if (!try_window (window, startp, 0))
@@ -16082,6 +16162,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   bool last_line_misfit = false;
   ptrdiff_t beg_unchanged, end_unchanged;
   int frame_line_height;
+  bool window_start_end_hook_suppress_wsf = false;
 
   SET_TEXT_POS (lpoint, PT, PT_BYTE);
   opoint = lpoint;
@@ -16272,13 +16353,16 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 
   /* If someone specified a new starting point but did not insist,
      check whether it can be used.  */
-  if ((w->optional_new_start || window_frozen_p (w))
+  if ((w->optional_new_start || w->window_start_end_hook_force || window_frozen_p (w))
       && CHARPOS (startp) >= BEGV
       && CHARPOS (startp) <= ZV)
     {
+
+      if (w->window_start_end_hook_force && !w->optional_new_start && !window_frozen_p (w) && !w->force_start)
+        window_start_end_hook_suppress_wsf = true;
+
       ptrdiff_t it_charpos;
 
-      w->optional_new_start = false;
       start_display (&it, w, startp);
       move_it_to (&it, PT, 0, it.last_visible_y, -1,
 		  MOVE_TO_POS | MOVE_TO_X | MOVE_TO_Y);
@@ -16299,14 +16383,28 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	    w->force_start = true;
 #ifdef GLYPH_DEBUG
 	  if (w->force_start)
-	    {
-	      if (window_frozen_p (w))
-		debug_method_add (w, "set force_start from frozen window start");
-	      else
-		debug_method_add (w, "set force_start from optional_new_start");
-	    }
+    {
+      if (window_frozen_p (w))
+        {
+        debug_method_add (w, "set force_start from frozen window start");
+      }
+      else if (w->optional_new_start)
+        {
+        debug_method_add (w, "set force_start from optional_new_start");
+      }
+      else if (w->window_start_end_hook_force)
+        {
+        debug_method_add (w, "set force_start from window_start_end_hook_force");
+      }
+      else
+        {
+        debug_method_add (w, "This situation is not yet contemplated.");
+      }
+    }
 #endif
 	}
+      w->optional_new_start = false;
+      w->window_start_end_hook_force = false;
     }
 
  force_start:
@@ -16334,11 +16432,18 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	 than to get into an infinite loop calling the hook functions
 	 and having them get more errors.  */
       if (!update_mode_line
-	  || ! NILP (Vwindow_scroll_functions))
+	  || ! NILP (Vwindow_scroll_functions)
+	  || ! NILP (BVAR (current_buffer, window_start_end_hook)))
 	{
 	  update_mode_line = true;
 	  w->update_mode_line = true;
-	  startp = run_window_scroll_functions (window, startp);
+
+  /* Run window scroll functions.  */
+  if (!window_start_end_hook_suppress_wsf)
+    startp = run_window_scroll_functions (window, startp);
+
+  run_window_start_end_hook (window, startp, it, "redisplay_window (force_start)");
+
 	}
 
       if (CHARPOS (startp) < BEGV)
@@ -16606,6 +16711,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	     because a window scroll function can have changed the
 	     buffer.  */
 	  || !NILP (Vwindow_scroll_functions)
+	  || !NILP (BVAR (current_buffer, window_start_end_hook))
 	  || MINI_WINDOW_P (w)
 	  || !(used_current_matrix_p
 	       = try_window_reusing_current_matrix (w)))
@@ -16803,6 +16909,8 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
   /* Run scroll hooks.  */
   startp = run_window_scroll_functions (window, it.current.pos);
 
+  run_window_start_end_hook (window, startp, it, "redisplay_window (recenter)");
+
   /* Redisplay the window.  */
   bool use_desired_matrix = false;
   if (!current_matrix_up_to_date_p
@@ -16811,6 +16919,7 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
       /* Don't use try_window_reusing_current_matrix in this case
 	 because it can have changed the buffer.  */
       || !NILP (Vwindow_scroll_functions)
+      || !NILP (BVAR (current_buffer, window_start_end_hook))
       || !just_this_one_p
       || MINI_WINDOW_P (w)
       || !(used_current_matrix_p
@@ -16931,7 +17040,17 @@ redisplay_window (Lisp_Object window, bool just_this_one_p)
 	  clear_glyph_matrix (w->desired_matrix);
 	  if (1 == try_window (window, it.current.pos,
 			       TRY_WINDOW_CHECK_MARGINS))
-	    goto done;
+    {
+      if (!NILP (BVAR (current_buffer, window_start_end_hook))
+          && w->window_start_end_hook_pending)
+        {
+          run_window_start_end_hook (window, it.current.pos, it, "redisplay_window (post-recenter)");
+          clear_glyph_matrix (w->desired_matrix);
+          try_window (window, it.current.pos, 0);
+        }
+      goto done;
+    }
+
 	}
 
       /* If centering point failed to make the whole line visible,
@@ -23686,6 +23805,20 @@ decode_mode_spec (struct window *w, register int c, int field_width,
 	  return "@";
       }
 
+    case 'w':
+      {
+  ptrdiff_t window_start = marker_position (w->start);
+  pint2str (decode_mode_spec_buf, width, window_start);
+  return decode_mode_spec_buf;
+      }
+
+    case 'W':
+      {
+  ptrdiff_t window_end = BUF_Z (b) - w->window_end_pos;
+  pint2str (decode_mode_spec_buf, width, window_end);
+  return decode_mode_spec_buf;
+      }
+
     case 'z':
       /* coding-system (not including end-of-line format) */
     case 'Z':
