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'.
This commit is contained in:
Martin Rudalics 2024-08-21 10:54:53 +02:00
parent 0b7f649614
commit bd647f3614
4 changed files with 126 additions and 21 deletions

View 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

View file

@ -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
---

View 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")

View file

@ -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