diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index f5963d984e9..656a44dfcbf 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -1647,6 +1647,45 @@ Deleting Windows are the opposite of what they are in those other functions. @end deffn +@cindex implicit deletion of windows +@cindex deleting windows implicitly + + The above commands delete windows explicitly. However, Emacs may also +delete a window implicitly when it thinks that it's more intuitive to +eliminate it rather than showing some unrelated buffer in it. Functions +that may delete windows implicitly are @code{kill-buffer} +(@pxref{Killing Buffers}), @code{quit-restore-window} (@pxref{Quitting +Windows}) and @code{bury-buffer} (@pxref{Buffer List}). Some of these +delete a window if and only if that window is dedicated to its buffer +(@pxref{Dedicated Windows}). Others delete a window when that window +has been created by @code{display-buffer} (@pxref{Displaying Buffers}). +Some will also try to delete a window's frame together with the window, +provided there are other frames on the same terminal and the frame does +not host the active minibuffer window. + + The hook described next can be used to avoid that a window gets +deleted by these functions. + +@defopt window-deletable-functions +This is an abnormal hook that can be used to avoid that a window gets +deleted implicitly. The value should be a list of functions that take +two arguments. The first argument is the window about to be deleted. +The second argument, if non-@code{nil}, means that the window is the +only window on its frame and would be deleted together with its frame. +The window's buffer is current when running this hook. + +If any of these functions returns @code{nil}, the window will not be +deleted and another buffer will be shown in it. This hook is run +(indirectly) by the functions @code{quit-restore-window}, +@code{kill-buffer} and @code{bury-buffer}. It is not run by functions +that delete windows explicitly like @code{delete-window}, +@code{delete-other-windows} or @code{delete-windows-on}. + +The purpose of this hook is to give its clients a chance to save a +window or its frame from deletion because they might still want to use +that window or frame for their own purposes. +@end defopt + @node Recombining Windows @section Recombining Windows @@ -2297,16 +2336,41 @@ Buffers and Windows the name of an existing buffer; if omitted or @code{nil}, it defaults to the current buffer. -The replacement buffer in each window is chosen via +The replacement buffer in each window is usually chosen via @code{switch-to-prev-buffer} (@pxref{Window History}). With the exception of side windows (@pxref{Side Windows}), any dedicated window displaying @var{buffer-or-name} is deleted if possible (@pxref{Dedicated Windows}). If such a window is the only window on its frame and there -are other frames on the same terminal, the frame is deleted as well. -If the dedicated window is the only window on the only frame on its +are other frames on the same terminal, the frame is deleted as well. If +the dedicated window is the only window on the only frame on its terminal, the buffer is replaced anyway. + +The main purpose of this function is to decide what to do with windows +whose buffers are about to be killed by @code{kill-buffer} +(@pxref{Killing Buffers}). It will, however, also remove the buffer +specified by @var{buffer-or-name} from the lists of previous and next +buffers (@pxref{Window History}) of all windows (including dead windows +that are only referenced by window configurations) and remove any +@code{quit-restore} or @code{quit-restore-prev} parameters +(@pxref{Window Parameters}) referencing that buffer. @end deffn +By default, @code{replace-buffer-in-windows} deletes only windows +dedicated to their buffers and ignores any @code{quit-restore} or +@code{quit-restore-prev} parameters of the windows it works on. The +following option is useful for circumventing these restrictions. + +@defopt kill-buffer-quit-windows +If this option is @code{nil}, @code{kill-buffer} (and in consequence +@code{replace-buffer-in-windows}) may only delete windows that are +dedicated to the buffer about to be killed. If this is non-@code{nil}, +@code{replace-buffer-in-windows} has @code{quit-restore-window} +(@pxref{Quitting Windows}) deal with any such window. That function may +delete such a window even if it's not dedicated to its buffer. Also, +@code{delete-windows-on} will use @code{quit-restore-window} as fallback +when a window cannot be deleted and another buffer must be shown in it. +@end defopt + @node Switching Buffers @section Switching to a Buffer in a Window @@ -4532,6 +4596,15 @@ Quitting Windows @item kill This means to kill @var{window}'s buffer. + +@item killing +This is handled like @code{kill} but assumes that @var{window}'s buffer +gets killed elsewhere. This value is used by +@code{replace-buffer-in-windows} and @code{quit-windows-on}. + +@item burying +This is handled like @code{bury} but assumes that @var{window}'s buffer +gets buried elsewhere. This value is used by @code{quit-windows-on}. @end table The argument @var{bury-or-kill} also specifies what to do with @@ -4541,14 +4614,18 @@ Quitting Windows delete the frame. Otherwise, the fate of the frame is determined by calling @code{frame-auto-hide-function} (see below) with that frame as sole argument. - -This function always sets @var{window}'s @code{quit-restore} parameter -to @code{nil} unless it deletes the window. @end defun -The window @var{window}'s @code{quit-restore} parameter (@pxref{Window -Parameters}) should be @code{nil} or a list of four elements: -@c FIXME: describe what quit-restore-window does if this is nil. + A window's @code{quit-restore} and @code{quit-restore-prev} parameters +(@pxref{Window Parameters}) guide @code{quit-restore-window} through its +process of dealing with its @var{window} argument. If such a parameter +is absent or @code{nil}, this usually means that the window has been +created by a command like @code{split-window-below} or +@code{split-window-right} (@pxref{Split Window,,, emacs, The GNU Emacs +Manual}) and @code{quit-restore-window} will delete the window only if +it was dedicated to its buffer. + + If non-@code{nil}, any such parameter is a list of four elements: @lisp (@var{method} @var{obuffer} @var{owindow} @var{this-buffer}) @@ -4586,17 +4663,17 @@ Quitting Windows @var{window}, it tries to select @var{owindow}. The fourth element, @var{this-buffer}, is the buffer whose displaying -set the @code{quit-restore} parameter. Quitting @var{window} may delete -that window only if it still shows that buffer. - -Quitting @var{window} tries to delete it if and only if (1) -@var{method} is either @code{window} or @code{frame}, (2) the window -has no history of previously-displayed buffers and (3) -@var{this-buffer} equals the buffer currently displayed in -@var{window}. If @var{window} is part of an atomic window -(@pxref{Atomic Windows}), quitting will try to delete the root of that -atomic window instead. In either case, it tries to avoid signaling an -error when @var{window} cannot be deleted. +set the @code{quit-restore} parameter. Quitting @var{window} will use +the information stored by that parameter if and only if it still shows +that buffer. + +@code{quit-restore-window} tries to delete its @var{window} if (1) +@var{method} is either @code{window} or @code{frame}, (2) the window has +no history of previously-displayed buffers and (3) @var{this-buffer} +equals the buffer currently displayed in @var{window}. If @var{window} +is part of an atomic window (@pxref{Atomic Windows}), it will try to +delete the root of that atomic window instead. In either case, it tries +to avoid signaling an error when @var{window} cannot be deleted. If @var{obuffer} is a list, and @var{prev-buffer} is still live, quitting displays @var{prev-buffer} in @var{window} according to the @@ -4608,6 +4685,58 @@ Quitting Windows buffers (@pxref{Window History}), the most recent buffer in that history will be displayed. + Conceptually, the @code{quit-restore} parameter is used for undoing +the first buffer display operation for a specific window. The +@code{quit-restore-prev} parameter is used for undoing the last buffer +display operation in a row of such operations for a specific window. +Any buffer display operation for that window that happened in between, +is undone by displaying the buffer previously shown in that window. + + @code{display-buffer} sets up the @code{quit-restore} parameter of any +window it uses when that window has been either created by it or has no +non-@code{nil} @code{quit-restore} parameter. If the window already has +a @code{quit-restore} parameter, @code{display-buffer} adds a +@code{quit-restore-prev} parameter whose @var{method} element is either +@code{same} or @code{other}. In either case, if the window already has +a @code{quit-restore-prev} or @code{quit-restore} parameter, it may +update that parameter's contents. + + @code{quit-restore-window} now first tries to find a suitable +@code{quit-restore-prev} parameter for its window telling which buffer +to show instead. If it doesn't find one, it will look for a suitable +@code{quit-restore} parameter to either delete the window or show +another buffer in it. + + Once it has used one of these parameters, @code{quit-restore-window} +resets it to @code{nil}. Parameters it did not use are left alone. Any +of these parameters are also reset by @code{replace-buffer-in-windows} +(@pxref{Buffers and Windows}) when they reference a buffer that is about +to be killed, either as the buffer specified by @var{prev-buffer} or as +the buffer specified by @var{this-buffer}. + + All operations on these parameters are supposed to preserve the +following invariant: If a window has a non-@code{nil} +@code{quit-restore-prev} parameter, it also has a non-@code{nil} +@code{quit-restore} parameter. + +The following option allows @code{quit-restore-window} to delete its +window more aggressively. + +@defopt quit-restore-window-no-switch +If this option is @code{nil}, @code{quit-restore-window} will always +call @code{switch-to-prev-buffer} unless the window has been made by +@code{display-buffer}. If this is @code{t}, @code{quit-restore-window} +will try hard to switch only to buffers previously shown in that window. +If this is the symbol @code{skip-first}, it will switch to a previous +buffer if and only the window has at least two previous buffers. + +In either case, if @code{quit-restore-window} doesn't switch to a +previous buffer, it will try to delete the window (and maybe its +frame) instead. Note also that if a window is dedicated, +@code{quit-restore-window} will usually not switch to a previous +buffer in it either. +@end defopt + @ignore @c FIXME: Should we document display-buffer-reuse-window? If we document display-buffer-record-window, it should be with @defun. @@ -6668,12 +6797,14 @@ Window Parameters @code{window-preserve-size} (@pxref{Preserving Window Sizes}). @item quit-restore +@item quit-restore-prev @vindex quit-restore@r{, a window parameter} -This parameter is installed by the buffer display functions +@vindex quit-restore-prev@r{, a window parameter} +These parameters ares installed by the buffer display functions (@pxref{Choosing Window}) and consulted by @code{quit-restore-window} -(@pxref{Quitting Windows}). It is a list of four elements, see the -description of @code{quit-restore-window} in @ref{Quitting Windows} -for details. +(@pxref{Quitting Windows}). They are lists of four elements, see the +description of @code{quit-restore-window} in @ref{Quitting Windows} for +details. @item window-side @itemx window-slot diff --git a/etc/NEWS b/etc/NEWS index c907ec40fa1..de7ae7b0326 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -43,6 +43,37 @@ The 'find-function', 'find-library', 'find-face-definition', and 'find-variable' commands now allow retrieving previous input using the usual minibuffer history commands. Each command has a separate history. + +** Windows + ++++ +*** New hook 'window-deletable-functions'. +This abnormal hook gives its client a way to save a window from getting +deleted implicitly by functions like 'kill-buffer', 'bury-buffer' and +'quit-restore-window', + ++++ +*** New option 'kill-buffer-quit-windows'. +This option has 'kill-buffer' call 'quit-restore-window' to handle the +further destiny of any window showing the buffer to be killed. + ++++ +*** New window parameter 'quit-restore-prev'. +This parameter is set up by 'display-buffer' when it detects that the +window used already has a 'quit-restore' parameter. Its presence gives +'quit-restore-window' a way to undo a sequence of buffer display +operations more intuitively. + ++++ +*** 'quit-restore-window' now handles the values 'killing' and 'burying' +for its BURY-OR-KILL argument just like 'kill' and 'bury' but assumes +that the actual killing or burying of the buffer is done by the caller. + ++++ +*** New option 'quit-restore-window-no-switch'. +With this option set, 'quit-restore-window' will delete its window more +aggressively rather than switching to some other buffer in it. + * Editing Changes in Emacs 31.1 diff --git a/lisp/window.el b/lisp/window.el index 006cfa19525..4687860db11 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -4104,12 +4104,35 @@ one-window-p (next-window base-window (if nomini 'arg) all-frames)))) ;;; Deleting windows. -(defun window-deletable-p (&optional window) +(defcustom window-deletable-functions nil + "Abnormal hook to decide whether a window may be implicitly deleted. +The value should be a list of functions that take two arguments. The +first argument is the window about to be deleted. The second argument +if non-nil, means that the window is the only window on its frame and +should be deleted together with its frame. The window's buffer is +current when running this hook. + +If any of these functions returns nil, the window will not be deleted +and another buffer will be shown in it. This hook is run implicitly by +the functions `quit-restore-window', `kill-buffer' and `bury-buffer'. +It is not run by `delete-window' and `delete-windows-on'. The purpose +of this hook is to give its clients a chance to save a window or its +frame from deletion because they might still want to use that window or +frame for their own purposes." + :type 'hook + :version "31.1" + :group 'windows) + +(defun window-deletable-p (&optional window no-run) "Return t if WINDOW can be safely deleted from its frame. WINDOW must be a valid window and defaults to the selected one. Return `frame' if WINDOW is the root window of its frame and that -frame can be safely deleted." +frame can be safely deleted. + +Unless the optional argument NO-RUN is non-nil, run the abnormal hook +`window-deletable-functions' and return nil if any function on that hook +returns nil." (setq window (window-normalize-window window)) (unless (or ignore-window-parameters @@ -4137,14 +4160,22 @@ window-deletable-p (and minibuf (eq frame (window-frame minibuf)) (not (eq (default-toplevel-value 'minibuffer-follows-selected-frame) - t))))) + t)))) + (or no-run + (not (with-current-buffer (window-buffer window) + (run-hook-with-args-until-failure + 'window-deletable-functions window t))))) 'frame)) ((window-minibuffer-p window) ;; If WINDOW is the minibuffer window of a non-minibuffer-only ;; frame, it cannot be deleted separately. nil) - ((or ignore-window-parameters - (not (eq window (window-main-window frame)))) + ((and (or ignore-window-parameters + (not (eq window (window-main-window frame)))) + (or no-run + (with-current-buffer (window-buffer window) + (run-hook-with-args-until-failure + 'window-deletable-functions window nil)))) ;; Otherwise, WINDOW can be deleted unless it is the main window ;; of its frame. t)))) @@ -4515,17 +4546,26 @@ record-window-buffer (push-window-buffer-onto-prev window) (run-hooks 'buffer-list-update-hook)))) -(defun unrecord-window-buffer (&optional window buffer) +(defun unrecord-window-buffer (&optional window buffer all) "Unrecord BUFFER in WINDOW. -WINDOW must be a live window and defaults to the selected one. -BUFFER must be a live buffer and defaults to the buffer of -WINDOW." +WINDOW must be a live window and defaults to the selected one. BUFFER +must be a live buffer and defaults to the buffer of WINDOW (although +that default hardly makes any sense). + +Make BUFFER disappear from most components specified by the object of +WINDOW. This includes the buffers previously shown in WINDOW as well as +any buffers mentioned by WINDOW's `quit-restore' and `quit-restore-prev' +parameters. + +This function is called by `replace-buffer-in-windows' which is mainly +concerned with finding another buffer for all windows showing a buffer +about to be killed. It's also called by `delete-windows-on' and +`quit-windows-on' and should be called wherever the traces of a buffer +should be erased from the window handling subsystem." (let* ((window (window-normalize-window window t)) (buffer (or buffer (window-buffer window)))) - (set-window-prev-buffers - window (assq-delete-all buffer (window-prev-buffers window))) - (set-window-next-buffers - window (delq buffer (window-next-buffers window))))) + (when (buffer-live-p buffer) + (window-discard-buffer-from-window buffer window all)))) (defun set-window-buffer-start-and-point (window buffer &optional start point) "Set WINDOW's buffer to BUFFER. @@ -4683,7 +4723,7 @@ switch-to-prev-buffer ((or switch-to-prev-buffer-skip (not switch-to-visible-buffer)) frame))) - entry new-buffer killed-buffers skipped) + entry new-buffer skipped) (when (window-minibuffer-p window) ;; Don't switch in minibuffer window. (unless (setq window (minibuffer-selected-window)) @@ -4699,14 +4739,14 @@ switch-to-prev-buffer (dolist (entry (window-prev-buffers window)) (when (and (not (eq (car entry) old-buffer)) (setq new-buffer (car entry)) - (or (buffer-live-p new-buffer) - (not (setq killed-buffers - (cons new-buffer killed-buffers)))) - (or (null pred) (funcall pred new-buffer)) + ;; Beware: new-buffer might have been killed by + ;; a function on 'buffer-predicate'. + (buffer-live-p new-buffer) + (or (null pred) (funcall pred new-buffer)) ;; When BURY-OR-KILL is nil, avoid switching to a ;; buffer in WINDOW's next buffers list. (or bury-or-kill (not (memq new-buffer next-buffers)))) - (if (switch-to-prev-buffer-skip-p skip window new-buffer bury-or-kill) + (if (switch-to-prev-buffer-skip-p skip window new-buffer bury-or-kill) (setq skipped new-buffer) (set-window-buffer-start-and-point window new-buffer (nth 1 entry) (nth 2 entry)) @@ -4740,11 +4780,7 @@ switch-to-prev-buffer ;; Scan reverted next buffers last (must not use nreverse ;; here!). (dolist (buffer (reverse next-buffers)) - ;; Actually, buffer _must_ be live here since otherwise it - ;; would have been caught in the scan of previous buffers. - (when (and (or (buffer-live-p buffer) - (not (setq killed-buffers - (cons buffer killed-buffers)))) + (when (and (buffer-live-p buffer) (not (eq buffer old-buffer)) (or (null pred) (funcall pred buffer)) (setq entry (assq buffer (window-prev-buffers window)))) @@ -4765,9 +4801,7 @@ switch-to-prev-buffer (assq old-buffer (window-prev-buffers window))))) ;; Remove `old-buffer' from WINDOW's previous and (restored list ;; of) next buffers. - (set-window-prev-buffers - window (assq-delete-all old-buffer (window-prev-buffers window))) - (set-window-next-buffers window (delq old-buffer next-buffers)) + (unrecord-window-buffer window old-buffer) (when entry ;; Append old-buffer's entry to list of WINDOW's previous ;; buffers so it's less likely to get switched to soon but @@ -4780,14 +4814,6 @@ switch-to-prev-buffer (set-window-next-buffers window (cons old-buffer (delq old-buffer next-buffers)))) - ;; Remove killed buffers from WINDOW's previous and next buffers. - (when killed-buffers - (dolist (buffer killed-buffers) - (set-window-prev-buffers - window (assq-delete-all buffer (window-prev-buffers window))) - (set-window-next-buffers - window (delq buffer (window-next-buffers window))))) - ;; Return new-buffer. new-buffer)) @@ -4819,7 +4845,7 @@ switch-to-next-buffer ((or switch-to-prev-buffer-skip (not switch-to-visible-buffer)) frame))) - new-buffer entry killed-buffers skipped) + new-buffer entry skipped) (when (window-minibuffer-p window) ;; Don't switch in minibuffer window. (unless (setq window (minibuffer-selected-window)) @@ -4832,9 +4858,7 @@ switch-to-next-buffer (catch 'found ;; Scan WINDOW's next buffers first. (dolist (buffer next-buffers) - (when (and (or (buffer-live-p buffer) - (not (setq killed-buffers - (cons buffer killed-buffers)))) + (when (and (buffer-live-p buffer) (not (eq buffer old-buffer)) (or (null pred) (funcall pred buffer)) (setq entry (assq buffer (window-prev-buffers window)))) @@ -4867,9 +4891,7 @@ switch-to-next-buffer (when (and (not (eq new-buffer (car entry))) (not (eq old-buffer (car entry))) (setq new-buffer (car entry)) - (or (buffer-live-p new-buffer) - (not (setq killed-buffers - (cons new-buffer killed-buffers)))) + (buffer-live-p new-buffer) (or (null pred) (funcall pred new-buffer))) (if (switch-to-prev-buffer-skip-p skip window new-buffer) (setq skipped (or skipped new-buffer)) @@ -4885,14 +4907,6 @@ switch-to-next-buffer ;; Remove `new-buffer' from and restore WINDOW's next buffers. (set-window-next-buffers window (delq new-buffer next-buffers)) - ;; Remove killed buffers from WINDOW's previous and next buffers. - (when killed-buffers - (dolist (buffer killed-buffers) - (set-window-prev-buffers - window (assq-delete-all buffer (window-prev-buffers window))) - (set-window-next-buffers - window (delq buffer (window-next-buffers window))))) - ;; Return new-buffer. new-buffer)) @@ -5044,6 +5058,18 @@ previous-buffer (not (or executing-kbd-macro noninteractive))) (user-error "No previous buffer")))))) +(defcustom kill-buffer-quit-windows nil + "Non-nil means killing buffers shall quit windows. +If this is nil, killing a buffer may only delete windows dedicated to +that buffer. Otherwise, `kill-buffer' has `quit-restore-window' deal +with any window showing the buffer to be killed. That function may +delete such a window even if it's not dedicated to its buffer. Also, +`delete-windows-on' will use `quit-restore-window' as fallback when a +window cannot be deleted otherwise." + :type 'boolean + :version "31.1" + :group 'windows) + (defun delete-windows-on (&optional buffer-or-name frame) "Delete all windows showing BUFFER-OR-NAME. BUFFER-OR-NAME may be a buffer or the name of an existing buffer @@ -5075,21 +5101,22 @@ delete-windows-on use \\[universal-argument] 0 to specify all windows only on the current terminal's frames. -If a frame's root window shows the buffer specified by -BUFFER-OR-NAME and is dedicated to that buffer and that frame -does not host the active minibuffer window and there is at least -one other frame on that frame's terminal, delete that frame. -Otherwise, do not delete a frame's root window if it shows the -buffer specified by BUFFER-OR-NAME and do not delete any frame's -main window showing that buffer either. Rather, in any such -case, call `switch-to-prev-buffer' to show another buffer in that -window and make sure the window is no more dedicated to its -buffer. - -If the buffer specified by BUFFER-OR-NAME is shown in a -minibuffer window, do nothing for that window. For any window -that does not show that buffer, remove the buffer from that -window's lists of previous and next buffers." +If a frame's root window shows the buffer specified by BUFFER-OR-NAME, +is dedicated to that buffer, that frame does not host the active +minibuffer window and there is at least one other frame on that frame's +terminal, delete that frame. Otherwise, do not delete a frame's root +window if it shows the buffer specified by BUFFER-OR-NAME and do not +delete any frame's main window showing that buffer either. Rather, in +any such case, call either `quit-restore-window' (provided +`kill-buffer-quit-windows' is non-nil) or `switch-to-prev-buffer' to +show another buffer in that window and make sure the window is no more +dedicated to its buffer. + +If the buffer specified by BUFFER-OR-NAME is shown in a minibuffer +window, do nothing for that window. For any window that does not show +that buffer, remove the buffer from that window's lists of previous and +next buffers and remove any `quit-restore' and `quit-restore-prev' +parameters naming it." (interactive (let ((frame (cond ((and (numberp current-prefix-arg) @@ -5107,11 +5134,12 @@ delete-windows-on frame))) (let ((buffer (window-normalize-buffer buffer-or-name)) ;; Handle the "inverted" meaning of the FRAME argument wrt other - ;; `window-list-1' based function. - (all-frames (cond ((not frame) t) ((eq frame t) nil) (t frame)))) - (dolist (window (window-list-1 nil nil all-frames)) + ;; `window-list-1' based functions. + (frames (cond ((not frame) t) ((eq frame t) nil) (t frame)))) + (dolist (window (window-list-1 nil 'nomini frames)) (if (eq (window-buffer window) buffer) - (let ((deletable (window-deletable-p window)) + ;; Don't run 'window-deletable-functions'. + (let ((deletable (window-deletable-p window t)) (dedicated (window-dedicated-p window))) (cond ((and (eq deletable 'frame) dedicated) @@ -5120,43 +5148,77 @@ delete-windows-on ((eq deletable t) ;; Delete window. (delete-window window)) + (kill-buffer-quit-windows + (quit-restore-window window 'bury) + (when (window-live-p window) + ;; Unrecord BUFFER in this window. + (unrecord-window-buffer window buffer t))) (t ;; In window switch to previous buffer. (set-window-dedicated-p window nil) (switch-to-prev-buffer window 'bury) - ;; Restore the dedicated 'side' flag. - (when (eq dedicated 'side) - (set-window-dedicated-p window 'side))))) + ;; Restore the dedicated 'side' flag. + (when (eq dedicated 'side) + (set-window-dedicated-p window 'side)) + (when (window-live-p window) + ;; Unrecord BUFFER in this window. + (unrecord-window-buffer window buffer t))))) ;; If a window doesn't show BUFFER, unrecord BUFFER in it. - (unrecord-window-buffer window buffer))))) + (unrecord-window-buffer window buffer t))))) (defun replace-buffer-in-windows (&optional buffer-or-name) "Replace BUFFER-OR-NAME with some other buffer in all windows showing it. -BUFFER-OR-NAME may be a buffer or the name of an existing buffer -and defaults to the current buffer. - -With the exception of side windows, when a window showing BUFFER-OR-NAME -is dedicated, that window is deleted. If that window is the only window -on its frame, the frame is deleted too when there are other frames left. -If there are no other frames left, some other buffer is displayed in that +BUFFER-OR-NAME may be a buffer or the name of an existing buffer and +defaults to the current buffer. Minibuffer windows are not considered. + +If the option `kill-buffer-quit-windows' is nil, behave as follows: With +the exception of side windows, when a window showing BUFFER-OR-NAME is +dedicated, delete that window. If that window is the only window on its +frame, delete its frame when there are other frames left. In any other +case, call `switch-to-prev-buffer' to display some other buffer in that window. -This function removes the buffer denoted by BUFFER-OR-NAME from -all window-local buffer lists." +If `kill-buffer-quit-windows' is non-nil, call `quit-restore-window' for +any window showing BUFFER-OR-NAME with the argument BURY-OR-KILL set to +`killing' to avoid that the latter kills the buffer prematurely. + +In either case, remove the buffer denoted by BUFFER-OR-NAME from the +lists of previous and next buffers of all windows and remove any +`quit-restore' or `quit-restore-prev' parameters mentioning it. + +If, for any window showing BUFFER-OR-NAME running the abnormal hook +`window-deletable-functions' returns nil, do not delete that window but +show some other buffer in that window. + +This function is called by `kill-buffer' which kills the buffer +specified by `buffer-or-name' afterwards. It never kills a buffer by +itself." (interactive "bBuffer to replace: ") (let ((buffer (window-normalize-buffer buffer-or-name))) + ;; Scan all windows. We have to unrecord BUFFER-OR-NAME in those + ;; not showing it. (dolist (window (window-list-1 nil nil t)) - (if (eq (window-buffer window) buffer) - ;; Delete a dedicated window unless it is a side window. - (let ((dedicated-side (eq (window-dedicated-p window) 'side))) - (when (or dedicated-side (not (window--delete window t t))) - ;; Switch to another buffer in that window. - (set-window-dedicated-p window nil) - (if (switch-to-prev-buffer window 'kill) + (when (eq (window-buffer window) buffer) + (if kill-buffer-quit-windows + (quit-restore-window window 'killing) + (let ((dedicated-side (eq (window-dedicated-p window) 'side))) + (when (or dedicated-side (not (window--delete window t 'kill))) + ;; Switch to another buffer in that window. + (set-window-dedicated-p window nil) + (if (switch-to-prev-buffer window 'kill) (and dedicated-side (set-window-dedicated-p window 'side)) - (window--delete window nil 'kill)))) - ;; Unrecord BUFFER in WINDOW. - (unrecord-window-buffer window buffer))))) + (window--delete window nil 'kill)))))) + + (when (window-live-p window) + ;; If the fourth elements of the 'quit-restore' or + ;; 'quit-restore-prev' parameters equal BUFFER, these + ;; parameters become useless - in 'quit-restore-window' the + ;; fourth element must equal the buffer of WINDOW in order to + ;; use that parameter. If BUFFER is mentioned in the second + ;; element of the parameter, 'quit-restore-window' cannot + ;; possibly show BUFFER instead; so this parameter becomes + ;; useless too. + (unrecord-window-buffer window buffer t))))) (defcustom quit-window-hook nil "Hook run before performing any other actions in the `quit-window' command." @@ -5164,6 +5226,23 @@ quit-window-hook :version "27.1" :group 'windows) +(defcustom quit-restore-window-no-switch nil + "Non-nil means `quit-restore-window' preferably won't switch buffers. +If this is nil, `quit-restore-window' unconditionally calls +`switch-to-prev-buffer' unless the window is dedicated or has been made +by `display-buffer'. If this is t, `quit-restore-window' will try to +delete the window unless a live buffer exists that was previously shown +in that window. If this is the symbol `skip-first', it will switch to a +previous buffer only if there are at least two of them. + +The net effect of making this non-nil is that if `quit-restore-window' +doesn't find a suitable buffer previously shown in the window, it will +rather try to delete the window (and maybe its frame) than show a buffer +the window has never shown before." + :type 'boolean + :version "31.1" + :group 'windows) + (defun window--quit-restore-select-window (window) "Select WINDOW after having quit another one. Do not select an inactive minibuffer window." @@ -5176,17 +5255,21 @@ quit-restore-window "Quit WINDOW and deal with its buffer. WINDOW must be a live window and defaults to the selected one. -According to information stored in WINDOW's `quit-restore' window -parameter either (1) delete WINDOW and its frame, (2) delete -WINDOW but leave its frame alone, (3) restore the buffer -previously shown in WINDOW, or (4) make WINDOW display some other -buffer. If WINDOW is not deleted, reset its `quit-restore' -parameter to nil. See Info node `(elisp) Quitting Windows' for -more details. +According to information stored in WINDOW's `quit-restore' and +`quit-restore-prev' parameters either (1) delete WINDOW and its +frame, (2) delete WINDOW but leave its frame alone, (3) restore the +buffer previously shown in WINDOW, or (4) make WINDOW display some other +buffer. In case (3) set any of these parameters to nil if it has been +used to restore the previously shown buffer. See Info node `(elisp) +Quitting Windows' for more details. -If WINDOW's dedicated flag is t, try to delete WINDOW. If it -equals the value `side', restore that value when WINDOW is not -deleted. +If WINDOW's dedicated flag is t, try to delete WINDOW. If it equals the +value `side', restore that value when WINDOW is not deleted. Whether +WINDOW or its frame get deleted can be further controlled via the option +`quit-restore-window-no-switch'. + +If running the abnormal hook `window-deletable-functions' returns nil, +do not delete WINDOW but show some other buffer in it. Optional second argument BURY-OR-KILL tells how to proceed with the buffer of WINDOW. The following values are handled: @@ -5206,21 +5289,31 @@ quit-restore-window most reliable remedy to not have `switch-to-prev-buffer' switch to this buffer again without killing the buffer. -`kill' means to kill WINDOW's buffer." +`kill' means to kill WINDOW's buffer. + +`killing' is like `kill' but means that WINDOW's buffer will get killed +elsewhere. This value is used by `replace-buffer-in-windows' and +`quit-windows-on'. + +`burying' is like `bury' but means that WINDOW's buffer will get buried +elsewhere. This value is used by `quit-windows-on'." (setq window (window-normalize-window window t)) (let* ((buffer (window-buffer window)) (quit-restore (window-parameter window 'quit-restore)) + (quit-restore-prev (window-parameter window 'quit-restore-prev)) (quit-restore-2 (nth 2 quit-restore)) + (quit-restore-prev-2 (nth 2 quit-restore-prev)) (prev-buffer (catch 'prev-buffer (dolist (buf (window-prev-buffers window)) (unless (eq (car buf) buffer) (throw 'prev-buffer (car buf)))))) (dedicated (window-dedicated-p window)) - quad entry) + quad entry reset-prev) (cond ;; First try to delete dedicated windows that are not side windows. ((and dedicated (not (eq dedicated 'side)) - (window--delete window 'dedicated (eq bury-or-kill 'kill))) + (window--delete + window 'dedicated (memq bury-or-kill '(kill killing)))) ;; If the previously selected window is still alive, select it. (window--quit-restore-select-window quit-restore-2)) ((and (not prev-buffer) @@ -5241,10 +5334,27 @@ quit-restore-window (window--delete window nil (eq bury-or-kill 'kill))) ;; If the previously selected window is still alive, select it. (window--quit-restore-select-window quit-restore-2)) - ((and (listp (setq quad (nth 1 quit-restore))) - (buffer-live-p (car quad)) - (eq (nth 3 quit-restore) buffer)) - ;; Show another buffer stored in quit-restore parameter. + ((and (or (and quit-restore-window-no-switch (not prev-buffer)) + ;; Ignore first of the previous buffers if + ;; 'quit-restore-window-no-switch' says so. + (and (eq quit-restore-window-no-switch 'skip-first) + (not (cdr (window-prev-buffers window))))) + ;; Delete WINDOW if possible. + (window--delete + window nil (memq bury-or-kill '(kill killing)))) + ;; If the previously selected window is still alive, select it. + (window--quit-restore-select-window quit-restore-2)) + ((or (and (listp (setq quad (nth 1 quit-restore-prev))) + (buffer-live-p (car quad)) + (eq (nth 3 quit-restore-prev) buffer) + ;; Use selected window from quit-restore-prev. + (setq quit-restore-2 quit-restore-prev-2) + ;; We want to reset quit-restore-prev only. + (setq reset-prev t)) + (and (listp (setq quad (nth 1 quit-restore))) + (buffer-live-p (car quad)) + (eq (nth 3 quit-restore) buffer))) + ;; Show another buffer stored in quit-restore(-prev) parameter. (when (and (integerp (nth 3 quad)) (if (window-combined-p window) (/= (nth 3 quad) (window-total-height window)) @@ -5269,27 +5379,26 @@ quit-restore-window ;; Deal with the buffer we just removed from WINDOW. (setq entry (and (eq bury-or-kill 'append) (assq buffer (window-prev-buffers window)))) - (when bury-or-kill + (when (memq bury-or-kill '(bury burying kill killing)) ;; Remove buffer from WINDOW's previous and next buffers. - (set-window-prev-buffers - window (assq-delete-all buffer (window-prev-buffers window))) - (set-window-next-buffers - window (delq buffer (window-next-buffers window)))) + (unrecord-window-buffer window buffer)) (when entry ;; Append old buffer's entry to list of WINDOW's previous ;; buffers so it's less likely to get switched to soon but ;; `display-buffer-in-previous-window' can nevertheless find it. (set-window-prev-buffers window (append (window-prev-buffers window) (list entry)))) - ;; Reset the quit-restore parameter. - (set-window-parameter window 'quit-restore nil) - ;; Select old window. + ;; Reset the quit-restore(-prev) parameter. + (set-window-parameter window 'quit-restore-prev nil) + (unless reset-prev + ;; If quit-restore-prev was not used, reset the quit-restore + ;; parameter + (set-window-parameter window 'quit-restore nil)) ;; If the previously selected window is still alive, select it. (window--quit-restore-select-window quit-restore-2)) (t - ;; Show some other buffer in WINDOW and reset the quit-restore - ;; parameter. - (set-window-parameter window 'quit-restore nil) + ;; Show some other buffer in WINDOW and leave the + ;; quit-restore(-prev) parameters alone (Juri's idea). ;; Make sure that WINDOW is no more dedicated. (set-window-dedicated-p window nil) ;; Try to switch to a previous buffer. Delete the window only if @@ -5297,16 +5406,14 @@ quit-restore-window (if (switch-to-prev-buffer window bury-or-kill) (when (eq dedicated 'side) (set-window-dedicated-p window 'side)) - (window--delete window nil (eq bury-or-kill 'kill)) - ;; If the previously selected window is still alive, select it. - (window--quit-restore-select-window quit-restore-2)))) - + (window--delete + window nil (memq bury-or-kill '(kill killing)))))) ;; Deal with the buffer. (cond ((not (buffer-live-p buffer))) ((eq bury-or-kill 'kill) (kill-buffer buffer)) - (bury-or-kill + ((eq bury-or-kill 'bury) (bury-buffer-internal buffer))))) (defun quit-window (&optional kill window) @@ -5336,18 +5443,31 @@ quit-windows-on BUFFER-OR-NAME. Optional argument FRAME is handled as by `delete-windows-on'. -This function calls `quit-window' on all candidate windows -showing BUFFER-OR-NAME." +This function calls `quit-restore-window' on all candidate windows +showing BUFFER-OR-NAME. In addition, it removes the buffer denoted by +BUFFER-OR-NAME from all window-local buffer lists and removes any +`quit-restore' or `quit-restore-prev' parameters mentioning it." (interactive "bQuit windows on (buffer):\nP") (let ((buffer (window-normalize-buffer buffer-or-name)) ;; Handle the "inverted" meaning of the FRAME argument wrt other - ;; `window-list-1' based function. - (all-frames (cond ((not frame) t) ((eq frame t) nil) (t frame)))) - (dolist (window (window-list-1 nil nil all-frames)) - (if (eq (window-buffer window) buffer) - (quit-window kill window) - ;; If a window doesn't show BUFFER, unrecord BUFFER in it. - (unrecord-window-buffer window buffer))))) + ;; `window-list' based function. + (frames (cond ((not frame) t) ((eq frame t) nil) (t frame)))) + (dolist (window (window-list-1 nil nil frames)) + (when (eq (window-buffer window) buffer) + (with-current-buffer buffer + (run-hooks 'quit-window-hook)) + (quit-restore-window + window (if kill 'killing 'burying))) + + (when (window-live-p window) + ;; Unrecord BUFFER in this window. + (unrecord-window-buffer window buffer t))) + + ;; Deal with BUFFER-OR-NAME. + (cond + ((not (buffer-live-p buffer))) + (kill (kill-buffer buffer)) + (t (bury-buffer-internal buffer))))) (defun window--combination-resizable (parent &optional horizontal) "Return number of pixels recoverable from height of window PARENT. @@ -6657,34 +6777,42 @@ display-buffer-record-window previously shown in the window, that buffer's window start and window point, and the window's height. The third element is the window selected at the time the parameter was created. The -fourth element is BUFFER." +fourth element is BUFFER. + +If TYPE is `reuse', BUFFER is different from the one currently displayed +in WINDOW, and WINDOW already has a `quit-restore' parameter, install or +update a `quit-restore-prev' parameter for this window. This allows for +quitting WINDOW in a similar fashion but also keeps the very first +`quit-restore' parameter stored for this window around. Consequently, +WINDOW (or its frame) can be eventually deleted by `quit-restore-widow' +if that parameter's fourth element equals WINDOW's buffer." (cond ((eq type 'reuse) - (if (eq (window-buffer window) buffer) - ;; WINDOW shows BUFFER already. Update WINDOW's quit-restore - ;; parameter, if any. - (let ((quit-restore (window-parameter window 'quit-restore))) + (let ((quit-restore (window-parameter window 'quit-restore))) + (if (eq (window-buffer window) buffer) + ;; WINDOW shows BUFFER already. Update WINDOW's quit-restore + ;; parameter, if any. (when (consp quit-restore) (setcar quit-restore 'same) ;; The selected-window might have changed in ;; between (Bug#20353). (unless (or (eq window (selected-window)) - (eq window (nth 2 quit-restore))) - (setcar (cddr quit-restore) (selected-window))))) - ;; WINDOW shows another buffer. - (with-current-buffer (window-buffer window) - (set-window-parameter - window 'quit-restore - (list 'other - ;; A quadruple of WINDOW's buffer, start, point and height. - (list (current-buffer) (window-start window) - ;; Preserve window-point-insertion-type (Bug#12855). - (copy-marker - (window-point window) window-point-insertion-type) - (if (window-combined-p window) - (window-total-height window) - (window-total-width window))) - (selected-window) buffer))))) + (eq window (nth 2 quit-restore))) + (setcar (cddr quit-restore) (selected-window)))) + ;; WINDOW shows another buffer. + (with-current-buffer (window-buffer window) + (set-window-parameter + window (if quit-restore 'quit-restore-prev 'quit-restore) + (list 'other + ;; A quadruple of WINDOW's buffer, start, point and height. + (list (current-buffer) (window-start window) + ;; Preserve window-point-insertion-type (Bug#12855). + (copy-marker + (window-point window) window-point-insertion-type) + (if (window-combined-p window) + (window-total-height window) + (window-total-width window))) + (selected-window) buffer)))))) ((eq type 'window) ;; WINDOW has been created on an existing frame. (set-window-parameter @@ -9129,6 +9257,9 @@ switch-to-buffer as prescribed by the option `switch-to-buffer-preserve-window-point'. Otherwise, these are left alone. +In either case, call `display-buffer-record-window' to avoid disrupting +a sequence of `display-buffer' operations using this window. + Return the buffer switched to." (interactive (let ((force-same-window @@ -9189,6 +9320,11 @@ switch-to-buffer buffer)) (displayed (and (eq preserve-win-point 'already-displayed) (get-buffer-window buffer 0)))) + + ;; Make sure quitting the window works. + (unless switch-to-buffer-obey-display-actions + (display-buffer-record-window 'reuse (selected-window) buffer)) + (set-window-buffer nil buffer) (when (and entry (or (eq preserve-win-point t) displayed)) ;; Try to restore start and point of buffer in the selected diff --git a/src/alloc.c b/src/alloc.c index 48b170b866f..06fe12cff3d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -7009,33 +7009,6 @@ mark_face_cache (struct face_cache *c) } } -/* Remove killed buffers or items whose car is a killed buffer from - LIST, and mark other items. Return changed LIST, which is marked. */ - -static Lisp_Object -mark_discard_killed_buffers (Lisp_Object list) -{ - Lisp_Object tail, *prev = &list; - - for (tail = list; CONSP (tail) && !cons_marked_p (XCONS (tail)); - tail = XCDR (tail)) - { - Lisp_Object tem = XCAR (tail); - if (CONSP (tem)) - tem = XCAR (tem); - if (BUFFERP (tem) && !BUFFER_LIVE_P (XBUFFER (tem))) - *prev = XCDR (tail); - else - { - set_cons_marked (XCONS (tail)); - mark_object (XCAR (tail)); - prev = xcdr_addr (tail); - } - } - mark_object (tail); - return list; -} - static void mark_frame (struct Lisp_Vector *ptr) { @@ -7090,15 +7063,6 @@ mark_window (struct Lisp_Vector *ptr) mark_glyph_matrix (w->current_matrix); mark_glyph_matrix (w->desired_matrix); } - - /* Filter out killed buffers from both buffer lists - in attempt to help GC to reclaim killed buffers faster. - We can do it elsewhere for live windows, but this is the - best place to do it for dead windows. */ - wset_prev_buffers - (w, mark_discard_killed_buffers (w->prev_buffers)); - wset_next_buffers - (w, mark_discard_killed_buffers (w->next_buffers)); } /* Entry of the mark stack. */ diff --git a/src/buffer.c b/src/buffer.c index 744b0ef5548..6ec40aff646 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -2012,6 +2012,13 @@ DEFUN ("kill-buffer", Fkill_buffer, Skill_buffer, 0, 1, "bKill buffer: ", buffer (bug#10114). */ replace_buffer_in_windows (buffer); + /* For dead windows that have not been collected yet, remove this + buffer from those windows' lists of previously and next shown + buffers and remove any 'quit-restore' or 'quit-restore-prev' + parameters mentioning the buffer. */ + if (XFIXNUM (BVAR (b, display_count)) > 0) + window_discard_buffer_from_dead_windows (buffer); + /* Exit if replacing the buffer in windows has killed our buffer. */ if (!BUFFER_LIVE_P (b)) return Qt; diff --git a/src/window.c b/src/window.c index 4bb36b6733a..559919689a3 100644 --- a/src/window.c +++ b/src/window.c @@ -3277,6 +3277,113 @@ window_pixel_to_total (Lisp_Object frame, Lisp_Object horizontal) } +/** Remove all occurrences of element whose car is BUFFER from ALIST. + Return changed ALIST. */ +static Lisp_Object +window_discard_buffer_from_alist (Lisp_Object buffer, Lisp_Object alist) +{ + Lisp_Object tail, *prev = &alist; + + for (tail = alist; CONSP (tail); tail = XCDR (tail)) + { + Lisp_Object tem = XCAR (tail); + + tem = XCAR (tem); + + if (EQ (tem, buffer)) + *prev = XCDR (tail); + else + prev = xcdr_addr (tail); + } + + return alist; +} + +/** Remove all occurrences of BUFFER from LIST. Return changed + LIST. */ +static Lisp_Object +window_discard_buffer_from_list (Lisp_Object buffer, Lisp_Object list) +{ + Lisp_Object tail, *prev = &list; + + for (tail = list; CONSP (tail); tail = XCDR (tail)) + if (EQ (XCAR (tail), buffer)) + *prev = XCDR (tail); + else + prev = xcdr_addr (tail); + + return list; +} + +/** Remove BUFFER from the lists of previous and next buffers of object + WINDOW. ALL true means remove any `quit-restore' and + `quit-restore-prev' parameter of WINDOW referencing BUFFER too. */ +static void +window_discard_buffer_from_window (Lisp_Object buffer, Lisp_Object window, bool all) +{ + struct window *w = XWINDOW (window); + + wset_prev_buffers + (w, window_discard_buffer_from_alist (buffer, w->prev_buffers)); + wset_next_buffers + (w, window_discard_buffer_from_list (buffer, w->next_buffers)); + + if (all) + { + Lisp_Object quit_restore = window_parameter (w, Qquit_restore); + Lisp_Object quit_restore_prev = window_parameter (w, Qquit_restore_prev); + Lisp_Object quad; + + if (EQ (buffer, Fnth (make_fixnum (3), quit_restore_prev)) + || (CONSP (quad = Fcar (Fcdr (quit_restore_prev))) + && EQ (Fcar (quad), buffer))) + Fset_window_parameter (window, Qquit_restore_prev, Qnil); + + if (EQ (buffer, Fnth (make_fixnum (3), quit_restore)) + || (CONSP (quad = Fcar (Fcdr (quit_restore))) + && EQ (Fcar (quad), buffer))) + { + Fset_window_parameter (window, Qquit_restore, + window_parameter (w, Qquit_restore_prev)); + Fset_window_parameter (window, Qquit_restore_prev, Qnil); + } + } +} + +/** Remove BUFFER from the lists of previous and next buffers and the + `quit-restore' and `quit-restore-prev' parameters of any dead + WINDOW. */ +void +window_discard_buffer_from_dead_windows (Lisp_Object buffer) +{ + struct Lisp_Hash_Table *h = XHASH_TABLE (window_dead_windows_table); + + DOHASH (h, k, v) + window_discard_buffer_from_window (buffer, v, true); +} + +DEFUN ("window-discard-buffer-from-window", Fwindow_discard_buffer, + Swindow_discard_buffer, 2, 3, 0, + doc: /* Discard BUFFER from WINDOW. +Discard specified live BUFFER from the lists of previous and next +buffers of specified live WINDOW. + +Optional argument ALL non-nil means discard any `quit-restore' and +`quit-restore-prev' parameters of WINDOW referencing BUFFER too. */) + (Lisp_Object buffer, Lisp_Object window, Lisp_Object all) +{ + if (!BUFFER_LIVE_P (XBUFFER (buffer))) + error ("Not a live buffer"); + + if (!WINDOW_LIVE_P (window)) + error ("Not a live window"); + + window_discard_buffer_from_window (buffer, window, !NILP (all)); + + return Qnil; +} + + DEFUN ("delete-other-windows-internal", Fdelete_other_windows_internal, Sdelete_other_windows_internal, 0, 2, "", doc: /* Make WINDOW fill its frame. @@ -4140,6 +4247,9 @@ set_window_buffer (Lisp_Object window, Lisp_Object buffer, w->window_end_vpos = 0; w->last_cursor_vpos = 0; + /* Discard BUFFER from WINDOW's previous and next buffers. */ + window_discard_buffer_from_window (buffer, window, false); + if (!(keep_margins_p && samebuf)) { /* If we're not actually changing the buffer, don't reset hscroll and vscroll. Resetting hscroll and vscroll here is problematic @@ -4402,6 +4512,10 @@ make_parent_window (Lisp_Object window, bool horflag) wset_buffer (p, Qnil); wset_combination (p, horflag, window); wset_combination_limit (p, Qnil); + /* Reset any previous and next buffers of p which have been installed + by the memcpy above. */ + wset_prev_buffers (p, Qnil); + wset_next_buffers (p, Qnil); wset_window_parameters (p, Qnil); } @@ -4426,10 +4540,6 @@ make_window (void) wset_vertical_scroll_bar_type (w, Qt); wset_horizontal_scroll_bar_type (w, Qt); wset_cursor_type (w, Qt); - /* These Lisp fields are marked specially so they're not set to nil by - allocate_window. */ - wset_prev_buffers (w, Qnil); - wset_next_buffers (w, Qnil); /* Initialize non-Lisp data. Note that allocate_window zeroes out all non-Lisp data, so do it only for slots which should not be zero. */ @@ -5252,6 +5362,11 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna unchain_marker (XMARKER (w->old_pointm)); unchain_marker (XMARKER (w->start)); wset_buffer (w, Qnil); + /* Add WINDOW to table of dead windows so when killing a buffer + WINDOW mentions, all references to that buffer can be removed + and the buffer be collected. */ + Fputhash (make_fixnum (w->sequence_number), + window, window_dead_windows_table); } if (NILP (s->prev) && NILP (s->next)) @@ -7356,12 +7471,21 @@ DEFUN ("set-window-configuration", Fset_window_configuration, } } + /* Remove window from the table of dead windows. */ + Fremhash (make_fixnum (w->sequence_number), + window_dead_windows_table); + if ((NILP (dont_set_miniwindow) || !MINI_WINDOW_P (w)) && BUFFERP (p->buffer) && BUFFER_LIVE_P (XBUFFER (p->buffer))) /* If saved buffer is alive, install it, unless it's a minibuffer we explicitly prohibit. */ { - wset_buffer (w, p->buffer); + if (!EQ (w->contents, p->buffer)) + { + wset_buffer (w, p->buffer); + window_discard_buffer_from_window (w->contents, window, false); + } + w->start_at_line_beg = !NILP (p->start_at_line_beg); set_marker_restricted (w->start, p->start, w->contents); set_marker_restricted (w->pointm, p->pointm, w->contents); @@ -7406,6 +7530,7 @@ DEFUN ("set-window-configuration", Fset_window_configuration, recreate *scratch* in the course (part of Juanma's bs-show scenario from March 2011). */ wset_buffer (w, other_buffer_safely (Fcurrent_buffer ())); + window_discard_buffer_from_window (w->contents, window, false); /* This will set the markers to beginning of visible range. */ set_marker_restricted_both (w->start, w->contents, 0, 0); @@ -7585,6 +7710,11 @@ delete_all_child_windows (Lisp_Object window) possible resurrection in Fset_window_configuration. */ wset_combination_limit (w, w->contents); wset_buffer (w, Qnil); + /* Add WINDOW to table of dead windows so when killing a buffer + WINDOW mentions, all references to that buffer can be removed + and the buffer be collected. */ + Fputhash (make_fixnum (w->sequence_number), + window, window_dead_windows_table); } Vwindow_list = Qnil; @@ -8594,6 +8724,8 @@ syms_of_window (void) DEFSYM (Qconfiguration, "configuration"); DEFSYM (Qdelete, "delete"); DEFSYM (Qdedicated, "dedicated"); + DEFSYM (Qquit_restore, "quit-restore"); + DEFSYM (Qquit_restore_prev, "quit-restore-prev"); DEFVAR_LISP ("temp-buffer-show-function", Vtemp_buffer_show_function, doc: /* Non-nil means call as function to display a help buffer. @@ -8917,6 +9049,17 @@ syms_of_window (void) displayed after a scrolling operation to be somewhat inaccurate. */); fast_but_imprecise_scrolling = false; + DEFVAR_LISP ("window-dead-windows-table", window_dead_windows_table, + doc: /* Hash table of dead windows. +Each entry in this table maps a window number to a window object. +Entries are added by `delete-window-internal' and are removed by the +garbage collector. + +This table is maintained by code in window.c and is made visible in +Elisp for testing purposes only. */); + window_dead_windows_table + = CALLN (Fmake_hash_table, QCweakness, Qt); + defsubr (&Sselected_window); defsubr (&Sold_selected_window); defsubr (&Sminibuffer_window); @@ -9032,6 +9175,7 @@ syms_of_window (void) defsubr (&Swindow_parameters); defsubr (&Swindow_parameter); defsubr (&Sset_window_parameter); + defsubr (&Swindow_discard_buffer); defsubr (&Swindow_cursor_type); defsubr (&Sset_window_cursor_type); } diff --git a/src/window.h b/src/window.h index 86932181252..335e0a3453e 100644 --- a/src/window.h +++ b/src/window.h @@ -142,6 +142,12 @@ #define WINDOW_H_INCLUDED as well. */ Lisp_Object contents; + /* A list of triples listing + buffers previously shown in this window. */ + Lisp_Object prev_buffers; + /* List of buffers re-shown in this window. */ + Lisp_Object next_buffers; + /* 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. */ @@ -218,14 +224,6 @@ #define WINDOW_H_INCLUDED struct glyph_matrix *current_matrix; struct glyph_matrix *desired_matrix; - /* The two Lisp_Object fields below are marked in a special way, - which is why they're placed after `current_matrix'. */ - /* A list of triples listing - buffers previously shown in this window. */ - Lisp_Object prev_buffers; - /* List of buffers re-shown in this window. */ - Lisp_Object next_buffers; - /* Number saying how recently window was selected. */ EMACS_INT use_time; @@ -1228,6 +1226,7 @@ #define CHECK_LIVE_WINDOW(WINDOW) \ extern void wset_buffer (struct window *, Lisp_Object); extern bool window_outdated (struct window *); extern ptrdiff_t window_point (struct window *w); +extern void window_discard_buffer_from_dead_windows (Lisp_Object); extern void init_window_once (void); extern void init_window (void); extern void syms_of_window (void);