Tab Bar Utilities

Not to be confused with the tabs you see in editors like VSCode, tab-bar tabs allow for easy switching between different collections of windows - like workspaces.

Here I define all the helper functions and utilities I use in my config that I use when working with tab-bar tabs, for the actual configuration of the tab-bar itself see this section.

Formatting Tabs

This article

emacs/lisp/alc-tab-bar.el
(defun alc-tab-bar-tab-name-format-hints (name _tab i)
  (if tab-bar-tab-hints (concat (format "-%d-" i) "") name))

(defun alc-tab-bar-tab-group-format-function (tab i &optional current-p)
  (propertize
   (concat (funcall tab-bar-tab-group-function tab))
   'face (if current-p 'tab-bar-tab-group-current 'tab-bar-tab-group-inactive)))

Grouping Tabs

I’m often switching between multiple projects and I will often have multiple tabs per project, so naturally, I want to group sets of tabs by project.

The emacs-solo/tab-group-from-project function from this article showed that it was possible to group tabs by project, but instead of explicitly moving a tab into the project group, I want tabs to be automatically placed into the correct group as they are created.

After some experimentation I was able to come up with the following function which I bind to C-x t p, overriding the default project-other-tab-command.

emacs/lisp/alc-tab-bar.el
(defun alc-tab-bar-project-other-tab-command ()
  "Run a project command, displaying resultant buffer in a new tab.

This implemented by hacking together parts of
`project--other-place-prefix' and `other-tab-prefix'.

The only difference between this version and `project-other-tab-command'
is that new tabs are automatically placed in a group named according to
the corresponding project.
"
  (interactive)
  (prefix-command-preserve-state)
  (display-buffer-override-next-command
   (lambda (buffer alist)
     (cons (progn
             (display-buffer-in-tab
              buffer (append alist '((tab-group . alc-tab-bar-buffer-to-project-name)
                                     (inhibit-same-window . nil))))
             (selected-window))
           'tab))
   nil "[other-tab]")
  (message "Display next project command buffer in a new tab...")
  (set-transient-map project-prefix-map))
  • I don’t know what prefix-command-preserve-state does… but project--other-place-prefix uses it and the docstring makes it sound important

  • display-buffer-override-next-command does what the name suggests, changing the display-buffer rules just for the next buffer shown.

  • display-buffer-in-tab accepts a tab-group option with alc-tab-bar-buffer-to-project-name returns the name of the group to place the tab into.

  • set-transient-map looks very interesting, in this case it takes the given keymap and makes it active just for the next command. However, the docstring looks like it is capable of much more!

Here is the implementation of alc-tab-bar-buffer-to-project-name, it uses the project-name function to return the name of the project associated with the target buffer.

emacs/lisp/alc-tab-bar.el
(defun alc-tab-bar-buffer-to-project-name (buffer alist)
  "Given BUFFER, return the corresponding project name, if any"
  (with-current-buffer buffer
    (if-let ((pr (project-current nil)))
        (format "[%s]" (project-name pr)))))

Tip

By default project-name returns the name of the folder the project is stored in but this can be overriden by setting the project-vc-name variable in the project’s .dir-locals.el file. Often I will set the project name to <owner>/<repo>, for example

((nil . ((project-vc-name . "swyddfa/esbonio"))))

Since I also use project-vc-extra-root-markers, most default project names are not that meaningful so I will also include the subriectory in the name

((nil . ((project-vc-name . "swyddfa/esbonio [lib/esbonio]"))))

Yes, that means names can be quite long, but they are much more useful to me!

Global Status Items

As well as using the tab bar to show… well tabs, the tab-bar-format-global variable can be included in tab-bar-format to show additional information.

Battery Info

emacs/lisp/alc-tab-bar.el
(display-battery-mode)

Time

emacs/lisp/alc-tab-bar.el
(setq display-time-format "%H:%M %d/%m/%y"
      display-time-default-load-average nil)
(display-time-mode)
emacs/lisp/alc-tab-bar.el
(provide 'alc-tab-bar)