TabBarMode

Last edit

Summary: Part of name shuffle

Changed:

< [new:PaulTipper:2009-06-05 21:51 UTC]

to

> [new:TabithaTipper:2009-06-05 21:51 UTC]

Changed:

< : - PaulTipper

to

> : - TabithaTipper


Description

Tabbar is an emacs minor mode that displays a tab bar at the top, similar to the idea of web browser’s tabs.

Download

The original code is at: http://sourceforge.net/projects/emhacks/ Note: the version on Sourceforge is really old.

Check if Debian has newer version in emacs-goodies-el package.

Introduction

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 M-x tabbar-local-mode.

Documentation

The tabbar mode does not have documentation except in the comments of the source file tabbar.el.

Customization

Tabbar mode has many customizable options, and it supports emacs customization system. To customize, type M-x customize-group [RET] tabbar [RET].

Tab groups

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.

--mcandre

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 (setq tabbar-buffer-groups-function 'your-function-name).)

 (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/.

Sort tabbar buffers by name

 (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))))))))))

Tab navigation made easy

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)

Windows-style behaviour and key bindings

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)

Pop-up tool-bar Solution 2 tabbar-ruler

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 tabbar-ruler.el.

Hide for special buffers

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 (b) (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))))

Disabling locally, depending on the MajorMode

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 label

 ;; 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)

Show buffers in the current directory

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.

Autohide

 (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))))
 (autohide-tabbar)

Speed up by not using images

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)

Integrating tabbar with eproject.el

Here is one approach for integrating tabbar groups with eproject.el:

  (defun my-tabbar-buffer-groups ()
    "Return the list of group names BUFFER belongs to.
    Return only one group for each buffer."
    (let ((projname (my-buffer-eproject-name)))
      (cond
       ((or (get-buffer-process (current-buffer)) (memq major-mode '(comint-mode compilation-mode))) '("Term"))
       ((string-equal "*" (substring (buffer-name) 0 1)) '("Misc"))
       (projname (list projname))
       ((memq major-mode '(emacs-lisp-mode python-mode emacs-lisp-mode c-mode c++-mode makefile-mode lua-mode vala-mode)) '("Coding"))
       ((memq major-mode '(javascript-mode js-mode nxhtml-mode html-mode css-mode)) '("HTML"))
       ((memq major-mode '(org-mode calendar-mode diary-mode)) '("Org"))
       ((memq major-mode '(dired-mode)) '("Dir"))
       (t '("Main")))))
  (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]...

Questions and Answers


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.

I use 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…).

- Alan

I don’t believe its in GNU Emacs CVS, a quick search : and a look in my CVS copy of emacs seems to confirm this (so I think the page is wrong above). Aquamacs has it, as does emhacks. They both claim to be tabbar 2.0 from a quick look, I’ve got tabbar 1.3 and it seems okay with GNU Emacs 23.0.60.1 :
- TabithaTipper

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.

- Kristi

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 (b)     ;; b added here. I don't know why
            (list "All")))

- dixiecko

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 scroll-conservatively to 10 and/or variable scroll-margin to 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


CategoryModes