Tabbar is an emacs minor mode that displays a tab bar at the top, similar to the idea of web browser’s tabs.
Note: Emacs 27.1 will come with two built-in tab modes, tab-bar-mode (per-frame) and tab-line-mode (per-window). See documentation for details. Tab-line-mode is similar to tabbar.el described on this page, with one buffer per tab. Tab-bar-mode is for storing one window configuration per tab.
Check if Debian has newer version in emacs-goodies-el package.
Tabbar is a package originally written by David Ponce from 2003 to 2005, and now maintained by others.
The mode comes in a single file,
tabbar.el, which has been extensively modified and used by AquamacsEmacs to be as close to modern UI’s tab bar as possible. A fork of the Aquamacs version of TabBarMode which is intended to be Emacs agnostic is available here. (See AquamacsEmacs’s official
website screenshot for a look.) You can also try to get the source code from AquamacsEmacs, but it might not work with standard Emacs.
Note that tabbar mode as implemented in Emacs is a bit different than tabs in web browsers or IDEs. In a web browser, each window may have several tabs, and each window’s tabs stay with that window. Namely, you cannot have Window A’s tab jump to window B, unless you drag it over (Firefox 3 supports this, for example. Safari 3.2 does not.). However, the tabs implemented in emacs with tabbar mode is more like a GUI-based display of buffer listing, displayed on top of the window. In particular, tabs does not stick to a particular emacs’s Frame, and conversely, each Emacs Frame can display a tab that is also shown in another Emacs Frame.
Also, emacs’s tabbar mode’s tab can be hidden, as if showing only the current “page” of a tab set, called a “group”. By default, tabs are grouped by major mode. So, for example, if current buffer is in html-mode, all the tabs shown are other buffers in html-mode. If there are no other html-mode buffers, then you will have only one tab, even if you have lots other buffers. All your dired buffers, C-mode buffers, java-buffers, etc, are in other tab group and is hidden. You have to click on a special tab widget to switch to them, or use keyboard shortcut to switch tab or tab group.
What is considered a tab group can be customized (see section Customization below). You can even set it up to include every buffer in one group.
Tab bar mode is implemented using a special Emacs display area (called the header line) at the top of the Emacs window, not the Emacs frame. (From this it follows that each Emacs window will have its own tab bar, no matter how many times you split the frame.) Some other modes also use this header line, for instance Info, Dired, Slime, ERC, ruler-mode, etc. These modes override tabbar for their own purposes, but you can get the tabs back by typing
The tabbar mode does not have documentation except in the comments of the source file
Tabbar mode has many customizable options, and it supports emacs customization system. To customize, type
M-x customize-group [RET] tabbar [RET].
There is also an out-of-the-box configuration available, which features, among other things, automatic project grouping, tab movement and multiple tab styles: https://github.com/manateelazycat/awesome-tab
To define your own tab grouping, you need to either set
tabbar-buffer-groups to your own function that returns a list of strings, or define your own function with another name and then write
(setq tabbar-buffer-groups-function 'your-function-name). Either way works.
On my machine,
(setq tabbar-buffer-groups-function (lambda () ... strangely has no effect, requiring
(defun tabbar-groups-function ()
... instead. If you’re using Homebrew Emacs on Mac OS X, this might work for you.
Here’s a example of tab group function that define all tabs to be one of two possible groups: “emacs” and “user”.
(defun my-tabbar-buffer-groups () ;; customize to show all normal files in one group "Returns the name of the tab group names the current buffer belongs to. There are two groups: Emacs buffers (those whose name starts with '*', plus dired buffers), and the rest. This works at least with Emacs v24.2 using tabbar.el v1.7." (list (cond ((string-equal "*" (substring (buffer-name) 0 1)) "emacs") ((eq major-mode 'dired-mode) "emacs") (t "user")))) (setq tabbar-buffer-groups-function 'my-tabbar-buffer-groups)
Another tabbar-buffer-groups example, based on the original
tabbar.el’s function of the same name. (Note that this function overrides the original
tabbar-buffer-groups function, so there’s no need to call
(defun 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 (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" ) ;; ((member (buffer-name) ;; '("*scratch*" "*Messages*" "*Help*")) ;; "Common" ;; ) ((string-equal "*" (substring (buffer-name) 0 1)) "Common" ) ((member (buffer-name) '("xyz" "day" "m3" "abi" "for" "nws" "eng" "f_g" "tim" "tmp")) "Main" ) ((eq major-mode 'dired-mode) "Dired" ) ((memq major-mode '(help-mode apropos-mode Info-mode Man-mode)) "Common" ) ((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" ) (t ;; Return `mode-name' if not blank, `major-mode' otherwise. (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)) ))))
Here’s a way to change the toggle tabbar groups:
(setq tbbr-md "all") (defun toggle-tabbar-mode () "Toggles tabbar modes - all buffers vs. defined in the `tabbar-buffer-groups'." (interactive) (if (string= tbbr-md "groups") (progn ;; then (setq tabbar-buffer-groups-function (lambda () (list "All"))) (setq tbbr-md "all")) (progn ;; else (setq tabbar-buffer-groups-function 'tabbar-buffer-groups) (setq tbbr-md "groups"))))
(Must be called twice in the first time of the given emacs session.)
This code simply puts all tabs in one group, “All”:
(setq tabbar-buffer-groups-function (lambda () (list "All")))
To put most buffers into one group, see http://www.ficml.org/jemimap/wordpress/2004/10/11/tabbar/.
The following will group the tabs by the git repository that the file is in. All files that are not in a git repository will be grouped together, and all buffers that do not have a file will be grouped together.
(defun find-git-dir (dir) "Search up the directory tree looking for a .git folder." (cond ((eq major-mode 'dired-mode) "Dired") ((not dir) "process") ((string= dir "/") "no-git") ((file-exists-p (concat dir "/.git")) dir) (t (find-git-dir (directory-file-name (file-name-directory dir))))))
(defun git-tabbar-buffer-groups () "Groups tabs in tabbar-mode by the git repository they are in." (list (find-git-dir (buffer-file-name (current-buffer))))) (setq tabbar-buffer-groups-function 'git-tabbar-buffer-groups)
(defun tabbar-add-tab (tabset object &optional append_ignored) "Add to TABSET a tab with value OBJECT if there isn't one there yet. If the tab is added, it is added at the beginning of the tab list, unless the optional argument APPEND is non-nil, in which case it is added at the end." (let ((tabs (tabbar-tabs tabset))) (if (tabbar-get-tab object tabset) tabs (let ((tab (tabbar-make-tab object tabset))) (tabbar-set-template tabset nil) (set tabset (sort (cons tab tabs) (lambda (a b) (string< (buffer-name (car a)) (buffer-name (car b))))))))))
You can define keystrokes for navigating among the tabs of one tab group. Here we use the meta-right-arrow and meta-left-arrow keystrokes. (These keys normally bind to functions right-word and left-word, which are also bound to ctrl-right-arrow and ctrl-left-arrow, so we’re not losing convenience.)
(global-set-key [M-left] 'tabbar-backward-tab) (global-set-key [M-right] 'tabbar-forward-tab)
These functions move the current tab to the left or to the right. Binding the functions to the C-S-<prior> and C-S-<next> key sequences is also shown.
(defun tabbar-move-current-tab-one-place-left () "Move current tab one place left, unless it's already the leftmost." (interactive) (let* ((bufset (tabbar-current-tabset t)) (old-bufs (tabbar-tabs bufset)) (first-buf (car old-bufs)) (new-bufs (list))) (if (string= (buffer-name) (format "%s" (car first-buf))) old-bufs ; the current tab is the leftmost (setq not-yet-this-buf first-buf) (setq old-bufs (cdr old-bufs)) (while (and old-bufs (not (string= (buffer-name) (format "%s" (car (car old-bufs)))))) (push not-yet-this-buf new-bufs) (setq not-yet-this-buf (car old-bufs)) (setq old-bufs (cdr old-bufs))) (if old-bufs ; if this is false, then the current tab's buffer name is mysteriously missing (progn (push (car old-bufs) new-bufs) ; this is the tab that was to be moved (push not-yet-this-buf new-bufs) (setq new-bufs (reverse new-bufs)) (setq new-bufs (append new-bufs (cdr old-bufs)))) (error "Error: current buffer's name was not found in Tabbar's buffer list.")) (set bufset new-bufs) (tabbar-set-template bufset nil) (tabbar-display-update))))
(defun tabbar-move-current-tab-one-place-right () "Move current tab one place right, unless it's already the rightmost." (interactive) (let* ((bufset (tabbar-current-tabset t)) (old-bufs (tabbar-tabs bufset)) (first-buf (car old-bufs)) (new-bufs (list))) (while (and old-bufs (not (string= (buffer-name) (format "%s" (car (car old-bufs)))))) (push (car old-bufs) new-bufs) (setq old-bufs (cdr old-bufs))) (if old-bufs ; if this is false, then the current tab's buffer name is mysteriously missing (progn (setq the-buffer (car old-bufs)) (setq old-bufs (cdr old-bufs)) (if old-bufs ; if this is false, then the current tab is the rightmost (push (car old-bufs) new-bufs)) (push the-buffer new-bufs)) ; this is the tab that was to be moved (error "Error: current buffer's name was not found in Tabbar's buffer list.")) (setq new-bufs (reverse new-bufs)) (setq new-bufs (append new-bufs (cdr old-bufs))) (set bufset new-bufs) (tabbar-set-template bufset nil) (tabbar-display-update)))
;; Key sequences "C-S-PgUp" and "C-S-PgDn" move the current tab to the left and to the right. (global-set-key (kbd "C-S-<prior>") 'tabbar-move-current-tab-one-place-left) (global-set-key (kbd "C-S-<next>") 'tabbar-move-current-tab-one-place-right)
The following will provide windows style (shift-)control tab behaviour and autoload tabbar-mode. Use a prefix to change groups and no prefix to change the buffer.
(dolist (func '(tabbar-mode tabbar-forward-tab tabbar-forward-group tabbar-backward-tab tabbar-backward-group)) (autoload func "tabbar" "Tabs at the top of buffers and easy control-tab navigation"))
(defmacro defun-prefix-alt (name on-no-prefix on-prefix &optional do-always) `(defun ,name (arg) (interactive "P") ,do-always (if (equal nil arg) ,on-no-prefix ,on-prefix)))
(defun-prefix-alt shk-tabbar-next (tabbar-forward-tab) (tabbar-forward-group) (tabbar-mode 1)) (defun-prefix-alt shk-tabbar-prev (tabbar-backward-tab) (tabbar-backward-group) (tabbar-mode 1))
(global-set-key [(control tab)] 'shk-tabbar-next) (global-set-key [(control shift tab)] 'shk-tabbar-prev)
This allows a popup tabbar AND a menu bar based on the cursor’s location. It also switches between tab-bar and ruler mode. It is included in EmacsPortable. It requries xpm images close-tab.xpm down.xpm left.xpm left-disabled.xpm right.xpm right-disabled.xpm up.xpm as well as
tabbar.el. It is located in
You can limit the display to just the non-scratch by adding the following code snippet to the .emacs file:
;; 1- if remove-if is not found, add here (require 'cl) ;; 2- in my emacs 23, I had to remove the "b" from "lambda (b)" (when (require 'tabbar nil t) (setq tabbar-buffer-groups-function (lambda () (list "All Buffers"))) (setq tabbar-buffer-list-function (lambda () (remove-if (lambda(buffer) (find (aref (buffer-name buffer) 0) " *")) (buffer-list)))) (tabbar-mode))
For the author, this only works if tabbar-mode is set on tob of this block and the when stuff is removed:
(require 'tabbar) (tabbar-mode) (setq tabbar-buffer-groups-function (lambda () (list "All Buffers")))
(setq tabbar-buffer-list-function (lambda () (remove-if (lambda(buffer) (find (aref (buffer-name buffer) 0) " *")) (buffer-list))))
Some buffers can be hidden with this code:
(setq *tabbar-ignore-buffers* '("idle.org" ".bbdb" "diary")) (setq tabbar-buffer-list-function (lambda () (remove-if (lambda (buffer) (and (not (eq (current-buffer) buffer)) ; Always include the current buffer. (loop for name in *tabbar-ignore-buffers* ;remove buffer name in this list. thereis (string-equal (buffer-name buffer) name)))) (buffer-list))))
I tried to enable the tabbar for my terminals only with the above configuration, and it turned out that it remains visible even when it does not contain any tab: some empty space remains as headline.
So, I use the following configuration to display the tabbar on certain buffers only (for buffers in
term-mode major mode in this case, but it can easily be extended). The tabbar is disabled on the other buffers.
(when (require 'tabbar nil t) ;; Enable tabbars globally: (tabbar-mode 1)
;; I use this minor-mode mainly as a global mode (see below): (define-minor-mode tabbar-on-term-only-mode "Display tabbar on terminals and buffers in fundamental mode only." :init-value t :lighter nil :keymap nil (if tabbar-on-term-only-mode ;; filter is enabled (if (eq major-mode 'term-mode); <- this can be easily customizable... (tabbar-local-mode -1) (tabbar-local-mode 1)) ;; always activate tabbar locally when we disable the minor mode: (tabbar-local-mode -1)))
(defun tabbar-on-term-only-mode-on () "Turn on tabbar if current buffer is a terminal." (unless (minibufferp) (tabbar-on-term-only-mode 1)))
;; Define a global switch for the mode. Note that this is not set for buffers ;; in fundamental mode. ;; ;; I use it 'cause some major modes do not run the ;; `after-change-major-mode-hook'... (define-globalized-minor-mode global-tabbar-on-term-only-mode tabbar-on-term-only-mode tabbar-on-term-only-mode-on)
;; Eventually, switch on this global filter for tabbars: (global-tabbar-on-term-only-mode 1))
;; Add a buffer modification state indicator in the tab label, and place a ;; space around the label to make it looks less crowd. (defadvice tabbar-buffer-tab-label (after fixup_tab_label_space_and_flag activate) (setq ad-return-value (if (and (buffer-modified-p (tabbar-tab-value tab)) (buffer-file-name (tabbar-tab-value tab))) (concat " + " (concat ad-return-value " ")) (concat " " (concat ad-return-value " ")))))
;; Called each time the modification state of the buffer changed. (defun ztl-modification-state-change () (tabbar-set-template tabbar-current-tabset nil) (tabbar-display-update))
;; First-change-hook is called BEFORE the change is made. (defun ztl-on-buffer-modification () (set-buffer-modified-p t) (ztl-modification-state-change)) (add-hook 'after-save-hook 'ztl-modification-state-change)
;; This doesn't work for revert, I don't know. ;;(add-hook 'after-revert-hook 'ztl-modification-state-change) (add-hook 'first-change-hook 'ztl-on-buffer-modification)
I have too many buffers to reasonably put them all into tabbar at the same time, and I wanted to group by project instead of by major-mode, so I use this:
(require 'tabbar) (tabbar-mode t) (setq tabbar-cycle-scope 'tabs) (setq tabbar-buffer-groups-function (lambda () (let ((dir (expand-file-name default-directory))) (cond ((member (buffer-name) '("*Completions*" "*scratch*" "*Messages*" "*Ediff Registry*")) (list "#misc")) ((string-match-p "/.emacs.d/" dir) (list ".emacs.d")) (t (list dir))))))
Certain buffers get put into the #misc group; everything under
~/.emacs.d/ gets put into one group, and everything else gets put into a group with its directory, so that all files in that directory (and associated buffers like compilation) get put together. For multi-directory projects I plan to add special cases like the one for
.emacs.d to group buffers under the parent directory.
(defun autohide-tabbar () "Make tabbar briefly show itself while you are switching buffers with shortcuts. Tested with GNU Emacs 23." (defvar *tabbar-autohide-delay* 3)
(interactive) (tabbar-mode nil) (defvar *tabbar-autohide-timer* nil) (defun renew-tabbar-autohide-timer () (if (timerp *tabbar-autohide-timer*) (cancel-timer *tabbar-autohide-timer*)) (setf *tabbar-autohide-timer* (run-with-timer 3 nil (lambda () (tabbar-mode nil) (setf *tabbar-autohide-timer* nil)))))
(global-set-key [C-next] (lambda () (interactive) (if tabbar-mode (tabbar-forward) (tabbar-mode t)) (renew-tabbar-autohide-timer))) (global-set-key [C-prior] (lambda () (interactive) (if tabbar-mode (tabbar-backward) (tabbar-mode t)) (renew-tabbar-autohide-timer))))
Tabbar can slow down Emacs considerably, especially simply moving the cursor up or down. This can be rectified by adding this to your .emacs file, which makes Tabbar use characters to represent the three images in the top-left corner:
(setq tabbar-use-images nil)
(defun tabbar-add-tab (tabset object &optional append) "Override tabbar-add-tab, makes new tab open on the right of the current tab" (let ((tabs (tabbar-tabs tabset))) (if (tabbar-get-tab object tabset) tabs (let ((tab (tabbar-make-tab object tabset)) (previous-tab (tabbar-selected-tab tabset)) (other-tabs (list))) (tabbar-set-template tabset nil) (if append (progn (while (not (string= (format "%s" (car previous-tab)) (format "%s" (car (car tabs))))) (push (car tabs) other-tabs) (setq tabs (cdr tabs))) (push (car tabs) other-tabs) (setq tabs (cdr tabs)) (push tab other-tabs) (setq other-tabs (reverse other-tabs)) (set tabset (append other-tabs tabs))) (set tabset(cons tab tabs)))))))
Here is one approach for integrating tabbar groups with
(defun my-buffer-eproject-name () (when eproject-root (let ((lst (eproject-projects)) (projectname)) (while lst (let ((proj (pop lst))) (when (string= eproject-root (cdr proj)) (setq projectname (car proj)) (setq lst nil)))) projectname)))
or even better:
(defun my-buffer-eproject-name () (when eproject-root (catch 'loop (let ((lst (eproject-projects))) (while lst (let ((proj (pop lst))) (if (string= eproject-root (cdr proj)) (throw 'loop (car proj)))))))))
An extra very useful feature is to show tabgroup in modeline:
(setq-default mode-line-format (list " " '(:eval (when (tabbar-mode-on-p) (concat (propertize (car (funcall tabbar-buffer-groups-function)) 'face 'font-lock-string-face) " > "))) '(:eval (propertize "%b" 'face 'font-lock-function-name-face [...] ))
This will show something like:
Projectname > file.ext (37, 1) [mode]...
Any way to turn off the tabbar for a specific window/frame, i.e., not globally and not per a specific buffer? – DrewAdams
Here is one way to do it. It is quite hackish and uses deprecated stuff, but it works fine on 24.1.
emacs --daemon with both, X and tty frames. My goal is: On ttys, no tab bar (or menu bar) should show in order to preserve screen real-estate. X frames should have all bells and whistles. Since this works on a per-frame base, you can easily adapt it to other scenarios.
;; Allow tab-bar-mode to be frame-local. This is deprecated and exploits ;; the fact that `default-header-line-format' is an alias of the global ;; value of `header-line-format'. Tested with Emacs 24.1. (make-variable-frame-local 'default-header-line-format)
;; These two are needed as the aliased frame-local value isn't recognized ;; unless the variable is explicitly accessed once. Do so on those two ;; occasions where this matters. Notably, `handle-switch-frame' need not ;; be advised. (defadvice select-frame (after adapt-select-frame-parameters activate) "Fix frame-local value of `default-header-line-format'." (interactive) default-header-line-format)
;; This absolutely must be after delete-frame, so ;; `delete-frame-functions' won't work. (defadvice delete-frame (after adapt-delete-frame-parameters activate) "Fix frame-local value of `default-header-line-format'." (interactive) default-header-line-format)
;; Example usage: Set per-terminal (frame-local) options. (defun customize-terminal-frame (frame) (cond ((frame-parameter frame 'window-system) (set-frame-parameter frame 'default-header-line-format '(:eval (tabbar-line))) (tabbar-init-tabsets-store)) (t (set-frame-parameter frame 'default-header-line-format nil) (set-frame-parameter frame 'menu-bar-lines 0))))
(add-hook 'after-make-frame-functions 'customize-terminal-frame)
Where is it on the GNU emacs CVS site? I was unable to find it (but have to confess to not being interested in spending all afternoon looking for it…).
I’m having trouble combining tabbar with color-theme mode. If I load a color-theme at startup, it isn’t applied to tabbar. Only if I manually change the color-theme mode, using M-x color-theme-charcoal-black, is the styling applied to tabbar. If anybpdy knoqws why this may be, please comment back here. – TroelsKn
I would like to change this mode’s behaviour. I want to show only the most N recent files for each mode, which get re-ordered each time i change buffer. Would it be possible to do this? Can anyone tell me which functions should i modify? I’ve been reading tabbar.el but i am not suere where to start with.
Thanks! – Matiyam
You could follow the defadvice defined in “Add a buffer modification state indicator in the label”, and instead of adding a + sign to mark modified buffers, you could sort the list according to the buffer’s last visit time. I don’t know if the last visit time is stored somewhere. If not, add a variable to each buffer and hook a function that updates that variable with the current time to the hook that’s executed when one leaves a buffer (couldn’t find what hook it is). – js
Is there a way to stop emacs from splitting the window when opening multiple files from the command line? (i.e. “emacs file1 file2”) The tabbar makes the window splitting unnecessary, but there doesn’t seem to be any command line arguments to stop this from happening. Thanks in advance.
You could put something like (delete-other-windows) at the end of your .emacs or alternatively use something like: “emacs file1 file2 --eval ‘(delete-other-windows)’” Its a bit of a hack I agree, and could cause problems in odd situations but it should do the trick.
(add-hook ‘window-setup-hook ‘delete-other-windows) – from http://stackoverflow.com/questions/1144729/how-do-i-prevent-emacs-from-horizontally-splitting-the-screen-when-opening-multip
Matiyam – sounds like swbuff/swbuff-y. It’s a different interface, though, but I’ve used shift-left/right to switch buffers with swbuff for awhile. I didn’t like the re-ordering, which is what led me to tabbar…
For emacs 23.1 I have to changed above codes to get list all buffers as one group:
(setq tabbar-buffer-groups-function (lambda () (list "All")))
The function expects one argument; the buffer name. Here is how the example above looks with a regular expression:
(lambda (a-buffer-name) (cond ((string-match "^\\*.+\\*$" a-buffer-name) '("Emacs")) (t '("Rest"))))
– Grant Rettke
Grant, should the above work in xemacs? I cannot get xemacs to force all files to a single buffer with multiple tabs. – Russell
Aquamacs tabbar work with standard emacs.Just check it out. – Emmett
Has anyone else managed to get the aquamacs tabbar mode working with gnu emacs, and could provide further instructions? Thanks - Josh
Aquamacs tabbar and even the fork doesn’t work with the recent bzr emacs. Need help! – Emmett
Tabbar is very nice, but I’ve noticed that at least on GNU Emacs v24.2.1, but when combined with setting variable
10 and/or variable
3, it breaks function
next-line. That is, when I’m pressing the down key, and the cursor reaches the bottom of the window, pressing the down key again causes a sort of weird, slow, jerky scrolling down, which is not completely but almost broken. --TeemuLeisti 2012-10-17
As teemuLeisti said, I found this bug,too. When I use tabbar mode, the behavior of scrolling down becomes very weird. Two or three presses of Arrow-Down are needed to move just one line. This makes Emacs difficult to use. – Qihao 2014-01-07
This seems no longer to be a problem when using Tabbar with Emacs v25.2.2. --TeemuLeisti 2018-06-06