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

From: Juri Linkov <[email-masked]>
Date: Fri, 14 Jun 2024 20:46:06 +0300

>>> But KILL means to delete the frame and the tab-bar code wants to prevent
>>> that.  Or what am I missing?
>>
>> For frames KILL defines whether to delete or iconify the frame.
>> There is no such thing as to "iconify" the tab.
>
> Don't you want to distinguish 'kill' non-nil where the frame should get
> deleted from 'kill' nil where nothing is done?  In the latter case the
> window cannot even be deleted because when the return value of
> 'window-deletable-p' equals 'frame' this implies that WINDOW is the root
> window of its frame.

This is the best that I can do, you can improve it if you want:

diff --git a/lisp/window.el b/lisp/window.el
index 604b9868921..155b0b55482 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4948,6 +4968,13 @@ frame-auto-hide-function
   :group 'frames
   :version "26.1")
 
+(defvar window-delete-frame-functions nil
+  "A list of functions to handle the frame deletion.
+Each of functions is called with two arguments: FRAME and KILL where
+KILL means the buffer shown in window will be killed.  When one of functions
+returns a non-nil value then `window--delete' will not delete the frame.
+The function can also perform own 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.
@@ -4962,6 +4989,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))
diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el
index 6ab6324540e..2f7578b842b 100644
--- a/lisp/tab-bar.el
+++ b/lisp/tab-bar.el
@@ -2659,6 +2659,17 @@ tab-switcher-mouse-select
   (goto-char (posn-point (event-end event)))
   (tab-switcher-select))
 
+
+(defun tab-bar-window-delete-frame (frame _kill)
+  "Handle frame deletion in `tab-bar-mode'.
+When there are more than one tab on the selected frame, then close
+the current tab.  In this case return t to not delete the frame
+in `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