diff options
author | Tom Willemsen | 2011-02-21 00:27:43 +0100 |
---|---|---|
committer | Tom Willemsen | 2011-02-21 00:27:43 +0100 |
commit | 62f897fdf5988840ee5538e30fbd3c7bcb715735 (patch) | |
tree | 4b389e2de2919676e4fe1c7f7716d33c94ecb7db /emacs.d/elisp/tabbar.el | |
parent | 294a0eef5da437bb6840acfd831e6d85b988bd60 (diff) | |
download | dotfiles-62f897fdf5988840ee5538e30fbd3c7bcb715735.tar.gz dotfiles-62f897fdf5988840ee5538e30fbd3c7bcb715735.zip |
New .emacs style
* .emacs only loops through .emacs.d and runs the *.el files there.
* files are automatically compiled before loading. But only if it
hasn't already been compiled.
* all emacs el files, save for the startup scripts, have been moved
to .emacs.d/elisp
Diffstat (limited to 'emacs.d/elisp/tabbar.el')
-rw-r--r-- | emacs.d/elisp/tabbar.el | 1932 |
1 files changed, 1932 insertions, 0 deletions
diff --git a/emacs.d/elisp/tabbar.el b/emacs.d/elisp/tabbar.el new file mode 100644 index 0000000..09db712 --- /dev/null +++ b/emacs.d/elisp/tabbar.el @@ -0,0 +1,1932 @@ +;;; Tabbar.el --- Display a tab bar in the header line + +;; Copyright (C) 2003, 2004, 2005 David Ponce + +;; Author: David Ponce <david@dponce.com> +;; Maintainer: David Ponce <david@dponce.com> +;; Created: 25 February 2003 +;; Keywords: convenience +;; Revision: $Id: tabbar.el,v 1.69 2006/06/08 08:27:39 ponced Exp $ + +(defconst tabbar-version "2.0") + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth +;; Floor, Boston, MA 02110-1301, USA. + +;;; Commentary: +;; +;; This library provides the Tabbar global minor mode to display a tab +;; bar in the header line of Emacs 21 and later versions. You can use +;; the mouse to click on a tab and select it. Also, three buttons are +;; displayed on the left side of the tab bar in this order: the +;; "home", "scroll left", and "scroll right" buttons. The "home" +;; button is a general purpose button used to change something on the +;; tab bar. The scroll left and scroll right buttons are used to +;; scroll tabs horizontally. Tabs can be divided up into groups to +;; maintain several sets of tabs at the same time (see also the +;; chapter "Core" below for more details on tab grouping). Only one +;; group is displayed on the tab bar, and the "home" button, for +;; example, can be used to navigate through the different groups, to +;; show different tab bars. +;; +;; In a graphic environment, using the mouse is probably the preferred +;; way to work with the tab bar. However, you can also use the tab +;; bar when Emacs is running on a terminal, so it is possible to use +;; commands to press special buttons, or to navigate cyclically +;; through tabs. +;; +;; These commands, and default keyboard shortcuts, are provided: +;; +;; `tabbar-mode' +;; Toggle the Tabbar global minor mode. When enabled a tab bar is +;; displayed in the header line. +;; +;; `tabbar-local-mode' (C-c <C-f10>) +;; Toggle the Tabbar-Local minor mode. Provided the global minor +;; mode is turned on, the tab bar becomes local in the current +;; buffer when the local minor mode is enabled. This permits to +;; see the tab bar in a buffer where the header line is already +;; used by another mode (like `Info-mode' for example). +;; +;; `tabbar-mwheel-mode' +;; Toggle the Tabbar-Mwheel global minor mode. When enabled you +;; can use the mouse wheel to navigate through tabs of groups. +;; +;; `tabbar-press-home' (C-c <C-home>) +;; `tabbar-press-scroll-left' (C-c <C-prior>) +;; `tabbar-press-scroll-right' (C-c <C-next>) +;; Simulate a mouse-1 click on respectively the "home", "scroll +;; left", and "scroll right" buttons. A numeric prefix argument +;; value of 2, or 3, respectively simulates a mouse-2, or mouse-3 +;; click. +;; +;; `tabbar-backward' (C-c <C-left>) +;; `tabbar-forward' (C-c <C-right>) +;; Are the basic commands to navigate cyclically through tabs or +;; groups of tabs. The cycle is controlled by the +;; `tabbar-cycle-scope' option. The default is to navigate +;; through all tabs across all existing groups of tabs. You can +;; change the default behavior to navigate only through the tabs +;; visible on the tab bar, or through groups of tabs only. Or use +;; the more specialized commands below. +;; +;; `tabbar-backward-tab' +;; `tabbar-forward-tab' +;; Navigate through the tabs visible on the tab bar. +;; +;; `tabbar-backward-group' (C-c <C-up>) +;; `tabbar-forward-group' (C-c <C-down>) +;; Navigate through existing groups of tabs. +;; +;; +;; Core +;; ---- +;; +;; The content of the tab bar is represented by an internal data +;; structure: a tab set. A tab set is a collection (group) of tabs, +;; identified by an unique name. In a tab set, at any time, one and +;; only one tab is designated as selected within the tab set. +;; +;; A tab is a simple data structure giving the value of the tab, and a +;; reference to its tab set container. A tab value can be any Lisp +;; object. Each tab object is guaranteed to be unique. +;; +;; A tab set is displayed on the tab bar through a "view" defined by +;; the index of the leftmost tab shown. Thus, it is possible to +;; scroll the tab bar horizontally by changing the start index of the +;; tab set view. +;; +;; The visual representation of a tab bar is a list of valid +;; `header-line-format' template elements, one for each special +;; button, and for each tab found into a tab set "view". When the +;; visual representation of a tab is required, the function specified +;; in the variable `tabbar-tab-label-function' is called to obtain it. +;; The visual representation of a special button is obtained by +;; calling the function specified in `tabbar-button-label-function', +;; which is passed a button name among `home', `scroll-left', or +;; `scroll-right'. There are also options and faces to customize the +;; appearance of buttons and tabs (see the code for more details). +;; +;; When the mouse is over a tab, the function specified in +;; `tabbar-help-on-tab-function' is called, which is passed the tab +;; and should return a help string to display. When a tab is +;; selected, the function specified in `tabbar-select-tab-function' is +;; called, which is passed the tab and the event received. +;; +;; Similarly, to control the behavior of the special buttons, the +;; following variables are available, for respectively the `home', +;; `scroll-left' and `scroll-right' value of `<button>': +;; +;; `tabbar-<button>-function' +;; Function called when <button> is selected. The function is +;; passed the mouse event received. +;; +;; `tabbar-<button>-help-function' +;; Function called with no arguments to obtain a help string +;; displayed when the mouse is over <button>. +;; +;; To increase performance, each tab set automatically maintains its +;; visual representation in a cache. As far as possible, the cache is +;; used to display the tab set, and refreshed only when necessary. +;; +;; Several tab sets can be maintained at the same time. Only one is +;; displayed on the tab bar, it is obtained by calling the function +;; specified in the variable `tabbar-current-tabset-function'. +;; +;; A special tab set is maintained, that contains the list of the +;; currently selected tabs in the existing tab sets. This tab set is +;; useful to show the existing tab sets in a tab bar, and switch +;; between them easily. The function `tabbar-get-tabsets-tabset' +;; returns this special tab set. +;; +;; +;; Buffer tabs +;; ----------- +;; +;; The default tab bar implementation provided displays buffers in +;; dedicated tabs. Selecting a tab, switch (mouse-1), or pop +;; (mouse-2), to the buffer it contains. +;; +;; The list of buffers put in tabs is provided by the function +;; specified in the variable `tabbar-buffer-list-function'. The +;; default function: `tabbar-buffer-list', excludes buffers whose name +;; starts with a space, when they are not visiting a file. +;; +;; Buffers are organized in groups, each one represented by a tab set. +;; A buffer can have no group, or belong to more than one group. The +;; function specified by the variable `tabbar-buffer-groups-function' +;; is called for each buffer to obtain the groups it belongs to. The +;; default function provided: `tabbar-buffer-groups' organizes buffers +;; depending on their major mode (see that function for details). +;; +;; The "home" button toggles display of buffer groups on the tab bar, +;; allowing to easily show another buffer group by clicking on the +;; associated tab. +;; +;; Known problems: +;; +;; Bug item #858306 at <http://sf.net/tracker/?group_id=79309>: +;; tabbar-mode crashes GNU Emacs 21.3 on MS-Windows 98/95. +;; + +;;; History: +;; + +;;; Code: + +;;; Options +;; +(defgroup tabbar nil + "Display a tab bar in the header line." + :group 'convenience) + +(defcustom tabbar-cycle-scope nil + "*Specify the scope of cyclic navigation through tabs. +The following scopes are possible: + +- `tabs' + Navigate through visible tabs only. +- `groups' + Navigate through tab groups only. +- default + Navigate through visible tabs, then through tab groups." + :group 'tabbar + :type '(choice :tag "Cycle through..." + (const :tag "Visible Tabs Only" tabs) + (const :tag "Tab Groups Only" groups) + (const :tag "Visible Tabs then Tab Groups" nil))) + +(defcustom tabbar-auto-scroll-flag t + "*Non-nil means to automatically scroll the tab bar. +That is, when a tab is selected outside of the tab bar visible area, +the tab bar is scrolled horizontally so the selected tab becomes +visible." + :group 'tabbar + :type 'boolean) + +(defvar tabbar-inhibit-functions '(tabbar-default-inhibit-function) + "List of functions to be called before displaying the tab bar. +Those functions are called one by one, with no arguments, until one of +them returns a non-nil value, and thus, prevents to display the tab +bar.") + +(defvar tabbar-current-tabset-function nil + "Function called with no argument to obtain the current tab set. +This is the tab set displayed on the tab bar.") + +(defvar tabbar-tab-label-function nil + "Function that obtains a tab label displayed on the tab bar. +The function is passed a tab and should return a string.") + +(defvar tabbar-select-tab-function nil + "Function that select a tab. +The function is passed a mouse event and a tab, and should make it the +selected tab.") + +(defvar tabbar-help-on-tab-function nil + "Function to obtain a help string for a tab. +The help string is displayed when the mouse is onto the button. The +function is passed the tab and should return a help string or nil for +none.") + +(defvar tabbar-button-label-function nil + "Function that obtains a button label displayed on the tab bar. +The function is passed a button name should return a propertized +string to display.") + +(defvar tabbar-home-function nil + "Function called when clicking on the tab bar home button. +The function is passed the mouse event received.") + +(defvar tabbar-home-help-function nil + "Function to obtain a help string for the tab bar home button. +The help string is displayed when the mouse is onto the button. +The function is called with no arguments.") + +(defvar tabbar-scroll-left-function 'tabbar-scroll-left + "Function that scrolls tabs on left. +The function is passed the mouse event received when clicking on the +scroll left button. It should scroll the current tab set.") + +(defvar tabbar-scroll-left-help-function 'tabbar-scroll-left-help + "Function to obtain a help string for the scroll left button. +The help string is displayed when the mouse is onto the button. +The function is called with no arguments.") + +(defvar tabbar-scroll-right-function 'tabbar-scroll-right + "Function that scrolls tabs on right. +The function is passed the mouse event received when clicking on the +scroll right button. It should scroll the current tab set.") + +(defvar tabbar-scroll-right-help-function 'tabbar-scroll-right-help + "Function to obtain a help string for the scroll right button. +The help string is displayed when the mouse is onto the button. +The function is called with no arguments.") + +;;; Misc. +;; +(eval-and-compile + (defalias 'tabbar-display-update + (if (fboundp 'force-window-update) + #'(lambda () (force-window-update (selected-window))) + 'force-mode-line-update))) + +(defsubst tabbar-click-p (event) + "Return non-nil if EVENT is a mouse click event." + (memq 'click (event-modifiers event))) + +(defun tabbar-shorten (str width) + "Return a shortened string from STR that fits in the given display WIDTH. +WIDTH is specified in terms of character display width in the current +buffer; see also `char-width'. If STR display width is greater than +WIDTH, STR is truncated and an ellipsis string \"...\" is inserted at +end or in the middle of the returned string, depending on available +room." + (let* ((n (length str)) + (sw (string-width str)) + (el "...") + (ew (string-width el)) + (w 0) + (i 0)) + (cond + ;; STR fit in WIDTH, return it. + ((<= sw width) + str) + ;; There isn't enough room for the ellipsis, STR is just + ;; truncated to fit in WIDTH. + ((<= width ew) + (while (< w width) + (setq w (+ w (char-width (aref str i))) + i (1+ i))) + (substring str 0 i)) + ;; There isn't enough room to insert the ellipsis in the middle + ;; of the truncated string, so put the ellipsis at end. + ((zerop (setq sw (/ (- width ew) 2))) + (setq width (- width ew)) + (while (< w width) + (setq w (+ w (char-width (aref str i))) + i (1+ i))) + (concat (substring str 0 i) el)) + ;; Put the ellipsis in the middle of the truncated string. + (t + (while (< w sw) + (setq w (+ w (char-width (aref str i))) + i (1+ i))) + (setq w (+ w ew)) + (while (< w width) + (setq n (1- n) + w (+ w (char-width (aref str n))))) + (concat (substring str 0 i) el (substring str n))) + ))) + +;;; Tab and tab set +;; +(defsubst tabbar-make-tab (object tabset) + "Return a new tab with value OBJECT. +TABSET is the tab set the tab belongs to." + (cons object tabset)) + +(defsubst tabbar-tab-value (tab) + "Return the value of tab TAB." + (car tab)) + +(defsubst tabbar-tab-tabset (tab) + "Return the tab set TAB belongs to." + (cdr tab)) + +(defvar tabbar-tabsets nil + "The tab sets store.") + +(defvar tabbar-tabsets-tabset nil + "The special tab set of existing tab sets.") + +(defvar tabbar-current-tabset nil + "The tab set currently displayed on the tab bar.") +(make-variable-buffer-local 'tabbar-current-tabset) + +(defvar tabbar-init-hook nil + "Hook run after tab bar data has been initialized. +You should use this hook to initialize dependent data.") + +(defsubst tabbar-init-tabsets-store () + "Initialize the tab set store." + (setq tabbar-tabsets (make-vector 31 0) + tabbar-tabsets-tabset (make-symbol "tabbar-tabsets-tabset")) + (put tabbar-tabsets-tabset 'start 0) + (run-hooks 'tabbar-init-hook)) + +(defvar tabbar-quit-hook nil + "Hook run after tab bar data has been freed. +You should use this hook to reset dependent data.") + +(defsubst tabbar-free-tabsets-store () + "Free the tab set store." + (setq tabbar-tabsets nil + tabbar-tabsets-tabset nil) + (run-hooks 'tabbar-quit-hook)) + +;; Define an "hygienic" function free of side effect between its local +;; variables and those of the callee. +(eval-and-compile + (defalias 'tabbar-map-tabsets + (let ((function (make-symbol "function")) + (result (make-symbol "result")) + (tabset (make-symbol "tabset"))) + `(lambda (,function) + "Apply FUNCTION to each tab set, and make a list of the results. +The result is a list just as long as the number of existing tab sets." + (let (,result) + (mapatoms + #'(lambda (,tabset) + (push (funcall ,function ,tabset) ,result)) + tabbar-tabsets) + ,result))))) + +(defun tabbar-make-tabset (name &rest objects) + "Make a new tab set whose name is the string NAME. +It is initialized with tabs build from the list of OBJECTS." + (let* ((tabset (intern name tabbar-tabsets)) + (tabs (mapcar #'(lambda (object) + (tabbar-make-tab object tabset)) + objects))) + (set tabset tabs) + (put tabset 'select (car tabs)) + (put tabset 'start 0) + tabset)) + +(defsubst tabbar-get-tabset (name) + "Return the tab set whose name is the string NAME. +Return nil if not found." + (intern-soft name tabbar-tabsets)) + +(defsubst tabbar-delete-tabset (tabset) + "Delete the tab set TABSET. +That is, remove it from the tab sets store." + (unintern tabset tabbar-tabsets)) + +(defsubst tabbar-tabs (tabset) + "Return the list of tabs in TABSET." + (symbol-value tabset)) + +(defsubst tabbar-tab-values (tabset) + "Return the list of tab values in TABSET." + (mapcar 'tabbar-tab-value (tabbar-tabs tabset))) + +(defsubst tabbar-get-tab (object tabset) + "Search for a tab with value OBJECT in TABSET. +Return the tab found, or nil if not found." + (assoc object (tabbar-tabs tabset))) + +(defsubst tabbar-member (tab tabset) + "Return non-nil if TAB is in TABSET." + (or (eq (tabbar-tab-tabset tab) tabset) + (memq tab (tabbar-tabs tabset)))) + +(defsubst tabbar-template (tabset) + "Return the cached visual representation of TABSET. +That is, a `header-line-format' template, or nil if the cache is +empty." + (get tabset 'template)) + +(defsubst tabbar-set-template (tabset template) + "Set the cached visual representation of TABSET to TEMPLATE. +TEMPLATE must be a valid `header-line-format' template, or nil to +cleanup the cache." + (put tabset 'template template)) + +(defsubst tabbar-selected-tab (tabset) + "Return the tab selected in TABSET." + (get tabset 'select)) + +(defsubst tabbar-selected-value (tabset) + "Return the value of the tab selected in TABSET." + (tabbar-tab-value (tabbar-selected-tab tabset))) + +(defsubst tabbar-selected-p (tab tabset) + "Return non-nil if TAB is the selected tab in TABSET." + (eq tab (tabbar-selected-tab tabset))) + +(defvar tabbar--track-selected nil) + +(defsubst tabbar-select-tab (tab tabset) + "Make TAB the selected tab in TABSET. +Does nothing if TAB is not found in TABSET. +Return TAB if selected, nil if not." + (when (tabbar-member tab tabset) + (unless (tabbar-selected-p tab tabset) + (tabbar-set-template tabset nil) + (setq tabbar--track-selected tabbar-auto-scroll-flag)) + (put tabset 'select tab))) + +(defsubst tabbar-select-tab-value (object tabset) + "Make the tab with value OBJECT, the selected tab in TABSET. +Does nothing if a tab with value OBJECT is not found in TABSET. +Return the tab selected, or nil if nothing was selected." + (tabbar-select-tab (tabbar-get-tab object tabset) tabset)) + +(defsubst tabbar-start (tabset) + "Return the index of the first visible tab in TABSET." + (get tabset 'start)) + +(defsubst tabbar-view (tabset) + "Return the list of visible tabs in TABSET. +That is, the sub-list of tabs starting at the first visible one." + (nthcdr (tabbar-start tabset) (tabbar-tabs tabset))) + +(defun tabbar-add-tab (tabset object &optional append) + "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 (if append + (append tabs (list tab)) + (cons tab tabs))))))) + +(defun tabbar-delete-tab (tab) + "Remove TAB from its tab set." + (let* ((tabset (tabbar-tab-tabset tab)) + (tabs (tabbar-tabs tabset)) + (sel (eq tab (tabbar-selected-tab tabset))) + (next (and sel (cdr (memq tab tabs))))) + (tabbar-set-template tabset nil) + (setq tabs (delq tab tabs)) + ;; When the selected tab is deleted, select the next one, if + ;; available, or the last one otherwise. + (and sel (tabbar-select-tab (car (or next (last tabs))) tabset)) + (set tabset tabs))) + +(defun tabbar-scroll (tabset count) + "Scroll the visible tabs in TABSET of COUNT units. +If COUNT is positive move the view on right. If COUNT is negative, +move the view on left." + (let ((start (min (max 0 (+ (tabbar-start tabset) count)) + (1- (length (tabbar-tabs tabset)))))) + (when (/= start (tabbar-start tabset)) + (tabbar-set-template tabset nil) + (put tabset 'start start)))) + +(defun tabbar-tab-next (tabset tab &optional before) + "Search in TABSET for the tab after TAB. +If optional argument BEFORE is non-nil, search for the tab before +TAB. Return the tab found, or nil otherwise." + (let* (last (tabs (tabbar-tabs tabset))) + (while (and tabs (not (eq tab (car tabs)))) + (setq last (car tabs) + tabs (cdr tabs))) + (and tabs (if before last (nth 1 tabs))))) + +(defun tabbar-current-tabset (&optional update) + "Return the tab set currently displayed on the tab bar. +If optional argument UPDATE is non-nil, call the user defined function +`tabbar-current-tabset-function' to obtain it. Otherwise return the +current cached copy." + (and update tabbar-current-tabset-function + (setq tabbar-current-tabset + (funcall tabbar-current-tabset-function))) + tabbar-current-tabset) + +(defun tabbar-get-tabsets-tabset () + "Return the tab set of selected tabs in existing tab sets." + (set tabbar-tabsets-tabset (tabbar-map-tabsets 'tabbar-selected-tab)) + (tabbar-scroll tabbar-tabsets-tabset 0) + (tabbar-set-template tabbar-tabsets-tabset nil) + tabbar-tabsets-tabset) + +;;; Faces +;; +(defface tabbar-default + '( + ;;(((class color grayscale) (background light)) + ;; :inherit variable-pitch + ;; :height 0.8 + ;; :foreground "gray50" + ;; :background "grey75" + ;; ) + (((class color grayscale) (background dark)) + :inherit variable-pitch + :height 0.8 + :foreground "grey75" + :background "gray50" + ) + (((class mono) (background light)) + :inherit variable-pitch + :height 0.8 + :foreground "black" + :background "white" + ) + (((class mono) (background dark)) + :inherit variable-pitch + :height 0.8 + :foreground "white" + :background "black" + ) + (t + :inherit variable-pitch + :height 0.8 + :foreground "gray50" + :background "gray75" + )) + "Default face used in the tab bar." + :group 'tabbar) + +(defface tabbar-unselected + '((t + :inherit tabbar-default + :box (:line-width 1 :color "white" :style released-button) + )) + "Face used for unselected tabs." + :group 'tabbar) + +(defface tabbar-selected + '((t + :inherit tabbar-default + :box (:line-width 1 :color "white" :style pressed-button) + :foreground "blue" + )) + "Face used for the selected tab." + :group 'tabbar) + +(defface tabbar-highlight + '((t + :underline t + )) + "Face used to highlight a tab during mouse-overs." + :group 'tabbar) + +(defface tabbar-separator + '((t + :inherit tabbar-default + :height 0.1 + )) + "Face used for separators between tabs." + :group 'tabbar) + +(defface tabbar-button + '((t + :inherit tabbar-default + :box (:line-width 1 :color "white" :style released-button) + :foreground "dark red" + )) + "Face used for tab bar buttons." + :group 'tabbar) + +(defface tabbar-button-highlight + '((t + :inherit tabbar-default + )) + "Face used to highlight a button during mouse-overs." + :group 'tabbar) + +(defcustom tabbar-background-color nil + "*Background color of the tab bar. +By default, use the background color specified for the +`tabbar-default' face (or inherited from another face), or the +background color of the `default' face otherwise." + :group 'tabbar + :type '(choice (const :tag "Default" nil) + (color))) + +(defsubst tabbar-background-color () + "Return the background color of the tab bar." + (or tabbar-background-color + (let* ((face 'tabbar-default) + (color (face-background face))) + (while (null color) + (or (facep (setq face (face-attribute face :inherit))) + (setq face 'default)) + (setq color (face-background face))) + color))) + +;;; Buttons and separator look and feel +;; +(defconst tabbar-button-widget + '(cons + (cons :tag "Enabled" + (string) + (repeat :tag "Image" + :extra-offset 2 + (restricted-sexp :tag "Spec" + :match-alternatives (listp)))) + (cons :tag "Disabled" + (string) + (repeat :tag "Image" + :extra-offset 2 + (restricted-sexp :tag "Spec" + :match-alternatives (listp)))) + ) + "Widget for editing a tab bar button. +A button is specified as a pair (ENABLED-BUTTON . DISABLED-BUTTON), +where ENABLED-BUTTON and DISABLED-BUTTON specify the value used when +the button is respectively enabled and disabled. Each button value is +a pair (STRING . IMAGE) where STRING is a string value, and IMAGE a +list of image specifications. +If IMAGE is non-nil, try to use that image, else use STRING. +If only the ENABLED-BUTTON image is provided, a DISABLED-BUTTON image +is derived from it.") + +;;; Home button +;; +(defvar tabbar-home-button-value nil + "Value of the home button.") + +(defconst tabbar-home-button-enabled-image + '((:type pbm :data "\ +P2 13 13 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 +6 0 255 255 255 255 255 255 255 255 255 255 9 130 9 255 255 255 255 +255 255 255 255 255 255 26 130 26 255 255 255 255 255 255 255 0 9 26 +41 130 41 26 9 0 255 255 255 255 5 145 140 135 130 125 120 115 5 255 +255 255 255 0 9 26 41 130 41 26 9 0 255 255 255 255 255 255 255 26 130 +26 255 255 255 255 255 255 255 255 255 255 9 130 9 255 255 255 255 255 +255 255 255 255 255 0 6 0 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 +")) + "Default image for the enabled home button.") + +(defconst tabbar-home-button-disabled-image + '((:type pbm :data "\ +P2 13 13 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 0 0 1 2 3 2 1 0 0 255 255 255 255 0 132 128 123 119 114 110 +106 0 255 255 255 255 0 0 1 2 3 2 1 0 0 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 255 +")) + "Default image for the disabled home button.") + +(defcustom tabbar-home-button + (cons (cons "[o]" tabbar-home-button-enabled-image) + (cons "[x]" tabbar-home-button-disabled-image)) + "The home button. +The variable `tabbar-button-widget' gives details on this widget." + :group 'tabbar + :type tabbar-button-widget + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of button value. + (setq tabbar-home-button-value nil))) + +;;; Scroll left button +;; +(defvar tabbar-scroll-left-button-value nil + "Value of the scroll left button.") + +(defconst tabbar-scroll-left-button-enabled-image + '((:type pbm :data "\ +P2 13 13 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 128 16 48 255 255 255 255 255 255 255 +255 144 28 86 128 0 255 255 255 255 255 255 160 44 92 159 135 113 0 +255 255 255 255 160 44 97 165 144 129 120 117 0 255 255 176 44 98 175 +174 146 127 126 127 128 0 255 255 0 160 184 156 143 136 134 135 137 +138 0 255 255 176 32 67 144 146 144 145 146 148 149 0 255 255 255 255 +160 42 75 140 154 158 159 160 0 255 255 255 255 255 255 160 40 74 154 +170 171 0 255 255 255 255 255 255 255 255 160 41 82 163 0 255 255 255 +255 255 255 255 255 255 255 160 32 48 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 +")) + "Default image for the enabled scroll left button. +A disabled button image will be automatically build from it.") + +(defcustom tabbar-scroll-left-button + (cons (cons " <" tabbar-scroll-left-button-enabled-image) + (cons " =" nil)) + "The scroll left button. +The variable `tabbar-button-widget' gives details on this widget." + :group 'tabbar + :type tabbar-button-widget + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of button value. + (setq tabbar-scroll-left-button-value nil))) + +;;; Scroll right button +;; +(defvar tabbar-scroll-right-button-value nil + "Value of the scroll right button.") + +(defconst tabbar-scroll-right-button-enabled-image + '((:type pbm :data "\ +P2 13 13 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +48 32 160 255 255 255 255 255 255 255 255 255 255 44 161 71 32 160 255 +255 255 255 255 255 255 255 36 157 163 145 62 32 160 255 255 255 255 +255 255 30 128 133 137 142 124 50 32 160 255 255 255 255 29 120 121 +124 126 126 124 105 42 32 176 255 255 31 126 127 128 128 128 128 126 +124 89 32 255 255 33 134 135 136 137 137 138 119 49 32 176 255 255 34 +143 144 145 146 128 54 32 160 255 255 255 255 36 152 153 134 57 32 160 +255 255 255 255 255 255 38 141 60 32 160 255 255 255 255 255 255 255 +255 48 32 160 255 255 255 255 255 255 255 255 255 255 255 255 255 255 +255 255 255 255 255 255 255 255 +")) + "Default image for the enabled scroll right button. +A disabled button image will be automatically build from it.") + +(defcustom tabbar-scroll-right-button + (cons (cons " >" tabbar-scroll-right-button-enabled-image) + (cons " =" nil)) + "The scroll right button. +The variable `tabbar-button-widget' gives details on this widget." + :group 'tabbar + :type tabbar-button-widget + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of button value. + (setq tabbar-scroll-right-button-value nil))) + +;;; Separator +;; +(defconst tabbar-separator-widget + '(cons (choice (string) + (number :tag "Space width" 0.2)) + (repeat :tag "Image" + :extra-offset 2 + (restricted-sexp :tag "Spec" + :match-alternatives (listp)))) + "Widget for editing a tab bar separator. +A separator is specified as a pair (STRING-OR-WIDTH . IMAGE) where +STRING-OR-WIDTH is a string value or a space width, and IMAGE a list +of image specifications. +If IMAGE is non-nil, try to use that image, else use STRING-OR-WIDTH. +The value (\"\"), or (0) hide separators.") + +(defvar tabbar-separator-value nil + "Value of the separator used between tabs.") + +(defcustom tabbar-separator (list 0.2) + "Separator used between tabs. +The variable `tabbar-separator-widget' gives details on this widget." + :group 'tabbar + :type tabbar-separator-widget + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of separator value. + (setq tabbar-separator-value nil))) + +;;; Images +;; +(defcustom tabbar-use-images t + "*Non-nil means to try to use images in tab bar. +That is for buttons and separators." + :group 'tabbar + :type 'boolean + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of all buttons and separator values. + (setq tabbar-separator-value nil + tabbar-home-button-value nil + tabbar-scroll-left-button-value nil + tabbar-scroll-right-button-value nil))) + +(defsubst tabbar-find-image (specs) + "Find an image, choosing one of a list of image specifications. +SPECS is a list of image specifications. See also `find-image'." + (when (and tabbar-use-images (display-images-p)) + (condition-case nil + (find-image specs) + (error nil)))) + +(defsubst tabbar-disable-image (image) + "From IMAGE, return a new image which looks disabled." + (setq image (copy-sequence image)) + (setcdr image (plist-put (cdr image) :conversion 'disabled)) + image) + +(defsubst tabbar-normalize-image (image &optional margin) + "Make IMAGE centered and transparent. +If optional MARGIN is non-nil, it must be a number of pixels to add as +an extra margin around the image." + (let ((plist (cdr image))) + (or (plist-get plist :ascent) + (setq plist (plist-put plist :ascent 'center))) + (or (plist-get plist :mask) + (setq plist (plist-put plist :mask '(heuristic t)))) + (or (not (natnump margin)) + (plist-get plist :margin) + (plist-put plist :margin margin)) + (setcdr image plist)) + image) + +;;; Button keymaps and callbacks +;; +(defun tabbar-make-mouse-keymap (callback) + "Return a keymap that call CALLBACK on mouse events. +CALLBACK is passed the received mouse event." + (let ((keymap (make-sparse-keymap))) + ;; Pass mouse-1, mouse-2 and mouse-3 events to CALLBACK. + (define-key keymap [header-line down-mouse-1] 'ignore) + (define-key keymap [header-line mouse-1] callback) + (define-key keymap [header-line down-mouse-2] 'ignore) + (define-key keymap [header-line mouse-2] callback) + (define-key keymap [header-line down-mouse-3] 'ignore) + (define-key keymap [header-line mouse-3] callback) + keymap)) + +(defsubst tabbar-make-mouse-event (&optional type) + "Return a mouse click event. +Optional argument TYPE is a mouse-click event or one of the +symbols `mouse-1', `mouse-2' or `mouse-3'. +The default is `mouse-1'." + (if (tabbar-click-p type) + type + (list (or (memq type '(mouse-2 mouse-3)) 'mouse-1) + (or (event-start nil) ;; Emacs 21.4 + (list (selected-window) (point) '(0 . 0) 0))))) + +;;; Buttons +;; +(defconst tabbar-default-button-keymap + (tabbar-make-mouse-keymap 'tabbar-select-button-callback) + "Default keymap of a button.") + +(defun tabbar-help-on-button (window object position) + "Return a help string or nil for none, for the button under the mouse. +WINDOW is the window in which the help was found (unused). +OBJECT is the button label under the mouse. +POSITION is the position in that label. +Call `tabbar-NAME-help-function' where NAME is the button name +associated to OBJECT." + (let* ((name (get-text-property position 'tabbar-button object)) + (funvar (and name + (intern-soft (format "tabbar-%s-help-function" + name))))) + (and (symbol-value funvar) + (funcall (symbol-value funvar))))) + +(defsubst tabbar-click-on-button (name &optional type) + "Handle a mouse click event on button NAME. +Call `tabbar-select-NAME-function' with the received, or simulated +mouse click event. +Optional argument TYPE is a mouse click event type (see the function +`tabbar-make-mouse-event' for details)." + (let ((funvar (intern-soft (format "tabbar-%s-function" name)))) + (when (symbol-value funvar) + (funcall (symbol-value funvar) (tabbar-make-mouse-event type)) + (tabbar-display-update)))) + +(defun tabbar-select-button-callback (event) + "Handle a mouse EVENT on a button. +Pass mouse click events on a button to `tabbar-click-on-button'." + (interactive "@e") + (when (tabbar-click-p event) + (let ((target (posn-string (event-start event)))) + (tabbar-click-on-button + (get-text-property (cdr target) 'tabbar-button (car target)) + event)))) + +(defun tabbar-make-button-keymap (name) + "Return a keymap to handle mouse click events on button NAME." + (if (fboundp 'posn-string) + tabbar-default-button-keymap + (let ((event (make-symbol "event"))) + (tabbar-make-mouse-keymap + `(lambda (,event) + (interactive "@e") + (and (tabbar-click-p ,event) + (tabbar-click-on-button ',name ,event))))))) + +;;; Button callbacks +;; +(defun tabbar-scroll-left (event) + "On mouse EVENT, scroll current tab set on left." + (when (eq (event-basic-type event) 'mouse-1) + (tabbar-scroll (tabbar-current-tabset) -1))) + +(defun tabbar-scroll-left-help () + "Help string shown when mouse is over the scroll left button." + "mouse-1: scroll tabs left.") + +(defun tabbar-scroll-right (event) + "On mouse EVENT, scroll current tab set on right." + (when (eq (event-basic-type event) 'mouse-1) + (tabbar-scroll (tabbar-current-tabset) 1))) + +(defun tabbar-scroll-right-help () + "Help string shown when mouse is over the scroll right button." + "mouse-1: scroll tabs right.") + +;;; Tabs +;; +(defconst tabbar-default-tab-keymap + (tabbar-make-mouse-keymap 'tabbar-select-tab-callback) + "Default keymap of a tab.") + +(defun tabbar-help-on-tab (window object position) + "Return a help string or nil for none, for the tab under the mouse. +WINDOW is the window in which the help was found (unused). +OBJECT is the tab label under the mouse. +POSITION is the position in that label. +Call `tabbar-help-on-tab-function' with the associated tab." + (when tabbar-help-on-tab-function + (let ((tab (get-text-property position 'tabbar-tab object))) + (funcall tabbar-help-on-tab-function tab)))) + +(defsubst tabbar-click-on-tab (tab &optional type) + "Handle a mouse click event on tab TAB. +Call `tabbar-select-tab-function' with the received, or simulated +mouse click event, and TAB. +Optional argument TYPE is a mouse click event type (see the function +`tabbar-make-mouse-event' for details)." + (when tabbar-select-tab-function + (funcall tabbar-select-tab-function + (tabbar-make-mouse-event type) tab) + (tabbar-display-update))) + +(defun tabbar-select-tab-callback (event) + "Handle a mouse EVENT on a tab. +Pass mouse click events on a tab to `tabbar-click-on-tab'." + (interactive "@e") + (when (tabbar-click-p event) + (let ((target (posn-string (event-start event)))) + (tabbar-click-on-tab + (get-text-property (cdr target) 'tabbar-tab (car target)) + event)))) + +(defun tabbar-make-tab-keymap (tab) + "Return a keymap to handle mouse click events on TAB." + (if (fboundp 'posn-string) + tabbar-default-tab-keymap + (let ((event (make-symbol "event"))) + (tabbar-make-mouse-keymap + `(lambda (,event) + (interactive "@e") + (and (tabbar-click-p ,event) + (tabbar-click-on-tab ',tab ,event))))))) + +;;; Tab bar construction +;; +(defun tabbar-button-label (name) + "Return a label for button NAME. +That is a pair (ENABLED . DISABLED), where ENABLED and DISABLED are +respectively the appearance of the button when enabled and disabled. +They are propertized strings which could display images, as specified +by the variable `tabbar-NAME-button'." + (let* ((btn (symbol-value + (intern-soft (format "tabbar-%s-button" name)))) + (on (tabbar-find-image (cdar btn))) + (off (and on (tabbar-find-image (cddr btn))))) + (when on + (tabbar-normalize-image on 1) + (if off + (tabbar-normalize-image off 1) + ;; If there is no disabled button image, derive one from the + ;; button enabled image. + (setq off (tabbar-disable-image on)))) + (cons + (propertize (or (caar btn) " ") 'display on) + (propertize (or (cadr btn) " ") 'display off)))) + +(defun tabbar-line-button (name) + "Return the display representation of button NAME. +That is, a propertized string used as an `header-line-format' template +element." + (let ((label (if tabbar-button-label-function + (funcall tabbar-button-label-function name) + (cons name name)))) + ;; Cache the display value of the enabled/disabled buttons in + ;; variables `tabbar-NAME-button-value'. + (set (intern (format "tabbar-%s-button-value" name)) + (cons + (propertize (car label) + 'tabbar-button name + 'face 'tabbar-button + 'mouse-face 'tabbar-button-highlight + 'pointer 'hand + 'local-map (tabbar-make-button-keymap name) + 'help-echo 'tabbar-help-on-button) + (propertize (cdr label) + 'face 'tabbar-button + 'pointer 'arrow))))) + +(defun tabbar-line-separator () + "Return the display representation of a tab bar separator. +That is, a propertized string used as an `header-line-format' template +element." + (let ((image (tabbar-find-image (cdr tabbar-separator)))) + ;; Cache the separator display value in variable + ;; `tabbar-separator-value'. + (setq tabbar-separator-value + (cond + (image + (propertize " " + 'face 'tabbar-separator + 'pointer 'arrow + 'display (tabbar-normalize-image image))) + ((numberp (car tabbar-separator)) + (propertize " " + 'face 'tabbar-separator + 'pointer 'arrow + 'display (list 'space + :width (car tabbar-separator)))) + ((propertize (or (car tabbar-separator) " ") + 'face 'tabbar-separator + 'pointer 'arrow)))) + )) + +(defsubst tabbar-line-buttons (tabset) + "Return a list of propertized strings for tab bar buttons. +TABSET is the tab set used to choose the appropriate buttons." + (list + (if tabbar-home-function + (car tabbar-home-button-value) + (cdr tabbar-home-button-value)) + (if (> (tabbar-start tabset) 0) + (car tabbar-scroll-left-button-value) + (cdr tabbar-scroll-left-button-value)) + (if (< (tabbar-start tabset) + (1- (length (tabbar-tabs tabset)))) + (car tabbar-scroll-right-button-value) + (cdr tabbar-scroll-right-button-value)) + tabbar-separator-value)) + +(defsubst tabbar-line-tab (tab) + "Return the display representation of tab TAB. +That is, a propertized string used as an `header-line-format' template +element. +Call `tabbar-tab-label-function' to obtain a label for TAB." + (concat (propertize + (if tabbar-tab-label-function + (funcall tabbar-tab-label-function tab) + tab) + 'tabbar-tab tab + 'local-map (tabbar-make-tab-keymap tab) + 'help-echo 'tabbar-help-on-tab + 'mouse-face 'tabbar-highlight + 'face (if (tabbar-selected-p tab (tabbar-current-tabset)) + 'tabbar-selected + 'tabbar-unselected) + 'pointer 'hand) + tabbar-separator-value)) + +(defun tabbar-line-format (tabset) + "Return the `header-line-format' value to display TABSET." + (let* ((sel (tabbar-selected-tab tabset)) + (tabs (tabbar-view tabset)) + (padcolor (tabbar-background-color)) + atsel elts) + ;; Initialize buttons and separator values. + (or tabbar-separator-value + (tabbar-line-separator)) + (or tabbar-home-button-value + (tabbar-line-button 'home)) + (or tabbar-scroll-left-button-value + (tabbar-line-button 'scroll-left)) + (or tabbar-scroll-right-button-value + (tabbar-line-button 'scroll-right)) + ;; Track the selected tab to ensure it is always visible. + (when tabbar--track-selected + (while (not (memq sel tabs)) + (tabbar-scroll tabset -1) + (setq tabs (tabbar-view tabset))) + (while (and tabs (not atsel)) + (setq elts (cons (tabbar-line-tab (car tabs)) elts) + atsel (eq (car tabs) sel) + tabs (cdr tabs))) + (setq elts (nreverse elts)) + ;; At this point the selected tab is the last elt in ELTS. + ;; Scroll TABSET and ELTS until the selected tab becomes + ;; visible. + (with-temp-buffer + (let ((truncate-partial-width-windows nil) + (inhibit-modification-hooks t) + deactivate-mark ;; Prevent deactivation of the mark! + start) + (setq truncate-lines nil + buffer-undo-list t) + (apply 'insert (tabbar-line-buttons tabset)) + (setq start (point)) + (while (and (cdr elts) ;; Always show the selected tab! + (progn + (delete-region start (point-max)) + (goto-char (point-max)) + (apply 'insert elts) + (goto-char (point-min)) + (> (vertical-motion 1) 0))) + (tabbar-scroll tabset 1) + (setq elts (cdr elts))))) + (setq elts (nreverse elts)) + (setq tabbar--track-selected nil)) + ;; Format remaining tabs. + (while tabs + (setq elts (cons (tabbar-line-tab (car tabs)) elts) + tabs (cdr tabs))) + ;; Cache and return the new tab bar. + (tabbar-set-template + tabset + (list (tabbar-line-buttons tabset) + (nreverse elts) + (propertize "%-" + 'face (list :background padcolor + :foreground padcolor) + 'pointer 'arrow))) + )) + +(defun tabbar-line () + "Return the header line templates that represent the tab bar. +Inhibit display of the tab bar in current window if any of the +`tabbar-inhibit-functions' return non-nil." + (cond + ((run-hook-with-args-until-success 'tabbar-inhibit-functions) + ;; Don't show the tab bar. + (setq header-line-format nil)) + ((tabbar-current-tabset t) + ;; When available, use a cached tab bar value, else recompute it. + (or (tabbar-template tabbar-current-tabset) + (tabbar-line-format tabbar-current-tabset))))) + +(defconst tabbar-header-line-format '(:eval (tabbar-line)) + "The tab bar header line format.") + +(defun tabbar-default-inhibit-function () + "Inhibit display of the tab bar in specified windows. +That is dedicated windows, and `checkdoc' status windows." + (or (window-dedicated-p (selected-window)) + (member (buffer-name) + (list " *Checkdoc Status*" + (if (boundp 'ispell-choices-buffer) + ispell-choices-buffer + "*Choices*"))))) + +;;; Cyclic navigation through tabs +;; +(defun tabbar-cycle (&optional backward type) + "Cycle to the next available tab. +The scope of the cyclic navigation through tabs is specified by the +option `tabbar-cycle-scope'. +If optional argument BACKWARD is non-nil, cycle to the previous tab +instead. +Optional argument TYPE is a mouse event type (see the function +`tabbar-make-mouse-event' for details)." + (let* ((tabset (tabbar-current-tabset t)) + (ttabset (tabbar-get-tabsets-tabset)) + ;; If navigation through groups is requested, and there is + ;; only one group, navigate through visible tabs. + (cycle (if (and (eq tabbar-cycle-scope 'groups) + (not (cdr (tabbar-tabs ttabset)))) + 'tabs + tabbar-cycle-scope)) + selected tab) + (when tabset + (setq selected (tabbar-selected-tab tabset)) + (cond + ;; Cycle through visible tabs only. + ((eq cycle 'tabs) + (setq tab (tabbar-tab-next tabset selected backward)) + ;; When there is no tab after/before the selected one, cycle + ;; to the first/last visible tab. + (unless tab + (setq tabset (tabbar-tabs tabset) + tab (car (if backward (last tabset) tabset)))) + ) + ;; Cycle through tab groups only. + ((eq cycle 'groups) + (setq tab (tabbar-tab-next ttabset selected backward)) + ;; When there is no group after/before the selected one, cycle + ;; to the first/last available group. + (unless tab + (setq tabset (tabbar-tabs ttabset) + tab (car (if backward (last tabset) tabset)))) + ) + (t + ;; Cycle through visible tabs then tab groups. + (setq tab (tabbar-tab-next tabset selected backward)) + ;; When there is no visible tab after/before the selected one, + ;; cycle to the next/previous available group. + (unless tab + (setq tab (tabbar-tab-next ttabset selected backward)) + ;; When there is no next/previous group, cycle to the + ;; first/last available group. + (unless tab + (setq tabset (tabbar-tabs ttabset) + tab (car (if backward (last tabset) tabset)))) + ;; Select the first/last visible tab of the new group. + (setq tabset (tabbar-tabs (tabbar-tab-tabset tab)) + tab (car (if backward (last tabset) tabset)))) + )) + (tabbar-click-on-tab tab type)))) + +;;;###autoload +(defun tabbar-backward () + "Select the previous available tab. +Depend on the setting of the option `tabbar-cycle-scope'." + (interactive) + (tabbar-cycle t)) + +;;;###autoload +(defun tabbar-forward () + "Select the next available tab. +Depend on the setting of the option `tabbar-cycle-scope'." + (interactive) + (tabbar-cycle)) + +;;;###autoload +(defun tabbar-backward-group () + "Go to selected tab in the previous available group." + (interactive) + (let ((tabbar-cycle-scope 'groups)) + (tabbar-cycle t))) + +;;;###autoload +(defun tabbar-forward-group () + "Go to selected tab in the next available group." + (interactive) + (let ((tabbar-cycle-scope 'groups)) + (tabbar-cycle))) + +;;;###autoload +(defun tabbar-backward-tab () + "Select the previous visible tab." + (interactive) + (let ((tabbar-cycle-scope 'tabs)) + (tabbar-cycle t))) + +;;;###autoload +(defun tabbar-forward-tab () + "Select the next visible tab." + (interactive) + (let ((tabbar-cycle-scope 'tabs)) + (tabbar-cycle))) + +;;; Button press commands +;; +(defsubst tabbar--mouse (number) + "Return a mouse button symbol from NUMBER. +That is mouse-2, or mouse-3 when NUMBER is respectively 2, or 3. +Return mouse-1 otherwise." + (cond ((eq number 2) 'mouse-2) + ((eq number 3) 'mouse-3) + ('mouse-1))) + +;;;###autoload +(defun tabbar-press-home (&optional arg) + "Press the tab bar home button. +That is, simulate a mouse click on that button. +A numeric prefix ARG value of 2, or 3, respectively simulates a +mouse-2, or mouse-3 click. The default is a mouse-1 click." + (interactive "p") + (tabbar-click-on-button 'home (tabbar--mouse arg))) + +;;;###autoload +(defun tabbar-press-scroll-left (&optional arg) + "Press the tab bar scroll-left button. +That is, simulate a mouse click on that button. +A numeric prefix ARG value of 2, or 3, respectively simulates a +mouse-2, or mouse-3 click. The default is a mouse-1 click." + (interactive "p") + (tabbar-click-on-button 'scroll-left (tabbar--mouse arg))) + +;;;###autoload +(defun tabbar-press-scroll-right (&optional arg) + "Press the tab bar scroll-right button. +That is, simulate a mouse click on that button. +A numeric prefix ARG value of 2, or 3, respectively simulates a +mouse-2, or mouse-3 click. The default is a mouse-1 click." + (interactive "p") + (tabbar-click-on-button 'scroll-right (tabbar--mouse arg))) + +;;; Mouse-wheel support +;; +(require 'mwheel) + +;;; Compatibility +;; +(defconst tabbar--mwheel-up-event + (symbol-value (if (boundp 'mouse-wheel-up-event) + 'mouse-wheel-up-event + 'mouse-wheel-up-button))) + +(defconst tabbar--mwheel-down-event + (symbol-value (if (boundp 'mouse-wheel-down-event) + 'mouse-wheel-down-event + 'mouse-wheel-down-button))) + +(defsubst tabbar--mwheel-key (event-type) + "Return a mouse wheel key symbol from EVENT-TYPE. +When EVENT-TYPE is a symbol return it. +When it is a button number, return symbol `mouse-<EVENT-TYPE>'." + (if (symbolp event-type) + event-type + (intern (format "mouse-%s" event-type)))) + +(defsubst tabbar--mwheel-up-p (event) + "Return non-nil if EVENT is a mouse-wheel up event." + (let ((x (event-basic-type event))) + (if (eq 'mouse-wheel x) + (< (car (cdr (cdr event))) 0) ;; Emacs 21.3 + ;; Emacs > 21.3 + (eq x tabbar--mwheel-up-event)))) + +;;; Basic commands +;; +;;;###autoload +(defun tabbar-mwheel-backward (event) + "Select the previous available tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-backward'." + (interactive "@e") + (tabbar-cycle t event)) + +;;;###autoload +(defun tabbar-mwheel-forward (event) + "Select the next available tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-forward'." + (interactive "@e") + (tabbar-cycle nil event)) + +;;;###autoload +(defun tabbar-mwheel-backward-group (event) + "Go to selected tab in the previous available group. +If there is only one group, select the previous visible tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-backward-group'." + (interactive "@e") + (let ((tabbar-cycle-scope 'groups)) + (tabbar-cycle t event))) + +;;;###autoload +(defun tabbar-mwheel-forward-group (event) + "Go to selected tab in the next available group. +If there is only one group, select the next visible tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-forward-group'." + (interactive "@e") + (let ((tabbar-cycle-scope 'groups)) + (tabbar-cycle nil event))) + +;;;###autoload +(defun tabbar-mwheel-backward-tab (event) + "Select the previous visible tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-backward-tab'." + (interactive "@e") + (let ((tabbar-cycle-scope 'tabs)) + (tabbar-cycle t event))) + +;;;###autoload +(defun tabbar-mwheel-forward-tab (event) + "Select the next visible tab. +EVENT is the mouse event that triggered this command. +Mouse-enabled equivalent of the command `tabbar-forward-tab'." + (interactive "@e") + (let ((tabbar-cycle-scope 'tabs)) + (tabbar-cycle nil event))) + +;;; Wrappers when there is only one generic mouse-wheel event +;; +;;;###autoload +(defun tabbar-mwheel-switch-tab (event) + "Select the next or previous tab according to EVENT." + (interactive "@e") + (if (tabbar--mwheel-up-p event) + (tabbar-mwheel-forward-tab event) + (tabbar-mwheel-backward-tab event))) + +;;;###autoload +(defun tabbar-mwheel-switch-group (event) + "Select the next or previous group of tabs according to EVENT." + (interactive "@e") + (if (tabbar--mwheel-up-p event) + (tabbar-mwheel-forward-group event) + (tabbar-mwheel-backward-group event))) + +;;; Minor modes +;; +(defsubst tabbar-mode-on-p () + "Return non-nil if Tabbar mode is on." + (eq (default-value 'header-line-format) + tabbar-header-line-format)) + +;;; Tabbar-Local mode +;; +(defvar tabbar--local-hlf nil) + +;;;###autoload +(define-minor-mode tabbar-local-mode + "Toggle local display of the tab bar. +With prefix argument ARG, turn on if positive, otherwise off. +Returns non-nil if the new state is enabled. +When turned on, if a local header line is shown, it is hidden to show +the tab bar. The tab bar is locally hidden otherwise. When turned +off, if a local header line is hidden or the tab bar is locally +hidden, it is shown again. Signal an error if Tabbar mode is off." + :group 'tabbar + :global nil + (unless (tabbar-mode-on-p) + (error "Tabbar mode must be enabled")) +;;; ON + (if tabbar-local-mode + (if (and (local-variable-p 'header-line-format) + header-line-format) + ;; A local header line exists, hide it to show the tab bar. + (progn + ;; Fail in case of an inconsistency because another local + ;; header line is already hidden. + (when (local-variable-p 'tabbar--local-hlf) + (error "Another local header line is already hidden")) + (set (make-local-variable 'tabbar--local-hlf) + header-line-format) + (kill-local-variable 'header-line-format)) + ;; Otherwise hide the tab bar in this buffer. + (setq header-line-format nil)) +;;; OFF + (if (local-variable-p 'tabbar--local-hlf) + ;; A local header line is hidden, show it again. + (progn + (setq header-line-format tabbar--local-hlf) + (kill-local-variable 'tabbar--local-hlf)) + ;; The tab bar is locally hidden, show it again. + (kill-local-variable 'header-line-format)))) + +;;; Tabbar mode +;; +(defvar tabbar-prefix-key [(control ?c)] + "The common prefix key used in Tabbar mode.") + +(defvar tabbar-prefix-map + (let ((km (make-sparse-keymap))) + (define-key km [(control home)] 'tabbar-press-home) + (define-key km [(control left)] 'tabbar-backward) + (define-key km [(control right)] 'tabbar-forward) + (define-key km [(control up)] 'tabbar-backward-group) + (define-key km [(control down)] 'tabbar-forward-group) + (define-key km [(control prior)] 'tabbar-press-scroll-left) + (define-key km [(control next)] 'tabbar-press-scroll-right) + (define-key km [(control f10)] 'tabbar-local-mode) + km) + "The key bindings provided in Tabbar mode.") + +(defvar tabbar-mode-map + (let ((km (make-sparse-keymap))) + (define-key km tabbar-prefix-key tabbar-prefix-map) + km) + "Keymap to use in Tabbar mode.") + +(defvar tabbar--global-hlf nil) + +;;;###autoload +(define-minor-mode tabbar-mode + "Toggle display of a tab bar in the header line. +With prefix argument ARG, turn on if positive, otherwise off. +Returns non-nil if the new state is enabled. + +\\{tabbar-mode-map}" + :group 'tabbar + :require 'tabbar + :global t + :keymap tabbar-mode-map + (if tabbar-mode +;;; ON + (unless (tabbar-mode-on-p) + ;; Save current default value of `header-line-format'. + (setq tabbar--global-hlf (default-value 'header-line-format)) + (tabbar-init-tabsets-store) + (setq-default header-line-format tabbar-header-line-format)) +;;; OFF + (when (tabbar-mode-on-p) + ;; Turn off Tabbar-Local mode globally. + (mapc #'(lambda (b) + (condition-case nil + (with-current-buffer b + (and tabbar-local-mode + (tabbar-local-mode -1))) + (error nil))) + (buffer-list)) + ;; Restore previous `header-line-format'. + (setq-default header-line-format tabbar--global-hlf) + (tabbar-free-tabsets-store)) + )) + +;;; Tabbar-Mwheel mode +;; +(defvar tabbar-mwheel-mode-map + (let ((km (make-sparse-keymap))) + (if (get 'mouse-wheel 'event-symbol-elements) + ;; Use one generic mouse wheel event + (define-key km [A-mouse-wheel] + 'tabbar-mwheel-switch-group) + ;; Use separate up/down mouse wheel events + (let ((up (tabbar--mwheel-key tabbar--mwheel-up-event)) + (down (tabbar--mwheel-key tabbar--mwheel-down-event))) + (define-key km `[header-line ,down] + 'tabbar-mwheel-backward-group) + (define-key km `[header-line ,up] + 'tabbar-mwheel-forward-group) + (define-key km `[header-line (control ,down)] + 'tabbar-mwheel-backward-tab) + (define-key km `[header-line (control ,up)] + 'tabbar-mwheel-forward-tab) + (define-key km `[header-line (shift ,down)] + 'tabbar-mwheel-backward) + (define-key km `[header-line (shift ,up)] + 'tabbar-mwheel-forward) + )) + km) + "Keymap to use in Tabbar-Mwheel mode.") + +;;;###autoload +(define-minor-mode tabbar-mwheel-mode + "Toggle use of the mouse wheel to navigate through tabs or groups. +With prefix argument ARG, turn on if positive, otherwise off. +Returns non-nil if the new state is enabled. + +\\{tabbar-mwheel-mode-map}" + :group 'tabbar + :require 'tabbar + :global t + :keymap tabbar-mwheel-mode-map + (when tabbar-mwheel-mode + (unless (and mouse-wheel-mode tabbar-mode) + (tabbar-mwheel-mode -1)))) + +(defun tabbar-mwheel-follow () + "Toggle Tabbar-Mwheel following Tabbar and Mouse-Wheel modes." + (tabbar-mwheel-mode (if (and mouse-wheel-mode tabbar-mode) 1 -1))) + +(add-hook 'tabbar-mode-hook 'tabbar-mwheel-follow) +(add-hook 'mouse-wheel-mode-hook 'tabbar-mwheel-follow) + +;;; Buffer tabs +;; +(defgroup tabbar-buffer nil + "Display buffers in the tab bar." + :group 'tabbar) + +(defcustom tabbar-buffer-home-button + (cons (cons "[+]" tabbar-home-button-enabled-image) + (cons "[-]" tabbar-home-button-disabled-image)) + "The home button displayed when showing buffer tabs. +The enabled button value is displayed when showing tabs for groups of +buffers, and the disabled button value is displayed when showing +buffer tabs. +The variable `tabbar-button-widget' gives details on this widget." + :group 'tabbar-buffer + :type tabbar-button-widget + :set '(lambda (variable value) + (custom-set-default variable value) + ;; Schedule refresh of button value. + (setq tabbar-home-button-value nil))) + +(defvar tabbar-buffer-list-function 'tabbar-buffer-list + "Function that returns the list of buffers to show in tabs. +That function is called with no arguments and must return a list of +buffers.") + +(defvar tabbar-buffer-groups-function 'tabbar-buffer-groups + "Function that gives the group names the current buffer belongs to. +It must return a list of group names, or nil if the buffer has no +group. Notice that it is better that a buffer belongs to one group.") + +(defun tabbar-buffer-list () + "Return the list of buffers to show in tabs. +Exclude buffers whose name starts with a space, when they are not +visiting a file. The current buffer is always included." + (delq nil + (mapcar #'(lambda (b) + (cond + ;; Always include the current buffer. + ((eq (current-buffer) b) b) + ((buffer-file-name b) b) + ((char-equal ?\ (aref (buffer-name b) 0)) nil) + ((buffer-live-p b) b))) + (buffer-list)))) + +(defun tabbar-buffer-mode-derived-p (mode parents) + "Return non-nil if MODE derives from a mode in PARENTS." + (let (derived) + (while (and (not derived) mode) + (if (memq mode parents) + (setq derived t) + (setq mode (get mode 'derived-mode-parent)))) + derived)) + +(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*")) + "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" + ) + (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)) + )))) + +;;; Group buffers in tab sets. +;; +(defvar tabbar--buffers nil) + +(defun tabbar-buffer-update-groups () + "Update tab sets from groups of existing buffers. +Return the the first group where the current buffer is." + (let ((bl (sort + (mapcar + #'(lambda (b) + (with-current-buffer b + (list (current-buffer) + (buffer-name) + (if tabbar-buffer-groups-function + (funcall tabbar-buffer-groups-function) + '("Common"))))) + (and tabbar-buffer-list-function + (funcall tabbar-buffer-list-function))) + #'(lambda (e1 e2) + (string-lessp (nth 1 e1) (nth 1 e2)))))) + ;; If the cache has changed, update the tab sets. + (unless (equal bl tabbar--buffers) + ;; Add new buffers, or update changed ones. + (dolist (e bl) + (dolist (g (nth 2 e)) + (let ((tabset (tabbar-get-tabset g))) + (if tabset + (unless (equal e (assq (car e) tabbar--buffers)) + ;; This is a new buffer, or a previously existing + ;; buffer that has been renamed, or moved to another + ;; group. Update the tab set, and the display. + (tabbar-add-tab tabset (car e) t) + (tabbar-set-template tabset nil)) + (tabbar-make-tabset g (car e)))))) + ;; Remove tabs for buffers not found in cache or moved to other + ;; groups, and remove empty tabsets. + (mapc 'tabbar-delete-tabset + (tabbar-map-tabsets + #'(lambda (tabset) + (dolist (tab (tabbar-tabs tabset)) + (let ((e (assq (tabbar-tab-value tab) bl))) + (or (and e (memq tabset + (mapcar 'tabbar-get-tabset + (nth 2 e)))) + (tabbar-delete-tab tab)))) + ;; Return empty tab sets + (unless (tabbar-tabs tabset) + tabset)))) + ;; The new cache becomes the current one. + (setq tabbar--buffers bl))) + ;; Return the first group the current buffer belongs to. + (car (nth 2 (assq (current-buffer) tabbar--buffers)))) + +;;; Tab bar callbacks +;; +(defvar tabbar--buffer-show-groups nil) + +(defsubst tabbar-buffer-show-groups (flag) + "Set display of tabs for groups of buffers to FLAG." + (setq tabbar--buffer-show-groups flag + ;; Redisplay the home button. + tabbar-home-button-value nil)) + +(defun tabbar-buffer-tabs () + "Return the buffers to display on the tab bar, in a tab set." + (let ((tabset (tabbar-get-tabset (tabbar-buffer-update-groups)))) + (tabbar-select-tab-value (current-buffer) tabset) + (when tabbar--buffer-show-groups + (setq tabset (tabbar-get-tabsets-tabset)) + (tabbar-select-tab-value (current-buffer) tabset)) + tabset)) + +(defun tabbar-buffer-button-label (name) + "Return a label for button NAME. +That is a pair (ENABLED . DISABLED), where ENABLED and DISABLED are +respectively the appearance of the button when enabled and disabled. +They are propertized strings which could display images, as specified +by the variable `tabbar-button-label'. +When NAME is 'home, return a different ENABLED button if showing tabs +or groups. Call the function `tabbar-button-label' otherwise." + (let ((lab (tabbar-button-label name))) + (when (eq name 'home) + (let* ((btn tabbar-buffer-home-button) + (on (tabbar-find-image (cdar btn))) + (off (tabbar-find-image (cddr btn)))) + ;; When `tabbar-buffer-home-button' does not provide a value, + ;; default to the enabled value of `tabbar-home-button'. + (if on + (tabbar-normalize-image on 1) + (setq on (get-text-property 0 'display (car lab)))) + (if off + (tabbar-normalize-image off 1) + (setq off (get-text-property 0 'display (car lab)))) + (setcar lab + (if tabbar--buffer-show-groups + (propertize (or (caar btn) (car lab)) 'display on) + (propertize (or (cadr btn) (car lab)) 'display off))) + )) + lab)) + +(defun tabbar-buffer-tab-label (tab) + "Return a label for TAB. +That is, a string used to represent it on the tab bar." + (let ((label (if tabbar--buffer-show-groups + (format "[%s]" (tabbar-tab-tabset tab)) + (format "%s" (tabbar-tab-value tab))))) + ;; Unless the tab bar auto scrolls to keep the selected tab + ;; visible, shorten the tab label to keep as many tabs as possible + ;; in the visible area of the tab bar. + (if tabbar-auto-scroll-flag + label + (tabbar-shorten + label (max 1 (/ (window-width) + (length (tabbar-view + (tabbar-current-tabset))))))))) + +(defun tabbar-buffer-help-on-tab (tab) + "Return the help string shown when mouse is onto TAB." + (if tabbar--buffer-show-groups + (let* ((tabset (tabbar-tab-tabset tab)) + (tab (tabbar-selected-tab tabset))) + (format "mouse-1: switch to buffer %S in group [%s]" + (buffer-name (tabbar-tab-value tab)) tabset)) + (format "mouse-1: switch to buffer %S\n\ +mouse-2: pop to buffer, mouse-3: delete other windows" + (buffer-name (tabbar-tab-value tab))) + )) + +(defun tabbar-buffer-select-tab (event tab) + "On mouse EVENT, select TAB." + (let ((mouse-button (event-basic-type event)) + (buffer (tabbar-tab-value tab))) + (cond + ((eq mouse-button 'mouse-2) + (pop-to-buffer buffer t)) + ((eq mouse-button 'mouse-3) + (delete-other-windows)) + (t + (switch-to-buffer buffer))) + ;; Don't show groups. + (tabbar-buffer-show-groups nil) + )) + +(defun tabbar-buffer-click-on-home (event) + "Handle a mouse click EVENT on the tab bar home button. +mouse-1, toggle the display of tabs for groups of buffers. +mouse-3, close the current buffer." + (let ((mouse-button (event-basic-type event))) + (cond + ((eq mouse-button 'mouse-1) + (tabbar-buffer-show-groups (not tabbar--buffer-show-groups))) + ((eq mouse-button 'mouse-3) + (kill-buffer nil)) + ))) + +(defun tabbar-buffer-help-on-home () + "Return the help string shown when mouse is onto the toggle button." + (concat + (if tabbar--buffer-show-groups + "mouse-1: show buffers in selected group" + "mouse-1: show groups of buffers") + ", mouse-3: close current buffer")) + +(defun tabbar-buffer-track-killed () + "Hook run just before actually killing a buffer. +In Tabbar mode, try to switch to a buffer in the current tab bar, +after the current buffer has been killed. Try first the buffer in tab +after the current one, then the buffer in tab before. On success, put +the sibling buffer in front of the buffer list, so it will be selected +first." + (and (eq header-line-format tabbar-header-line-format) + (eq tabbar-current-tabset-function 'tabbar-buffer-tabs) + (eq (current-buffer) (window-buffer (selected-window))) + (let ((bl (tabbar-tab-values (tabbar-current-tabset))) + (b (current-buffer)) + found sibling) + (while (and bl (not found)) + (if (eq b (car bl)) + (setq found t) + (setq sibling (car bl))) + (setq bl (cdr bl))) + (when (and (setq sibling (or (car bl) sibling)) + (buffer-live-p sibling)) + ;; Move sibling buffer in front of the buffer list. + (save-current-buffer + (switch-to-buffer sibling)))))) + +;;; Tab bar buffer setup +;; +(defun tabbar-buffer-init () + "Initialize tab bar buffer data. +Run as `tabbar-init-hook'." + (setq tabbar--buffers nil + tabbar--buffer-show-groups nil + tabbar-current-tabset-function 'tabbar-buffer-tabs + tabbar-tab-label-function 'tabbar-buffer-tab-label + tabbar-select-tab-function 'tabbar-buffer-select-tab + tabbar-help-on-tab-function 'tabbar-buffer-help-on-tab + tabbar-button-label-function 'tabbar-buffer-button-label + tabbar-home-function 'tabbar-buffer-click-on-home + tabbar-home-help-function 'tabbar-buffer-help-on-home + ) + (add-hook 'kill-buffer-hook 'tabbar-buffer-track-killed)) + +(defun tabbar-buffer-quit () + "Quit tab bar buffer. +Run as `tabbar-quit-hook'." + (setq tabbar--buffers nil + tabbar--buffer-show-groups nil + tabbar-current-tabset-function nil + tabbar-tab-label-function nil + tabbar-select-tab-function nil + tabbar-help-on-tab-function nil + tabbar-button-label-function nil + tabbar-home-function nil + tabbar-home-help-function nil + ) + (remove-hook 'kill-buffer-hook 'tabbar-buffer-track-killed)) + +(add-hook 'tabbar-init-hook 'tabbar-buffer-init) +(add-hook 'tabbar-quit-hook 'tabbar-buffer-quit) + +(provide 'tabbar) + +(run-hooks 'tabbar-load-hook) + +;;; tabbar.el ends here |