Emacs tabbar tuning
I like tabbar-mode
and the buffer cycling options it provides. I can cycle between buffers in a single group (especially useful when cycling scope is limited to tabs
) and between different groups. This makes it much easier to manage many open buffers within different contexts (such as projects).
(require 'tabbar)
(tabbar-mode t)
(setq tabbar-cycle-scope 'tabs)
(global-set-key (kbd "s-{") 'tabbar-backward-group)
(global-set-key (kbd "s-}") 'tabbar-forward-group)
(global-set-key (kbd "s-[") 'tabbar-backward)
(global-set-key (kbd "s-]") 'tabbar-forward)
Grouping
The grouping defaults are reasonable (by major mode name), but could be better. We'll use the default tabbar-buffer-groups-function
from tabbar.el
as a basis. This section contains the entire function budy.
(defun my-tabbar-buffer-groups ()
"Return the list of group names the current buffer belongs to.
Return a list of one element based on major mode."
(list
*Process* group
The default grouping separates "process" buffers into their own group. This is a problem with flycheck
, for example. When flycheck does its thing the buffer briefly becomes a "process" buffer, causing tabbar to yank it between groups. This makes for a very jittery tab bar. Let's fix that by removing the "Process" group altogether:
(setq my-group-by-project nil)
(cond
;; ((or (get-buffer-process (current-buffer))
;; ;; Check if the major mode derives from `comint-mode' or
;; ;; `compilation-mode'.
;; (tabbar-buffer-mode-derived-p
;; major-mode '(comint-mode compilation-mode)))
;; "Process"
;; )
OK, better. Now our flychecked files stay grouped by major mode.
((member (buffer-name)
'("*scratch*" "*Messages*"))
"Common"
)
((eq major-mode 'dired-mode)
"Dired"
)
((memq major-mode
'(help-mode apropos-mode Info-mode Man-mode))
"Help"
)
((memq major-mode
'(rmail-mode
rmail-edit-mode vm-summary-mode vm-mode mail-mode
mh-letter-mode mh-show-mode mh-folder-mode
gnus-summary-mode message-mode gnus-group-mode
gnus-article-mode score-mode gnus-browse-killed-mode))
"Mail"
)
Grouping by project
While working on multiple projects the tab bar quicly becomes unwieldy. I prefer to group mine by "project name:mode name" instead. I use projectile
for getting the name of the project.
Also, for small projects I like to be able to group by project name alone. For this I defined a simple toggle variable my-group-by-project
.
(t
;; Return `mode-name' if not blank, `major-mode' otherwise.
(let ((group
(if (and (stringp mode-name)
;; Take care of preserving the match-data because this
;; function is called when updating the header line.
(save-match-data (string-match "[^ ]" mode-name)))
mode-name
(symbol-name major-mode))))
(if (projectile-project-p)
(if my-group-by-project
(projectile-project-name)
(format "%s:%s" (projectile-project-name) group))
group))
))))
Performance tuning
tabbar-mode
calls tabbar-buffer-groups-function
A LOT -- for each open buffer for every single keystroke. Since projectile-project-name
is not a super fast function this will slow Emacs down. Assuming we never want buffers to change groups, we could quasi-memoize this function and cache group names per every project. We can do this with a pseudo-closure.
(defun my-cached (func)
"Turn a function into a cache dict."
(lexical-let ((table (make-hash-table :test 'equal))
(f func))
(lambda (key)
(let ((value (gethash key table)))
(if value
value
(puthash key (funcall f) table))))))
;; evaluate again to clear cache
(setq cached-ppn (my-cached 'my-tabbar-buffer-groups))
(defun my-tabbar-groups-by-project ()
(funcall cached-ppn (buffer-name)))
(setq tabbar-buffer-groups-function 'my-tabbar-groups-by-project)
Now, tabbar will be fetching a cached group name for existing buffers. To wipe the cache we recreate the closure by reevaluating.
Finally, wire the toggle (we must clear the cache for regrouping to take effect):
(defun my-toggle-group-by-project ()
(interactive)
(setq my-group-by-project (not my-group-by-project))
(message "Grouping by project alone: %s"
(if my-group-by-project "enabled" "disabled"))
(setq cached-ppn (my-cached 'my-tabbar-buffer-groups)))