Subject: bug#71386: 29.1; Frame is auto-deleted even when it has multiple tabs

From: Juri Linkov <[email-masked]>
Date: Sun, 09 Jun 2024 19:59:29 +0300

> If I may interject a bit, I think it would be better if the tab is
> closed (i.e., tab-bar-close-tab is called) and another tab is
> displayed when the dedicated buffer is killed if its the only one in
> a window.

Thanks, good suggestion.

> For example, I changed `window--delete` on my machine to
> have this
>
> (if (and tab-bar-mode
>          (> (length (tab-bar-tabs)) 1))
>     (tab-bar-close-tab)
>   (delete-frame frame))
>
> In your patch, `tab-bar-window-delete-frame-p` doesn't do that, nor
> should it as it is a predicate.  Maybe
> `window-delete-frame-predicate-functions` should be renamed to
> `window-delete-frame-actions` or something and `tab-bar-close-tab`
> should be called in `tab-bar-window-delete-frame-p` (removing `-p`
> probably) -- in fact, the `delete-frame` code itself could be added as
> such an action, if we are not averse to changes to the interface.

It should be sufficient to rename it to just 'window-delete-frame-functions'.
Then it's not a predicate, and also follows the naming convention of hooks
having the '-functions' suffix.

> Also, just FYI, your patch doesn't allow the branches with
> `auto-hide-function` and `frame-auto-hide-function` to be called when
> `kill` is nil.  I don't know the side-effects of not calling these
> functions in such cases, but might be worth checking.

Probably the frame should not be hidden after closing the tab,
so these branches should not be handled.

Ok, here is the patch that supports your initial case:
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el
index 6ab6324540e..485ea1d5dd0 100644
--- a/lisp/tab-bar.el
+++ b/lisp/tab-bar.el
@@ -2659,6 +2659,16 @@ tab-switcher-mouse-select
   (goto-char (posn-point (event-end event)))
   (tab-switcher-select))
 
+
+(defun tab-bar-window-delete-frame (frame _kill)
+  "Whether FRAME should be deleted when other tabs are available for that frame.
+Instead of deleting the frame, close the current tab.
+Used via `window-delete-frame-predicate-functions' by `window--delete'."
+  (and tab-bar-mode (> (length (funcall tab-bar-tabs-function frame)) 1)
+       (progn (tab-bar-close-tab) t)))
+
+(add-hook 'window-delete-frame-functions #'tab-bar-window-delete-frame)
+
 
 (defun tab-bar--reusable-frames (all-frames)
   (cond
diff --git a/lisp/window.el b/lisp/window.el
index 2208346ec8c..b1e877b82a8 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4968,6 +4968,11 @@ frame-auto-hide-function
   :group 'frames
   :version "26.1")
 
+(defvar window-delete-frame-functions nil
+  "Don't delete frame when one of functions returns t.
+Each of functions is called with two arguments: FRAME and KILL.
+The function can perform an action instead of deleting the frame.")
+
 (defun window--delete (&optional window dedicated-only kill)
   "Delete WINDOW if possible.
 WINDOW must be a live window and defaults to the selected one.
@@ -4982,6 +4987,10 @@ window--delete
        ((eq deletable 'frame)
 	(let ((frame (window-frame window)))
 	  (cond
+	   ((run-hook-with-args-until-success
+	     'window-delete-frame-functions
+	     frame kill)
+	    nil)
 	   (kill
 	    (delete-frame frame))
            ((functionp (frame-parameter frame 'auto-hide-function))