From: Martin Rudalics Date: Wed, 21 Aug 2024 08:54:53 +0000 (+0200) Subject: Fix two issues with 'window-deletable-p' X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=0d6dca4d0d59f22493c458227ef924b545c002c6;p=emacs.git 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'. (cherry picked from commit bd647f361415d6c06913e4fa11a1a7ab6ab4ce02) --- 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 1e4d8bb6e06..131679bc4a1 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -59,8 +59,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. +++ @@ -68,6 +68,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 7183d39e192..d5c29753a4f 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 cf1b8de7f1f..cc7a6a00eb5 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -4108,8 +4108,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 @@ -4146,23 +4146,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) @@ -4172,7 +4162,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