Permanently display workspaces *in the tab-bar*

We have Permanently display workspaces in minibuffer already, but we can also hijack the tab-bar to have a truly permanent display of workspaces without encumbering the minibuffer. Note that this removes any display of Emacs’ native tabs. Personally, I find them unnecessary given that we have workspaces already.

(after! persp-mode
  ;; alternative, non-fancy version which only centers the output of +workspace--tabline
  (defun workspaces-formatted ()
    (+doom-dashboard--center (frame-width) (+workspace--tabline)))

  (defun hy/invisible-current-workspace ()
    "The tab bar doesn't update when only faces change (i.e. the
current workspace), so we invisibly print the current workspace
name as well to trigger updates"
    (propertize (safe-persp-name (get-current-persp)) 'invisible t))

  (customize-set-variable 'tab-bar-format '(workspaces-formatted tab-bar-format-align-right hy/invisible-current-workspace))

  ;; don't show current workspaces when we switch, since we always see them
  (advice-add #'+workspace/display :override #'ignore)
  ;; same for renaming and deleting (and saving, but oh well)
  (advice-add #'+workspace-message :override #'ignore))

;; need to run this later for it to not break frame size for some reason
(run-at-time nil nil (cmd! (tab-bar-mode +1)))

We can also get a bit fancier and change faces and the workspace format, yielding a tab-bar like this:

  '(+workspace-tab-face :inherit default :family "Jost" :height 135)
  '(+workspace-tab-selected-face :inherit (highlight +workspace-tab-face)))

(after! persp-mode
  (defun workspaces-formatted ()
    ;; fancy version as in screenshot
    (+doom-dashboard--center (frame-width)
                             (let ((names (or persp-names-cache nil))
                                   (current-name (safe-persp-name (get-current-persp))))
                                (cl-loop for name in names
                                         for i to (length names)
                                         (concat (propertize (format " %d" (1+ i)) 'face
                                                             `(:inherit ,(if (equal current-name name)
                                                               :weight bold))
                                                 (propertize (format " %s " name) 'face
                                                             (if (equal current-name name)
                                " "))))
;; other persp-mode related configuration

1 Like

This looks good, adapted for my private config & using perspectives.el rather than persp-mode (extremely minimal changes)

(declare-function "persp-names" 'perspective)

(defgroup lkn-tab-bar nil
  "Options related to my custom tab-bar."
  :group 'tab-bar)

(defgroup lkn-tab-bar-faces nil
  "Faces for the tab-bar."
  :group 'lkn-tab-bar)

(defface lkn-tab-bar-workspace-tab
  '((t :inherit default))
  "Face for a workspace tab."
  :group 'lkn-tab-bar-faces)

(defface lkn-tab-bar-selected-workspace-tab
  '((t :inherit (highlight lkn-tab-bar-workspace-tab)))
  "Face for a selected workspace tab."
  :group 'lkn-tab-bar-faces)

(defun lkn-tab-bar--workspaces ()
  "Return a list of the current workspaces."
  (let ((persps (persp-names))
	(persp (persp-current-name)))
     (lambda (acc elm)
       (let ((face (if (equal persp elm)
	   (propertize (format " %d" (1+ (cl-position elm persps)))
		       `(:inherit ,face
			 :weight bold))
	   (propertize (format " %s " elm)
		       'face `,face)))))
     `(,(propertize (persp-current-name) 'invisible t))))))

(customize-set-variable 'global-mode-string '((:eval (lkn-tab-bar--workspaces)) " "))
(customize-set-variable 'tab-bar-format '(tab-bar-format-global))
(customize-set-variable 'tab-bar-mode t)