From a97a61b630624f5a6ec917db92e2985c56b20aa0 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Mon, 31 Mar 2025 20:40:17 +0300 Subject: [PATCH] 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. --- lisp/window.el | 27 ++++++--- test/lisp/tab-bar-tests.el | 110 ++++++++++++++++++++++++++++++++++++- 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/lisp/window.el b/lisp/window.el index 438c998be9e..1b5ad34dc19 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -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 diff --git a/test/lisp/tab-bar-tests.el b/test/lisp/tab-bar-tests.el index a749db1c512..c0e12cf159c 100644 --- a/test/lisp/tab-bar-tests.el +++ b/test/lisp/tab-bar-tests.el @@ -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