unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Window change functions
@ 2018-12-25  9:41 martin rudalics
  2018-12-25 20:13 ` Gnus crash (was: Window change functions) Juri Linkov
  2018-12-26 19:55 ` Window change functions Stefan Monnier
  0 siblings, 2 replies; 18+ messages in thread
From: martin rudalics @ 2018-12-25  9:41 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 3358 bytes --]

The attached patch should radically change the way we run functions
when window changes occur.  The only hook it does not affect is
'window-scroll-functions'.  For the remainder we now would have the
following four hooks:

(1) 'window-state-change-functions' runs when a window got a new
     buffer or was created or deleted since last redisplay.

(2) 'window-size-change-functions' runs when a window was created or
     got a new buffer, body or total size since last redisplay.

(3) 'window-configuration-change-hook' runs as (1) and (2) together.

(4) 'window-selection-change-functions' run when a window got
     (de-)selected since last redisplay.

In contrast with current behavior (3) does not trigger immediately
when, for example, a window is created, deleted or resized or gets a
another buffer assigned.  Rather, the code now only sets an internal
frame-local flag in these cases.  During redisplay, the code inspects
these flags, checks whether any window of the corresponding frame
really changed and calls the functions on the hooks only in that case.

This means that, for example, running 'save-window-excursion' will
call such hooks only if a redisplay is embedded within that form.  It
also means that any code that changes windows internally won't have to
care about calling `run-window-configuration-change-hook' any more.

In addition, there are now seven functions that give more information
about the state of windows at the last time hooks were run for them.
Please see the documentation in the "Window Hooks" section of the
Emacs manual for these and the new behavior in general.

A number of issues are yet unresolved:

- Some functions on 'window-configuration-change-hook' (notably in
   erc-track.el and rcirc.el) put further code on 'post-command-hook'.
   This concept will be broken since 'post-command-hook' now runs
   before 'window-configuration-change-hook'.

   The solution in the cases I've seen is to run that code right in
   'window-configuration-change-hook'.  xterm.el has a bit more
   convoluted code whose purpose I haven't been able to figure out yet.
   The solution should be similar though.

- It's not entirely clear to me whether the current position where
   change functions are called is good.  The choice was rather
   deliberate - a place where we look whether frame sizes were
   changed externally.  Feel free to propose a better place.

- Currently, there is no hook that runs state, size and selection
   changes in combination.  If such a hook is needed please tell me.

- For efficiency, local and global hooks are run in a somewhat
   intermingled way.  If you see any problems with that, please tell
   me.

- Ideally, we should run 'window-scroll-functions' in the same group
   too.  But currently run_window_scroll_functions returns a value that
   is used by redisplay and I don't know whether changing that could
   have nasty complications.

- Running window change functions generally does not save/restore
   things like the current buffer, selected window, frame or match
   data.  I don't see a real motivation to do that since functions run
   by these hooks could change arbitrary things.  But if someone feels
   strongly about them I can make the necessary provisions.

Please apply the patch, run your Emacs with it and report anomalies.

Thanks for your attention, martin

[-- Attachment #2: window-change-functions.diff --]
[-- Type: text/plain, Size: 79021 bytes --]

diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi
index f2f6cd3..0633739 100644
--- a/doc/lispref/windows.texi
+++ b/doc/lispref/windows.texi
@@ -568,12 +568,6 @@ Window Sizes
 children.
 @end defun
 
-@defun window-pixel-height-before-size-change &optional Lisp_Object &optional window
-This function returns the height of window @var{window} in pixels at the
-time @code{window-size-change-functions} was run for the last time on
-@var{window}'s frame (@pxref{Window Hooks}).
-@end defun
-
 @cindex window pixel width
 @cindex pixel width of a window
 @cindex total pixel width of a window
@@ -588,12 +582,6 @@ Window Sizes
 the screen areas spanned by its children.
 @end defun
 
-@defun window-pixel-width-before-size-change &optional Lisp_Object &optional window
-This function returns the width of window @var{window} in pixels at the
-time @code{window-size-change-functions} was run for the last time on
-@var{window}'s frame (@pxref{Window Hooks}).
-@end defun
-
 @cindex full-width window
 @cindex full-height window
   The following functions can be used to determine whether a given
@@ -5695,10 +5683,6 @@ Window Configurations
 windows might be opened in other frames (@pxref{Choosing Window}), and
 @code{save-window-excursion} only saves and restores the window
 configuration on the current frame.
-
-Do not use this macro in @code{window-size-change-functions}; exiting
-the macro triggers execution of @code{window-size-change-functions},
-leading to an endless loop.
 @end defmac
 
 @defun window-configuration-p object
@@ -6009,27 +5993,26 @@ Window Parameters
 versions of Emacs.
 @end table
 
+
 @node Window Hooks
 @section Hooks for Window Scrolling and Changes
 @cindex hooks for window operations
 
-This section describes how a Lisp program can take action whenever a
-window displays a different part of its buffer or a different buffer.
-There are three actions that can change this: scrolling the window,
-switching buffers in the window, and changing the size of the window.
-The first two actions run @code{window-scroll-functions}; the last runs
-@code{window-size-change-functions}.
+This section describes how Lisp programs can take action after a
+window has been scrolled or other window modifications occurred.  We
+first consider the case where a window shows a different part of its
+buffer.
 
 @defvar window-scroll-functions
 This variable holds a list of functions that Emacs should call before
-redisplaying a window with scrolling.  Displaying a different buffer in
-the window also runs these functions.
+redisplaying a window with scrolling.  Displaying a different buffer
+in a window and making a new window also call these functions.
 
-This variable is not a normal hook, because each function is called with
-two arguments: the window, and its new display-start position.  At the
-time of the call, the display-start position of the window argument is
-already set to its new value, and the buffer to be displayed in the
-window is already set as the current buffer.
+This variable is not a normal hook, because each function is called
+with two arguments: the window, and its new display-start position.
+At the time of the call, the display-start position of the argument
+window is already set to its new value, and the buffer to be displayed
+in the window is set as the current buffer.
 
 These functions must take care when using @code{window-end}
 (@pxref{Window Start and End}); if you need an up-to-date value, you
@@ -6040,63 +6023,226 @@ Window Hooks
 work.
 @end defvar
 
-@defun run-window-scroll-functions &optional window
-This function calls @code{window-scroll-functions} for the specified
-@var{window}, which defaults to the selected window.
-@end defun
+In addition, you can use @code{jit-lock-register} to register a Font
+Lock fontification function, which will be called whenever parts of a
+buffer are (re)fontified because a window was scrolled or its size
+changed.  @xref{Other Font Lock Variables}.
+
+@cindex window change functions
+   The remainder of this section covers four hooks that are called at
+the end of redisplay provided a significant, non-scrolling change of a
+window has been detected.  For simplicity, these hooks and the
+functions they call will be collectively referred to as @dfn{window
+change functions}.
+
+@cindex window state change
+The first of these hooks is run after a @dfn{window state change} is
+detected, which means that a window was created, deleted or assigned
+another buffer.
+
+@defvar window-state-change-functions
+This variable specifies functions called at the end of redisplay when
+window states have changed.  The value should be a list of functions
+that take one argument.
+
+Functions specified buffer-locally are called for any window showing
+the corresponding buffer if that window has been created or assigned
+that buffer since the last time window change functions were run.  In
+this case the window is passed as argument.
+
+Functions specified by the default value are called for a frame if at
+least one window on that frame has been added, deleted or assigned
+another buffer since the last time window change functions were run.
+In this case the frame is passed as argument.
+@end defvar
+
+@cindex window size change
+The second of these hooks is run after a @dfn{window size change} has
+been detected which means that a window was created, assigned another
+buffer, or changed its total size or that of its text area.
 
 @defvar window-size-change-functions
-This variable holds a list of functions to be called if the size of any
-window changes for any reason.  The functions are called once per
-redisplay, and once for each frame on which size changes have occurred.
-
-Each function receives the frame as its sole argument.  To find out
-whether a specific window has changed size, compare the return values of
-@code{window-pixel-width-before-size-change} and
-@code{window-pixel-width} respectively
-@code{window-pixel-height-before-size-change} and
-@code{window-pixel-height} for that window (@pxref{Window Sizes}).
-
-The buffer-local value of this hook is run once for the buffer and the
-frame in question, provided at least one window showing the buffer on
-that frame has changed its size.  As it still receives the frame as
-its sole argument, any function called on a buffer-local basis will be
-oblivious to which window(s) showing the buffer changed its (their)
-size and has to check out these windows by using the method described
-in the previous paragraph.
-
-These function are usually only called when at least one window was
-added or has changed size since the last time this hook was run for
-the associated frame.  In some rare cases this hook also runs when a
-window that was added intermittently has been deleted afterwards.  In
-these cases none of the windows on the frame will appear to have
-changed its size.
+This variable specifies functions called at the end of redisplay when
+a window size change occurred.  The value should be a list of
+functions that take one argument.
+
+Functions specified buffer-locally are called for any window showing
+the corresponding buffer if that window has been added or assigned
+another buffer, total or body size since the last time window change
+functions were run.  In this case the window is passed as argument.
+
+Functions specified by the default value are called for a frame if at
+least one window on that frame has been added or assigned another
+buffer, total or body size since the last time window change functions
+were run.  In this case the frame is passed as argument.
+@end defvar
+
+@cindex window selection change
+The third of these hooks is run after a @dfn{window selection change}
+has selected another window since the last redisplay.
+
+@defvar window-selection-change-functions
+This variable specifies functions called at the end of redisplay when
+the selected window or a frame's selected window has changed.  The
+value should be a list of functions that take one argument.
+
+Functions specified buffer-locally are called for any window showing
+the corresponding buffer if that window has been selected or
+deselected (among all windows or among all windows on its frame) since
+the last time window change functions were run.  In this case the
+window is passed as argument.
+
+Functions specified by the default value are called for a frame if
+that frame has been selected or deselected or the frame's selected
+window has changed since the last time window change functions were
+run.  In this case the frame is passed as argument.
 @end defvar
 
+@cindex window configuration change
+The fourth of these hooks is run when a @dfn{window configuration
+change} has been detected which means that either the state or the
+size of a window changed.
+
 @defvar window-configuration-change-hook
-A normal hook that is run every time the window configuration of a
-frame changes.  Window configuration changes include splitting and
-deleting windows, and the display of a different buffer in a window.
-
-The hook can be also used for tracking changes of window sizes.  It
-is, however, not run when the size of a frame changes or automatic
-resizing of a minibuffer window (@pxref{Minibuffer Windows}) changes
-the size of another window.  As a rule, adding a function to
-@code{window-size-change-functions}, see above, is the recommended way
-for reliably tracking size changes of any window.
-
-The buffer-local value of this hook is run once for each window on the
-affected frame, with the relevant window selected and its buffer
-current.  The global value of this hook is run once for the modified
-frame, with that frame selected.
+This variable specifies functions called at the end of redisplay when
+either the state or the size of a window has changed.  The value
+should be a list of functions that take no argument.
+
+Functions specified buffer-locally are called for any window showing
+the corresponding buffer if at least one window on that frame has been
+added, deleted or assigned another buffer, total or body size since
+the last time window change functions were run.  Each call is
+performed with the window showing the buffer temporarily selected and
+its buffer current.
+
+Functions specified by the default value are called for each frame if
+at least one window on that frame has been added, deleted or assigned
+another buffer, total or body size since the last time window change
+functions were run.  Each call is performed with the frame temporarily
+selected and the selected window's buffer current.
 @end defvar
 
-@defun run-window-configuration-change-hook &optional frame
-This function runs @code{window-configuration-change-hook} for the
-specified @var{frame}, which defaults to the selected frame.
+Window change functions are called at the end of redisplay for each
+frame as follows: First, any buffer-local window state change
+function, window size change function and selected window change
+functions are called in this order.  Next, the default values for
+these functions are called in the same order.  Then any buffer-local
+window configuration change functions are called followed by functions
+specified by the default value of those functions.
+
+   Window change functions are run for a specific frame only if a
+corresponding change was registered for that frame earlier.  Such
+changes include the creation or deletion of a window or the assignment
+of another buffer or size to a window.  Note that even when such a
+change has been registered, this does not mean that any of the hooks
+described above is run.  If, for example, a change was registered
+within the scope of a window excursion (@pxref{Window
+Configurations}), this will trigger a call of window change functions
+only if that excursion still persists at the time change functions are
+run.  If it is exited earlier, hooks will be run only if registered by
+a change outside the scope of that excursion.
+
+   While window change functions are run, the functions described next
+can be called to get more insight into what has changed for a specific
+window or frame since the last redisplay.  All these functions take a
+live window as single, optional argument, defaulting to the selected
+window.
+
+@defun window-old-buffer &optional window
+This function returns the buffer shown in @var{window} at the last
+time window change functions were run for @var{window}'s frame.  If it
+returns @code{nil}, @var{window} has been created after that.  If it
+returns @code{t}, @var{window} was not shown at that time but has been
+restored from a previously saved window configuration afterwards.
+Otherwise, the return value is the buffer shown by @code{window} at
+that time.
 @end defun
 
-  In addition, you can use @code{jit-lock-register} to register a Font
-Lock fontification function, which will be called whenever parts of a
-buffer are (re)fontified because a window was scrolled or its size
-changed.  @xref{Other Font Lock Variables}.
+@defun window-old-pixel-width &optional window
+This function returns the total pixel width of @var{window} the
+last time window change functions found @code{window} live on its
+frame.  It is zero if @code{window} was created after that.
+@end defun
+
+@defun window-old-pixel-height &optional window
+This function returns the total pixel height of @var{window} the last
+time window change functions found @code{window} live on its frame.
+It is zero if @code{window} was created after that.
+@end defun
+
+@defun window-old-body-pixel-width &optional window
+This function returns the pixel width of @var{window}'s text area the
+last time window change functions found @code{window} live on its
+frame.  It is zero if @code{window} was created after that.
+@end defun
+
+@defun window-old-body-pixel-height &optional window
+This function returns the pixel height of @var{window}'s text area the
+last time window change functions found @code{window} live on its
+frame.  It is zero if @code{window} was created after that.
+@end defun
+
+In order to find out which window or frame was selected the last time
+window change functions were run, the following functions can be used:
+
+@defun frame-old-selected-window &optional frame
+This function returns the selected window of @var{frame} at the last
+time window change functions were run.  If omitted or @code{nil}
+@var{frame} defaults to the selected frame.
+@end defun
+
+@defun old-selected-window
+This function returns the selected window at the last time window
+change functions were run.
+@end defun
+
+@defun old-selected-frame
+This function returns the selected frame at the last time window
+change functions were run.
+@end defun
+
+Note that window change functions provide no information about which
+windows have been deleted since the last time they were run.  If
+necessary, an application should remember any window showing a
+specific buffer in a local variable of that buffer and update it in a
+function run by the default value of
+@code{window-state-change-functions} or
+@code{window-configuration-change-hook} (the only hooks triggered by
+the deletion of windows).
+
+   The following caveats should be considered when adding a function
+to window change functions:
+
+@itemize @bullet
+@item
+Some operations will not trigger a call of window change functions.
+These include showing another buffer in a minibuffer window or any
+change of a tooltip window.
+
+@item
+Window change functions should not create or delete windows or change
+the buffer, size or selection status of any window because there is no
+guarantee that the information about such a change will be propagated
+to other window change functions.  If at all, any such change should
+be executed only by the last function listed by the default value of
+@code{window-configuration-change-hook}.
+
+@item
+Macros like @code{save-window-excursion}, @code{with-selected-window}
+or @code{with-current-buffer} can be used when running window change
+functions.
+
+@item
+Running window change functions does not save and restore match data.
+Unless running @code{window-configuration-change-hook} it does not
+save or restore the selected window or frame or the current buffer
+either.
+
+@item
+Any redisplay triggering the run of window change functions may be
+aborted.  If the abort occurs before window change functions have run
+to their completion, they will be run again with the previous values,
+that is, as if redisplay had not been performed.  If aborted later,
+they will be run with the new values, that is, as if redisplay had
+been actually performed.
+@end itemize
diff --git a/lisp/frame.el b/lisp/frame.el
index 56b8c54..b722e6f 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -1745,20 +1745,17 @@ frame-size-changed-p
   (let* ((frame (window-normalize-frame frame))
          (root (frame-root-window frame))
          (mini (minibuffer-window frame))
-         (mini-height-before-size-change 0)
+         (mini-old-height 0)
          (mini-height 0))
     ;; FRAME's minibuffer window counts iff it's on FRAME and FRAME is
     ;; not a minibuffer-only frame.
     (when (and (eq (window-frame mini) frame) (not (eq mini root)))
-      (setq mini-height-before-size-change
-            (window-pixel-height-before-size-change mini))
+      (setq mini-old-height (window-old-pixel-height mini))
       (setq mini-height (window-pixel-height mini)))
     ;; Return non-nil when either the width of the root or the sum of
     ;; the heights of root and minibuffer window changed.
-    (or (/= (window-pixel-width-before-size-change root)
-            (window-pixel-width root))
-        (/= (+ (window-pixel-height-before-size-change root)
-               mini-height-before-size-change)
+    (or (/= (window-old-pixel-width root) (window-pixel-width root))
+        (/= (+ (window-old-pixel-height root) mini-old-height)
             (+ (window-pixel-height root) mini-height)))))
 \f
 ;;;; Frame/display capabilities.
diff --git a/lisp/window.el b/lisp/window.el
index 50aec86..8797238 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -2041,6 +2041,8 @@ window-resizable-p
 ;; Aliases of functions defined in window.c.
 (defalias 'window-height 'window-total-height)
 (defalias 'window-width 'window-body-width)
+(defalias 'window-pixel-width-before-size-change 'window-old-pixel-width)
+(defalias 'window-pixel-height-before-size-change 'window-old-pixel-height)
 
 (defun window-full-height-p (&optional window)
   "Return t if WINDOW is as high as its containing frame.
@@ -2757,8 +2759,7 @@ window--resize-mini-window
 	;; The following routine catches the case where we want to resize
 	;; a minibuffer-only frame.
 	(when (resize-mini-window-internal window)
-	  (window--pixel-to-total frame)
-	  (run-window-configuration-change-hook frame))))))
+	  (window--pixel-to-total frame))))))
 
 (defun window--resize-apply-p (frame &optional horizontal)
   "Return t when a window on FRAME shall be resized vertically.
@@ -2856,9 +2857,7 @@ window-resize
 	(window--resize-siblings window delta horizontal ignore))
       (when (window--resize-apply-p frame horizontal)
 	(if (window-resize-apply frame horizontal)
-	    (progn
-	      (window--pixel-to-total frame horizontal)
-	      (run-window-configuration-change-hook frame))
+	    (window--pixel-to-total frame horizontal)
 	  (error "Failed to apply resizing %s" window))))
      (t
       (error "Cannot resize window %s" window)))))
@@ -3577,9 +3576,7 @@ adjust-window-trailing-edge
 	;; Don't report an error in the standard case.
 	(when (window--resize-apply-p frame horizontal)
 	  (if (window-resize-apply frame horizontal)
-	      (progn
-		(window--pixel-to-total frame horizontal)
-		(run-window-configuration-change-hook frame))
+	      (window--pixel-to-total frame horizontal)
 	    ;; But do report an error if applying the changes fails.
 	    (error "Failed adjusting window %s" window))))))))
 
@@ -4110,7 +4107,6 @@ delete-window
 	  ;; `delete-window-internal' has selected a window that should
 	  ;; not be selected, fix this here.
 	  (other-window -1 frame))
-	(run-window-configuration-change-hook frame)
 	(window--check frame)
 	;; Always return nil.
 	nil))))
@@ -4196,7 +4192,6 @@ delete-other-windows
       ;; If WINDOW is the main window of its frame do nothing.
       (unless (eq window main)
 	(delete-other-windows-internal window main)
-	(run-window-configuration-change-hook frame)
 	(window--check frame))
       ;; Always return nil.
       nil)))
@@ -5184,7 +5179,6 @@ split-window
 	  (unless size
             (window--sanitize-window-sizes horizontal))
 
-	  (run-window-configuration-change-hook frame)
 	  (run-window-scroll-functions new)
 	  (window--check frame)
 	  ;; Always return the new window.
@@ -5415,15 +5409,13 @@ balance-windows
     (balance-windows-1 window)
     (when (window--resize-apply-p frame)
       (window-resize-apply frame)
-      (window--pixel-to-total frame)
-      (run-window-configuration-change-hook frame))
+      (window--pixel-to-total frame))
     ;; Balance horizontally.
     (window--resize-reset (window-frame window) t)
     (balance-windows-1 window t)
     (when (window--resize-apply-p frame t)
       (window-resize-apply frame t)
-      (window--pixel-to-total frame t)
-      (run-window-configuration-change-hook frame))))
+      (window--pixel-to-total frame t))))
 
 (defun window-fixed-size-p (&optional window direction)
   "Return t if WINDOW cannot be resized in DIRECTION.
@@ -9413,15 +9405,8 @@ window--adjust-process-windows
               (when size
                 (set-process-window-size process (cdr size) (car size))))))))))
 
-;; Remove the following call in Emacs 27, running
-;; 'window-size-change-functions' should suffice.
-(add-hook 'window-configuration-change-hook 'window--adjust-process-windows)
-
-;; Catch any size changes not handled by
-;; 'window-configuration-change-hook' (Bug#32720, "another issue" in
-;; Bug#33230).
-(add-hook 'window-size-change-functions (lambda (_frame)
-                                          (window--adjust-process-windows)))
+(add-hook 'window-size-change-functions
+          (lambda (_frame) (window--adjust-process-windows)))
 \f
 ;; Some of these are in tutorial--default-keys, so update that if you
 ;; change these.
diff --git a/src/frame.c b/src/frame.c
index 4371ef7..985e2a8 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -55,9 +55,11 @@
 #endif
 
 /* The currently selected frame.  */
-
 Lisp_Object selected_frame;
 
+/* The selected frame the last time window change functions were run.  */
+Lisp_Object old_selected_frame;
+
 /* A frame which is not just a mini-buffer, or NULL if there are no such
    frames.  This is usually the most recent such frame that was selected.  */
 
@@ -855,7 +857,8 @@ struct frame *
   f->ns_transparent_titlebar = false;
 #endif
 #endif
-
+  /* This one should never be zero.  */
+  f->change_stamp = 1;
   root_window = make_window ();
   rw = XWINDOW (root_window);
   if (mini_p)
@@ -1451,7 +1454,8 @@ of them (the selected terminal frame) is actually displayed.
   return do_switch_frame (frame, 1, 0, norecord);
 }
 
-DEFUN ("handle-switch-frame", Fhandle_switch_frame, Shandle_switch_frame, 1, 1, "^e",
+DEFUN ("handle-switch-frame", Fhandle_switch_frame,
+       Shandle_switch_frame, 1, 1, "^e",
        doc: /* Handle a switch-frame event EVENT.
 Switch-frame events are usually bound to this function.
 A switch-frame event is an event Emacs sends itself to
@@ -1471,6 +1475,18 @@ of them (the selected terminal frame) is actually displayed.
 {
   return selected_frame;
 }
+
+DEFUN ("old-selected-frame", Fold_selected_frame,
+       Sold_selected_frame, 0, 0, 0,
+       doc: /* Return the old selected FRAME.
+FRAME must be a live frame and defaults to the selected one.
+
+The return value is the frame selected the last time window change
+functions were run.  */)
+  (void)
+{
+  return old_selected_frame;
+}
 \f
 DEFUN ("frame-list", Fframe_list, Sframe_list,
        0, 0, 0,
@@ -6098,9 +6114,10 @@ This variable is effective only with the X toolkit (and there only when
   defsubr (&Swindow_system);
   defsubr (&Sframe_windows_min_size);
   defsubr (&Smake_terminal_frame);
-  defsubr (&Shandle_switch_frame);
   defsubr (&Sselect_frame);
+  defsubr (&Shandle_switch_frame);
   defsubr (&Sselected_frame);
+  defsubr (&Sold_selected_frame);
   defsubr (&Sframe_list);
   defsubr (&Sframe_parent);
   defsubr (&Sframe_ancestor_p);
diff --git a/src/frame.h b/src/frame.h
index ad7376a..707be77 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -125,6 +125,10 @@ struct frame
      The selected window of the selected frame is Emacs's selected window.  */
   Lisp_Object selected_window;
 
+  /* This frame's selected window when run_window_change_functions was
+     called the last time on this frame.  */
+  Lisp_Object old_selected_window;
+
   /* This frame's minibuffer window.
      Most frames have their own minibuffer windows,
      but only the selected frame's minibuffer window
@@ -321,9 +325,14 @@ struct frame
      cleared.  */
   bool_bf explicit_name : 1;
 
-  /* True if configuration of windows on this frame has changed since
-     last call of run_window_size_change_functions.  */
-  bool_bf window_configuration_changed : 1;
+  /* True if at least one window on this frame changed since the last
+     call of run_window_change_functions.  Changes are either "state
+     changes" (a window has been created, deleted or got assigned
+     another buffer) or "size changes" (the total or body size of a
+     window changed).  run_window_change_functions exits early unless
+     either this flag is true or a window selection happened on this
+     frame.  */
+  bool_bf window_change : 1;
 
   /* True if the mouse has moved on this display device
      since the last time we checked.  */
@@ -406,6 +415,20 @@ struct frame
 
   /* Bitfield area ends here.  */
 
+  /* This frame's change stamp, set the last time window change
+     functions were run for this frame.  Should never be 0 because
+     that's the change stamp of a new window.  A window was not on a
+     frame the last run_window_change_functions was called on it if
+     it's change stamp differs from that of its frame.  */
+  int change_stamp;
+
+  /* This frame's number of windows, set the last time window change
+     functions were run for this frame.  Should never be 0 even for
+     minibuffer-only frames.  If no window has been added, this allows
+     to detect whether a window was deleted on this frame since the
+     last time run_window_change_functions was called on it.  */
+  ptrdiff_t number_of_windows;
+
   /* Number of lines (rounded up) of tool bar.  REMOVE THIS  */
   int tool_bar_lines;
 
@@ -662,6 +685,11 @@ struct frame
   f->selected_window = val;
 }
 INLINE void
+fset_old_selected_window (struct frame *f, Lisp_Object val)
+{
+  f->old_selected_window = val;
+}
+INLINE void
 fset_title (struct frame *f, Lisp_Object val)
 {
   f->title = val;
@@ -908,10 +936,9 @@ struct frame
    are frozen on frame F.  */
 #define FRAME_WINDOWS_FROZEN(f) (f)->frozen_window_starts
 
-/* True if the frame's window configuration has changed since last call
-   of run_window_size_change_functions.  */
-#define FRAME_WINDOW_CONFIGURATION_CHANGED(f)	\
-  (f)->window_configuration_changed
+/* True if at least one window changed on frame F since the last time
+   window change functions were run on F.  */
+#define FRAME_WINDOW_CHANGE(f) (f)->window_change
 
 /* The minibuffer window of frame F, if it has one; otherwise nil.  */
 #define FRAME_MINIBUF_WINDOW(f) f->minibuffer_window
@@ -919,8 +946,10 @@ struct frame
 /* The root window of the window tree of frame F.  */
 #define FRAME_ROOT_WINDOW(f) f->root_window
 
-/* The currently selected window of the window tree of frame F.  */
+/* The currently selected window of frame F.  */
 #define FRAME_SELECTED_WINDOW(f) f->selected_window
+/* The old selected window of frame F.  */
+#define FRAME_OLD_SELECTED_WINDOW(f) f->old_selected_window
 
 #define FRAME_INSERT_COST(f) (f)->insert_line_cost
 #define FRAME_DELETE_COST(f) (f)->delete_line_cost
@@ -1215,6 +1244,7 @@ struct frame
   (f)->iconified = (eassert (0 <= (i) && (i) <= 1), (i))
 
 extern Lisp_Object selected_frame;
+extern Lisp_Object old_selected_frame;
 
 #if ! (defined USE_GTK || defined HAVE_NS)
 extern int frame_default_tool_bar_height;
diff --git a/src/window.c b/src/window.c
index 14b3364..8297022 100644
--- a/src/window.c
+++ b/src/window.c
@@ -77,6 +77,11 @@ static struct window *set_window_scroll_bars (struct window *, Lisp_Object,
    FRAME_SELECTED_WINDOW (selected_frame).  */
 Lisp_Object selected_window;
 
+/* The value of selected_window at the last time window change
+   functions were run.  This is always the same as
+   FRAME_OLD_SELECTED_WINDOW (old_selected_frame).  */
+Lisp_Object old_selected_window;
+
 /* A list of all windows for use by next_window and Fwindow_list.
    Functions creating or deleting windows should invalidate this cache
    by setting it to nil.  */
@@ -304,6 +309,12 @@ struct window *
   adjust_window_count (w, 1);
 }
 
+static void
+wset_old_buffer (struct window *w, Lisp_Object val)
+{
+  w->old_buffer = val;
+}
+
 DEFUN ("windowp", Fwindowp, Swindowp, 1, 1, 0,
        doc: /* Return t if OBJECT is a window and nil otherwise.  */)
   (Lisp_Object object)
@@ -428,6 +439,22 @@ struct window *
   return window;
 }
 
+DEFUN ("frame-old-selected-window", Fframe_old_selected_window,
+       Sframe_old_selected_window, 0, 1, 0,
+       doc: /* Return old selected window of FRAME.
+FRAME must be a live frame and defaults to the selected one.
+
+The return value is the window selected on FRAME the last time window
+change functions were run for FRAME.  */)
+  (Lisp_Object frame)
+{
+  if (NILP (frame))
+    frame = selected_frame;
+  CHECK_LIVE_FRAME (frame);
+
+  return XFRAME (frame)->old_selected_window;
+}
+
 DEFUN ("set-frame-selected-window", Fset_frame_selected_window,
        Sset_frame_selected_window, 2, 3, 0,
        doc: /* Set selected window of FRAME to WINDOW.
@@ -465,6 +492,16 @@ struct window *
   return selected_window;
 }
 
+DEFUN ("old-selected-window", Fold_selected_window,
+       Sold_selected_window, 0, 0, 0,
+       doc: /* Return the old selected window.
+The return value is the window selected the last time window change
+functions were run.  */)
+  (void)
+{
+  return old_selected_window;
+}
+
 EMACS_INT window_select_count;
 
 /* If select_window is called with inhibit_point_swap true it will
@@ -597,9 +634,33 @@ struct window *
   (Lisp_Object window)
 {
   struct window *w = decode_any_window (window);
+
   return WINDOW_LEAF_P (w) ? w->contents : Qnil;
 }
 
+DEFUN ("window-old-buffer", Fwindow_old_buffer, Swindow_old_buffer, 0, 1, 0,
+       doc: /* Return the old buffer displayed by WINDOW.
+WINDOW must be a live window and defaults to the selected one.
+
+The return value is the buffer shown in WINDOW at the last time window
+change functions were run.  It is nil if WINDOW was created after
+that.  It is t if WINDOW has been restored from a window configuration
+after that.  */)
+  (Lisp_Object window)
+{
+  struct window *w = decode_live_window (window);
+
+  return (NILP (w->old_buffer)
+	  /* A new window.  */
+	  ? Qnil
+	  : (w->change_stamp != WINDOW_XFRAME (w)->change_stamp)
+	  /* A window restored from a configuration.  */
+	  ? Qt
+	  /* A window that was live the last time seen by window
+	     change functions.  */
+	  : w->old_buffer);
+}
+
 DEFUN ("window-parent", Fwindow_parent, Swindow_parent, 0, 1, 0,
        doc: /* Return the parent window of window WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
@@ -723,34 +784,32 @@ WINDOW are never (re-)combined with WINDOW's siblings.  */)
   return make_fixnum (decode_valid_window (window)->pixel_height);
 }
 
-DEFUN ("window-pixel-width-before-size-change",
-       Fwindow_pixel_width_before_size_change,
-       Swindow_pixel_width_before_size_change, 0, 1, 0,
-       doc: /* Return pixel width of window WINDOW before last size changes.
+DEFUN ("window-old-pixel-width", Fwindow_old_pixel_width,
+       Swindow_old_pixel_width, 0, 1, 0,
+       doc: /* Return old total pixel width of WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 
-The return value is the pixel width of WINDOW at the last time
-`window-size-change-functions' was run.  It's zero if WINDOW was made
-after that.  */)
+The return value is the total pixel width of WINDOW after the last
+time window change functions found WINDOW live on its frame.  It is
+zero if WINDOW was created after that.  */)
   (Lisp_Object window)
 {
   return (make_fixnum
-	  (decode_valid_window (window)->pixel_width_before_size_change));
+	  (decode_valid_window (window)->old_pixel_width));
 }
 
-DEFUN ("window-pixel-height-before-size-change",
-       Fwindow_pixel_height_before_size_change,
-       Swindow_pixel_height_before_size_change, 0, 1, 0,
-       doc: /* Return pixel height of window WINDOW before last size changes.
+DEFUN ("window-old-pixel-height", Fwindow_old_pixel_height,
+       Swindow_old_pixel_height, 0, 1, 0,
+       doc: /* Return old total pixel height of WINDOW.
 WINDOW must be a valid window and defaults to the selected one.
 
-The return value is the pixel height of WINDOW at the last time
-`window-size-change-functions' was run.  It's zero if WINDOW was made
-after that.  */)
+The return value is the total pixel height of WINDOW after the last
+time window change functions found WINDOW live on its frame.  It is
+zero if WINDOW was created after that.  */)
   (Lisp_Object window)
 {
   return (make_fixnum
-	  (decode_valid_window (window)->pixel_height_before_size_change));
+	  (decode_valid_window (window)->old_pixel_height));
 }
 
 DEFUN ("window-total-height", Fwindow_total_height, Swindow_total_height, 0, 2, 0,
@@ -984,6 +1043,26 @@ horizontally combined (a window that has a left or right sibling) is
 	      0);
 }
 
+DEFUN ("window-body-width", Fwindow_body_width, Swindow_body_width, 0, 2, 0,
+       doc: /* Return the width of WINDOW's text area.
+WINDOW must be a live window and defaults to the selected one.  Optional
+argument PIXELWISE non-nil means return the width in pixels.  The return
+value does not include any vertical dividers, fringes or marginal areas,
+or scroll bars.
+
+If PIXELWISE is nil, return the largest integer smaller than WINDOW's
+pixel width divided by the character width of WINDOW's frame.  This
+means that if a column at the right of the text area is only partially
+visible, that column is not counted.
+
+Note that the returned value includes the column reserved for the
+continuation glyph.  */)
+  (Lisp_Object window, Lisp_Object pixelwise)
+{
+  return make_fixnum (window_body_width (decode_live_window (window),
+					 !NILP (pixelwise)));
+}
+
 DEFUN ("window-body-height", Fwindow_body_height, Swindow_body_height, 0, 2, 0,
        doc: /* Return the height of WINDOW's text area.
 WINDOW must be a live window and defaults to the selected one.  Optional
@@ -1001,24 +1080,34 @@ horizontally combined (a window that has a left or right sibling) is
 					  !NILP (pixelwise)));
 }
 
-DEFUN ("window-body-width", Fwindow_body_width, Swindow_body_width, 0, 2, 0,
-       doc: /* Return the width of WINDOW's text area.
-WINDOW must be a live window and defaults to the selected one.  Optional
-argument PIXELWISE non-nil means return the width in pixels.  The return
-value does not include any vertical dividers, fringes or marginal areas,
-or scroll bars.
+DEFUN ("window-old-body-pixel-width",
+       Fwindow_old_body_pixel_width,
+       Swindow_old_body_pixel_width, 0, 1, 0,
+       doc: /* Return old width of WINDOW's text area in pixels.
+WINDOW must be a live window and defaults to the selected one.
 
-If PIXELWISE is nil, return the largest integer smaller than WINDOW's
-pixel width divided by the character width of WINDOW's frame.  This
-means that if a column at the right of the text area is only partially
-visible, that column is not counted.
+The return value is the pixel width of WINDOW's text area after the
+last time window change functions found WINDOW live on its frame.  It
+is zero if WINDOW was created after that.  */)
+  (Lisp_Object window)
+{
+  return (make_fixnum
+	  (decode_live_window (window)->old_body_pixel_width));
+}
 
-Note that the returned value includes the column reserved for the
-continuation glyph.  */)
-  (Lisp_Object window, Lisp_Object pixelwise)
+DEFUN ("window-old-body-pixel-height",
+       Fwindow_old_body_pixel_height,
+       Swindow_old_body_pixel_height, 0, 1, 0,
+       doc: /* Return old height of WINDOW's text area in pixels.
+WINDOW must be a live window and defaults to the selected one.
+
+The return value is the pixel height of WINDOW's text area after the
+last time window change functions found WINDOW live on its frame.  It
+is zero if WINDOW was created after that.  */)
+  (Lisp_Object window)
 {
-  return make_fixnum (window_body_width (decode_live_window (window),
-					 !NILP (pixelwise)));
+  return (make_fixnum
+	  (decode_live_window (window)->old_body_pixel_height));
 }
 
 DEFUN ("window-mode-line-height", Fwindow_mode_line_height,
@@ -3264,7 +3353,7 @@ depends on the value of (window-start WINDOW), so if calling this
   adjust_frame_glyphs (f);
   unblock_input ();
 
-  run_window_configuration_change_hook (f);
+  FRAME_WINDOW_CHANGE (f) = true;
 
   return Qnil;
 }
@@ -3318,6 +3407,15 @@ depends on the value of (window-start WINDOW), so if calling this
     Fselect_frame (frame, Qt);
 }
 
+/**
+ * run_window_configuration_change_hook:
+ *
+ * Run any functions on 'window-configuration-change-hook' for the
+ * frame specified by F.  The buffer-local values are run with the
+ * window showing the buffer selected.  The default value is run with
+ * the frame specified by F selected.  All functions are called with
+ * the selected window's buffer current.
+ */
 static void
 run_window_configuration_change_hook (struct frame *f)
 {
@@ -3371,7 +3469,10 @@ depends on the value of (window-start WINDOW), so if calling this
 DEFUN ("run-window-configuration-change-hook", Frun_window_configuration_change_hook,
        Srun_window_configuration_change_hook, 0, 1, 0,
        doc: /* Run `window-configuration-change-hook' for FRAME.
-If FRAME is omitted or nil, it defaults to the selected frame.  */)
+If FRAME is omitted or nil, it defaults to the selected frame.
+
+This function should not be needed any more and will be therefore
+considered obsolete.  */)
   (Lisp_Object frame)
 {
   run_window_configuration_change_hook (decode_live_frame (frame));
@@ -3381,130 +3482,388 @@ depends on the value of (window-start WINDOW), so if calling this
 DEFUN ("run-window-scroll-functions", Frun_window_scroll_functions,
        Srun_window_scroll_functions, 0, 1, 0,
        doc: /* Run `window-scroll-functions' for WINDOW.
-If WINDOW is omitted or nil, it defaults to the selected window.  */)
+If WINDOW is omitted or nil, it defaults to the selected window.
+
+This function is curently only called by 'split-window' for the new
+window after it has established the size of the new window.  */)
   (Lisp_Object window)
 {
-  if (! NILP (Vwindow_scroll_functions))
+  struct window *w = decode_live_window (window);
+  ptrdiff_t count = SPECPDL_INDEX ();
+
+  record_unwind_current_buffer ();
+  Fset_buffer (w->contents);
+  if (!NILP (Vwindow_scroll_functions))
     run_hook_with_args_2 (Qwindow_scroll_functions, window,
-			  Fmarker_position (decode_live_window (window)->start));
+			  Fmarker_position (w->start));
+  unbind_to (count, Qnil);
+
   return Qnil;
 }
 
 
-/* Compare old and present pixel sizes of windows in tree rooted at W.
-   Return true iff any of these windows differs in size.  */
-
-static bool
-window_size_changed (struct window *w)
+/**
+ * window_sub_list:
+ *
+ * Return list of live windows constructed by traversing any window
+ * sub-tree rooted at WINDOW in preorder followed by right siblings of
+ * WINDOW.  Called from outside with second argument WINDOWS nil.  The
+ * returned list is in reverse order.
+ */
+static Lisp_Object
+window_sub_list (Lisp_Object window, Lisp_Object windows)
 {
-  if (w->pixel_width != w->pixel_width_before_size_change
-      || w->pixel_height != w->pixel_height_before_size_change)
-    return true;
 
-  if (WINDOW_INTERNAL_P (w))
+  struct window *w = XWINDOW (window);
+
+  while (w)
     {
-      w = XWINDOW (w->contents);
-      while (w)
-	{
-	  if (window_size_changed (w))
-	    return true;
+      if (WINDOW_INTERNAL_P (w))
+	windows = window_sub_list (w->contents, windows);
+      else
+	windows = Fcons (window, windows);
 
-	  w = NILP (w->next) ? 0 : XWINDOW (w->next);
-	}
+      window = w->next;
+      w = NILP (window) ? 0 : XWINDOW (window);
     }
 
-  return false;
+  return windows;
 }
 
-/* Set before size change pixel sizes of windows in tree rooted at W to
-   their present pixel sizes.  */
 
-static void
-window_set_before_size_change_sizes (struct window *w)
+/**
+ * window_change_record_windows:
+ *
+ * Record changes for all live windows found by traversing any window
+ * sub-tree rooted at WINDOW in preorder followed by any right
+ * siblings of WINDOW.  This sets the old buffer, old pixel and old
+ * body pixel sizes of each live window found to the respective
+ * current values.  It also sets the change stamp of each window found
+ * to STAMP.  Return the number of live windows found.
+ *
+ * When not called by itself recursively, WINDOW is its frame's root
+ * window, STAMP is the current change stamp of WINDOW's frame and
+ * NUMBER is 0.
+ */
+static ptrdiff_t
+window_change_record_windows (Lisp_Object window, int stamp, ptrdiff_t number)
 {
-  w->pixel_width_before_size_change = w->pixel_width;
-  w->pixel_height_before_size_change = w->pixel_height;
+  struct window *w = XWINDOW (window);
 
-  if (WINDOW_INTERNAL_P (w))
+  while (w)
     {
-      w = XWINDOW (w->contents);
-      while (w)
+      if (WINDOW_INTERNAL_P (w))
+	number = window_change_record_windows (w->contents, stamp, number);
+      else
 	{
-	  window_set_before_size_change_sizes (w);
-	  w = NILP (w->next) ? 0 : XWINDOW (w->next);
+	  number += 1;
+	  w->change_stamp = stamp;
+	  wset_old_buffer (w, w->contents);
+	  w->old_pixel_width = w->pixel_width;
+	  w->old_pixel_height = w->pixel_height;
+	  w->old_body_pixel_width = window_body_width (w, true);
+	  w->old_body_pixel_height = window_body_height (w, true);
 	}
+
+      w = NILP (w->next) ? 0 : XWINDOW (w->next);
     }
+
+  return number;
 }
 
 
-void
-run_window_size_change_functions (Lisp_Object frame)
+/**
+ * window_change_record_frame:
+ *
+ * Record changes for FRAME.  This records FRAME's selected window,
+ * updates FRAME's change stamp, records the state of all live windows
+ * of FRAME via window_change_record_windows and resets FRAME's
+ * window_change flag.
+ */
+static void
+window_change_record_frame (Lisp_Object frame)
 {
   struct frame *f = XFRAME (frame);
-  struct window *r = XWINDOW (FRAME_ROOT_WINDOW (f));
 
-  if (NILP (Vrun_hooks)
-      || !(f->can_x_set_window_size)
-      || !(f->after_make_frame))
-    return;
+  /* Record selected window.  */
+  fset_old_selected_window (f, FRAME_SELECTED_WINDOW (f));
+
+  /* Bump up FRAME's change stamp.  If this wraps, make it 1 to avoid
+     that a new window (whose change stamp is always set to 0) gets
+     reported as "existing before".  */
+  f->change_stamp += 1;
+  if (f->change_stamp == 0)
+    f->change_stamp = 1;
+
+  /* Bump up the change stamps of all live windows on this frame so
+     the next call of this function can tell whether any of them
+     "existed before" and record state for each of these windows.  */
+  f->number_of_windows
+    = window_change_record_windows (f->root_window, f->change_stamp, 0);
+
+  /* Reset our flag.  */
+  FRAME_WINDOW_CHANGE (f) = false;
+}
+
+
+/**
+ * window_change_record:
+ *
+ * Record selected window in old_selected_window and selected frame in
+ * old_selected_frame.
+ */
+static void
+window_change_record (void)
+{
+  /* Strictly spoken we don't need old_selected_window at all - its
+     value is the old selected window of old_selected_frame.  */
+  old_selected_window = selected_window;
+  old_selected_frame = selected_frame;
+}
 
-  if (FRAME_WINDOW_CONFIGURATION_CHANGED (f)
-      /* Here we implicitly exclude the possibility that the height of
-	 FRAME and its minibuffer window both change leaving the height
-	 of FRAME's root window alone.  */
-      || window_size_changed (r))
+
+/**
+ * run_window_change_functions_1:
+ *
+ * Run window change functions specified by SYMBOL with argument
+ * WINDOW_OR_FRAME.  If BUFFER is nil, WINDOW_OR_FRAME specifies a
+ * frame.  In this case, run the default value of SYMBOL.  Otherwise,
+ * WINDOW_OR_FRAME denotes a window showing BUFFER.  In this case, run
+ * the buffer local value of SYMBOL in BUFFER, if any.
+ */
+static void
+run_window_change_functions_1 (Lisp_Object symbol, Lisp_Object buffer,
+			       Lisp_Object window_or_frame)
+{
+  Lisp_Object funs = Qnil;
+
+  if (NILP (buffer))
+    funs = Fdefault_value (symbol);
+  else if (Fassoc (symbol, BVAR (XBUFFER (buffer), local_var_alist), Qnil))
+    /* Don't run global value buffer-locally.  */
+    funs = buffer_local_value (symbol, buffer);
+
+  while (CONSP (funs))
     {
-      Lisp_Object globals = Fdefault_value (Qwindow_size_change_functions);
-      Lisp_Object windows = Fwindow_list (frame, Qlambda, Qnil);
-      /* The buffers for which the local hook was already run.  */
-      Lisp_Object buffers = Qnil;
+      if (!EQ (XCAR (funs), Qt))
+	safe_call1 (XCAR (funs), window_or_frame);
+      funs = XCDR (funs);
+    }
+}
+
 
+/**
+ * run_window_change_functions:
+ *
+ * Run window change functions for each live frame.  This function
+ * must be called from a "safe" position in redisplay_internal.
+ *
+ * Do not run any functions for a frame whose window_change flag is
+ * nil and where no window selection happened since the last time this
+ * function was called.  Also, skip any tooltip frame.
+ *
+ * The change functions run are, in this order:
+ *
+ * 'window-state-change-functions' which are run for a window that
+ * changed its buffer or that was not shown the last time window
+ * change functions were run.  The default value is also run when a
+ * window was deleted since the last time window change functions were
+ * run.
+ *
+ * `window-size-change-functions' run for a window that changed its
+ * body or total size, a window that changed its buffer or a window
+ * that was not shown the last time window change functions were run.
+ *
+ * `window-selected-change-functions' run for a window that was
+ * (de-)selected since the last time window change functions were run.
+ *
+ * A buffer-local value of these functions is run if and only if the
+ * window for which the functions are run, currently shows the buffer.
+ * Each call gets one argument - the window showing the buffer.  This
+ * means that the buffer-local value of these functions may be called
+ * as many times at the buffer is shown on the frame.
+ *
+ * The default value of these functions is called only after all
+ * buffer-local values for all of these functions have been run.  Each
+ * such call receives one argument - the frame for which this function
+ * is run.
+ *
+ * After the three change functions cited above have been run in the
+ * indicated way, functions on 'window-configuration-change-hook' are
+ * run.  A buffer-local value is run if a window shows that buffer and
+ * has either changed its buffer or its body or total size or did not
+ * appear on this frame since the last time window change functions
+ * were run.  The functions are called without argument and the
+ * buffer's window selected.  The default value is run without
+ * argument and the frame for which the function is run selected.
+ *
+ * This function does not save and restore match data.  Any functions
+ * it calls are responsible for doing that themselves.
+ */
+void
+run_window_change_functions (void)
+{
+  Lisp_Object tail, frame;
+  bool selected_frame_change = !EQ (selected_frame, old_selected_frame);
+  ptrdiff_t count_outer = SPECPDL_INDEX ();
+
+  record_unwind_protect_void (window_change_record);
+
+  FOR_EACH_FRAME (tail, frame)
+    {
+      struct frame *f = XFRAME (frame);
+      Lisp_Object root = FRAME_ROOT_WINDOW (f);
+      bool frame_window_change = FRAME_WINDOW_CHANGE (f);
+      bool window_state_change, window_size_change;
+      bool frame_state_change = false, frame_size_change = false;
+      bool frame_selected_change
+	= (selected_frame_change
+	   && (EQ (frame, old_selected_frame)
+	       || EQ (frame, selected_frame)));
+      bool frame_selected_window_change
+	= !EQ (FRAME_OLD_SELECTED_WINDOW (f), FRAME_SELECTED_WINDOW (f));
+      bool window_deleted = false;
+      Lisp_Object windows;
+      ptrdiff_t number_of_windows;
+      ptrdiff_t count_inner = SPECPDL_INDEX ();
+
+      if (!f->can_x_set_window_size
+	  || !f->after_make_frame
+	  || FRAME_TOOLTIP_P (f)
+	  || !(frame_window_change
+	       || frame_selected_change
+	       || frame_selected_window_change))
+	/* Either we cannot run hooks for this frame yet or no window
+	   change has been reported for this frame since the last time
+	   we ran window change functions on it.  */
+	continue;
+
+      /* Analyze windows and run buffer locals hooks in pre-order.  */
+      windows = Fnreverse (window_sub_list (root, Qnil));
+      number_of_windows = 0;
+
+      record_unwind_protect (window_change_record_frame, frame);
+
+      /* The following loop collects all data needed to tell whether
+	 the default value of a hook shall be run and runs any buffer
+	 local hooks right away.  */
       for (; CONSP (windows); windows = XCDR (windows))
 	{
 	  Lisp_Object window = XCAR (windows);
-	  Lisp_Object buffer = Fwindow_buffer (window);
-
-	  /* Run a buffer-local value only once for that buffer and
-	     only if at least one window showing that buffer on FRAME
-	     actually changed its size.  Note that the function is run
-	     with FRAME as its argument and as such oblivious to the
-	     window checked below.  */
-	  if (window_size_changed (XWINDOW (window))
-	      && !NILP (Flocal_variable_p (Qwindow_size_change_functions, buffer))
-	      && NILP (Fmemq (buffer, buffers)))
-	    {
-	      Lisp_Object locals
-		= Fbuffer_local_value (Qwindow_size_change_functions, buffer);
-
-	      while (CONSP (locals))
-		{
-		  if (!EQ (XCAR (locals), Qt))
-		    safe_call1 (XCAR (locals), frame);
-		  locals = XCDR (locals);
-		}
-
-	      buffers = Fcons (buffer, buffers);
-	    }
+	  struct window *w = XWINDOW (window);
+	  Lisp_Object buffer = WINDOW_BUFFER (w);
+
+	  /* Count this window even if it has been deleted while
+	     running a hook.  */
+	  number_of_windows += 1;
+
+	  if (!WINDOW_LIVE_P (window))
+	    continue;
+
+	  /* A "state change" means either the window's buffer changed
+	     or the window was not part of this frame the last time
+	     window change functions were run for it.  */
+	  window_state_change =
+	    (frame_window_change
+	     && (!EQ (buffer, w->old_buffer)
+		 || w->change_stamp != f->change_stamp));
+	  /* A "size change" means either a state change or that the
+	     total or body size of the window has changed.
+
+	     Note: A state change implies a size change because either
+	     this window didn't show the buffer before or this window
+	     didn't show the buffer the last time the window change
+	     functions were run.  In either case, an application
+	     tracing size changes in a buffer-locally fashion might
+	     want to be informed about that change.  */
+	  window_size_change =
+	    (frame_window_change
+	     && (window_state_change
+		 || w->pixel_width != w->old_pixel_width
+		 || w->pixel_height != w->old_pixel_height
+		 || window_body_width (w, true) != w->old_body_pixel_width
+		 || window_body_height (w, true) != w->old_body_pixel_height));
+
+	  /* The following two are needed when running the default
+	     values for this frame below.  */
+	  frame_state_change = frame_state_change || window_state_change;
+	  frame_size_change = frame_size_change || window_size_change;
+
+	  if (window_state_change)
+	    run_window_change_functions_1
+	      (Qwindow_state_change_functions, buffer, window);
+
+	  if (window_size_change && WINDOW_LIVE_P (window))
+	    run_window_change_functions_1
+	      (Qwindow_size_change_functions, buffer, window);
+
+	  /* This window's selection has changed when it it was
+	     (de-)selected as its frame's or the globally selected
+	     window.  */
+	  if (((frame_selected_change
+		&& (EQ (window, old_selected_window)
+		    || EQ (window, selected_window)))
+	       || (frame_selected_window_change
+		   && (EQ (window, FRAME_OLD_SELECTED_WINDOW (f))
+		       || EQ (window, FRAME_SELECTED_WINDOW (f)))))
+	      && WINDOW_LIVE_P (window))
+	    run_window_change_functions_1
+	      (Qwindow_selection_change_functions, buffer, window);
 	}
 
-      while (CONSP (globals))
-	{
-	  if (!EQ (XCAR (globals), Qt))
-	    safe_call1 (XCAR (globals), frame);
-	  globals = XCDR (globals);
-	}
+      /* When the number of windows on a frame has decreased, at least
+	 one window of that frame was deleted.  In that case, we want
+	 to run the default state and configuration change hooks.  The
+	 default size change hook is not necessarily run in that case,
+	 but usually will be unless the deletion was "compensated" by
+	 a reduction of the frame size or an increase of a minibuffer
+	 window size.  */
+      window_deleted = number_of_windows < f->number_of_windows;
+      /* A frame's state changed when one of its windows has changed
+	 state or at least one window was deleted.  */
+      if ((frame_state_change || window_deleted) && FRAME_LIVE_P (f))
+	run_window_change_functions_1
+	  (Qwindow_state_change_functions, Qnil, frame);
+
+      /* A size change occurred when at least one of the frame's
+	 windows has changed size.  */
+      if (frame_size_change && FRAME_LIVE_P (f))
+	run_window_change_functions_1
+	  (Qwindow_size_change_functions, Qnil, frame);
+
+      /* A frame has changed its window selection when its selected
+	 window has changed or when it was (de-)selected.  */
+      if ((frame_selected_change || frame_selected_window_change)
+	  && FRAME_LIVE_P (f))
+	run_window_change_functions_1
+	  (Qwindow_selection_change_functions, Qnil, frame);
+
+      /* A frame's configuration changed when one of its windows has
+	 changed state or size or at least one window was deleted.  */
+      if ((frame_size_change || window_deleted) && FRAME_LIVE_P (f))
+	/* This will run any buffer local window configuration change
+	   hook as well.  */
+	run_window_configuration_change_hook (f);
 
-      window_set_before_size_change_sizes (r);
+      if (!FRAME_LIVE_P (f))
+	continue;
 
-      if (FRAME_HAS_MINIBUF_P (f) && !FRAME_MINIBUF_ONLY_P (f))
-	/* Record size of FRAME's minibuffer window too.  */
-	window_set_before_size_change_sizes
-	  (XWINDOW (FRAME_MINIBUF_WINDOW (f)));
+      /* Internal bookkeeping.  */
+      if (frame_window_change)
+	Vwindow_changes =
+	  Fcons (make_fixnum (XFIXNUM (Fcar (Vwindow_changes)) + 1),
+		 make_fixnum (XFIXNUM (Fcdr (Vwindow_changes))
+			      + ((frame_state_change || frame_size_change)
+				 ? 1 : 0)));
 
-      FRAME_WINDOW_CONFIGURATION_CHANGED (f) = false;
+      /* Record changes (via window_change_record_frame) for this
+	 frame, even when an unhandled error occurred.  */
+      unbind_to (count_inner, Qnil);
     }
-}
 
+  /* Record selected window and frame.  */
+  unbind_to (count_outer, Qnil);
+}
 
 /* Make WINDOW display BUFFER.  RUN_HOOKS_P means it's allowed
    to run hooks.  See make_frame for a case where it's not allowed.
@@ -3581,14 +3940,18 @@ depends on the value of (window-start WINDOW), so if calling this
       apply_window_adjustment (w);
     }
 
-  if (run_hooks_p)
-    {
-      if (!NILP (Vwindow_scroll_functions))
-	run_hook_with_args_2 (Qwindow_scroll_functions, window,
-			      Fmarker_position (w->start));
-      if (!samebuf)
-	run_window_configuration_change_hook (XFRAME (WINDOW_FRAME (w)));
-    }
+  if (run_hooks_p && !NILP (Vwindow_scroll_functions))
+    run_hook_with_args_2 (Qwindow_scroll_functions, window,
+			  Fmarker_position (w->start));
+
+  /* Ensure that window change functions are run later if the buffer
+     differs and the window is neither a mini nor a pseudo window.
+
+     Note: Running window change functions for the minibuffer is noisy
+     and was generally suppressed in the past.  Is there any reason we
+     should run them?  */
+  if (!samebuf && !MINI_WINDOW_P (w) && !WINDOW_PSEUDO_P (w))
+    FRAME_WINDOW_CHANGE (XFRAME (w->frame)) = true;
 
   unbind_to (count, Qnil);
 }
@@ -3828,8 +4191,6 @@ depends on the value of (window-start WINDOW), so if calling this
   w->phys_cursor_width = -1;
 #endif
   w->sequence_number = ++sequence_number;
-  w->pixel_width_before_size_change = 0;
-  w->pixel_height_before_size_change = 0;
   w->scroll_bar_width = -1;
   w->scroll_bar_height = -1;
   w->column_number_displayed = -1;
@@ -4095,6 +4456,9 @@ depends on the value of (window-start WINDOW), so if calling this
   else
     /* Bug#15957.  */
     w->window_end_valid = false;
+
+  if (!WINDOW_PSEUDO_P (w))
+    FRAME_WINDOW_CHANGE (WINDOW_XFRAME (w)) = true;
 }
 
 
@@ -4559,17 +4923,11 @@ SIDE t (or `right') specifies that the new window shall be located on
   block_input ();
   window_resize_apply (p, horflag);
   adjust_frame_glyphs (f);
-  /* Set buffer of NEW to buffer of reference window.  Don't run
-     any hooks.  */
-  set_window_buffer (new, r->contents, false, true);
+  /* Set buffer of NEW to buffer of reference window.  */
+  set_window_buffer (new, r->contents, true, true);
+  FRAME_WINDOW_CHANGE (f) = true;
   unblock_input ();
 
-  /* Maybe we should run the scroll functions in Elisp (which already
-     runs the configuration change hook).  */
-  if (! NILP (Vwindow_scroll_functions))
-    run_hook_with_args_2 (Qwindow_scroll_functions, new,
-			  Fmarker_position (n->start));
-  /* Return NEW.  */
   return new;
 }
 
@@ -4720,6 +5078,8 @@ SIDE t (or `right') specifies that the new window shall be located on
 	}
       else
 	unblock_input ();
+
+      FRAME_WINDOW_CHANGE (f) = true;
     }
   else
     /* We failed: Relink WINDOW into window tree.  */
@@ -6310,7 +6670,6 @@ struct saved_window
 
   Lisp_Object window, buffer, start, pointm, old_pointm;
   Lisp_Object pixel_left, pixel_top, pixel_height, pixel_width;
-  Lisp_Object pixel_height_before_size_change, pixel_width_before_size_change;
   Lisp_Object left_col, top_line, total_cols, total_lines;
   Lisp_Object normal_cols, normal_lines;
   Lisp_Object hscroll, min_hscroll, hscroll_whole, suspend_auto_hscroll;
@@ -6426,12 +6785,6 @@ struct saved_window
       struct window *root_window;
       struct window **leaf_windows;
       ptrdiff_t i, k, n_leaf_windows;
-      /* Records whether a window has been added or removed wrt the
-	 original configuration.  */
-      bool window_changed = false;
-      /* Records whether a window has changed its buffer wrt the
-	 original configuration.  */
-      bool buffer_changed = false;
 
       /* Don't do this within the main loop below: This may call Lisp
 	 code and is thus potentially unsafe while input is blocked.  */
@@ -6441,11 +6794,6 @@ struct saved_window
 	  window = p->window;
 	  w = XWINDOW (window);
 
-	  if (NILP (w->contents))
-	    /* A dead window that will be resurrected, the window
-	       configuration will change.  */
-	    window_changed = true;
-
 	  if (BUFFERP (w->contents)
 	      && !EQ (w->contents, p->buffer)
 	      && BUFFER_LIVE_P (XBUFFER (p->buffer)))
@@ -6530,10 +6878,6 @@ struct saved_window
 	  w->pixel_top = XFIXNAT (p->pixel_top);
 	  w->pixel_width = XFIXNAT (p->pixel_width);
 	  w->pixel_height = XFIXNAT (p->pixel_height);
-	  w->pixel_width_before_size_change
-	    = XFIXNAT (p->pixel_width_before_size_change);
-	  w->pixel_height_before_size_change
-	    = XFIXNAT (p->pixel_height_before_size_change);
 	  w->left_col = XFIXNAT (p->left_col);
 	  w->top_line = XFIXNAT (p->top_line);
 	  w->total_cols = XFIXNAT (p->total_cols);
@@ -6581,9 +6925,6 @@ struct saved_window
 	  if (BUFFERP (p->buffer) && BUFFER_LIVE_P (XBUFFER (p->buffer)))
 	    /* If saved buffer is alive, install it.  */
 	    {
-	      if (!EQ (w->contents, p->buffer))
-		/* Record buffer configuration change.  */
-		buffer_changed = true;
 	      wset_buffer (w, p->buffer);
 	      w->start_at_line_beg = !NILP (p->start_at_line_beg);
 	      set_marker_restricted (w->start, p->start, w->contents);
@@ -6617,8 +6958,6 @@ struct saved_window
 	  else if (!NILP (w->start))
 	    /* Leaf window has no live buffer, get one.  */
 	    {
-	      /* Record buffer configuration change.  */
-	      buffer_changed = true;
 	      /* Get the buffer via other_buffer_safely in order to
 		 avoid showing an unimportant buffer and, if necessary, to
 		 recreate *scratch* in the course (part of Juanma's bs-show
@@ -6666,10 +7005,7 @@ struct saved_window
       /* Now, free glyph matrices in windows that were not reused.  */
       for (i = 0; i < n_leaf_windows; i++)
 	if (NILP (leaf_windows[i]->contents))
-	  {
-	    free_window_matrices (leaf_windows[i]);
-	    window_changed = true;
-	  }
+	  free_window_matrices (leaf_windows[i]);
 
       /* Allow x_set_window_size again and apply frame size changes if
 	 needed.  */
@@ -6699,35 +7035,10 @@ struct saved_window
 	 selected window.  */
       if (FRAME_LIVE_P (XFRAME (data->selected_frame)))
 	do_switch_frame (data->selected_frame, 0, 0, Qnil);
-
-      if (window_changed)
-	/* At least one window has been added or removed.  Run
-	   `window-configuration-change-hook' and make sure
-	   `window-size-change-functions' get run later.
-
-	   We have to do this in order to capture the following
-	   scenario: Suppose our frame contains two live windows W1 and
-	   W2 and 'set-window-configuration' replaces them by two
-	   windows W3 and W4 that were dead the last time
-	   run_window_size_change_functions was run.  If W3 and W4 have
-	   the same values for their old and new pixel sizes but these
-	   values differ from those of W1 and W2, the sizes of our
-	   frame's two live windows changed but window_size_changed has
-	   no means to detect that fact.
-
-	   Obviously, this will get us false positives, for example,
-	   when we restore the original configuration with W1 and W2
-	   before run_window_size_change_functions gets called.  */
-	{
-	  run_window_configuration_change_hook (f);
-	  FRAME_WINDOW_CONFIGURATION_CHANGED (f) = true;
-	}
-      else if (buffer_changed)
-	/* At least one window has changed its buffer.  Run
-	   `window-configuration-change-hook' only.  */
-	run_window_configuration_change_hook (f);
     }
 
+  FRAME_WINDOW_CHANGE (f) = true;
+
   if (!NILP (new_current_buffer))
     {
       Fset_buffer (new_current_buffer);
@@ -6889,10 +7200,6 @@ struct glyph *
       p->pixel_top = make_fixnum (w->pixel_top);
       p->pixel_width = make_fixnum (w->pixel_width);
       p->pixel_height = make_fixnum (w->pixel_height);
-      p->pixel_width_before_size_change
-	= make_fixnum (w->pixel_width_before_size_change);
-      p->pixel_height_before_size_change
-	= make_fixnum (w->pixel_height_before_size_change);
       p->left_col = make_fixnum (w->left_col);
       p->top_line = make_fixnum (w->top_line);
       p->total_cols = make_fixnum (w->total_cols);
@@ -7581,9 +7888,9 @@ Value is a list of the form (WIDTH COLUMNS VERTICAL-TYPE HEIGHT LINES
 {
   struct frame *f = make_initial_frame ();
   XSETFRAME (selected_frame, f);
-  Vterminal_frame = selected_frame;
+  old_selected_frame = Vterminal_frame = selected_frame;
   minibuf_window = f->minibuffer_window;
-  selected_window = f->selected_window;
+  old_selected_window = selected_window = f->selected_window;
 }
 
 void
@@ -7604,6 +7911,8 @@ Value is a list of the form (WIDTH COLUMNS VERTICAL-TYPE HEIGHT LINES
 
   DEFSYM (Qwindow_configuration_change_hook, "window-configuration-change-hook");
   DEFSYM (Qwindow_size_change_functions, "window-size-change-functions");
+  DEFSYM (Qwindow_state_change_functions, "window-state-change-functions");
+  DEFSYM (Qwindow_selection_change_functions, "window-selection-change-functions");
   DEFSYM (Qwindowp, "windowp");
   DEFSYM (Qwindow_configuration_p, "window-configuration-p");
   DEFSYM (Qwindow_live_p, "window-live-p");
@@ -7688,24 +7997,66 @@ Value is a list of the form (WIDTH COLUMNS VERTICAL-TYPE HEIGHT LINES
   Vwindow_point_insertion_type = Qnil;
   DEFSYM (Qwindow_point_insertion_type, "window_point_insertion_type");
 
-  DEFVAR_LISP ("window-configuration-change-hook",
-	       Vwindow_configuration_change_hook,
-	       doc: /* Functions to call when window configuration changes.
-The buffer-local value is run once per window, with the relevant window
-selected; while the global value is run only once for the modified frame,
-with the relevant frame selected.  */);
-  Vwindow_configuration_change_hook = Qnil;
+  DEFVAR_LISP ("window-state-change-functions", Vwindow_state_change_functions,
+	       doc: /* Functions called during redisplay when window states have changed.
+The value should be a list of functions that take one argument.
+
+Functions specified buffer-locally are called for each window showing
+the corresponding buffer if and only if that window has been added or
+changed its buffer since the last redisplay.  In this case the window
+is passed as argument.
+
+Functions specified by the default value are called for each frame if
+at least one window on that frame has been added, deleted or changed
+its buffer since the last redisplay.  In this case the frame is passed
+as argument.  */);
+  Vwindow_state_change_functions = Qnil;
 
   DEFVAR_LISP ("window-size-change-functions", Vwindow_size_change_functions,
-    doc: /* Functions called during redisplay, if window sizes have changed.
+	       doc: /* Functions called during redisplay when window sizes have changed.
 The value should be a list of functions that take one argument.
-During the first part of redisplay, for each frame, if any of its windows
-have changed size since the last redisplay, or have been split or deleted,
-all the functions in the list are called, with the frame as argument.
-If redisplay decides to resize the minibuffer window, it calls these
-functions on behalf of that as well.  */);
+
+Functions specified buffer-locally are called for each window showing
+the corresponding buffer if and only if that window has been added or
+changed its buffer or its total or body size since the last redisplay.
+In this case the window is passed as argument.
+
+Functions specified by the default value are called for each frame if
+at least one window on that frame has been added or changed its buffer
+or its total or body size since the last redisplay.  In this case the
+frame is passed as argument.  */);
   Vwindow_size_change_functions = Qnil;
 
+  DEFVAR_LISP ("window-selection-change-functions", Vwindow_selection_change_functions,
+	       doc: /* Functions called during redisplay when the selected window has changed.
+The value should be a list of functions that take one argument.
+
+Functions specified buffer-locally are called for each window showing
+the corresponding buffer if and only if that window has been selected
+or deselected since the last redisplay.  In this case the window is
+passed as argument.
+
+Functions specified by the default value are called for each frame if
+the frame's selected window has changed since the last redisplay.  In
+this case the frame is passed as argument.  */);
+  Vwindow_selection_change_functions = Qnil;
+
+  DEFVAR_LISP ("window-configuration-change-hook", Vwindow_configuration_change_hook,
+	       doc: /* Functions called during redisplay when window configuration has changed.
+The value should be a list of functions that take no argument.
+
+Functions specified buffer-locally are called for each window showing
+the corresponding buffer if at least one window on that frame has been
+added, deleted or changed its buffer or its total or body size since
+the last redisplay.  Each call is performed with the window showing
+the buffer temporarily selected.
+
+Functions specified by the default value are called for each frame if
+at least one window on that frame has been added, deleted or changed
+its buffer or its total or body size since the last redisplay.  Each
+call is performed with the frame temporarily selected.  */);
+  Vwindow_configuration_change_hook = Qnil;
+
   DEFVAR_LISP ("recenter-redisplay", Vrecenter_redisplay,
 	       doc: /* Non-nil means `recenter' redraws entire frame.
 If this option is non-nil, then the `recenter' command with a nil
@@ -7816,7 +8167,13 @@ this value for parameters without read syntax (like windows or frames).
 displayed after a scrolling operation to be somewhat inaccurate.  */);
   Vfast_but_imprecise_scrolling = false;
 
+  DEFVAR_LISP ("window-changes",
+	       Vwindow_changes,
+	       doc: /* Cons.  */);
+  Vwindow_changes = Fcons (make_fixnum (0), make_fixnum (0));
+
   defsubr (&Sselected_window);
+  defsubr (&Sold_selected_window);
   defsubr (&Sminibuffer_window);
   defsubr (&Swindow_minibuffer_p);
   defsubr (&Swindowp);
@@ -7826,10 +8183,12 @@ this value for parameters without read syntax (like windows or frames).
   defsubr (&Sframe_root_window);
   defsubr (&Sframe_first_window);
   defsubr (&Sframe_selected_window);
+  defsubr (&Sframe_old_selected_window);
   defsubr (&Sset_frame_selected_window);
   defsubr (&Spos_visible_in_window_p);
   defsubr (&Swindow_line_height);
   defsubr (&Swindow_buffer);
+  defsubr (&Swindow_old_buffer);
   defsubr (&Swindow_parent);
   defsubr (&Swindow_top_child);
   defsubr (&Swindow_left_child);
@@ -7840,8 +8199,10 @@ this value for parameters without read syntax (like windows or frames).
   defsubr (&Swindow_use_time);
   defsubr (&Swindow_pixel_width);
   defsubr (&Swindow_pixel_height);
-  defsubr (&Swindow_pixel_width_before_size_change);
-  defsubr (&Swindow_pixel_height_before_size_change);
+  defsubr (&Swindow_old_pixel_width);
+  defsubr (&Swindow_old_pixel_height);
+  defsubr (&Swindow_old_body_pixel_width);
+  defsubr (&Swindow_old_body_pixel_height);
   defsubr (&Swindow_total_width);
   defsubr (&Swindow_total_height);
   defsubr (&Swindow_normal_size);
diff --git a/src/window.h b/src/window.h
index 4bb6293..a71a8e8 100644
--- a/src/window.h
+++ b/src/window.h
@@ -142,6 +142,11 @@ struct window
        as well.  */
     Lisp_Object contents;
 
+    /* The old buffer of this window, set to this window's buffer by
+       run_window_change_functions every time it sees this window.
+       Unused for internal windows.  */
+    Lisp_Object old_buffer;
+
     /* A marker pointing to where in the text to start displaying.
        BIDI Note: This is the _logical-order_ start, i.e. the smallest
        buffer position visible in the window, not necessarily the
@@ -229,6 +234,14 @@ struct window
     /* Unique number of window assigned when it was created.  */
     EMACS_INT sequence_number;
 
+    /* The change stamp of this window.  Set to 0 when the window is
+       created, it is set to its frame's change stamp every time
+       run_window_change_functions is run on that frame with this
+       window live.  It is left alone when the window exists only
+       within a window configuration.  Not useful for internal
+       windows.  */
+    int change_stamp;
+
     /* The upper left corner pixel coordinates of this window, as
        integers relative to upper left corner of frame = 0, 0.  */
     int pixel_left;
@@ -243,10 +256,13 @@ struct window
     int pixel_width;
     int pixel_height;
 
-    /* The pixel sizes of the window at the last time
-       `window-size-change-functions' was run.  */
-    int pixel_width_before_size_change;
-    int pixel_height_before_size_change;
+    /* The pixel and pixel body sizes of the window at the last time
+       run_window_change_functions was run with this window live.  Not
+       useful for internal windows.  */
+    int old_pixel_width;
+    int old_pixel_height;
+    int old_body_pixel_width;
+    int old_body_pixel_height;
 
     /* The size of the window.  */
     int total_cols;
@@ -1023,6 +1039,7 @@ struct window
    This value is always the same as FRAME_SELECTED_WINDOW (selected_frame).  */
 
 extern Lisp_Object selected_window;
+extern Lisp_Object old_selected_window;
 
 /* This is a time stamp for window selection, so we can find the least
    recently used window.  Its only users are Fselect_window,
@@ -1051,7 +1068,7 @@ extern Lisp_Object window_from_coordinates (struct frame *, int, int,
 extern void shrink_mini_window (struct window *, bool);
 extern int window_relative_x_coord (struct window *, enum window_part, int);
 
-void run_window_size_change_functions (Lisp_Object);
+void run_window_change_functions (void);
 
 /* Make WINDOW display BUFFER.  RUN_HOOKS_P means it's allowed
    to run hooks.  See make_frame for a case where it's not allowed.  */
diff --git a/src/xdisp.c b/src/xdisp.c
index 4201bdc..94742c2 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -2786,6 +2786,7 @@ static Lisp_Object calc_line_height_property (struct it *, Lisp_Object,
 	       struct glyph_row *row, enum face_id base_face_id)
 {
   enum face_id remapped_base_face_id = base_face_id;
+  int body_width = 0, body_height = 0;
 
   /* Some precondition checks.  */
   eassert (w != NULL && it != NULL);
@@ -2962,7 +2963,7 @@ static Lisp_Object calc_line_height_property (struct it *, Lisp_Object,
     {
       /* Mode lines, menu bar in terminal frames.  */
       it->first_visible_x = 0;
-      it->last_visible_x = WINDOW_PIXEL_WIDTH (w);
+      it->last_visible_x = body_width = WINDOW_PIXEL_WIDTH (w);
     }
   else
     {
@@ -2982,8 +2983,12 @@ static Lisp_Object calc_line_height_property (struct it *, Lisp_Object,
       else
 	it->first_visible_x =
 	  window_hscroll_limited (w, it->f) * FRAME_COLUMN_WIDTH (it->f);
-      it->last_visible_x = (it->first_visible_x
-			    + window_box_width (w, TEXT_AREA));
+
+      body_width = window_box_width (w, TEXT_AREA);
+      if (!w->pseudo_window_p && !MINI_WINDOW_P (w)
+	  && body_width != w->old_body_pixel_width)
+	FRAME_WINDOW_CHANGE (it->f) = true;
+      it->last_visible_x = it->first_visible_x + body_width;
 
       /* If we truncate lines, leave room for the truncation glyph(s) at
 	 the right margin.  Otherwise, leave room for the continuation
@@ -2997,7 +3002,8 @@ static Lisp_Object calc_line_height_property (struct it *, Lisp_Object,
 	}
 
       it->header_line_p = window_wants_header_line (w);
-      it->current_y = WINDOW_HEADER_LINE_HEIGHT (w) + w->vscroll;
+      body_height = WINDOW_HEADER_LINE_HEIGHT (w);
+      it->current_y =  body_height + w->vscroll;
     }
 
   /* Leave room for a border glyph.  */
@@ -3006,6 +3012,10 @@ static Lisp_Object calc_line_height_property (struct it *, Lisp_Object,
     it->last_visible_x -= 1;
 
   it->last_visible_y = window_text_bottom_y (w);
+  body_height += it->last_visible_y;
+  if (!w->pseudo_window_p && !MINI_WINDOW_P (w)
+      && body_height != w->old_body_pixel_height)
+    FRAME_WINDOW_CHANGE (it->f) = true;
 
   /* For mode lines and alike, arrange for the first glyph having a
      left box line if the face specifies a box.  */
@@ -12170,8 +12180,6 @@ static void ATTRIBUTE_FORMAT_PRINTF (1, 0)
 	      && !XBUFFER (w->contents)->text->redisplay)
 	    continue;
 
-	  run_window_size_change_functions (frame);
-
 	  if (FRAME_PARENT_FRAME (f))
 	    continue;
 
@@ -14089,20 +14097,6 @@ static void debug_method_add (struct window *, char const *, ...)
     {
       echo_area_display (false);
 
-      /* If echo_area_display resizes the mini-window, the redisplay and
-	 window_sizes_changed flags of the selected frame are set, but
-	 it's too late for the hooks in window-size-change-functions,
-	 which have been examined already in prepare_menu_bars.  So in
-	 that case we call the hooks here only for the selected frame.  */
-      if (sf->redisplay)
-	{
-	  ptrdiff_t count1 = SPECPDL_INDEX ();
-
-	  record_unwind_save_match_data ();
-	  run_window_size_change_functions (selected_frame);
-	  unbind_to (count1, Qnil);
-	}
-
       if (message_cleared_p)
 	update_miniwindow_p = true;
 
@@ -14119,15 +14113,6 @@ static void debug_method_add (struct window *, char const *, ...)
 	   && (current_buffer->clip_changed || window_outdated (w))
 	   && resize_mini_window (w, false))
     {
-      if (sf->redisplay)
-	{
-	  ptrdiff_t count1 = SPECPDL_INDEX ();
-
-	  record_unwind_save_match_data ();
-	  run_window_size_change_functions (selected_frame);
-	  unbind_to (count1, Qnil);
-	}
-
       /* Resized active mini-window to fit the size of what it is
          showing if its contents might have changed.  */
       must_finish = true;
@@ -14317,7 +14302,19 @@ static void debug_method_add (struct window *, char const *, ...)
 		  && (w = XWINDOW (selected_window)) != sw)
 		goto retry;
 
-	      /* We used to always goto end_of_redisplay here, but this
+	      if (!NILP (Vrun_hooks))
+		{
+		  run_window_change_functions ();
+
+		  /* If windows or buffers changed or selected_window
+		     changed, redisplay again.  */
+		  if ((windows_or_buffers_changed)
+		      || (WINDOWP (selected_window)
+			  && (w = XWINDOW (selected_window)) != sw))
+		    goto retry;
+		}
+
+		/* We used to always goto end_of_redisplay here, but this
 		 isn't enough if we have a blinking cursor.  */
 	      if (w->cursor_off_p == w->last_cursor_off_p)
 		goto end_of_redisplay;
@@ -14676,9 +14673,22 @@ static void debug_method_add (struct window *, char const *, ...)
   /* If we just did a pending size change, or have additional
      visible frames, or selected_window changed, redisplay again.  */
   if ((windows_or_buffers_changed && !pending)
-      || (WINDOWP (selected_window) && (w = XWINDOW (selected_window)) != sw))
+      || (WINDOWP (selected_window)
+	  && (w = XWINDOW (selected_window)) != sw))
     goto retry;
 
+  if (!NILP (Vrun_hooks))
+    {
+      run_window_change_functions ();
+
+      /* If windows or buffers changed or selected_window changed,
+	 redisplay again.  */
+      if ((windows_or_buffers_changed)
+	  || (WINDOWP (selected_window)
+	      && (w = XWINDOW (selected_window)) != sw))
+	goto retry;
+    }
+
   /* Clear the face and image caches.
 
      We used to do this only if consider_all_windows_p.  But the cache


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Gnus crash (was: Window change functions)
  2018-12-25  9:41 Window change functions martin rudalics
@ 2018-12-25 20:13 ` Juri Linkov
  2018-12-25 20:39   ` Gnus crash Stefan Monnier
  2018-12-26 19:55 ` Window change functions Stefan Monnier
  1 sibling, 1 reply; 18+ messages in thread
From: Juri Linkov @ 2018-12-25 20:13 UTC (permalink / raw)
  To: emacs-devel

When I expanded the attachment in Martin's post and tried to reply
Gnus failed with this backtrace:

Debugger entered--Lisp error: (overflow-error)
  ash(1570750665751610680287048192715050493321652740677087741354063489367958372944402762303863525891468439745041079653487874462211178162750957169987909662219671360085831713550434457940158853542980158941162950498738935688959958660035112271378047554570846555848659730364353761255340796293979970914147423301463359563437677768703608144937599538431771611884925959624687191699257249865954423912410298936280151999755183780426325115816777317573522392620718308595259625079842943227971994656059169791902875545215242730707633777255698631665502619931010364694247547289438972636480156578534419587372921568648611347903958578064708181028369718792597411926315765827178542319159726041239148157387623656351891775255630360438396443532269229983594184399840096493751880104660236200589684148553510546931856300655916302443164503424831007940533099397945413890266455795035093220977814869084235606053032380552647603890141599701982023227508331959630122893647128270418721248143868244863660762467807003315430171808717496478517725259790560057275832363479881992251232110507213533800892623900044524852080391422478457966714833781546776750445772564778728565519757283705683844641452678717810010167412117116488538079457977136030037923319099890391457321804919825353794691218435829551840827856390928976189850486882828877718126724239171831113170798212658491756091093489210684310351557259980673200102703538193030626725304606220480633834215266666113268533857487018489871689388728562861267213093005419477080869249487202033703860284727953155337926336112345532220950310717990024554778759136715235015009387972440289954357331151081601896932137805299533111092125647453303786078323400903146679238525031185245858479119478758632696829011950100172544399247295072813294428870690329023058257619863277241055101676073514869075959561414597292390531736327387322832463672891308771350024259281604500787929736876900860946387252432345829974733826310950744935753381208537467791466159983601156871941312318367962193532502819097829725250412291884062728358075554946913303889231732673429742106973399215740069492516696982007279843378482616064424423600789292749117157875986137451670261522987352159502558224794759118310990086626810471379755840054463732916458979191077215183762031245014808784882635345797145462269810634952260888643942334433820580170750457398273267525660984517971224359795015817374470384078951242178565911297213184582517505059643323465490206302135181132426024496880247034749581527190494990179546006687976339205638350252432590199751129377141521560560169695158446677934264447318135760490478140453801993417858663730943083581282500657203502171300675738837399609891992906605263136298698487344067846585794956194584155273869849807554618522001116070882109357330619741129741248485926046463802790977183452798073478245195746729751866812998709763799712327352910763313173391994176867374999846906555321919016868513889870341695837619990352190381716629505439336730458101654290474664491255618385812446164566553160698951596331104209997295869125687408827154603551710054181652355877522618070723368228791827051841991727510772077922681495999740181142459606184131161135011203841595907117341969043698443679869955608025953084677959110131495872111775842384812608175580078032415315194610247916049749508025103424859526586843140336172267614116843357903701484395302878363151687392095554010387654878109127375111940965841598934158711441402451910559520955834887667815823808002578591204085014485802747128257160042727307423485086315058142316826071978554963712250933775497558754800298135354112014907684153947885752992026784271955283946290351782964446803401676186685775790106042158972005649343273760507818150565241522963510996440087989549003905014012606810708404320714996188375054044708925719373763720155469505057798510200691513610654019420581356960976975041736484875430425946536695639599287897358366279860312080482553866415988789071687402061034719110413010488732150757927452597702671760751021303102683258388927011413216195525693358022350665852753490000818870505881135771044486615779392453631667518554546005966545917932603754818946860390284052363646272990577934768389123670710269976975388641619712441302092869745511227908226036185489997026171855445204610817809119913108963531307259409696663074639196914560766637017104765292632914964898592532900221564042932878071215237464436365208675116732492624384422241825062402594192010856441357013144052847444399045037870679689799858610729713778070750502972201263989526851527468971713419406420968517951405024501852688719433901515571326145996281261582725398605411605884155063587893149235760147204777003969853655345023808828839599395978046990238231815306358359710007091494820411920902141440198653088126852288211076886795024081216828996335571758025584396510009664390610455903057175173808171672659915645638645116236258335845001232649642576819835210141566414933818470109296604504414272486524674696937566576401171672910203723954782049866717468480778776312058364347122504216544548993467872841609428483560800640727128248880973466445636113093658059879451607708888339112610351966293655088527159565211255933664478302200511730507000040263696010196130060127225282985258977183831375544232964954717476954968329902973102235173804393108935492165639162541991961961725999555423598731708269835462623392747591859564002505660222137086755160691238027617249248575894835280507915194770968370401345041306090209356227770781990242482162936883803324725022760357142051357912952595496709742253879979088376461285894114537740617234773081543855461305250964603839124828265402554281891395284401532344071595972716162571775474752238288270557568551091389776901537953200650980175546576746680302986198619417438197195493953742265785833336084605255573541433502321336742452559452211785413067265915127079901280725241272037747645593349499274638321982506226321829789853215255880652949214420177623138218023974065711363848875887611291461799485596654219458615178458861074924641566816080813532117330164170735826506058593336493506949940848765499363819979320939282982919375398914069089805838577960069559874710450749184504343158625601442750728642144320053369391401453263161778602373943877045175788126537500233005203199557624967965590793781756009465530421223673808125596433197260511783663578786736138935644611510704657775055723748995739319607080698291706672519903182694009317085805385536758377672539801974784561084785214848661651222229053227670685928377893767647252686625276960330847323544380262772513558097705217910173841586565717809284675233221080231577130823498565742290973934353578363786123901377008356061462259702760414143669277697923481329382036082365581631119452213080205665607711034939062213504411839471693389012471682488207713408247282801606252308689854690991601896392437152376807047885836807124651272249276394406673746266600513339531302441597329328860879198543150618344495818220381486120081970644299110858298968002121740143081288079508139563209087295527719367173168106768375506915606943108549245716620373830060141970205040892622981573408438652748229262836768577133291430512630955280803424724196127579267317846847791376967994664996778215743464113305881127859658342136025422526253331635631460506627004611980787081842254551741462578148603623855829369322786137060685891291435194631140068361800483271128634749132132971353603620027024716484537448423106675324994710468053889512394893514334837153395533834348403921843671090279258326994681148154509142162580964894514471308191594274686102557491312601314533860301136068373599944983953814096508300771470878732452631263022848315030291186148299024986122924683473526707343847812189841432922530601815116420801242787912391307630864812660550751151441018948323178392943560820702138906652858349160822646791001547422000184496198987157809150883422120230755195456097442988540143448596056548712752753003317770322809336050244720398302577934785597169760136574525128019546627344011889203396286619068849129954531193420633612854885803802028725694443666004393974334342839965984776174178847000942641371823118150601089025658453549601454016096632495098715234237737176508065775340702080190358723948115393840433060000135598401094463183398927598889793497606519586967113700162278801725782749877410404659302708245664210238655750720082079538875176974692552920936501606606866569929169371791780977726922614891904613289635968686335449239351647568523633272258748557443869529954049787099190708822709999587242809457640363519838368205065844373807448294617856937050824123130756707175729799495498867380865901987198815749951911265076033136010908797500118678461298299071823888778813050060422793934707182580123940234065675763271656487864405754352206491046961368704917958363407322427782920321120787151833432821373252241273319727783106163181784755054932997817507792158953375871008606599013586326661243229111891681266673099502750317006015399105651919684706503486939756806007613925606641851237937317483021713469913903086727804422339884805998572673584655941174330298906121283561184439269671912454162169811425388932551233741967427676073130322377387228566618751579691517401874934142375618638565641434531979391643418418296019687297216744686905789482942057916823775965016956349482781018825717425728267312926628503916210488590960638212311560316707367250827209463031721213405364096098235203281375434873225662816660057684490146270016910974472130183462622496937435337881407565728560430654198835983450858692110039199266587158950122136797056323673162239550438089016927087194452134724631284743650624396821116447126504783357221047016443805086970143542327891642694149471485034627907556174566025282280816849181801325002274245249691005633948914485712292713596959940707698840210731995236740262302323685101544937634564908890042802426208394131598044272530340479140221106497137169286392319298493544706752464560336968291504098255428420846541170305202429635356061245490374542890966442130280424204148288444904945395767103969899445014933541749732539658807347710122597366671305425375694081854603205929931041330937461688253894901352731943414265432081355411059383743573276799038858594595087368828032503479766598256796643318484869798545140616919865647029200121865777998648674157750624081325065061451934977721998893221079289360196669920514923164564274321120804668943645288103919764187905240053740379095998394627054458208067015238598106750470017166436256350367159401514866114047168501427664061167504547192631712278134447069465451606533249540401038194879427088113243695328308941049053489961478641549732230575823330552857439223962077143377753355496321924642967260800568327005478985563377663698937199634526103812678055193333425476532476800645165184819225440228578995323217890498934989975999349463090121527463421731182793699055502201340746723104254381672606051450003281505419239559286671904987768902643944838421866396544103637453931397093317788489897585191484240468673988770306376524567065386250977903718366395367437520526423616282186163555298892765497085643313108838598408165731186769954161062809443775359858295740748231129052190862890678814903745381159426007065661733272152418418244428802289398461708421479707802008381459211305351032261768984111439973305767620084398922350753215119969041788207230118594836537346215892811893676227830814306055680686651182097371450226967582993373329369530945639536971866942510840455505314830652214110460706792176109205029751902295432598900433794304411546040289245476053389923203252073364567851160528027697364325149918245325173074049521697752644322658909049144183365449120800373431865613878361024431642226500226923759208670269965005105539927973630635270391844277760486857885233286395288701818953103936157267880154624676703068272347576554630598722025531009123242428095953182258790181017681952179240955486976899449994286834157581029331893001287404650758450616035482178719444626468271636156240238593061366308886541535070935277498754600760399977028013070922998040776227331520508529438414189265501488158086084232541099481846581119335940304298307468890150676471161998273228005471632466256319189704587830254414682980805911074191185605072384574932593384383868234571210670295215497402218923160908921833673108493846408653071874165584511701039586640905719359079972525672777572128849930364071696001966344600368845505581474833305142233743179109957679508544995189066527080525816601874325614762645524106062236953841099806688613236237265076757981977742242840756068260373271130514284856956286048707030734172026504374209040162881546424557349110082554228219644891646218665541034055449248685331029190497622906671775196650191908069330507496023923402217343007345135829681311621146000194509744218086754387379280825058807232662312073992774992384815088494682415579470970545921844795462763180823547508287114895587641113228713056294629392940072315657483031255555862757460874335977012553403185956274245857112590188214236557249741615800433633923955172763685587377312566306962183791899684708669917519155677618365109535485707989807965641435742218572177978364018916758672451155069745818390748684767524126825809738896116997535476729355350227678567624487001928731977484481441747207859762179908179079680002457223560318085554732254944529459031673238264078503205130289781446953270860912862472705191828890299673689887857192443044330338916196549713545899718366520088568244271313208175897529008349195967054970360273439718485895095347185792800950803763426207109512252071826521077734646233330692715447065084378636373343489235937550441437187816007578953285506092095442598009712925518198302840491071352043522894618007220293272582858202342073027975388313142251074441254271244984757647353254757750851465963839733933702087486639965196241950699066493762324836840372508410774536774294086489902739615766400741888421525911893659900330750792567830585227618559326599711608002812890404488783703163301887706094937451473837913773066588882007199237037744493309624865603546003013443931649731407811253183934613659500969960294921439554855535539957431924547230036555582441116101817387033359813463056760471199050827844536436401631284715340910065538065073525997320878109908513077517434413375505892820164639755721761587525285272131870448693912217703961700271010126455222108848631162141328944399609761410378875137769136125655960975308521486768994195223951632788357717372722049430612049492505773884232007549961432891553051170598244347475050895552962123731726649468502395530741597788858003189693322538233903879055308175498853188440034828486572304329414592097409892851865373162707394423210888500988981089943339310759607998960158198082896514267806684962509066264717801933812936020744024111055570427497008052508389244672680592579364946078529899018648011768875058276097346060147507229692442916934216313870781657104439299554684363919210562718032966990722490570658290053779192067218248386120328626141121740337304958340404446503592630624284417817367468427060502284991316067692881120467504791559049942321679757915778081397531311795463350893089662757887007145878454612894775442575866568051353558536032613956933183288278909320862224511819928931926087760431605129877744431791837841017874052997241375032070635805713137701240170666526909858586338721498982126415459415888526849660659890822181395105778375695203696843378475870090613502579329799616917080365220143311333343180787431827865876966195072416718938447062762864625946143199521678657026620820158804040901985641487552764534550664405749342755264110959226256490584640151451697194298621186011888876358998016132011265936400529684751494902263440007992579107076490667717896845664580212027516998978432797956799000239036054513572081225451869328583114041518093238565614922253863992340851791109291693703193743352968055380747180139334359017449643496893825244641231944189398846013973212655492665761467914792688176191670811234860009580796375324220602649377220782888703804275430260269339143975191959190991568571930207382609638696474285096451319299932497563783464425391830819844990638035737087507347721819203924937228402887871171257974538830530546200401421585142542002724296067796934523595293133329473945306059997039497901120830114966973931946816908467556561485969371125010336131347426090152856620281203291807903774743576059255456327545409952450927285796551013504214565811189078109344140542687527841056353660546661524894874644431705740263431931166975324883388622123037826476842043476207415914036904771687607732321067112365854193933154255316704184951840347321383247644723725561199888444061801803003925006595410503220914972910436121338800431390343460450082001779016419472265287355743745862095514224877614726436039990997101115014800095186949219579148088603024921326561853330828417662097751412640453711558142121610043788263315496748171695822161414381820529725181316263759001343802064007527641930659430083656078735786067460023960459845880523747755381633558832633705832267913617717549573012548896818919898491097039529394355250100674831301421620270174056248949085297182404750867402089714578239391385207577630780971700351073710065019379058273447056455577180676445621964869287463215815707215169412904575955420879234231107750429048602256323472766401006297716187329179831238013745224308808861740381159264070102328654853933533538672748865957860860728435816127388742473575882990994954268778231357531929324072584783531298159016109384630503090567358032753570523540604854190394587060924932124127537474653722100947672574229675207049018504959400044649733005326052537084764854248476094356527236052565705935769341276837497355291540007411851639501221596117413767462010140950339291635309821848131932536393808606467324485699405599130710591315794280304370200032004954879769926107166916552463535624286606607302073554001465092361217486274848172317476550164067286546146126114938768744615454724467708549347534629438619084713258521625277750567211469838419963273941741001555552853731607701510053214306920813239693313936953865054895116390780246360490597312606925400323334979454929355182338291451101431885348556239949730095992855068642681172751633617667443512142709899134290642101381136855999952631988152077983295715104837312509048816772247797004799675552076869713350494182822782925456194816639310256511168678895712675381690580499970257016828478555806394391501563545636381422700137506779318235658035863896363668712069621634099834702040331816301304146081579446851455575866804459403106433903621507781954662184135484970839627816834298111594472519668413330594038492527814692773000078988326563317726039355918609650307627116532308858853311138498945431394856728653691322401445549217903534754948030982055018601753714422908796615429383825611641302401944880700299505754954940083158959310968902531294374981830026430792865205826308719471614424626556104213131213858620568164943983770364226505528404708356893403919567936905326149986345604087681062788277742634222835931963652778420067922648326404356758532391377661464496177635212572803518109241706982592723841933162679193143960115303823847888531624989834050095546487127551559342007143051313580350752214374300038789839090674319925794542988474586072825678290286019996209115379348332227925192667535025879625790084865648253004343535073717254920042080947854797239897045962007770100897569357950041000539373684079352782768041376293185703121986306170097918812494181529400030705617333903135012011281083635170126877400214155130969990398464871701011076020233368121454506195935547747916365410664422466813163143475789582534625503739106958757590029703552314671204951206479236619183710550481995748206661862152358234208635715243597661388560922984186310030296910267096684288197501158411990588478338174940196398006358306818318908887799180411050612189612621768834070051144298656416407777360988618570403878857293198878505199386092531055269532033450775979014060649498629096412427014347161539296748769373657196095132102212025740365954278356995574586007196533490147846998567087674138587919995230904278798149607835129727420685089241377458049674557267880487175053314354389347058769887929711508052762338014 1)
  message-checksum()
  message--yank-original-internal(nil)
  (let nil (message--yank-original-internal 'nil))
  eval((let nil (message--yank-original-internal 'nil)))
  message-yank-original()
  gnus-inews-yank-articles((165707))
  gnus-summary-reply((165707) t)
  gnus-summary-reply-with-original(nil t)
  gnus-summary-wide-reply-with-original(nil)
  funcall-interactively(gnus-summary-wide-reply-with-original nil)
  call-interactively(gnus-summary-wide-reply-with-original nil nil)
  command-execute(gnus-summary-wide-reply-with-original)

When I tried to reply again to the same post, Emacs ate all my
memory and almost froze.



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Gnus crash
  2018-12-25 20:13 ` Gnus crash (was: Window change functions) Juri Linkov
@ 2018-12-25 20:39   ` Stefan Monnier
  2018-12-26  9:19     ` Paul Eggert
  0 siblings, 1 reply; 18+ messages in thread
From: Stefan Monnier @ 2018-12-25 20:39 UTC (permalink / raw)
  To: emacs-devel

> Debugger entered--Lisp error: (overflow-error)
>   ash(...)
>   message-checksum()
>   message--yank-original-internal(nil)

Sounds like bug#33083


        Stefan




^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Gnus crash
  2018-12-25 20:39   ` Gnus crash Stefan Monnier
@ 2018-12-26  9:19     ` Paul Eggert
  2018-12-26 22:22       ` Juri Linkov
  0 siblings, 1 reply; 18+ messages in thread
From: Paul Eggert @ 2018-12-26  9:19 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Juri Linkov, emacs-devel

Stefan Monnier wrote:
>> Debugger entered--Lisp error: (overflow-error)
>>    ash(...)
>>    message-checksum()
>>    message--yank-original-internal(nil)
> 
> Sounds like bug#33083

Should be fixed now.



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2018-12-25  9:41 Window change functions martin rudalics
  2018-12-25 20:13 ` Gnus crash (was: Window change functions) Juri Linkov
@ 2018-12-26 19:55 ` Stefan Monnier
  2018-12-27  9:36   ` martin rudalics
  1 sibling, 1 reply; 18+ messages in thread
From: Stefan Monnier @ 2018-12-26 19:55 UTC (permalink / raw)
  To: emacs-devel

> The attached patch should radically change the way we run functions
> when window changes occur.  The only hook it does not affect is
> 'window-scroll-functions'.  For the remainder we now would have the
> following four hooks:
>
> (1) 'window-state-change-functions' runs when a window got a new
>     buffer or was created or deleted since last redisplay.

I'd call it `window-buffer-change-functions` because the notion of
"window-state" includes much more than just whether it's alive and which
buffer it displays.

> (2) 'window-size-change-functions' runs when a window was created or
>     got a new buffer, body or total size since last redisplay.
>
> (3) 'window-configuration-change-hook' runs as (1) and (2) together.
>
> (4) 'window-selection-change-functions' run when a window got
>     (de-)selected since last redisplay.

These sound good, thanks.

One question is where they're consulted and run.
E.g. for (4) one window got deselected and another got selected, so
I guess it's run twice and is looked up each time in the corresponding
window's buffer?

For (1) in response to set-window-buffer, will the hook be run in the
buffer before the change or after the change (and in which buffer will
the hook's value be consulted)?

> This means that, for example, running 'save-window-excursion' will
> call such hooks only if a redisplay is embedded within that form.  It
> also means that any code that changes windows internally won't have to
> care about calling `run-window-configuration-change-hook' any more.

That generally sounds like a very good idea for
window-selection-change-functions.

For 'window-size-change-functions and 'window-state-change-functions
(and hence window-configuration-change-hook), it's sufficiently rare to
change the size (or the window's buffer) of a window and revert it
before the next redisplay that I'm not sure it's beneficial.

Tho, it is clearly normal for a window to change size
several times within a given command (e.g. balance-windows-area does
just that to incrementally settle on a balanced result).

Is this delaying done on-purpose for all hooks individually, or is there
some underlying reason why it's helpful to delay them all?

> - Some functions on 'window-configuration-change-hook' (notably in
>   erc-track.el and rcirc.el) put further code on 'post-command-hook'.
>   This concept will be broken since 'post-command-hook' now runs
>   before 'window-configuration-change-hook'.

This does sound potentially problematic, indeed.  Looking at those two,
I don't see why they postpone the work to post-command-hook, tho.
They use the exact same code, with the exact same comment (erc-track
copied it from rcirc), but neither clearly explains why they do that.
The comment just says:

  (unless (minibuffer-window-active-p (minibuffer-window))
    ;; delay this until command has finished to make sure window is
    ;; actually visible before clearing activity
    (add-hook 'post-command-hook #'erc-modified-channels-update)))

I'm wondering which scenario caused problem.

>   xterm.el has a bit more convoluted code whose purpose I haven't been
>   able to figure out yet.  The solution should be similar though.

I don't see anything there that would suffer from being postponed.

> +@defvar window-state-change-functions
> +This variable specifies functions called at the end of redisplay when
> +window states have changed.  The value should be a list of functions
> +that take one argument

If it's run "at the end of redisplay", then I think it's too late: those
hooks will often want to change something visual, and they will want it
to appear right away, so it should be run just *before* redisplay.

> +Functions specified buffer-locally are called for any window showing
> +the corresponding buffer if that window has been created or assigned
> +that buffer since the last time window change functions were run.  In
> +this case the window is passed as argument.

Hmm... so to detect when a specific buffer stops being displayed you
need to use the global part of the hook and you're not told which was
the previously displayed buffer, so you need to keep track of
that yourself.

>  @defvar window-size-change-functions
[...]
> -Each function receives the frame as its sole argument.  To find out
> -whether a specific window has changed size, compare the return values of
> -@code{window-pixel-width-before-size-change} and
> -@code{window-pixel-width} respectively
> -@code{window-pixel-height-before-size-change} and
> -@code{window-pixel-height} for that window (@pxref{Window Sizes}).
[...]
> +Functions specified buffer-locally are called for any window showing
> +the corresponding buffer if that window has been added or assigned
> +another buffer, total or body size since the last time window change
> +functions were run.  In this case the window is passed as argument.

The new text doesn't mention the window-pixel-height-before-size-change
functions any more.  Is it just an oversight or is there a reason
for that?

> +Functions specified buffer-locally are called for any window showing
> +the corresponding buffer if that window has been selected or
> +deselected (among all windows or among all windows on its frame) since
> +the last time window change functions were run.  In this case the
> +window is passed as argument.

IIUC this hook is hence also run for changes to frame-selected-window,
even when that frame is not selected?  I wonder if it's a good idea.
frame-selected-window is a fairly obscure detail, in my experience.


        Stefan "who hasn't looked at the code"




^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Gnus crash
  2018-12-26  9:19     ` Paul Eggert
@ 2018-12-26 22:22       ` Juri Linkov
  0 siblings, 0 replies; 18+ messages in thread
From: Juri Linkov @ 2018-12-26 22:22 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Stefan Monnier, emacs-devel

>>> Debugger entered--Lisp error: (overflow-error)
>>>    ash(...)
>>>    message-checksum()
>>>    message--yank-original-internal(nil)
>>
>> Sounds like bug#33083
>
> Should be fixed now.

Thanks, I confirm that this is now fixed.



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2018-12-26 19:55 ` Window change functions Stefan Monnier
@ 2018-12-27  9:36   ` martin rudalics
  2018-12-27 15:56     ` Eli Zaretskii
  2019-01-05  6:13     ` Stefan Monnier
  0 siblings, 2 replies; 18+ messages in thread
From: martin rudalics @ 2018-12-27  9:36 UTC (permalink / raw)
  To: Stefan Monnier, emacs-devel

 >> (1) 'window-state-change-functions' runs when a window got a new
 >>      buffer or was created or deleted since last redisplay.
 >
 > I'd call it `window-buffer-change-functions` because the notion of
 > "window-state" includes much more than just whether it's alive and which
 > buffer it displays.

OK.  While deleting a window doesn't really change its buffer, a state
change could be admittedly considered as something more substantial.

 >> (2) 'window-size-change-functions' runs when a window was created or
 >>      got a new buffer, body or total size since last redisplay.
 >>
 >> (3) 'window-configuration-change-hook' runs as (1) and (2) together.
 >>
 >> (4) 'window-selection-change-functions' run when a window got
 >>      (de-)selected since last redisplay.
 >
 > These sound good, thanks.
 >
 > One question is where they're consulted and run.
 > E.g. for (4) one window got deselected and another got selected, so
 > I guess it's run twice and is looked up each time in the corresponding
 > window's buffer?

Yes.  The buffer local hooks are run for every window the buffer is in
that gets selected or deselected with the respective window passed as
argument.  This means that if a buffer B is shown in two windows W1
and W2 and W1 gets deselected and W2 gets selected, the buffer-local
hook is run twice for the same buffer - once with W1 and once with W2
as argument.

The global hooks are run once for each frame where a selection or
deselection occurred with the frame as argument.  So if a new frame
gets selected there will be two calls of the global hook - once for
the old and once for the now selected frame.

 > For 'window-size-change-functions and 'window-state-change-functions
 > (and hence window-configuration-change-hook), it's sufficiently rare to
 > change the size (or the window's buffer) of a window and revert it
 > before the next redisplay that I'm not sure it's beneficial.
 >
 > Tho, it is clearly normal for a window to change size
 > several times within a given command (e.g. balance-windows-area does
 > just that to incrementally settle on a balanced result).
 >
 > Is this delaying done on-purpose for all hooks individually, or is there
 > some underlying reason why it's helpful to delay them all?

There are several reasons that favor delaying:

(1) When finishing a window excursion by restoring the previous
     configuration we should run 'window-configuration-change-hook' if
     and only if something really changed.  Telling whether something
     really changed is hard and I doubt current Emacs does it right.

(2) 'balance-windows-area' is a good example for why we should delay
     calling size change functions IMO: Its intermediate steps are not
     really interesting for any client of that hook.

(3) Delaying hooks and running them all in one bunch allows to
     reliably look at possibly related changes by consulting the
     'window-old-...' functions.

(4) Some clients do the delaying themselves by putting an according
     function on 'post-command-hook'.  This won't be needed any more
     with delayed execution.

 >> - Some functions on 'window-configuration-change-hook' (notably in
 >>    erc-track.el and rcirc.el) put further code on 'post-command-hook'.
 >>    This concept will be broken since 'post-command-hook' now runs
 >>    before 'window-configuration-change-hook'.
 >
 > This does sound potentially problematic, indeed.  Looking at those two,
 > I don't see why they postpone the work to post-command-hook, tho.
 > They use the exact same code, with the exact same comment (erc-track
 > copied it from rcirc), but neither clearly explains why they do that.
 > The comment just says:
 >
 >    (unless (minibuffer-window-active-p (minibuffer-window))
 >      ;; delay this until command has finished to make sure window is
 >      ;; actually visible before clearing activity
 >      (add-hook 'post-command-hook #'erc-modified-channels-update)))
 >
 > I'm wondering which scenario caused problem.

I don't use them and cannot tell.  But since change functions
would now run after 'post-command-hook' running
'erc-modified-channels-update' from 'window-configuration-change-hook'
right away should satisfy the author's intentions.

 >>    xterm.el has a bit more convoluted code whose purpose I haven't been
 >>    able to figure out yet.  The solution should be similar though.
 >
 > I don't see anything there that would suffer from being postponed.

Neither do I.

 >> +@defvar window-state-change-functions
 >> +This variable specifies functions called at the end of redisplay when
 >> +window states have changed.  The value should be a list of functions
 >> +that take one argument
 >
 > If it's run "at the end of redisplay", then I think it's too late: those
 > hooks will often want to change something visual, and they will want it
 > to appear right away, so it should be run just *before* redisplay.

Note that 'window-size-change-functions' are currently already run
right in the middle of redisplay.  Often, window sizes are correct
only *after* redisplay.  Think of minibuffer window resizing or
changes in the fringes, margins or modeline sub-structures.  But a
final word on the location of the call will have to be told by Eli.

 > Hmm... so to detect when a specific buffer stops being displayed you
 > need to use the global part of the hook and you're not told which was
 > the previously displayed buffer, so you need to keep track of
 > that yourself.

Correct.  The right position to detect when a "specific buffer stops
being displayed" is (1) 'kill-buffer-hook' and *would be* (2) a
'before-delete-window-hook' because right after its deletion a window
might get GCed immediately and (2) would not have anything else but
the buffer itself to work upon.  So if someone sees a need for (2)
please tell me and I'll add such a hook (it won't cost much but some
lines in the manual).

 > The new text doesn't mention the window-pixel-height-before-size-change
 > functions any more.  Is it just an oversight or is there a reason
 > for that?

It's been renamed to 'window-old-pixel-height' so the 'window-old-'
prefix matches that of its new colleagues.

 >> +Functions specified buffer-locally are called for any window showing
 >> +the corresponding buffer if that window has been selected or
 >> +deselected (among all windows or among all windows on its frame) since
 >> +the last time window change functions were run.  In this case the
 >> +window is passed as argument.
 >
 > IIUC this hook is hence also run for changes to frame-selected-window,
 > even when that frame is not selected?  I wonder if it's a good idea.
 > frame-selected-window is a fairly obscure detail, in my experience.

If someone changes it separately (that is, sets it for a non-selected
frame), there is now a hook to trace that.  Otherwise, any change of
'frame-selected-window' is just a side-effect of changing the selected
window and will not be noticed by clients.

 >          Stefan "who hasn't looked at the code"

He could try it though.

martin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2018-12-27  9:36   ` martin rudalics
@ 2018-12-27 15:56     ` Eli Zaretskii
  2019-01-05  6:17       ` Stefan Monnier
  2019-01-05  6:13     ` Stefan Monnier
  1 sibling, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2018-12-27 15:56 UTC (permalink / raw)
  To: martin rudalics; +Cc: monnier, emacs-devel

> Date: Thu, 27 Dec 2018 10:36:33 +0100
> From: martin rudalics <rudalics@gmx.at>
> 
>  >> +@defvar window-state-change-functions
>  >> +This variable specifies functions called at the end of redisplay when
>  >> +window states have changed.  The value should be a list of functions
>  >> +that take one argument
>  >
>  > If it's run "at the end of redisplay", then I think it's too late: those
>  > hooks will often want to change something visual, and they will want it
>  > to appear right away, so it should be run just *before* redisplay.
> 
> Note that 'window-size-change-functions' are currently already run
> right in the middle of redisplay.  Often, window sizes are correct
> only *after* redisplay.  Think of minibuffer window resizing or
> changes in the fringes, margins or modeline sub-structures.  But a
> final word on the location of the call will have to be told by Eli.

I don't think I can utter that final word, primarily because I don't
understand Stefan's concerns.  Stefan, could you please elaborate?



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2018-12-27  9:36   ` martin rudalics
  2018-12-27 15:56     ` Eli Zaretskii
@ 2019-01-05  6:13     ` Stefan Monnier
  2019-01-05 10:18       ` martin rudalics
  1 sibling, 1 reply; 18+ messages in thread
From: Stefan Monnier @ 2019-01-05  6:13 UTC (permalink / raw)
  To: martin rudalics; +Cc: emacs-devel

> There are several reasons that favor delaying:
>
> (1) When finishing a window excursion by restoring the previous
>     configuration we should run 'window-configuration-change-hook' if
>     and only if something really changed.  Telling whether something
>     really changed is hard and I doubt current Emacs does it right.

I can't remember the last time I found save-window-excursion to be
useful, so I don't find this use case important.

> (2) 'balance-windows-area' is a good example for why we should delay
>     calling size change functions IMO: Its intermediate steps are not
>     really interesting for any client of that hook.

This is indeed a valid case.  While I do use balance-windows-area, I'm
not sure how important this is, tho.

> (3) Delaying hooks and running them all in one bunch allows to
>     reliably look at possibly related changes by consulting the
>     'window-old-...' functions.

I don't understand this.  If we run the hook right away, the old state
is easy to get to as well, isn't it?

> (4) Some clients do the delaying themselves by putting an according
>     function on 'post-command-hook'.  This won't be needed any more
>     with delayed execution.

The flip side is that while it's currently easy to delay the execution
using post-command-hook, it will be impossible to *un*delay the
execution with the new setup.  That's what worries me.

> Note that 'window-size-change-functions' are currently already run
> right in the middle of redisplay.  Often, window sizes are correct
> only *after* redisplay.  Think of minibuffer window resizing or

OK, miniwindow resizing is a valid case.

>> Hmm... so to detect when a specific buffer stops being displayed you
>> need to use the global part of the hook and you're not told which was
>> the previously displayed buffer, so you need to keep track of
>> that yourself.
>
> Correct.  The right position to detect when a "specific buffer stops
> being displayed" is (1) 'kill-buffer-hook' and *would be* (2) a
> 'before-delete-window-hook' because right after its deletion a window
> might get GCed immediately and (2) would not have anything else but
> the buffer itself to work upon.  So if someone sees a need for (2)
> please tell me and I'll add such a hook (it won't cost much but some
> lines in the manual).

If we run window-buffer-change-functions eagerly (i.e. from
set-window-buffer, as well as when creating/deleting a window), then
it's easy to let the hook access the "old buffer" that's being replaced
(we can even pass it as a parameter to the hook functions).

>> IIUC this hook is hence also run for changes to frame-selected-window,
>> even when that frame is not selected?  I wonder if it's a good idea.
>> frame-selected-window is a fairly obscure detail, in my experience.
> If someone changes it separately (that is, sets it for a non-selected
> frame), there is now a hook to trace that.

Right, and my question is: why bother?  I think it makes the API more
complex with zero benefit.

> He could try it though.

Didn't notice anything funny.


        Stefan



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2018-12-27 15:56     ` Eli Zaretskii
@ 2019-01-05  6:17       ` Stefan Monnier
  2019-01-05  7:20         ` Eli Zaretskii
  2019-01-05 10:18         ` martin rudalics
  0 siblings, 2 replies; 18+ messages in thread
From: Stefan Monnier @ 2019-01-05  6:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: martin rudalics, emacs-devel

>>  >> +@defvar window-state-change-functions
>>  >> +This variable specifies functions called at the end of redisplay when
>>  >> +window states have changed.  The value should be a list of functions
>>  >> +that take one argument
>>  > If it's run "at the end of redisplay", then I think it's too late: those
>>  > hooks will often want to change something visual, and they will want it
>>  > to appear right away, so it should be run just *before* redisplay.
>> Note that 'window-size-change-functions' are currently already run
>> right in the middle of redisplay.  Often, window sizes are correct
>> only *after* redisplay.  Think of minibuffer window resizing or
>> changes in the fringes, margins or modeline sub-structures.  But a
>> final word on the location of the call will have to be told by Eli.
> I don't think I can utter that final word, primarily because I don't
> understand Stefan's concerns.  Stefan, could you please elaborate?

For example, I have a window-size-change-functions which I use to
re-balance windows (using balance-window-area) after a frame resize.
It doesn't care about the exact window sizes when it's called, so having
correct window sizes when it's called is not necessary.  OTOH redisplay
will need to happen right after it was run, because it changes
window sizes.  So the best time to run it is right before redisplay.


        Stefan



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-05  6:17       ` Stefan Monnier
@ 2019-01-05  7:20         ` Eli Zaretskii
  2019-01-05 10:18         ` martin rudalics
  1 sibling, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2019-01-05  7:20 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: rudalics, emacs-devel

> From: Stefan Monnier <monnier@IRO.UMontreal.CA>
> Cc: martin rudalics <rudalics@gmx.at>, emacs-devel@gnu.org
> Date: Sat, 05 Jan 2019 01:17:57 -0500
> 
> >>  > If it's run "at the end of redisplay", then I think it's too late: those
> >>  > hooks will often want to change something visual, and they will want it
> >>  > to appear right away, so it should be run just *before* redisplay.
> >> Note that 'window-size-change-functions' are currently already run
> >> right in the middle of redisplay.  Often, window sizes are correct
> >> only *after* redisplay.  Think of minibuffer window resizing or
> >> changes in the fringes, margins or modeline sub-structures.  But a
> >> final word on the location of the call will have to be told by Eli.
> > I don't think I can utter that final word, primarily because I don't
> > understand Stefan's concerns.  Stefan, could you please elaborate?
> 
> For example, I have a window-size-change-functions which I use to
> re-balance windows (using balance-window-area) after a frame resize.
> It doesn't care about the exact window sizes when it's called, so having
> correct window sizes when it's called is not necessary.  OTOH redisplay
> will need to happen right after it was run, because it changes
> window sizes.  So the best time to run it is right before redisplay.

Since redisplay never resizes windows, except when it resizes the
mini-window, I don't see how redisplay is relevant to this issue.  And
if resizing a mini-window is the reason why Martin decided to run this
hook at the end of redisplay, we can arrange for an immediate
additional redisplay cycle given some special return value of the hook
function, then the user will never see an inaccurate display.



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-05  6:13     ` Stefan Monnier
@ 2019-01-05 10:18       ` martin rudalics
  0 siblings, 0 replies; 18+ messages in thread
From: martin rudalics @ 2019-01-05 10:18 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

 > I can't remember the last time I found save-window-excursion to be
 > useful, so I don't find this use case important.

Taking into account how often it's used in our code base, this use
case is important.  But let's substitute 'set-window-configuration'
for it, or 'window-state-put'.

 >> (2) 'balance-windows-area' is a good example for why we should delay
 >>      calling size change functions IMO: Its intermediate steps are not
 >>      really interesting for any client of that hook.
 >
 > This is indeed a valid case.  While I do use balance-windows-area, I'm
 > not sure how important this is, tho.

It's one example for how many size changes and corresponding calls of
'window-configuration-change-hook' may happen in one and the same
function call.

 >> (3) Delaying hooks and running them all in one bunch allows to
 >>      reliably look at possibly related changes by consulting the
 >>      'window-old-...' functions.
 >
 > I don't understand this.  If we run the hook right away, the old state
 > is easy to get to as well, isn't it?

But after running the hook what would be the "old state"?  I provide
four interrelated hooks and all of them need to know the old state.
Running an arbitrary hook in between would invalidate any consistent
notion of an "old state".

 >> (4) Some clients do the delaying themselves by putting an according
 >>      function on 'post-command-hook'.  This won't be needed any more
 >>      with delayed execution.
 >
 > The flip side is that while it's currently easy to delay the execution
 > using post-command-hook, it will be impossible to *un*delay the
 > execution with the new setup.  That's what worries me.

Since the hooks are run after 'post-command-hook' there's indeed no
way to undelay them.  But the effect of running that "something else"
from 'post-command-hook' or from ‘window-configuration-change-hook’
right away should be the same.

 >> Note that 'window-size-change-functions' are currently already run
 >> right in the middle of redisplay.  Often, window sizes are correct
 >> only *after* redisplay.  Think of minibuffer window resizing or
 >
 > OK, miniwindow resizing is a valid case.

Here I'm not sure though (and Eli was completely sceptical about it).
'window-configuration-change-hook' happily lived decades without
reacting to minibuffer size changes until someone turned up that
stone.  This is one of the areas that must be observed cautiously.

 > If we run window-buffer-change-functions eagerly (i.e. from
 > set-window-buffer, as well as when creating/deleting a window), then
 > it's easy to let the hook access the "old buffer" that's being replaced
 > (we can even pass it as a parameter to the hook functions).

But then we would have to run it for every single instance of
'set-window-buffer' or 'with-selected-window'.  The main objective of
the patch was to avoid precisely that (and to not fall into a similar
trap when providing a window selection hook).

 >>> IIUC this hook is hence also run for changes to frame-selected-window,
 >>> even when that frame is not selected?  I wonder if it's a good idea.
 >>> frame-selected-window is a fairly obscure detail, in my experience.
 >> If someone changes it separately (that is, sets it for a non-selected
 >> frame), there is now a hook to trace that.
 >
 > Right, and my question is: why bother?  I think it makes the API more
 > complex with zero benefit.

Maybe - but why then provide a thing like 'frame-selected-window' in
the first place?

Anyway given the fact that one of our basic invariants is

(eq (selected-window) (frame-selected-window (selected-frame)))

there is also zero cost for the admittedly small benefit.

 >> He could try it though.
 >
 > Didn't notice anything funny.

Thanks for trying.

martin




^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-05  6:17       ` Stefan Monnier
  2019-01-05  7:20         ` Eli Zaretskii
@ 2019-01-05 10:18         ` martin rudalics
  2019-01-06 17:22           ` Stefan Monnier
  1 sibling, 1 reply; 18+ messages in thread
From: martin rudalics @ 2019-01-05 10:18 UTC (permalink / raw)
  To: Stefan Monnier, Eli Zaretskii; +Cc: emacs-devel

 > For example, I have a window-size-change-functions which I use to
 > re-balance windows (using balance-window-area) after a frame resize.

Have you ever tried without that?  Resizing a frame should resize
windows proportionally which, if windows were balanced before the
resizing, should leave them balanced afterwards.

 > It doesn't care about the exact window sizes when it's called, so having
 > correct window sizes when it's called is not necessary.  OTOH redisplay
 > will need to happen right after it was run, because it changes
 > window sizes.  So the best time to run it is right before redisplay.

Not really.  The run_window_change_functions calls in redisplay come
right after do_pending_window_change calls.  So the former should in
the fastest possible way pick up any changes caused by the latter.

martin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-05 10:18         ` martin rudalics
@ 2019-01-06 17:22           ` Stefan Monnier
  2019-01-07  9:03             ` martin rudalics
  0 siblings, 1 reply; 18+ messages in thread
From: Stefan Monnier @ 2019-01-06 17:22 UTC (permalink / raw)
  To: martin rudalics; +Cc: Eli Zaretskii, emacs-devel

>> For example, I have a window-size-change-functions which I use to
>> re-balance windows (using balance-window-area) after a frame resize.
> Have you ever tried without that?

Yes, of course.

> Resizing a frame should resize windows proportionally which, if
> windows were balanced before the resizing, should leave them
> balanced afterwards.

Yes, it works OK most of the time, but repeated resizing seems to
accumulate errors (it's better now with pixelwise window sizing, tho),
and it doesn't correctly handle more complex setups where some windows
have a fixed size and where I want relative area to be preserved for
windows of different aspect ratios (where horizontal resizing of the
frame may require vertical resizing of some windows, for example).

>> It doesn't care about the exact window sizes when it's called, so having
>> correct window sizes when it's called is not necessary.  OTOH redisplay
>> will need to happen right after it was run, because it changes
>> window sizes.  So the best time to run it is right before redisplay.
> Not really.  The run_window_change_functions calls in redisplay come
> right after do_pending_window_change calls.  So the former should in
> the fastest possible way pick up any changes caused by the latter.

Hmm... so frame resizing only triggers late... that's a good point.

So I guess the remaining cases are hypothetical ones (e.g. if you
want to put the window dimension in the mode-line).  I haven't tried
those to see if they'd be handled correctly.


        Stefan



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-06 17:22           ` Stefan Monnier
@ 2019-01-07  9:03             ` martin rudalics
  2019-01-08 18:10               ` Stefan Monnier
  0 siblings, 1 reply; 18+ messages in thread
From: martin rudalics @ 2019-01-07  9:03 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel

 > So I guess the remaining cases are hypothetical ones (e.g. if you
 > want to put the window dimension in the mode-line).  I haven't tried
 > those to see if they'd be handled correctly.

I have them in the mode lines all the time and haven't encountered any
problems so far.

martin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-07  9:03             ` martin rudalics
@ 2019-01-08 18:10               ` Stefan Monnier
  2019-01-09 10:02                 ` martin rudalics
  0 siblings, 1 reply; 18+ messages in thread
From: Stefan Monnier @ 2019-01-08 18:10 UTC (permalink / raw)
  To: emacs-devel

>> So I guess the remaining cases are hypothetical ones (e.g. if you
>> want to put the window dimension in the mode-line).  I haven't tried
>> those to see if they'd be handled correctly.
> I have them in the mode lines all the time and haven't encountered any
> problems so far.

Great.  Then while I think delaying the execution like you do has some
downsides, I agree that overall the upsides largely make up for it.


        Stefan




^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-08 18:10               ` Stefan Monnier
@ 2019-01-09 10:02                 ` martin rudalics
  2019-01-11  9:23                   ` martin rudalics
  0 siblings, 1 reply; 18+ messages in thread
From: martin rudalics @ 2019-01-09 10:02 UTC (permalink / raw)
  To: Stefan Monnier, emacs-devel

 > Great.  Then while I think delaying the execution like you do has some
 > downsides, I agree that overall the upsides largely make up for it.

I plan to install it in a couple of days so we can see which problems
it causes.

martin



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Window change functions
  2019-01-09 10:02                 ` martin rudalics
@ 2019-01-11  9:23                   ` martin rudalics
  0 siblings, 0 replies; 18+ messages in thread
From: martin rudalics @ 2019-01-11  9:23 UTC (permalink / raw)
  To: Stefan Monnier, emacs-devel; +Cc: Mark Oteiza

 > I plan to install it in a couple of days so we can see which problems
 > it causes.

These changes are on master now.

Mark Oteiza: I have cced you because the changes might have broken the
"XTerm window titles" behavior you implemented.  Please have a look.

Thanks, martin



^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2019-01-11  9:23 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-12-25  9:41 Window change functions martin rudalics
2018-12-25 20:13 ` Gnus crash (was: Window change functions) Juri Linkov
2018-12-25 20:39   ` Gnus crash Stefan Monnier
2018-12-26  9:19     ` Paul Eggert
2018-12-26 22:22       ` Juri Linkov
2018-12-26 19:55 ` Window change functions Stefan Monnier
2018-12-27  9:36   ` martin rudalics
2018-12-27 15:56     ` Eli Zaretskii
2019-01-05  6:17       ` Stefan Monnier
2019-01-05  7:20         ` Eli Zaretskii
2019-01-05 10:18         ` martin rudalics
2019-01-06 17:22           ` Stefan Monnier
2019-01-07  9:03             ` martin rudalics
2019-01-08 18:10               ` Stefan Monnier
2019-01-09 10:02                 ` martin rudalics
2019-01-11  9:23                   ` martin rudalics
2019-01-05  6:13     ` Stefan Monnier
2019-01-05 10:18       ` martin rudalics

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).