From bd647f361415d6c06913e4fa11a1a7ab6ab4ce02 Mon Sep 17 00:00:00 2001 From: Martin Rudalics Date: Wed, 21 Aug 2024 10:54:53 +0200 Subject: [PATCH] Fix two issues with 'window-deletable-p' * lisp/window.el (window-deletable-functions): Clarify doc-string. (window-deletable-p): Handle check whether WINDOW's frame can be deleted via new function 'frame-deletable-p' (a comparison with the frame returned by 'next-frame' fails in too many cases). Do not try to run 'window-deletable-functions' in WINDOW's buffer when WINDOW is internal. * lisp/frame.el (frame-deletable-p): New function. * doc/lispref/frames.texi (Deleting Frames): Describe new function 'frame-deletable-p'. * etc/NEWS: Mention 'frame-deletable-p'. --- doc/lispref/frames.texi | 37 ++++++++++++++++++++++ etc/NEWS | 11 +++++-- lisp/frame.el | 68 +++++++++++++++++++++++++++++++++++++++++ lisp/window.el | 31 ++++++++----------- 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 8744687a531..0d3b9e0c33d 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -2760,6 +2760,43 @@ With the prefix argument @var{iconify}, the frames are iconified rather than deleted. @end deffn +The following function checks whether a frame can be safely deleted. It +is useful to avoid that a subsequent call of @code{delete-frame} throws +an error. + +@defun frame-deletable-p &optional frame +This function returns non-@code{nil} if the frame specified by +@var{frame} can be safely deleted. @var{frame} must be a live frame and +defaults to the selected frame. + +A frame cannot be safely deleted in the following cases: + +@itemize @bullet +@item +It is the only visible or iconified frame (@pxref{Visibility of +Frames}). + +@item +It hosts the active minibuffer window and minibuffer windows do not +follow the selected frame (@pxref{Basic Minibuffer,,, emacs}). + +@item +All other visible or iconified frames are either child frames +(@pxref{Child Frames}) or have a non-@code{nil} @code{delete-before} +parameter. + +@item +The frame or one of its descendants hosts the minibuffer window of a +frame that is not a descendant of the frame (@pxref{Child Frames}). +@end itemize + +These conditions cover most cases where @code{delete-frame} might fail +when called from top-level. They do not catch some special cases like, +for example, deleting a frame during a drag-and-drop operation +(@pxref{Drag and Drop}). In any such case, it will be better to wrap +the @code{delete-frame} call in a @code{condition-case} form. +@end defun + @node Finding All Frames @section Finding All Frames diff --git a/etc/NEWS b/etc/NEWS index c5e3bb5a6c3..9a9b6ead241 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -65,8 +65,8 @@ window used already has a 'quit-restore' parameter. Its presence gives 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 +*** 'quit-restore-window' handles new values for BURY-OR-KILL argument. +The values 'killing' and 'burying' are like 'kill' and 'bury' but assume that the actual killing or burying of the buffer is done by the caller. +++ @@ -74,6 +74,13 @@ that the actual killing or burying of the buffer is done by the caller. With this option set, 'quit-restore-window' will delete its window more aggressively rather than switching to some other buffer in it. +** Frames + ++++ +*** New function 'frame-deletable-p'. +Calling this function before 'delete-frame' is useful to avoid that the +latter throws an error when the argument frame cannot be deleted. + ** Tab Bars and Tab Lines --- diff --git a/lisp/frame.el b/lisp/frame.el index 64f0d054df8..1b5aa8cff08 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -115,6 +115,74 @@ appended when the minibuffer frame is created." (sexp :tag "Value"))) :group 'frames) +(defun frame-deletable-p (&optional frame) + "Return non-nil if specified FRAME can be safely deleted. +FRAME must be a live frame and defaults to the selected frame. + +FRAME cannot be safely deleted in the following cases: + +- FRAME is the only visible or iconified frame. + +- FRAME hosts the active minibuffer window that does not follow the + selected frame. + +- All other visible or iconified frames are either child frames or have + a non-nil `delete-before' parameter. + +- FRAME or one of its descendants hosts the minibuffer window of a frame + that is not a descendant of FRAME. + +This covers most cases where `delete-frame' might fail when called from +top-level. It does not catch some special cases like, for example, +deleting a frame during a drag-and-drop operation. In any such case, it +will be better to wrap the `delete-frame' call in a `condition-case' +form." + (setq frame (window-normalize-frame frame)) + (let ((active-minibuffer-window (active-minibuffer-window)) + deletable) + (catch 'deletable + (when (and active-minibuffer-window + (eq (window-frame active-minibuffer-window) frame) + (not (eq (default-toplevel-value + 'minibuffer-follows-selected-frame) + t))) + (setq deletable nil) + (throw 'deletable nil)) + + (let ((frames (delq frame (frame-list)))) + (dolist (other frames) + ;; A suitable "other" frame must be either visible or + ;; iconified. Child frames and frames with a non-nil + ;; 'delete-before' parameter do not qualify as other frame - + ;; either of these will depend on a "suitable" frame found in + ;; this loop. + (unless (or (frame-parent other) + (frame-parameter other 'delete-before) + (not (frame-visible-p other))) + (setq deletable t)) + + ;; Some frame not descending from FRAME may use the minibuffer + ;; window of FRAME or the minibuffer window of a frame + ;; descending from FRAME. + (when (let* ((minibuffer-window (minibuffer-window other)) + (minibuffer-frame + (and minibuffer-window + (window-frame minibuffer-window)))) + (and minibuffer-frame + ;; If the other frame is a descendant of + ;; FRAME, it will be deleted together with + ;; FRAME ... + (not (frame-ancestor-p frame other)) + ;; ... but otherwise the other frame must + ;; neither use FRAME nor any descendant of + ;; it as minibuffer frame. + (or (eq minibuffer-frame frame) + (frame-ancestor-p frame minibuffer-frame)))) + (setq deletable nil) + (throw 'deletable nil)))) + + deletable))) + (defun handle-delete-frame (event) "Handle delete-frame events from the X server." (interactive "e") diff --git a/lisp/window.el b/lisp/window.el index 75c3b29b5dd..f4226fa4428 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -4109,8 +4109,8 @@ and no others." 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. +should be deleted together with its frame. If the window is live, its +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 @@ -4147,23 +4147,13 @@ returns nil." ;; WINDOW's frame can be deleted only if there are other frames ;; on the same terminal, and it does not contain the active ;; minibuffer. - (unless (or (eq frame (next-frame frame 0)) - ;; We can delete our frame only if no other frame - ;; currently uses our minibuffer window. - (catch 'other - (dolist (other (frame-list)) - (when (and (not (eq other frame)) - (eq (window-frame (minibuffer-window other)) - frame)) - (throw 'other t)))) - (let ((minibuf (active-minibuffer-window))) - (and minibuf (eq frame (window-frame minibuf)) - (not (eq (default-toplevel-value - 'minibuffer-follows-selected-frame) - t)))) + (unless (or (not (frame-deletable-p (window-frame window))) (or no-run - (not (with-current-buffer (window-buffer window) - (run-hook-with-args-until-failure + (if (window-live-p window) + (not (with-current-buffer (window-buffer window) + (run-hook-with-args-until-failure + 'window-deletable-functions window t))) + (not (run-hook-with-args-until-failure 'window-deletable-functions window t))))) 'frame)) ((window-minibuffer-p window) @@ -4173,7 +4163,10 @@ returns nil." ((and (or ignore-window-parameters (not (eq window (window-main-window frame)))) (or no-run - (with-current-buffer (window-buffer window) + (if (window-live-p window) + (with-current-buffer (window-buffer window) + (run-hook-with-args-until-failure + 'window-deletable-functions window nil)) (run-hook-with-args-until-failure 'window-deletable-functions window nil)))) ;; Otherwise, WINDOW can be deleted unless it is the main window