emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH] tab-bar.el: add defcustoms for functions to call after openi


From: Robert Cochran
Subject: Re: [PATCH] tab-bar.el: add defcustoms for functions to call after opening and before closing
Date: Mon, 02 Dec 2019 14:46:46 -0800
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (gnu/linux)

Hi all,

I've taken a little bit of time to rethink how I wanted this to
work. Namely:

1. Trying to make a single hook do two separate things (determining
whether to prevent tab closing and tasks on tab close) just made things
overly complicated. Tab close prevention has been split off into a new
hook, `tab-bar-prevent-close-functions'.

2. I realized that it's possible when creating a new tab to just delay
the actual creation of the tab in the frame's tab list. This makes it
possible to directly modify the tab passed in to
tab-bar-tab-post-open-functions, ie `(setf (alist-get 'name tab) "New Name")`
from within a hook function. This means it's not really
necessary to make new accessors.

To be honest, I've been having a hard time coming up with good examples
of how to use these hooks that aren't arbitrary, contrived, or don't
otherwise require support in other places (more than one of the possible
examples I was thinking of works best when another mode or command, such
as gdb or mpc, has the ability to automatically start in a new
tab).

New patch, as well as a file of examples for the new hooks,
follow. Again, these new hooks still need to be documented in the
manual, which I will be glad to do as soon as a design is nailed down.

Thanks,
-- 
~Robert Cochran

>From d433b8075ec461ea293a2f95f3dbb7768fc0ad1e Mon Sep 17 00:00:00 2001
From: Robert Cochran <address@hidden>
Date: Fri, 8 Nov 2019 11:29:43 -0800
Subject: [PATCH] Add hooks for after tab open, before close, and to prevent
 closing

* lisp/tab-bar.el (tab-bar-tab-post-open-functions,
tab-bar-tab-prevent-close-functions, tab-bar-tab-pre-close-functions):
New defcustoms
(tab-bar-new-tab-to, tab-bar-close-tab): Use new defcustoms
---
 lisp/tab-bar.el | 132 +++++++++++++++++++++++++++++++-----------------
 1 file changed, 87 insertions(+), 45 deletions(-)

diff --git a/lisp/tab-bar.el b/lisp/tab-bar.el
index 5eb332884c..51cbc16aba 100644
--- a/lisp/tab-bar.el
+++ b/lisp/tab-bar.el
@@ -659,6 +659,15 @@ tab-bar-new-tab-to
   :group 'tab-bar
   :version "27.1")
 
+(defcustom tab-bar-tab-post-open-functions nil
+  "List of functions to call after creating a new tab.
+The current tab is supplied as an argument. Any modifications
+made to the tab argument will be applied after all functions are
+called."
+  :type '(repeat function)
+  :group 'tab-bar
+  :version "27.1")
+
 (defun tab-bar-new-tab-to (&optional to-index)
   "Add a new tab at the absolute position TO-INDEX.
 TO-INDEX counts from 1.  If no TO-INDEX is specified, then add
@@ -693,6 +702,10 @@ tab-bar-new-tab-to
                           ('right (1+ (or from-index 0)))))))
       (setq to-index (max 0 (min (or to-index 0) (length tabs))))
       (cl-pushnew to-tab (nthcdr to-index tabs))
+
+      (run-hook-with-args 'tab-bar-tab-post-open-functions
+                          (nth to-index tabs))
+
       (when (eq to-index 0)
         ;; pushnew handles the head of tabs but not frame-parameter
         (set-frame-parameter nil 'tabs tabs)))
@@ -747,6 +760,24 @@ tab-bar-close-last-tab-choice
   :group 'tab-bar
   :version "27.1")
 
+(defcustom tab-bar-tab-prevent-close-functions nil
+  "List of functions to call to determine whether to close a tab.
+The tab to be closed and a boolean indicating whether or not it
+is the only tab in the frame are supplied as arguments. If any
+function returns a non-nil value, the tab will not be closed."
+  :type '(repeat function)
+  :group 'tab-bar
+  :version "27.1")
+
+(defcustom tab-bar-tab-pre-close-functions nil
+  "List of functions to call before closing a tab.
+The tab to be closed and a boolean indicating whether or not it
+is the only tab in the frame are supplied as arguments,
+respectively."
+  :type '(repeat function)
+  :group 'tab-bar
+  :version "27.1")
+
 (defun tab-bar-close-tab (&optional arg to-index)
   "Close the tab specified by its absolute position ARG.
 If no ARG is specified, then close the current tab and switch
@@ -759,52 +790,63 @@ tab-bar-close-tab
   (interactive "P")
   (let* ((tabs (funcall tab-bar-tabs-function))
          (current-index (tab-bar--current-tab-index tabs))
-         (close-index (if (integerp arg) (1- arg) current-index)))
-    (if (= 1 (length tabs))
-        (pcase tab-bar-close-last-tab-choice
-          ('nil
-           (signal 'user-error '("Attempt to delete the sole tab in a frame")))
-          ('delete-frame
-           (delete-frame))
-          ('tab-bar-mode-disable
-           (tab-bar-mode -1))
-          ((pred functionp)
-           ;; Give the handler function the full extent of the tab's
-           ;; data, not just it's name and explicit-name flag.
-           (funcall tab-bar-close-last-tab-choice (tab-bar--tab))))
-
-      ;; More than one tab still open
-      (when (eq current-index close-index)
-        ;; Select another tab before deleting the current tab
-        (let ((to-index (or (if to-index (1- to-index))
-                            (pcase tab-bar-close-tab-select
-                              ('left (1- current-index))
-                              ('right (if (> (length tabs) (1+ current-index))
-                                          (1+ current-index)
-                                        (1- current-index)))
-                              ('recent (tab-bar--tab-index-recent 1 tabs))))))
-          (setq to-index (max 0 (min (or to-index 0) (1- (length tabs)))))
-          (tab-bar-select-tab (1+ to-index))
-          ;; Re-read tabs after selecting another tab
-          (setq tabs (funcall tab-bar-tabs-function))))
-
-      (let ((close-tab (nth close-index tabs)))
-        (push `((frame . ,(selected-frame))
-                (index . ,close-index)
-                (tab . ,(if (eq (car close-tab) 'current-tab)
-                            (tab-bar--tab)
-                          close-tab)))
-              tab-bar-closed-tabs)
-        (set-frame-parameter nil 'tabs (delq close-tab tabs)))
-
-      (when (and tab-bar-mode
-                 (and (natnump tab-bar-show)
-                      (<= (length tabs) tab-bar-show)))
-        (tab-bar-mode -1))
+         (close-index (if (integerp arg) (1- arg) current-index))
+         (last-tab-p (= 1 (length tabs)))
+         (prevent-close (run-hook-with-args-until-success
+                         'tab-bar-tab-prevent-close-functions
+                         (nth close-index tabs)
+                         last-tab-p)))
+
+    (unless prevent-close
+      (run-hook-with-args 'tab-bar-tab-pre-close-functions
+                          (nth close-index tabs)
+                          last-tab-p)
+
+      (if (= 1 (length tabs))
+          (pcase tab-bar-close-last-tab-choice
+            ('nil
+             (signal 'user-error '("Attempt to delete the sole tab in a 
frame")))
+            ('delete-frame
+             (delete-frame))
+            ('tab-bar-mode-disable
+             (tab-bar-mode -1))
+            ((pred functionp)
+             ;; Give the handler function the full extent of the tab's
+             ;; data, not just it's name and explicit-name flag.
+             (funcall tab-bar-close-last-tab-choice (tab-bar--tab))))
+
+        ;; More than one tab still open
+        (when (eq current-index close-index)
+          ;; Select another tab before deleting the current tab
+          (let ((to-index (or (if to-index (1- to-index))
+                              (pcase tab-bar-close-tab-select
+                                ('left (1- current-index))
+                                ('right (if (> (length tabs) (1+ 
current-index))
+                                            (1+ current-index)
+                                          (1- current-index)))
+                                ('recent (tab-bar--tab-index-recent 1 
tabs))))))
+            (setq to-index (max 0 (min (or to-index 0) (1- (length tabs)))))
+            (tab-bar-select-tab (1+ to-index))
+            ;; Re-read tabs after selecting another tab
+            (setq tabs (funcall tab-bar-tabs-function))))
+
+        (let ((close-tab (nth close-index tabs)))
+          (push `((frame . ,(selected-frame))
+                  (index . ,close-index)
+                  (tab . ,(if (eq (car close-tab) 'current-tab)
+                              (tab-bar--tab)
+                            close-tab)))
+                tab-bar-closed-tabs)
+          (set-frame-parameter nil 'tabs (delq close-tab tabs)))
+
+        (when (and tab-bar-mode
+                   (and (natnump tab-bar-show)
+                        (<= (length tabs) tab-bar-show)))
+          (tab-bar-mode -1))
 
-      (force-mode-line-update)
-      (unless tab-bar-mode
-        (message "Deleted tab and switched to %s" tab-bar-close-tab-select)))))
+        (force-mode-line-update)
+        (unless tab-bar-mode
+          (message "Deleted tab and switched to %s" 
tab-bar-close-tab-select))))))
 
 (defun tab-bar-close-tab-by-name (name)
   "Close the tab by NAME."
-- 
2.23.0

---
patch ends here
---
tab-buffer.el begins here
---

;; -*- lexical-binding: t -*-

(defmacro tab-ex/save-tab-excursion (&rest body)
  (declare (indent defun))
  (let ((old-tab-sym (gensym)))
    `(let ((,old-tab-sym (tab-bar--current-tab-index)))
       (unwind-protect (progn ,@body)
         (tab-bar-select-tab (1+ ,old-tab-sym))))))

(defmacro tab-ex/with-current-tab (new-tab &rest body)
  (declare (indent 1))
  `(tab-ex/save-tab-excursion
     (tab-bar-select-tab (1+ (tab-bar--tab-index ,new-tab)))
     ;; No need to wrap body in a progn here; we're already in a progn in the
     ;; parent macro.
     ,@body))

(defun tab-ex/tab-buffers (tab)
  ;; Windows aren't usable after exiting `tab-ex/with-current-tab's
  ;; scope, so you have to extract the buffers out of them *then*,
  ;; otherwise they go stale (likely as a result of them no longer
  ;; being 'visible') and you can't pull the buffers out of them
  ;; anymore
  (delete-dups (if (eq (car tab) 'current-tab)
                   (mapcar #'window-buffer
                           (window-list-1 (frame-first-window) 'nomini))
                 (tab-ex/with-current-tab tab
                   (mapcar #'window-buffer
                           (window-list-1 (frame-first-window) 'nomini))))))

(defun tab-ex/gud-tab-explicit-name (tab)
  "Rename tab and enable gud-many-buffers when tab is made with the gud core 
buffer.

When using GUD in many-buffers mode, rename the tab to something
static so that it's easy to tell at a glance what the tab
contains despite the various buffer names."
  (let ((tab-buffers (tab-ex/tab-buffers tab)))
    (dolist (buf tab-buffers)
      (with-current-buffer buf
        (when (and (eq major-mode 'gud-mode))
          ;; It's probably better to have a way to just have gud make
          ;; a new tab and always have many windows on, but this is
          ;; just an example.
          (gdb-many-windows)
          (save-match-data
            (string-match "\\*gud-\\(.*\\)\\*" (buffer-name))
            (setf (alist-get 'name tab) (format "GUD: %s"
                                                (match-string 1 (buffer-name)))
                  (alist-get 'explicit-name tab) t)))))))

(defun tab-ex/tab-has-unsaved-files (tab _lastp)
  ;; Don't let the user close a tab if they have unsaved buffers, to
  ;; make sure they don't lose track of unsaved buffers, for
  ;; example. In this case, it doesn't matter if this tab is the last
  ;; one or not.
  (cl-loop for buffer in (tab-ex/tab-buffers tab)
           always (and (with-current-buffer buffer buffer-file-name)
                       (not (buffer-modified-p buffer)))))

(defun tab-ex/kill-non-file-buffers-in-tab (tab lastp)
  ;; Don't kill the buffers if the tab is going to still be visible in
  ;; the end of it.
  (unless lastp
    (tab-ex/with-current-tab tab
      ;; Just kill all the visible non-file buffers, for the sake of example.
      (mapc #'kill-buffer (cl-loop for buffer in (tab-ex/tab-buffers tab)
                                   unless (with-current-buffer buffer 
buffer-file-name)
                                   collect buffer)))))

(add-hook 'tab-bar-tab-post-open-functions
          #'tab-ex/gud-tab-explicit-name)

(add-hook 'tab-bar-tab-prevent-close-functions
          #'tab-ex/tab-has-unsaved-files)

(add-hook 'tab-bar-tab-pre-close-functions
          #'tab-ex/kill-non-file-buffers-in-tab)

reply via email to

[Prev in Thread] Current Thread [Next in Thread]