]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix two issues with 'window-deletable-p'
authorMartin Rudalics <rudalics@gmx.at>
Wed, 21 Aug 2024 08:54:53 +0000 (10:54 +0200)
committerEshel Yaron <me@eshelyaron.com>
Wed, 21 Aug 2024 09:56:29 +0000 (11:56 +0200)
* 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)

doc/lispref/frames.texi
etc/NEWS
lisp/frame.el
lisp/window.el

index 8744687a53137801d9bde20011d6cc73a1883b93..0d3b9e0c33d4e5f6b1bc4ac328043b82291b97bb 100644 (file)
@@ -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
index 1e4d8bb6e066d03371c76cb60db8d69add0b72a4..131679bc4a1259019799456694c43e7f9805b48c 100644 (file)
--- 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
 
 ---
index 7183d39e1926370e6377079bf984c9678efc79e4..d5c29753a4ffdbd3b8bfc8b1a490b9aa3c9afd7f 100644 (file)
@@ -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")
index cf1b8de7f1f89b640f4a2ab9fdbac541ea008b74..cc7a6a00eb5a36391527714998a7ccc3aeeaaabe 100644 (file)
@@ -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