diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index f2f6cd3..c91e438 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 @@ -5817,10 +5801,10 @@ Window Parameters is the selected window. @end defun -By default, the functions that save and restore window configurations or the -states of windows (@pxref{Window Configurations}) do not care about -window parameters. This means that when you change the value of a -parameter within the body of a @code{save-window-excursion}, the +By default, the functions that save and restore window configurations +or the states of windows (@pxref{Window Configurations}) do not care +about window parameters. This means that when you change the value of +a parameter within the body of a @code{save-window-excursion}, the previous value is not restored when that macro exits. It also means that when you restore via @code{window-state-put} a window state saved earlier by @code{window-state-get}, all cloned windows have their @@ -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 buffer change +The first of these hooks is run after a @dfn{window buffer change} is +detected, which means that a window was created, deleted or assigned +another buffer. + +@defvar window-buffer-change-functions +This variable specifies functions called at the end of redisplay when +window buffers 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 buffer 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 buffer 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 buffer 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-buffer-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/erc/erc-track.el b/lisp/erc/erc-track.el index d1f4d4a..5120509 100644 --- a/lisp/erc/erc-track.el +++ b/lisp/erc/erc-track.el @@ -640,7 +640,7 @@ erc-window-configuration-change (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))) + (erc-modified-channels-update))) (defvar erc-modified-channels-update-inside nil "Variable to prevent running `erc-modified-channels-update' multiple @@ -669,8 +669,7 @@ erc-modified-channels-update (erc-modified-channels-remove-buffer buffer)))) erc-modified-channels-alist) (when removed-channel - (erc-modified-channels-display))) - (remove-hook 'post-command-hook 'erc-modified-channels-update))) + (erc-modified-channels-display))))) (defvar erc-track-mouse-face (if (featurep 'xemacs) 'modeline-mousable 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))))) ;;;; Frame/display capabilities. diff --git a/lisp/net/rcirc.el b/lisp/net/rcirc.el index 0c72e47..33c4f3b 100644 --- a/lisp/net/rcirc.el +++ b/lisp/net/rcirc.el @@ -2064,9 +2064,7 @@ rcirc-visible-buffers (defvar rcirc-visible-buffers nil) (defun rcirc-window-configuration-change () (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 'rcirc-window-configuration-change-1))) + (rcirc-window-configuration-change-1))) (defun rcirc-window-configuration-change-1 () ;; clear activity and overlay arrows @@ -2090,9 +2088,7 @@ rcirc-window-configuration-change-1 rcirc-activity))) ;; update the mode-line string (unless (equal old-activity rcirc-activity) - (rcirc-update-activity-string))) - - (remove-hook 'post-command-hook 'rcirc-window-configuration-change-1)) + (rcirc-update-activity-string)))) ;;; buffer name abbreviation diff --git a/lisp/window.el b/lisp/window.el index f1e5664..eb3e468 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. @@ -9411,15 +9403,7 @@ 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))) ;; 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; +} 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 a69b115..2ae58e9 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 states 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-buffer-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_buffer_change, window_size_change; + bool frame_buffer_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 "buffer 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_buffer_change = + (frame_window_change + && (!EQ (buffer, w->old_buffer) + || w->change_stamp != f->change_stamp)); + /* A "size change" means either a buffer change or that the + total or body size of the window has changed. + + Note: A buffer 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_buffer_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_buffer_change = frame_buffer_change || window_buffer_change; + frame_size_change = frame_size_change || window_size_change; + + if (window_buffer_change) + run_window_change_functions_1 + (Qwindow_buffer_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 buffer 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 changed buffers when one of its windows has changed + its buffer or at least one window was deleted. */ + if ((frame_buffer_change || window_deleted) && FRAME_LIVE_P (f)) + run_window_change_functions_1 + (Qwindow_buffer_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 buffer 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_buffer_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); @@ -7583,9 +7890,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 @@ -7606,6 +7913,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_buffer_change_functions, "window-buffer-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"); @@ -7690,24 +7999,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-buffer-change-functions", Vwindow_buffer_change_functions, + doc: /* Functions called during redisplay when window buffers 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_buffer_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 @@ -7818,7 +8169,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); @@ -7828,10 +8185,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); @@ -7842,8 +8201,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 4cb1050..cce26db 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. */ @@ -12200,8 +12210,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; @@ -14119,20 +14127,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; @@ -14149,15 +14143,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; @@ -14347,7 +14332,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; @@ -14706,9 +14703,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