Improve logic of tab handling when quitting windows (bug#71386)

* lisp/window.el (window-deletable-p): Add tab logic that returns
the symbol 'tab' for a set of predefined conditions.
(window--delete): Call 'tab-bar-close-tab' when 'window-deletable-p'
returns the symbol 'tab'.
(quit-restore-window): Remove tab logic and merge it with frame logic.

* test/lisp/tab-bar-tests.el (tab-bar-tests-close-other-tabs-default)
(tab-bar-tests-close-other-tabs-with-arg): Clean up tabs afterwards.
(tab-bar-tests-quit-restore-window): New test.
This commit is contained in:
Juri Linkov 2025-03-31 20:40:17 +03:00
parent 513a05dd87
commit a97a61b630
2 changed files with 128 additions and 9 deletions

View file

@ -4187,6 +4187,21 @@ returns nil."
(let ((frame (window-frame window)))
(cond
((and tab-bar-mode
;; Fall back to frame handling in case of less than 2 tabs
(> (length (funcall tab-bar-tabs-function frame)) 1)
;; Close the tab with the initial window (bug#59862)
(or (eq (nth 1 (window-parameter window 'quit-restore)) 'tab)
;; or with the dedicated window (bug#71386)
(and (window-dedicated-p window)
(frame-root-window-p window)))
;; Don't close the tab if more windows were created explicitly
(< (seq-count (lambda (w)
(memq (car (window-parameter w 'quit-restore))
'(window tab frame same)))
(window-list-1 nil 'nomini))
2))
'tab)
((frame-root-window-p window)
;; WINDOW's frame can be deleted only if there are other frames
;; on the same terminal, and it does not contain the active
@ -5022,6 +5037,10 @@ if WINDOW gets deleted or its frame is auto-hidden."
(unless (and dedicated-only (not (window-dedicated-p window)))
(let ((deletable (window-deletable-p window)))
(cond
((eq deletable 'tab)
(tab-bar-close-tab)
(message "Tab closed after deleting the last window")
'tab)
((eq deletable 'frame)
(let ((frame (window-frame window)))
(cond
@ -5388,13 +5407,7 @@ elsewhere. This value is used by `quit-windows-on'."
;; If the previously selected window is still alive, select it.
(window--quit-restore-select-window quit-restore-2))
((and (not prev-buffer)
(eq (nth 1 quit-restore) 'tab)
(eq (nth 3 quit-restore) buffer))
(tab-bar-close-tab)
;; If the previously selected window is still alive, select it.
(window--quit-restore-select-window quit-restore-2))
((and (not prev-buffer)
(or (eq (nth 1 quit-restore) 'frame)
(or (memq (nth 1 quit-restore) '(frame tab))
(and (eq (nth 1 quit-restore) 'window)
;; If the window has been created on an existing
;; frame and ended up as the sole window on that

View file

@ -42,10 +42,116 @@
(should (eq (length tab-bar-closed-tabs) 0)))
(ert-deftest tab-bar-tests-close-other-tabs-default ()
(tab-bar-tests-close-other-tabs nil))
(tab-bar-tests-close-other-tabs nil)
;; Clean up tabs afterwards
(tab-bar-tabs-set nil))
(ert-deftest tab-bar-tests-close-other-tabs-with-arg ()
(dotimes (i 5) (tab-bar-tests-close-other-tabs i)))
(dotimes (i 5) (tab-bar-tests-close-other-tabs i))
;; Clean up tabs afterwards
(tab-bar-tabs-set nil))
(ert-deftest tab-bar-tests-quit-restore-window ()
(let* ((frame-params (when noninteractive
'((window-system . nil)
(tty-type . "linux"))))
(pop-up-frame-alist frame-params)
(frame-auto-hide-function 'delete-frame))
;; 1.1. 'quit-restore-window' should delete the frame
;; from initial window (bug#59862)
(progn
(should (eq (length (frame-list)) 1))
(other-frame-prefix)
(info)
(should (eq (length (frame-list)) 2))
(should (equal (buffer-name) "*info*"))
(view-echo-area-messages)
(other-window 1)
(should (eq (length (window-list)) 2))
(should (equal (buffer-name) "*Messages*"))
(quit-window)
(should (eq (length (window-list)) 1))
(should (equal (buffer-name) "*info*"))
(quit-window)
(should (eq (length (frame-list)) 1)))
;; 1.2. 'quit-restore-window' should not delete the frame
;; from non-initial window (bug#59862)
(progn
(should (eq (length (frame-list)) 1))
(other-frame-prefix)
(info)
(should (eq (length (frame-list)) 2))
(should (equal (buffer-name) "*info*"))
(view-echo-area-messages)
(should (eq (length (window-list)) 2))
(should (equal (buffer-name) "*info*"))
(quit-window)
(should (eq (length (window-list)) 1))
(should (eq (length (frame-list)) 2))
;; FIXME: uncomment (should (equal (buffer-name) "*Messages*"))
(quit-window)
(should (eq (length (frame-list)) 2))
;; Clean up the frame afterwards
(delete-frame))
;; 2.1. 'quit-restore-window' should close the tab
;; from initial window (bug#59862)
(progn
(should (eq (length (tab-bar-tabs)) 1))
(other-tab-prefix)
(info)
(should (eq (length (tab-bar-tabs)) 2))
(should (equal (buffer-name) "*info*"))
(view-echo-area-messages)
(other-window 1)
(should (eq (length (window-list)) 2))
(should (equal (buffer-name) "*Messages*"))
(quit-window)
(should (eq (length (window-list)) 1))
(should (equal (buffer-name) "*info*"))
(quit-window)
(should (eq (length (tab-bar-tabs)) 1)))
;; 2.2. 'quit-restore-window' should not close the tab
;; from non-initial window (bug#59862)
(progn
(should (eq (length (tab-bar-tabs)) 1))
(other-tab-prefix)
(info)
(should (eq (length (tab-bar-tabs)) 2))
(should (equal (buffer-name) "*info*"))
(view-echo-area-messages)
(should (eq (length (window-list)) 2))
(should (equal (buffer-name) "*info*"))
(quit-window)
(should (eq (length (window-list)) 1))
(should (eq (length (tab-bar-tabs)) 2))
(should (equal (buffer-name) "*Messages*"))
(quit-window)
(should (eq (length (tab-bar-tabs)) 2))
;; Clean up the tab afterwards
(tab-close))
;; 3. Don't delete the frame with dedicated window
;; from the second tab (bug#71386)
(with-selected-frame (make-frame frame-params)
(switch-to-buffer (generate-new-buffer "test1"))
(tab-new)
(switch-to-buffer (generate-new-buffer "test2"))
(set-window-dedicated-p (selected-window) t)
(kill-buffer)
(should (eq (length (frame-list)) 2))
(should (eq (length (tab-bar-tabs)) 1))
;; But now should delete the frame with dedicated window
;; from the last tab
(set-window-dedicated-p (selected-window) t)
(kill-buffer)
(should (eq (length (frame-list)) 1)))
;; Clean up tabs afterwards
(tab-bar-tabs-set nil)))
(provide 'tab-bar-tests)
;;; tab-bar-tests.el ends here