Subject: bug#21333: bug#21869: [Patch] Redisplay: after echo area diminishes in size, Follow Mode windows aren't resynchronised.

From: Alan Mackenzie <[email-masked]>
Date: Tue, 17 Nov 2015 17:56:23 +0000

On Mon, Nov 16, 2015 at 03:03:57PM +0000, Alan Mackenzie wrote:
> On Mon, Nov 09, 2015 at 10:50:57PM +0200, Eli Zaretskii wrote:
> > > Date: Mon, 9 Nov 2015 19:42:00 +0000
> > > From: Alan Mackenzie <[email-masked]>
> > > Cc: [email-masked], Pip Cet <[email-masked]>

> > > > Probably bug#830 and bug#21333.

> Quick summary of the bug: with Follow Mode active, with a multi-line
> display in the echo area, do C-f.  The echo area resizes to one line,
> but the Follow Mode windows don't get resynchronised.

> Quick summary of the cause: redisplay_internal calls prepare_menu_bars
> (which invokes the window-size-change-functions hook) before it calls
> echo_area_display (which resizes the echo area).  Thus changes to the
> echo area size aren't yet in place when window-size-change-functions is
> invoked.

> I propose the following strategy to fix the bug:

> 1. Call resize_mini_window from redisplay_internal before the call to
>   prepare_menu_bars.
> 2. Remove the call to resize_mini_window from display_echo_area_1, and
>   make that function of type void.
> 3. Change the contract of echo_area_display, such that the echo area
>   must have been set to the correct height before calling it.
> 4. Adapt message3_nolog (the only other function which calls
>   echo_area_display) to call resize_mini_window.

> As a result of these changes, any change in the size of the echo area
> would be taken into account when invoking window-size-change-functions.

> An advantage of this change is that a recursive invocation of
> redisplay_internal inside echo_area_display could be removed, improving
> performance.

No it couldn't.  That invocation is not a recursive one, it's the main
call of redisplay_internal for the message functions.

Here is a patch implementing the above scheme.  It should also fix
bug#21333 (or, at least, go a long way towards fixing it):

Invoke window-size-change-functions after changing echo area height.

Fixes bug #21869 and probably bug #21333.  Separate the resizing of the echo
area from the displaying of text in it.  This allows
window-size-change-functions to be invoked after the former, but before the
latter, according to its specification.

src/xdisp.c (message3_nolog): Invoke resize_mini_window_1 before calling
echo_area_display.
(display_echo_area): Change type to (static) void.  Change contract such that
the echo area must have been resized, if nec., before calling, and that this
function doesn't resize.
(display_echo_area_1): Now always return false, since the function never
resizes.
(echo_area_display): Add extra parameter `window_height_changed_p', replacing
a local of the same name.  The function no longer resizes the echo area.
(redisplay_internal): Invoke resize_mini_window_1 before calling
prepare_menu_bars (which invokes window-size-change-functions).

diff --git a/src/xdisp.c b/src/xdisp.c
index 30dfac5..68e56b7 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -800,6 +800,9 @@ static int redisplay_mode_lines (Lisp_Object, bool);
 static void handle_line_prefix (struct it *);
 
 static void handle_stop_backwards (struct it *, ptrdiff_t);
+static bool with_echo_area_buffer (struct window *, int,
+				   bool (*)(ptrdiff_t, Lisp_Object),
+				   ptrdiff_t, Lisp_Object);
 static void unwind_with_echo_area_buffer (Lisp_Object);
 static Lisp_Object with_echo_area_buffer_unwind_data (struct window *);
 static bool current_message_1 (ptrdiff_t, Lisp_Object);
@@ -815,7 +818,7 @@ static void push_it (struct it *, struct text_pos *);
 static void iterate_out_of_display_property (struct it *);
 static void pop_it (struct it *);
 static void redisplay_internal (void);
-static void echo_area_display (bool);
+static void echo_area_display (bool, bool);
 static void redisplay_windows (Lisp_Object);
 static void redisplay_window (Lisp_Object, bool);
 static Lisp_Object redisplay_window_error (Lisp_Object);
@@ -10234,8 +10237,10 @@ message3_nolog (Lisp_Object m)
       /* Get the frame containing the mini-buffer
 	 that the selected frame is using.  */
       Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (sf);
+      struct window *mw = XWINDOW (mini_window);
       Lisp_Object frame = XWINDOW (mini_window)->frame;
       struct frame *f = XFRAME (frame);
+      bool window_height_changed_p;
 
       if (FRAME_VISIBLE_P (sf) && !FRAME_VISIBLE_P (f))
 	Fmake_frame_visible (frame);
@@ -10253,7 +10258,12 @@ message3_nolog (Lisp_Object m)
 	clear_message (true, true);
 
       do_pending_window_change (false);
-      echo_area_display (true);
+      if (window_height_changed_p =
+	  with_echo_area_buffer (mw, display_last_displayed_message_p,
+				 resize_mini_window_1,
+				 (intptr_t) mw, Qt))
+	FRAME_WINDOW_SIZES_CHANGED (sf) = true;
+      echo_area_display (true, window_height_changed_p);
       do_pending_window_change (false);
       if (FRAME_TERMINAL (f)->frame_up_to_date_hook)
 	(*FRAME_TERMINAL (f)->frame_up_to_date_hook) (f);
@@ -10711,15 +10721,15 @@ setup_echo_area_for_printing (bool multibyte_p)
 }

-/* Display an echo area message in window W.  Value is true if W's
-   height is changed.  If display_last_displayed_message_p,
-   display the message that was last displayed, otherwise
-   display the current message.  */
+/* Display an echo area message in window W.  If
+   display_last_displayed_message_p, display the message that was last
+   displayed, otherwise display the current message.  The window
+   height of W must already have been set for the message.  */
 
-static bool
+static void
 display_echo_area (struct window *w)
 {
-  bool no_message_p, window_height_changed_p;
+  bool no_message_p;
 
   /* Temporarily disable garbage collections while displaying the echo
      area.  This is done because a GC can print a message itself.
@@ -10735,24 +10745,22 @@ display_echo_area (struct window *w)
   bool i = display_last_displayed_message_p;
   no_message_p = NILP (echo_area_buffer[i]);
 
-  window_height_changed_p
-    = with_echo_area_buffer (w, display_last_displayed_message_p,
-			     display_echo_area_1,
-			     (intptr_t) w, Qnil);
+  with_echo_area_buffer (w, display_last_displayed_message_p,
+			 display_echo_area_1,
+			 (intptr_t) w, Qnil);
 
   if (no_message_p)
     echo_area_buffer[i] = Qnil;
 
   unbind_to (count, Qnil);
-  return window_height_changed_p;
 }

 /* Helper for display_echo_area.  Display the current buffer which
    contains the current echo area message in window W, a mini-window,
-   a pointer to which is passed in A1.  A2..A4 are currently not used.
-   Change the height of W so that all of the message is displayed.
-   Value is true if height of W was changed.  */
+   a pointer to which is passed in A1.  The height of W must already
+   have been set to the correct height for the message.  Always
+   returns false.  */
 
 static bool
 display_echo_area_1 (ptrdiff_t a1, Lisp_Object a2)
@@ -10767,11 +10775,6 @@ display_echo_area_1 (ptrdiff_t a1, Lisp_Object a2)
      here.  */
   forget_escape_and_glyphless_faces ();
 
-  /* Do this before displaying, so that we have a large enough glyph
-     matrix for the display.  If we can't get enough space for the
-     whole text, display the last N lines.  That works by setting w->start.  */
-  bool window_height_changed_p = resize_mini_window (w, false);
-
   /* Use the starting position chosen by resize_mini_window.  */
   SET_TEXT_POS_FROM_MARKER (start, w->start);
 
@@ -10780,7 +10783,7 @@ display_echo_area_1 (ptrdiff_t a1, Lisp_Object a2)
   XSETWINDOW (window, w);
   try_window (window, start, 0);
 
-  return window_height_changed_p;
+  return false;
 }

@@ -11196,16 +11199,17 @@ clear_garbaged_frames (void)
 }

-/* Redisplay the echo area of the selected frame.  If UPDATE_FRAME_P, update
-   selected_frame.  */
+/* Redisplay the echo area of the selected frame.  If UPDATE_FRAME_P,
+   update selected_frame.  WINDOW_HEIGHT_CHANGED_P states whether the
+   echo area's height has just been changed.  It is ignored unless
+   UPDATE_FRAME_P is true.  */
 
 static void
-echo_area_display (bool update_frame_p)
+echo_area_display (bool update_frame_p, bool window_height_changed_p)
 {
   Lisp_Object mini_window;
   struct window *w;
   struct frame *f;
-  bool window_height_changed_p = false;
   struct frame *sf = SELECTED_FRAME ();
 
   mini_window = FRAME_MINIBUF_WINDOW (sf);
@@ -11230,7 +11234,7 @@ echo_area_display (bool update_frame_p)
   if (!NILP (echo_area_buffer[0]) || minibuf_level == 0)
     {
       echo_area_window = mini_window;
-      window_height_changed_p = display_echo_area (w);
+      display_echo_area (w);
       w->must_be_updated_p = true;
 
       /* Update the display, unless called from redisplay_internal.
@@ -13497,6 +13501,19 @@ redisplay_internal (void)
   /* Clear frames marked as garbaged.  */
   clear_garbaged_frames ();
 
+  /* Resize the echo area, if needed, before the invocation of
+     window-size-change-functions.  */
+  {
+    Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (sf);
+    struct window *mw = XWINDOW (mini_window);
+
+    if (!MINI_WINDOW_P (XWINDOW (selected_window))
+	&& with_echo_area_buffer (mw, display_last_displayed_message_p,
+				  resize_mini_window_1,
+				  (intptr_t) mw, Qnil))
+      FRAME_WINDOW_SIZES_CHANGED (sf) = true;
+  }
+
   /* Build menubar and tool-bar items.  */
   if (NILP (Vmemory_full))
     prepare_menu_bars ();
@@ -13534,7 +13551,7 @@ redisplay_internal (void)
 	     echo-area doesn't show through.  */
 	  && !MINI_WINDOW_P (XWINDOW (selected_window))))
     {
-      echo_area_display (false);
+      echo_area_display (false, false);
 
       if (message_cleared_p)
 	update_miniwindow_p = true;

-- 
Alan Mackenzie (Nuremberg, Germany).