Add Numbers to Emacs tab-bar-mode Tab Titles for Quick Access

For about two months now, I’ve been using tab-bar-mode in Emacs to have multiple “workspaces” open.

I was fine with buffer switching, too. But tab-bar-mode not only allows you to switch between buffers, but also window configurations – that means one tab will have 1 window with 1 buffer visiting a file, while another might have a gazillion split panes. So the main upside for me is to arrange stuff into panes aka windows, and then open a new tab to do something in a single-pane view.

One thing I added recently is enumeration of tabs so I can jump to them by number. The default tab-bar-keymap is activated via C-x t, and I didn’t need C-x t 1C-x t 9. I remapped these keys to go to tabs 1–9. Note that I leave C-x t 0 to close the current tab and switch to another, similar to how C-x 0 closes a window pane.

But with more than 3 tabs open at the same time, I had to count tabs to make this work. That sucks.

So I tried to insert the tab index into its title. But appending (1)(9) takes up 3 characters, which is quite a lot even with a variable pitch (aka non-monospaced font).

I eventually settled for prepending a circled unicode character. For reuse, I extracted an association list of integer values to circled unicode number characters:

(defvar ct/circle-numbers-alist
  '((0 . "⓪")
    (1 . "①")
    (2 . "②")
    (3 . "③")
    (4 . "④")
    (5 . "⑤")
    (6 . "⑥")
    (7 . "⑦")
    (8 . "⑧")
    (9 . "⑨"))
  "Alist of integers to strings of circled unicode numbers.")

You can use this mapping via e.g. (alist-get 4 ct/circle-numers-alist) and would get "④".

Circled numbers prepended to tabs. Using San Francisco UI (macOS system font) and slightly modified modus-themes for the colors.

Now here’s how I modified the tab bar format function to display that (and a couple other things) through basic string concatenation:

(defun ct/tab-bar-tab-name-format-default (tab i)
  (let ((current-p (eq (car tab) 'current-tab))
        (tab-num (if (and tab-bar-tab-hints (< i 10))
                     (alist-get i ct/circle-numbers-alist) "")))
    (propertize
     (concat tab-num
             " "
             (alist-get 'name tab)
             (or (and tab-bar-close-button-show
                      (not (eq tab-bar-close-button-show
                               (if current-p 'non-selected 'selected)))
                      tab-bar-close-button)
                 "")
             " ")
     'face (funcall tab-bar-tab-face-function tab))))
(setq tab-bar-tab-name-format-function #'ct/tab-bar-tab-name-format-default)

Update 2022-09-19: sorpet pointed out in the comments that you need to enable tab hints:

(setq tab-bar-tab-hints t)

Totally forgot about that!

Update 2022-12-29: I’ve updated my code to use SF Symbols to render the numbers a bit tighter.