myhome/.emacs.d/plugins/awesome-tab.el

1925 lines
72 KiB
EmacsLisp
Raw Permalink Normal View History

;;; awesome-tab.el --- Provide an out of box configuration to use tab in Emacs.
;; Filename: awesome-tab.el
;; Description: Provide an out of box configuration to use awesome-tab in Emacs.
;; Author: Andy Stewart <lazycat.manatee@gmail.com>
;; Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
;; Copyright (C) 2018, Andy Stewart, all rights reserved.
;; Created: 2018-09-17 22:14:34
;; Version: 7.3
;; Last-Updated: 2020-04-10 12:36:52
;; By: Andy Stewart
;; URL: http://www.emacswiki.org/emacs/download/awesome-tab.el
;; Keywords:
;; Compatibility: GNU Emacs 27.0.50
;;
;; Features that might be required by this library:
;;
;; `cl-lib' `color' `which-func'
;;
;;; This file is NOT part of GNU Emacs
;;; License
;;
;; 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 3, 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:
;;
;; Provide an out of box configuration to use tab in Emacs.
;;
;;; Installation:
;;
;; Put awesome-tab.el to your load-path.
;; The load-path is usually ~/elisp/.
;; It's set in your ~/.emacs like this:
;; (add-to-list 'load-path (expand-file-name "~/elisp"))
;;
;; And the following to your ~/.emacs startup file.
;;
;; (require 'awesome-tab)
;; (awesome-tab-mode t)
;;
;; No need more.
;;
;; You can use below commands for you need:
;;
;; `awesome-tab-switch-group'
;; `awesome-tab-select-beg-tab'
;; `awesome-tab-select-end-tab'
;; `awesome-tab-forward-tab-other-window'
;; `awesome-tab-backward-tab-other-window'
;; `awesome-tab-kill-other-buffers-in-current-group'
;; `awesome-tab-kill-all-buffers-in-current-group'
;; `awesome-tab-kill-match-buffers-in-current-group'
;; `awesome-tab-keep-match-buffers-in-current-group'
;; `awesome-tab-move-current-tab-to-left'
;; `awesome-tab-move-current-tab-to-right'
;;
;; If you're helm fans, you need add below code in your helm config:
;;
;; (awesome-tab-build-helm-source)
;;
;;; Customize:
;;
;; `awesome-tab-cycle-scope'
;; `awesome-tab-label-fixed-length'
;; `awesome-tab-auto-scroll-flag'
;; `awesome-tab-common-group-name'
;; `awesometab-hide-tabs-hooks'
;; `awesome-tab-height'
;; `awesome-tab-display-sticky-function-name'
;; `awesome-tab-display-icon'
;; `awesome-tab-show-tab-index'
;;
;;; Change log:
;;
;; 2020/04/10
;; * Fix issue #81
;;
;; 2020/03/22
;; * Support EAF mode.
;; * Add options for customize active bar.
;;
;; 2020/03/21
;; * Remove unnecessary tab style and include active bar.
;;
;; 2020/02/13
;; * Add `awesome-tab-all-the-icons-is-load-p' option.
;; * Fix window-live-p error when click tab.
;;
;; 2020/01/13
;; * Add new option `awesome-tab-label-max-length'.
;;
;; 2019/12/22
;; * Add flycheck temp buffer in hide black list.
;;
;; 2019/10/10
;; * Fix issue #58 click tab select window of tab first.
;;
;; 2019/09/12
;; * Fix top-line above tab issue.
;;
;; 2019/08/13
;; * Add new option `awesome-tab-height'.
;;
;; 2019/08/03
;; * Adjust default value of `awesome-tab-ace-quit-keys'.
;;
;; 2019/08/02
;; * Refactroy `awesome-tab-ace-jump'.
;; * Refactory variable name.
;; * Remove `awesome-tab-adjust-buffer-order' since `awesome-tab-ace-jump' is enough.
;;
;; 2019/08/01
;; * Quit when user press Ctrl + g.
;; * Make ace string use foreground same as `font-lock-function-name-face'.
;; * Remove `awesome-tab-prefix-map'
;;
;; 2019/07/18
;; * Use ema2159's way to render icon.
;;
;; 2019/07/17
;; * Init `header-line' height from `default' face,
;; `header-line' default inhibit from `mode-line',
;; awesome-tab icon will disappear if `mode-line' height set with 0.1 by other plugins (such as awesome-tray).
;; * Use `stringp' instead `ignore-errors' in `awesome-tab-icon-for-tab'.
;;
;; 2019/07/15
;; * Don't call `awesome-tab-adjust-buffer-order' if user use mouse click tab.
;;
;; 2019/07/01
;; * Make awesome-tab's colors change with user selected theme, thank you so much AmaiKinono.
;; * Adjust dark mode background tab's color.
;; * Remove local-mode code.
;; * Refactory code.
;;
;; 2019/06/30
;; * Add customize option `awesome-tab-display-icon' .
;;
;; 2019/06/28
;; * Fix messages buffer icon an FontAwesome errors, thanks ema2159. ;)
;; * Set height of tab face, avoid tab render error when user don't load any third theme.
;; * Make `header-line' background same as default face.
;;
;; 2019/06/26
;; * Fix error of void function awesome-tab-separator-separator-height
;;
;; 2019/06/25
;; * Still display tab if all-the-icons cause "Error during redisplay" error in MacOS.
;;
;; 2019/06/24
;; * Use ema2159's patch to fix icon face performance.
;;
;; 2019/06/23
;; * Render file icon in tab when `all-the-icons' is load.
;; * Use `all-the-icons-icon-for-buffer' to display icon for dired mode.
;; * Support color icon.
;; * Don't customize background of tab, use `default' face's background as tab background.
;;
;; 2019/04/14
;; * Make `awesome-tab-last-sticky-func-name' default with nil.
;;
;; 2019/03/21
;; * Make `awesome-tab-last-sticky-func-name' as buffer variable.
;;
;; 2019/03/19
;; * If `tab-index' more than length of visible tabs, selet the last tab.
;;
;; 2019/03/18
;; * Add new command `awesome-tab-select-visible-tab'.
;;
;; 2019/03/16
;; * Fix integerp error.
;;
;; 2019/03/14
;; * Try to fix numberp error.
;;
;; 2019/03/12
;; * Display sticky function name in tab.
;;
;; 2019/03/09
;; * Absorb powerline code, keep single file.
;; * Remove some separator face that not suitable for displaying tab.
;; * Add option `awesome-tab-style'.
;;
;; 2019/03/07
;; * Add `cl' dependence.
;;
;; 2019/03/03
;; * Automatically adsorb tabs after switching tabs, making switch tabs quickly.
;; * Fix many typo errors.
;; * Add `awesome-tab-adjust-buffer-order-function'.
;; * Don't trigger by awesome-tab command, it's annoying.
;;
;; 2019/02/23
;; * Significantly optimize the performance of switching tab by avoiding excessive calls `project-current'.
;; * Use `powerline' render tab, it's beautiful!!!
;;
;; 2018/12/27
;; * Tab will hide if ```awesome-tab-hide-tab-function``` return t, you can write your own code to customize hide rules.
;;
;; 2018/11/16
;; * Open new tab on right of current one.
;;
;; 2018/11/14
;; * Remove wheel features, emacser should only use the keyboard to operate Emacs.
;;
;; 2018/11/01
;; * Remove `projectile' depend.
;;
;; 2018/10/29
;; * Add `mwheel' depend.
;;
;; 2018/09/29
;; * Add new command `awesome-tab-kill-other-buffers-in-current-group'
;; * Not enable mode default.
;;
;; 2018/09/25
;; * Adjust magit regexp to only match magit buffer, not file that named with magit.
;;
;; 2018/09/22
;; * Adjust `awesome-tab-buffer-list' to hide unused buffer to user.
;;
;; 2018/09/20
;; * Remove empty header line from magit buffers.
;; * Add new function `awesome-tab-kill-match-buffers-in-current-group', it's handy in mixin mode, such as web-mode.
;; * Add new function `awesome-tab-keep-match-buffers-in-current-group', it's handy in mixin mode, such as web-mode.
;; * Fix error cause by `awesome-tab-kill-buffer-match-rule'.
;;
;; 2018/09/18
;; * Fix unselect tab height and add option `awesome-tab-hide-tab-rules'
;; * Use `awesome-tab-groups-hash' store every buffer's project name avoid performance issue cause by `projectile-project-name'.
;;
;; 2018/09/17
;; * First released.
;;
;;; Acknowledgements:
;;
;; casouri: documentation and many useful patches.
;; AmaiKinono: contributed to the patch that make tab color change with the theme automatically
;;
;;; TODO
;;
;;
;;
;;; Require
(require 'cl-lib)
(require 'color)
;;; Code:
;;;;;;;;;;;;;;;;;;;;;;; Awesome-Tab source code ;;;;;;;;;;;;;;;;;;;;;;;
(defgroup awesome-tab nil
"Display a tab bar in the header line."
:group 'convenience)
(defcustom awesome-tab-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 'awesome-tab
: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 awesome-tab-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 'awesome-tab
:type 'boolean)
(defcustom awesome-tab-common-group-name "Common"
"If the current buffer does not belong to any project,
the group name uses the name of this variable."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-label-fixed-length 0
"Fixed length of label. Set to 0 if dynamic."
:group 'awesome-tab
:type 'int)
(defcustom awesome-tab-label-max-length 30
"Max length of label. Set to 0 if dynamic."
:group 'awesome-tab
:type 'int)
(defcustom awesometab-hide-tabs-hooks
'(magit-status-mode-hook magit-popup-mode-hook reb-mode-hook)
"Some buffer's header line is empty that make its window insufficient of space to display all content.
Feel free to add hook in this option. ;)"
:type '(repeat symbol)
:group 'awesome-tab)
(defcustom awesome-tab-display-sticky-function-name nil
"Non-nil to display sticky function name in tab.
Sticky function is the function at the top of the current window sticky."
:group 'awesome-tab
:type 'boolean)
(defcustom awesome-tab-display-icon t
"Non-nil to display icon in tab, this feature need `all-the-icons' is loaded.
Set this option with nil if you don't like icon in tab."
:group 'awesome-tab
:type 'boolean)
(defcustom awesome-tab-ace-keys '(?j ?k ?l ?s ?d ?f)
"Keys used for `awesome-tab-ace-jump'."
:group 'awesome-tab
:set #'(lambda (symbol value)
(set-default symbol value)
(let ((1k-seqs nil)
(2k-seqs nil))
(dolist (a value)
(dolist (b value)
(push (list a b) 2k-seqs))
(push (list a) 1k-seqs))
(setq awesome-tab-ace-2-key-seqs (nreverse 2k-seqs))
(setq awesome-tab-ace-1-key-seqs (nreverse 1k-seqs))))
:type '(repeat :tag "Keys" character))
(defcustom awesome-tab-ace-quit-keys '(?\C-g ?q ?\s)
"Keys used to quit from ace jumping."
:group 'awesome-tab
:type '(repeat :tag "Keys" character))
(defcustom awesome-tab-ace-str-style 'replace-icon
"Position of ace strings."
:group 'awesome-tab
:type '(choice
(const :tag "Replace icon" replace-icon)
(const :tag "Left" left)
(const :tag "Right" right)))
(defcustom awesome-tab-height 190
"The height of tab face."
:group 'awesome-tab
:type 'int)
(defcustom awesome-tab-icon-v-adjust 0
"The v-adjust of tab icon."
:group 'awesome-tab
:type 'float)
(defcustom awesome-tab-icon-height 0.6
"The height of icon.
It will render top-line on tab when you set this variable bigger than 0.9."
:group 'awesome-tab
:type 'float)
(defcustom awesome-tab-show-tab-index nil
"Non-nil to display index in tab."
:group 'awesome-tab
:type 'boolean)
(defcustom awesome-tab-index-format-str "[%d] "
"Format string for tab index."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-dark-selected-foreground-color nil
"Foreground for selected tab in dark theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-dark-unselected-foreground-color nil
"Foreground for unselected tab in dark theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-light-selected-foreground-color nil
"Foreground for selected tab in light theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-light-unselected-foreground-color nil
"Foreground for unselected tab in light theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-terminal-dark-select-background-color "#222222"
"Select background color for terminal dark mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-dark-select-foreground-color "#AAAAAA"
"Select foreground color for terminal dark mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-dark-unselect-background-color "#000000"
"Unselect background color for terminal dark mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-dark-unselect-foreground-color "#AAAAAA"
"Unselect foreground color for terminal dark mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-light-select-background-color "#AAAAAA"
"Select background color for terminal light mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-light-select-foreground-color "#333333"
"Select foreground color for terminal light mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-light-unselect-background-color "#333333"
"Unselect background color for terminal light mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-terminal-light-unselect-foreground-color "#AAAAAA"
"Unselect foreground color for terminal light mode."
:group 'awesome-tab
:type 'string)
(defcustom awesome-tab-dark-active-bar-color nil
"The bar color for active tab in dark theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-light-active-bar-color nil
"The bar color for active tab in light theme."
:group 'awesome-tab
:type '(choice (const nil) string))
(defcustom awesome-tab-dark-unselected-blend 0.8
"The blend value for unselected background of dark mode.
Lower value means more contrast."
:group 'awesome-tab
:type 'float)
(defcustom awesome-tab-light-unselected-blend 0.9
"The blend value for unselected background of light mode.
Lower value means more contrast."
:group 'awesome-tab
:type 'float)
(defcustom awesome-tab-active-bar-width 3
"The width of active bar."
:group 'awesome-tab
:type 'integer)
(defcustom awesome-tab-active-bar-height 30
"The height of active bar."
:group 'awesome-tab
:type 'integer)
(defvar-local awesome-tab-ace-state nil
"Whether current buffer is doing `awesome-tab-ace-jump' or not.")
(defvar awesome-tab-hide-tab-function 'awesome-tab-hide-tab
"Function to hide tab.
This fucntion accepet tab name, tab will hide if this function return ni.")
(defvar awesome-tab-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 awesome-tab-select-tab-function nil
"Function that select a tab.
The function is passed a tab, and should make it the
selected tab.")
(defvar awesome-tab-buffer-list-function 'awesome-tab-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 awesome-tab-buffer-groups-function 'awesome-tab-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.")
(defvar awesome-tab-ace-1-key-seqs nil
"List of 1-key sequences used by `awesome-tab-ace-jump'")
(defvar awesome-tab-ace-2-key-seqs nil
"List of 2-key sequences used by `awesome-tab-ace-jump'")
(defvar awesome-tab-all-the-icons-is-load-p (ignore-errors (require 'all-the-icons))
"Return non-nil if `all-the-icons' is load, `require' will have performance problem, so don't call it dynamically.")
;;; Misc.
;;
(eval-and-compile
(defalias 'awesome-tab-display-update
(if (fboundp 'force-window-update)
#'(lambda () (force-window-update (selected-window)))
'force-mode-line-update)))
(defmacro awesome-tab-kill-buffer-match-rule (match-rule)
`(save-excursion
(mapc #'(lambda (buffer)
(with-current-buffer buffer
(when (string-equal current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t))))
(when (funcall ,match-rule buffer)
(kill-buffer buffer))
)))
(buffer-list))))
;; Copied from s.el
(defun awesome-tab-truncate-string (len s &optional ellipsis)
"If S is longer than LEN, cut it down and add ELLIPSIS to the end.
The resulting string, including ellipsis, will be LEN characters
long.
When not specified, ELLIPSIS defaults to ...."
(declare (pure t) (side-effect-free t))
(unless ellipsis
(setq ellipsis "..."))
(if (> (length s) len)
(format "%s%s" (substring s 0 (- len (length ellipsis))) ellipsis)
(concat s (make-string (- len (length s)) ? ))))
(defun awesome-tab-refresh-display ()
"Refresh the display of tabs. Put this in your user-defined hooks to
make sure the face colors are always right."
(interactive)
(awesome-tab-map-tabsets (lambda (x) (awesome-tab-set-template x nil)))
(awesome-tab-display-update))
;;; Tab and tab set
;;
(defsubst awesome-tab-make-tab (object tabset)
"Return a new tab with value OBJECT.
TABSET is the tab set the tab belongs to."
(cons object tabset))
(defsubst awesome-tab-tab-value (tab)
"Return the value of tab TAB."
(car tab))
(defsubst awesome-tab-tab-tabset (tab)
"Return the tab set TAB belongs to."
(cdr tab))
(defvar awesome-tab-tabsets nil
"The tab sets store.")
(defvar awesome-tab-tabsets-tabset nil
"The special tab set of existing tab sets.")
(defvar awesome-tab-current-tabset nil
"The tab set currently displayed on the tab bar.")
(make-variable-buffer-local 'awesome-tab-current-tabset)
(defvar awesome-tab-init-hook nil
"Hook run after tab bar data has been initialized.
You should use this hook to initialize dependent data.")
(defvar awesome-tab-active-bar nil)
(defsubst awesome-tab-init-tabsets-store ()
"Initialize the tab set store."
(setq awesome-tab-tabsets (make-vector 31 0)
awesome-tab-tabsets-tabset (make-symbol "awesome-tab-tabsets-tabset"))
(put awesome-tab-tabsets-tabset 'start 0)
(run-hooks 'awesome-tab-init-hook))
(defvar awesome-tab-quit-hook nil
"Hook run after tab bar data has been freed.
You should use this hook to reset dependent data.")
(defsubst awesome-tab-free-tabsets-store ()
"Free the tab set store."
(setq awesome-tab-tabsets nil
awesome-tab-tabsets-tabset nil)
(run-hooks 'awesome-tab-quit-hook))
;; Define an "hygienic" function free of side effect between its local
;; variables and those of the callee.
(eval-and-compile
(defalias 'awesome-tab-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))
awesome-tab-tabsets)
,result)))))
(defun awesome-tab-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 awesome-tab-tabsets))
(tabs (mapcar #'(lambda (object)
(awesome-tab-make-tab object tabset))
objects)))
(set tabset tabs)
(put tabset 'select (car tabs))
(put tabset 'start 0)
tabset))
(defsubst awesome-tab-get-tabset (name)
"Return the tab set whose name is the string NAME.
Return nil if not found."
(intern-soft name awesome-tab-tabsets))
(defsubst awesome-tab-delete-tabset (tabset)
"Delete the tab set TABSET.
That is, remove it from the tab sets store."
(unintern tabset awesome-tab-tabsets))
(defsubst awesome-tab-tabs (tabset)
"Return the list of tabs in TABSET."
(symbol-value tabset))
(defsubst awesome-tab-tab-values (tabset)
"Return the list of tab values in TABSET."
(mapcar 'awesome-tab-tab-value (awesome-tab-tabs tabset)))
(defsubst awesome-tab-get-tab (object tabset)
"Search for a tab with value OBJECT in TABSET.
Return the tab found, or nil if not found."
(assoc object (awesome-tab-tabs tabset)))
(defsubst awesome-tab-member (tab tabset)
"Return non-nil if TAB is in TABSET."
(or (eq (awesome-tab-tab-tabset tab) tabset)
(memq tab (awesome-tab-tabs tabset))))
(defsubst awesome-tab-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 awesome-tab-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 awesome-tab-selected-tab (tabset)
"Return the tab selected in TABSET."
(get tabset 'select))
(defsubst awesome-tab-selected-value (tabset)
"Return the value of the tab selected in TABSET."
(awesome-tab-tab-value (awesome-tab-selected-tab tabset)))
(defsubst awesome-tab-selected-p (tab tabset)
"Return non-nil if TAB is the selected tab in TABSET."
(eq tab (awesome-tab-selected-tab tabset)))
(defvar awesome-tab--track-selected nil)
(defsubst awesome-tab-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 (awesome-tab-member tab tabset)
(unless (awesome-tab-selected-p tab tabset)
(awesome-tab-set-template tabset nil)
(setq awesome-tab--track-selected awesome-tab-auto-scroll-flag))
(put tabset 'select tab)))
(defsubst awesome-tab-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."
(awesome-tab-select-tab (awesome-tab-get-tab object tabset) tabset))
(defsubst awesome-tab-start (tabset)
"Return the index of the first visible tab in TABSET."
(get tabset 'start))
(defsubst awesome-tab-view (tabset)
"Return the list of visible tabs in TABSET.
That is, the sub-list of tabs starting at the first visible one."
(nthcdr (awesome-tab-start tabset) (awesome-tab-tabs tabset)))
(defun awesome-tab-add-tab (tabset object)
"Return tab if it has opend.
Otherwise insert new tab on right of current tab."
(let ((tabs (awesome-tab-tabs tabset)))
(if (awesome-tab-get-tab object tabset)
tabs
(let* ((tab (awesome-tab-make-tab object tabset))
(selected (awesome-tab-selected-tab tabset))
(selected-index (cl-position (car selected) (mapcar 'car tabs))))
(awesome-tab-set-template tabset nil)
(set tabset (awesome-tab-insert-at tabs selected-index tab))
))))
(defun awesome-tab-insert-at (list index insert-element)
(let ((counter 0)
(result '()))
(dolist (element list)
(if (equal counter index)
(setq result (append result (list element insert-element)))
(setq result (append result (list element))))
(setq counter (+ 1 counter)))
result))
(defun awesome-tab-delete-tab (tab)
"Remove TAB from its tab set."
(let* ((tabset (awesome-tab-tab-tabset tab))
(tabs (awesome-tab-tabs tabset))
(sel (eq tab (awesome-tab-selected-tab tabset)))
(next (and sel (cdr (memq tab tabs)))))
(awesome-tab-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 (awesome-tab-select-tab (car (or next (last tabs))) tabset))
(set tabset tabs)))
(defun awesome-tab-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 (+ (awesome-tab-start tabset) count))
(1- (length (awesome-tab-tabs tabset))))))
(when (/= start (awesome-tab-start tabset))
(awesome-tab-set-template tabset nil)
(put tabset 'start start))))
(defun awesome-tab-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 (awesome-tab-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 awesome-tab-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
`awesome-tab-current-tabset-function' to obtain it. Otherwise return the
current cached copy."
(and update awesome-tab-current-tabset-function
(setq awesome-tab-current-tabset
(funcall awesome-tab-current-tabset-function)))
awesome-tab-current-tabset)
(defun awesome-tab-get-tabsets-tabset ()
"Return the tab set of selected tabs in existing tab sets."
(set awesome-tab-tabsets-tabset (awesome-tab-map-tabsets 'awesome-tab-selected-tab))
(awesome-tab-scroll awesome-tab-tabsets-tabset 0)
(awesome-tab-set-template awesome-tab-tabsets-tabset nil)
awesome-tab-tabsets-tabset)
;;; Faces
;;
(defface awesome-tab-unselected-face
'((t))
"Face used for unselected tabs.
Do not customize this. It's attribute will be calculated on the
fly to fit your theme."
:group 'awesome-tab)
(defface awesome-tab-selected-face
'((t))
"Face used for the selected tab.
Do not customize this. It's attribute will be calculated on the
fly to fit your theme."
:group 'awesome-tab)
(defface awesome-tab-unselected-ace-face
'((t (:inherit 'font-lock-function-name-face)))
"Face used for ace string on unselected tabs.
Note that the background will be calculated on the fly, so
customize the background will not have any effect."
:group 'awesome-tab)
(defface awesome-tab-selected-ace-face
'((t (:inherit 'font-lock-function-name-face)))
"Face used for ace string on selected tabs.
Note that the background will be calculated on the fly, so
customize the background will not have any effect."
:group 'awesome-tab)
(defface awesome-tab-unselected-index-face
'((t (:inherit 'font-lock-function-name-face)))
"Face used for index on unselected tabs.
Note that the background will be calculated on the fly, so
customize the background will not have any effect."
:group 'awesome-tab)
(defface awesome-tab-selected-index-face
'((t (:inherit 'font-lock-function-name-face)))
"Face used for index on selected tabs.
Note that the background will be calculated on the fly, so
customize the background will not have any effect."
:group 'awesome-tab)
;;; Tabs
;;
(defun awesome-tab-make-header-line-mouse-map (mouse function)
(let ((map (make-sparse-keymap)))
(define-key map (vector 'header-line mouse) function)
map))
(defun awesome-tab-color-blend (c1 c2 alpha)
"Blend two colors C1 and C2 with ALPHA.
C1 and C2 are hexidecimal strings.
ALPHA is a number between 0.0 and 1.0 which corresponds to the
influence of C1 on the result."
(apply #'(lambda (r g b)
(format "#%02x%02x%02x"
(ash r -8)
(ash g -8)
(ash b -8)))
(cl-mapcar
(lambda (x y)
(round (+ (* x alpha) (* y (- 1 alpha)))))
(color-values c1) (color-values c2))))
(defun awesome-tab-adjust-color-with-theme ()
"We need adjust awesome-tab's colors when user switch new theme."
(let* ((bg-mode (frame-parameter nil 'background-mode))
(select-tab-background (awesome-tab-get-select-background-color))
(unselect-tab-background (awesome-tab-get-unslect-background-color)))
(when (display-graphic-p)
(setq awesome-tab-active-bar (awesome-tab-make-xpm awesome-tab-active-bar-width awesome-tab-active-bar-height)))
(set-face-attribute 'header-line nil :height awesome-tab-height)
(set-face-attribute 'awesome-tab-selected-face nil
:foreground (awesome-tab-get-select-foreground-color)
:distant-foreground (awesome-tab-get-select-foreground-color)
:background select-tab-background)
(set-face-attribute 'awesome-tab-unselected-face nil
:foreground (awesome-tab-get-unselect-foreground-color)
:distant-foreground (awesome-tab-get-unselect-foreground-color)
:background unselect-tab-background)
(set-face-attribute 'awesome-tab-selected-ace-face nil
:background select-tab-background)
(set-face-attribute 'awesome-tab-unselected-ace-face nil
:background unselect-tab-background)
(set-face-attribute 'awesome-tab-selected-index-face nil
:background select-tab-background)
(set-face-attribute 'awesome-tab-unselected-index-face nil
:background unselect-tab-background)))
(defun awesome-tab-get-select-foreground-color ()
(let ((bg-mode (frame-parameter nil 'background-mode)))
(if (display-graphic-p)
(cond ((eq bg-mode 'dark) (or awesome-tab-dark-selected-foreground-color
(face-foreground 'font-lock-doc-face)))
((eq bg-mode 'light) (or awesome-tab-light-selected-foreground-color
(face-foreground 'font-lock-doc-face))))
(cond ((eq bg-mode 'dark) awesome-tab-terminal-dark-select-foreground-color)
((eq bg-mode 'light) awesome-tab-terminal-light-select-foreground-color))
)))
(defun awesome-tab-get-unselect-foreground-color ()
(let ((bg-mode (frame-parameter nil 'background-mode)))
(if (display-graphic-p)
(cond ((eq bg-mode 'dark) (or awesome-tab-dark-unselected-foreground-color
(face-foreground 'font-lock-comment-face)))
((eq bg-mode 'light) (or awesome-tab-light-unselected-foreground-color
(face-foreground 'font-lock-comment-face))))
(cond ((eq bg-mode 'dark) awesome-tab-terminal-dark-unselect-foreground-color)
((eq bg-mode 'light) awesome-tab-terminal-light-unselect-foreground-color))
)))
(defun awesome-tab-get-select-background-color ()
(let ((bg-mode (frame-parameter nil 'background-mode)))
(if (display-graphic-p)
(face-background 'default)
(cond ((eq bg-mode 'dark) awesome-tab-terminal-dark-select-background-color)
((eq bg-mode 'light) awesome-tab-terminal-light-select-background-color))
)))
(defun awesome-tab-get-unslect-background-color ()
(let* ((bg-mode (frame-parameter nil 'background-mode)))
(if (display-graphic-p)
(cond ((eq bg-mode 'dark)
(awesome-tab-color-blend (face-background 'default) "#000000" awesome-tab-dark-unselected-blend))
((eq bg-mode 'light)
(awesome-tab-color-blend (face-background 'default) "#000000" awesome-tab-light-unselected-blend)))
(cond ((eq bg-mode 'dark) awesome-tab-terminal-dark-unselect-background-color)
((eq bg-mode 'light) awesome-tab-terminal-light-unselect-background-color))
)))
(defun awesome-tab-line-format (tabset)
"Return the `header-line-format' value to display TABSET."
;; Adjust color with theme.
(awesome-tab-adjust-color-with-theme)
;; Reder tab line.
(let* ((sel (awesome-tab-selected-tab tabset))
(tabs (awesome-tab-view tabset))
(bg-mode (frame-parameter nil 'background-mode))
(header-line-color (awesome-tab-get-unslect-background-color))
atsel elts)
;; Track the selected tab to ensure it is always visible.
(when awesome-tab--track-selected
(while (not (memq sel tabs))
(awesome-tab-scroll tabset -1)
(setq tabs (awesome-tab-view tabset)))
(while (and tabs (not atsel))
(setq elts (cons (awesome-tab-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.
(let (buffer-list-update-hook)
(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)
(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)))
(awesome-tab-scroll tabset 1)
(setq elts (cdr elts))))))
(setq elts (nreverse elts))
(setq awesome-tab--track-selected nil))
;; Format remaining tabs.
(while tabs
(setq elts (cons (awesome-tab-line-tab (car tabs)) elts)
tabs (cdr tabs)))
;; Cache and return the new tab bar.
(awesome-tab-set-template
tabset
(list (nreverse elts)
(propertize "%-"
'face (list :background header-line-color
:foreground header-line-color
:distant-foreground header-line-color)
'pointer 'arrow)))))
(defun awesome-tab-line ()
"Return the header line templates that represent the tab bar.
Inhibit display of the tab bar in current window `awesome-tab-hide-tab-function' return nil."
(cond
((awesome-tab-hide-tab-cached (current-buffer))
;; Don't show the tab bar.
(setq header-line-format nil))
((awesome-tab-current-tabset t)
;; When available, use a cached tab bar value, else recompute it.
(or (awesome-tab-template awesome-tab-current-tabset)
(awesome-tab-line-format awesome-tab-current-tabset)))))
(defconst awesome-tab-header-line-format '(:eval (awesome-tab-line))
"The tab bar header line format.")
;;; Cyclic navigation through tabs
;;
(defun awesome-tab-cycle (&optional backward type)
"Cycle to the next available tab.
The scope of the cyclic navigation through tabs is specified by the
option `awesome-tab-cycle-scope'.
If optional argument BACKWARD is non-nil, cycle to the previous tab
instead."
(let* ((tabset (awesome-tab-current-tabset t))
(ttabset (awesome-tab-get-tabsets-tabset))
;; If navigation through groups is requested, and there is
;; only one group, navigate through visible tabs.
(cycle (if (and (eq awesome-tab-cycle-scope 'groups)
(not (cdr (awesome-tab-tabs ttabset))))
'tabs
awesome-tab-cycle-scope))
selected tab)
(when tabset
(setq selected (awesome-tab-selected-tab tabset))
(cond
;; Cycle through visible tabs only.
((eq cycle 'tabs)
(setq tab (awesome-tab-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 (awesome-tab-tabs tabset)
tab (car (if backward (last tabset) tabset))))
)
;; Cycle through tab groups only.
((eq cycle 'groups)
(setq tab (awesome-tab-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 (awesome-tab-tabs ttabset)
tab (car (if backward (last tabset) tabset))))
)
(t
;; Cycle through visible tabs then tab groups.
(setq tab (awesome-tab-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 (awesome-tab-tab-next ttabset selected backward))
;; When there is no next/previous group, cycle to the
;; first/last available group.
(unless tab
(setq tabset (awesome-tab-tabs ttabset)
tab (car (if backward (last tabset) tabset))))
;; Select the first/last visible tab of the new group.
(setq tabset (awesome-tab-tabs (awesome-tab-tab-tabset tab))
tab (car (if backward (last tabset) tabset))))
))
(awesome-tab-buffer-select-tab tab))))
;;;###autoload
(defun awesome-tab-backward ()
"Select the previous available tab.
Depend on the setting of the option `awesome-tab-cycle-scope'."
(interactive)
(awesome-tab-cycle t))
;;;###autoload
(defun awesome-tab-forward ()
"Select the next available tab.
Depend on the setting of the option `awesome-tab-cycle-scope'."
(interactive)
(awesome-tab-cycle))
;;;###autoload
(defun awesome-tab-backward-group ()
"Go to selected tab in the previous available group."
(interactive)
(let ((awesome-tab-cycle-scope 'groups))
(awesome-tab-cycle t)))
;;;###autoload
(defun awesome-tab-forward-group ()
"Go to selected tab in the next available group."
(interactive)
(let ((awesome-tab-cycle-scope 'groups))
(awesome-tab-cycle)))
;;;###autoload
(defun awesome-tab-backward-tab ()
"Select the previous visible tab."
(interactive)
(let ((awesome-tab-cycle-scope 'tabs))
(awesome-tab-cycle t)))
;;;###autoload
(defun awesome-tab-forward-tab ()
"Select the next visible tab."
(interactive)
(let ((awesome-tab-cycle-scope 'tabs))
(awesome-tab-cycle)))
;;; Minor modes
;;
(defsubst awesome-tab-mode-on-p ()
"Return non-nil if Awesome-Tab mode is on."
(eq (default-value 'header-line-format)
awesome-tab-header-line-format))
;;; Awesome-Tab mode
;;
(defvar awesome-tab-mode-map
(let ((km (make-sparse-keymap)))
km)
"Keymap to use in Awesome-Tab mode.")
(defvar awesome-tab--global-hlf nil)
;;;###autoload
(define-minor-mode awesome-tab-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.
\\{awesome-tab-mode-map}"
:group 'awesome-tab
:require 'awesome-tab
:global t
:keymap awesome-tab-mode-map
(if awesome-tab-mode
;;; ON
(unless (awesome-tab-mode-on-p)
;; Save current default value of `header-line-format'.
(setq awesome-tab--global-hlf (default-value 'header-line-format))
(awesome-tab-init-tabsets-store)
(setq-default header-line-format awesome-tab-header-line-format))
;;; OFF
(when (awesome-tab-mode-on-p)
;; Restore previous `header-line-format'.
(setq-default header-line-format awesome-tab--global-hlf)
(awesome-tab-free-tabsets-store))
))
;;; Buffer tabs
;;
(defgroup awesome-tab-buffer nil
"Display buffers in the tab bar."
:group 'awesome-tab)
(defun awesome-tab-filter (condp lst)
(delq nil
(mapcar (lambda (x) (and (funcall condp x) x)) lst)))
(defun awesome-tab-filter-out (condp lst)
(delq nil
(mapcar (lambda (x) (if (funcall condp x) nil x)) lst)))
(defun awesome-tab-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."
(awesome-tab-filter-out
'awesome-tab-hide-tab-cached
(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 awesome-tab-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))
;;; Group buffers in tab sets.
;;
(defvar awesome-tab--buffers nil)
(defun awesome-tab-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 awesome-tab-buffer-groups-function
(funcall awesome-tab-buffer-groups-function)
'(awesome-tab-common-group-name)))))
(and awesome-tab-buffer-list-function
(funcall awesome-tab-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 awesome-tab--buffers)
;; Add new buffers, or update changed ones.
(dolist (e bl)
(dolist (g (nth 2 e))
(let ((tabset (awesome-tab-get-tabset g)))
(if tabset
(unless (equal e (assq (car e) awesome-tab--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.
(awesome-tab-add-tab tabset (car e))
(awesome-tab-set-template tabset nil))
(awesome-tab-make-tabset g (car e))))))
;; Remove tabs for buffers not found in cache or moved to other
;; groups, and remove empty tabsets.
(mapc 'awesome-tab-delete-tabset
(awesome-tab-map-tabsets
#'(lambda (tabset)
(dolist (tab (awesome-tab-tabs tabset))
(let ((e (assq (awesome-tab-tab-value tab) bl)))
(or (and e (memq tabset
(mapcar 'awesome-tab-get-tabset
(nth 2 e))))
(awesome-tab-delete-tab tab))))
;; Return empty tab sets
(unless (awesome-tab-tabs tabset)
tabset))))
;; The new cache becomes the current one.
(setq awesome-tab--buffers bl)))
;; Return the first group the current buffer belongs to.
(car (nth 2 (assq (current-buffer) awesome-tab--buffers))))
;;; Tab bar callbacks
;;
(defvar awesome-tab--buffer-show-groups nil)
(defsubst awesome-tab-buffer-show-groups (flag)
"Set display of tabs for groups of buffers to FLAG."
(setq awesome-tab--buffer-show-groups flag))
(defun awesome-tab-buffer-tabs ()
"Return the buffers to display on the tab bar, in a tab set."
(let ((tabset (awesome-tab-get-tabset (awesome-tab-buffer-update-groups))))
(awesome-tab-select-tab-value (current-buffer) tabset)
(when awesome-tab--buffer-show-groups
(setq tabset (awesome-tab-get-tabsets-tabset))
(awesome-tab-select-tab-value (current-buffer) tabset))
tabset))
(defsubst awesome-tab-line-tab (tab)
"Return the display representation of tab TAB.
That is, a propertized string used as an `header-line-format' template
element."
(propertize
(awesome-tab-buffer-tab-label tab)
'pointer 'hand
'local-map (purecopy (awesome-tab-make-header-line-mouse-map
'mouse-1
`(lambda (event) (interactive "e")
(let ((tab-window (window-at (cadr (mouse-position))
(cddr (mouse-position))
(car (mouse-position)))))
(when tab-window
(select-window tab-window)
(awesome-tab-buffer-select-tab ',tab))))))))
(defun awesome-tab-buffer-tab-label (tab)
"Return a label for TAB.
That is, a string used to represent it on the tab bar."
(let* ((is-active-tab (awesome-tab-selected-p tab (awesome-tab-current-tabset)))
(tab-face (if is-active-tab 'awesome-tab-selected-face 'awesome-tab-unselected-face))
(index-face (if is-active-tab 'awesome-tab-selected-index-face 'awesome-tab-unselected-index-face))
(current-buffer-index (cl-position tab (awesome-tab-view (awesome-tab-current-tabset t))))
(ace-str-face (if is-active-tab 'awesome-tab-selected-ace-face 'awesome-tab-unselected-ace-face))
(current-buffer-index (cl-position tab (awesome-tab-view awesome-tab-current-tabset)))
(ace-str (if awesome-tab-ace-state (elt ace-strs current-buffer-index) ""))
(ace-state awesome-tab-ace-state))
(concat
;; Active bar.
(when (and is-active-tab awesome-tab-active-bar)
(propertize awesome-tab-active-bar))
;; Ace string.
(when (and ace-state (eq awesome-tab-ace-str-style 'left))
(propertize ace-str 'face ace-str-face))
;; Tab icon.
(when (and awesome-tab-display-icon
awesome-tab-all-the-icons-is-load-p)
(propertize " " 'face tab-face))
(if (and ace-state (eq awesome-tab-ace-str-style 'replace-icon))
(propertize ace-str 'face ace-str-face)
(awesome-tab-icon-for-tab tab tab-face))
;; Tab label.
(propertize (awesome-tab-tab-name tab) 'face tab-face)
;; Ace string.
(when (and ace-state (eq awesome-tab-ace-str-style 'right))
(propertize ace-str 'face ace-str-face))
;; Tab index.
(when awesome-tab-show-tab-index
(propertize (format awesome-tab-index-format-str (+ current-buffer-index 1)) 'face index-face))
)))
(defun awesome-tab-make-xpm (width height)
"Create an XPM bitmap via WIDTH and HEIGHT."
(when (and (display-graphic-p)
(image-type-available-p 'xpm))
(propertize
" " 'display
(let ((data (make-list height (make-list width 1)))
(color (pcase (frame-parameter nil 'background-mode)
('dark (or awesome-tab-dark-active-bar-color
(face-background 'highlight)))
('light (or awesome-tab-light-active-bar-color
(face-background 'highlight))))))
(ignore-errors
(create-image
(concat
(format
"/* XPM */\nstatic char * percent[] = {\n\"%i %i 2 1\",\n\". c %s\",\n\" c %s\","
(length (car data)) (length data) color color)
(apply #'concat
(cl-loop with idx = 0
with len = (length data)
for dl in data
do (cl-incf idx)
collect
(concat
"\""
(cl-loop for d in dl
if (= d 0) collect (string-to-char " ")
else collect (string-to-char "."))
(if (eq idx len) "\"};" "\",\n")))))
'xpm t :ascent 'center))))))
(defun awesome-tab-tab-name (tab)
"Render tab's name.
Tab name will truncate if option `awesome-tab-truncate-string' big than zero."
(format " %s "
(let ((bufname (awesome-tab-buffer-name (car tab))))
(cond ((> awesome-tab-label-fixed-length 0)
(awesome-tab-truncate-string awesome-tab-label-fixed-length bufname))
((> awesome-tab-label-max-length 0)
(let ((ellipsis "..."))
(if (> (length bufname) awesome-tab-label-max-length)
(format "%s%s" (substring bufname 0 (- awesome-tab-label-max-length (length ellipsis))) ellipsis)
bufname)))
(t
bufname))
)))
(defun awesome-tab-icon-for-tab (tab face)
"When tab buffer's file is exists, use `all-the-icons-icon-for-file' to fetch file icon.
Otherwise use `all-the-icons-icon-for-buffer' to fetch icon for buffer."
(when (and awesome-tab-display-icon
awesome-tab-all-the-icons-is-load-p)
(let* ((tab-buffer (car tab))
(tab-file (buffer-file-name tab-buffer))
(background (face-background face))
(icon
(cond
;; Use `all-the-icons-icon-for-file' if current file is exists.
((and
tab-file
(file-exists-p tab-file))
(all-the-icons-icon-for-file tab-file :v-adjust awesome-tab-icon-v-adjust :height awesome-tab-icon-height))
;; Use `all-the-icons-icon-for-mode' for current tab buffer at last.
(t
(with-current-buffer tab-buffer
(if (derived-mode-p tab-buffer 'eaf-mode)
(all-the-icons-faicon "html5" :v-adjust awesome-tab-icon-v-adjust :height awesome-tab-icon-height)
(all-the-icons-icon-for-mode major-mode :v-adjust awesome-tab-icon-v-adjust :height awesome-tab-icon-height))
)))))
(when (and icon
;; `get-text-property' need icon is string type.
(stringp icon))
;; Thanks ema2159 for code block ;)
(propertize icon 'face `(:inherit ,(get-text-property 0 'face icon) :background ,background))))))
(defun awesome-tab-buffer-name (tab-buffer)
"Get buffer name of tab.
Will merge sticky function name in tab if option `awesome-tab-display-sticky-function-name' is non-nil."
(if (and awesome-tab-display-sticky-function-name
(boundp 'awesome-tab-last-sticky-func-name)
awesome-tab-last-sticky-func-name
(equal tab-buffer (current-buffer)))
(format "%s [%s]" (buffer-name tab-buffer) awesome-tab-last-sticky-func-name)
(buffer-name tab-buffer)))
(defvar awesome-tab-last-scroll-y 0
"Holds the scroll y of window from the last run of post-command-hooks.")
(defun awesome-tab-monitor-window-scroll ()
"This function is used to monitor the window scroll.
Currently, this function is only use for option `awesome-tab-display-sticky-function-name'."
(when awesome-tab-display-sticky-function-name
(let ((scroll-y (window-start)))
(when (and scroll-y
(integerp scroll-y))
(unless (equal scroll-y awesome-tab-last-scroll-y)
(let ((func-name (save-excursion
(goto-char scroll-y)
(require 'which-func)
(which-function))))
(when (or
(not (boundp 'awesome-tab-last-sticky-func-name))
(not (equal func-name awesome-tab-last-sticky-func-name)))
(set (make-local-variable 'awesome-tab-last-sticky-func-name) func-name)
;; Use `ignore-errors' avoid integerp error when execute `awesome-tab-line-format'.
(ignore-errors
(awesome-tab-line-format awesome-tab-current-tabset))
))))
(setq awesome-tab-last-scroll-y scroll-y))))
(add-hook 'post-command-hook 'awesome-tab-monitor-window-scroll)
(defun awesome-tab-buffer-select-tab (tab)
"Select tab."
(let ((buffer (awesome-tab-tab-value tab)))
(switch-to-buffer buffer)
(awesome-tab-buffer-show-groups nil)
(awesome-tab-display-update)
))
(defun awesome-tab-buffer-track-killed ()
"Hook run just before actually killing a buffer.
In Awesome-Tab 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 awesome-tab-header-line-format)
(eq awesome-tab-current-tabset-function 'awesome-tab-buffer-tabs)
(eq (current-buffer) (window-buffer (selected-window)))
(let ((bl (awesome-tab-tab-values (awesome-tab-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 awesome-tab-buffer-init ()
"Initialize tab bar buffer data.
Run as `awesome-tab-init-hook'."
(setq awesome-tab--buffers nil
awesome-tab--buffer-show-groups nil
awesome-tab-current-tabset-function 'awesome-tab-buffer-tabs
awesome-tab-select-tab-function 'awesome-tab-buffer-select-tab
)
(add-hook 'kill-buffer-hook 'awesome-tab-buffer-track-killed))
(defun awesome-tab-buffer-quit ()
"Quit tab bar buffer.
Run as `awesome-tab-quit-hook'."
(setq awesome-tab--buffers nil
awesome-tab--buffer-show-groups nil
awesome-tab-current-tabset-function nil
awesome-tab-select-tab-function nil
)
(remove-hook 'kill-buffer-hook 'awesome-tab-buffer-track-killed))
(add-hook 'awesome-tab-init-hook 'awesome-tab-buffer-init)
(add-hook 'awesome-tab-quit-hook 'awesome-tab-buffer-quit)
;;;;;;;;;;;;;;;;;;;;;;; Interactive functions ;;;;;;;;;;;;;;;;;;;;;;;
(defun awesome-tab-switch-group (&optional groupname)
"Switch tab groups using ido."
(interactive)
(let* ((tab-buffer-list (mapcar
#'(lambda (b)
(with-current-buffer b
(list (current-buffer)
(buffer-name)
(funcall awesome-tab-buffer-groups-function) )))
(funcall awesome-tab-buffer-list-function)))
(groups (awesome-tab-get-groups))
(group-name (or groupname
(completing-read "Switch to group: "
groups nil t))) )
(catch 'done
(mapc
#'(lambda (group)
(when (equal group-name (car (car (cdr (cdr group)))))
(throw 'done (switch-to-buffer (car (cdr group))))))
tab-buffer-list) )))
(defun awesome-tab-select-end-tab ()
"Select end tab of current tabset."
(interactive)
(awesome-tab-select-beg-tab t))
(defun awesome-tab-select-beg-tab (&optional backward type)
"Select beginning tab of current tabs.
If BACKWARD is non-nil, move backward, otherwise move forward.
TYPE is default option."
(interactive)
(let* ((tabset (awesome-tab-current-tabset t))
(ttabset (awesome-tab-get-tabsets-tabset))
(cycle (if (and (eq awesome-tab-cycle-scope 'groups)
(not (cdr (awesome-tab-tabs ttabset))))
'tabs
awesome-tab-cycle-scope))
selected tab)
(when tabset
(setq selected (awesome-tab-selected-tab tabset))
(setq tabset (awesome-tab-tabs tabset)
tab (car (if backward (last tabset) tabset)))
(awesome-tab-buffer-select-tab tab))))
(defun awesome-tab-backward-tab-other-window (&optional reversed)
"Move to left tab in other window.
Optional argument REVERSED default is move backward, if reversed is non-nil move forward."
(interactive)
(other-window 1)
(if reversed
(awesome-tab-forward-tab)
(awesome-tab-backward-tab))
(other-window -1))
(defun awesome-tab-forward-tab-other-window ()
"Move to right tab in other window."
(interactive)
(awesome-tab-backward-tab-other-window t))
(defun awesome-tab-move-current-tab-to-right ()
"Move current tab one place right, unless it's already the rightmost."
(interactive)
(let* ((bufset (awesome-tab-current-tabset t))
(old-bufs (awesome-tab-tabs bufset))
(first-buf (car old-bufs))
(new-bufs (list)))
(while (and
old-bufs
(not (string= (buffer-name) (format "%s" (car (car old-bufs))))))
(push (car old-bufs) new-bufs)
(setq old-bufs (cdr old-bufs)))
(if old-bufs ; if this is false, then the current tab's buffer name is mysteriously missing
(progn
(setq the-buffer (car old-bufs))
(setq old-bufs (cdr old-bufs))
(if old-bufs ; if this is false, then the current tab is the rightmost
(push (car old-bufs) new-bufs))
(push the-buffer new-bufs)) ; this is the tab that was to be moved
(error "Error: current buffer's name was not found in Awesome-Tab's buffer list."))
(setq new-bufs (reverse new-bufs))
(setq new-bufs (append new-bufs (cdr old-bufs)))
(set bufset new-bufs)
(awesome-tab-set-template bufset nil)
(awesome-tab-display-update)))
(defun awesome-tab-move-current-tab-to-left ()
"Move current tab one place left, unless it's already the leftmost."
(interactive)
(let* ((bufset (awesome-tab-current-tabset t))
(old-bufs (awesome-tab-tabs bufset))
(first-buf (car old-bufs))
(new-bufs (list)))
(if (string= (buffer-name) (format "%s" (car first-buf)))
old-bufs ; the current tab is the leftmost
(setq not-yet-this-buf first-buf)
(setq old-bufs (cdr old-bufs))
(while (and
old-bufs
(not (string= (buffer-name) (format "%s" (car (car old-bufs))))))
(push not-yet-this-buf new-bufs)
(setq not-yet-this-buf (car old-bufs))
(setq old-bufs (cdr old-bufs)))
(if old-bufs ; if this is false, then the current tab's buffer name is mysteriously missing
(progn
(push (car old-bufs) new-bufs) ; this is the tab that was to be moved
(push not-yet-this-buf new-bufs)
(setq new-bufs (reverse new-bufs))
(setq new-bufs (append new-bufs (cdr old-bufs))))
(error "Error: current buffer's name was not found in Awesome-Tab's buffer list."))
(set bufset new-bufs)
(awesome-tab-set-template bufset nil)
(awesome-tab-display-update))))
(defun awesome-tab-move-current-tab-to-beg ()
"Move current tab to the first position."
(interactive)
(let* ((bufset (awesome-tab-current-tabset t))
(bufs (copy-sequence (awesome-tab-tabs bufset)))
(current-tab-index
(cl-position (current-buffer) (mapcar #'car bufs)))
(current-tab (elt bufs current-tab-index)))
(setq bufs (delete current-tab bufs))
(push current-tab bufs)
(set bufset bufs)
(awesome-tab-set-template bufset nil)
(awesome-tab-display-update)))
(defun awesome-tab-kill-all-buffers-in-current-group ()
"Kill all buffers in current group."
(interactive)
(let* ((current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t)))))
;; Kill all buffers in current group.
(awesome-tab-kill-buffer-match-rule
(lambda (buffer) t))
;; Switch to next group.
(awesome-tab-forward-group)
))
(defun awesome-tab-kill-other-buffers-in-current-group ()
"Kill all buffers except current buffer in current group."
(interactive)
(let* ((current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t))))
(currentbuffer (current-buffer)))
;; Kill all buffers in current group.
(awesome-tab-kill-buffer-match-rule
(lambda (buffer) (not (equal buffer currentbuffer))))
))
(defun awesome-tab-kill-match-buffers-in-current-group ()
"Kill all buffers match extension in current group."
(interactive)
(let* ((current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t))))
(extension-names (awesome-tab-get-extensions))
match-extension)
;; Read extension need to kill.
(setq match-extension (completing-read "Kill buffers suffix with: "
extension-names nil t))
;; Kill all buffers match extension in current group.
(awesome-tab-kill-buffer-match-rule
(lambda (buffer)
(let ((filename (buffer-file-name buffer)))
(and filename (string-equal (file-name-extension filename) match-extension))
)))
;; Switch to next group if last file killed.
(when (equal (length extension-names) 1)
(awesome-tab-forward-group))
))
(defun awesome-tab-keep-match-buffers-in-current-group ()
"Keep all buffers match extension in current group."
(interactive)
(let* ((current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t))))
(extension-names (awesome-tab-get-extensions))
match-extension)
;; Read extension need to kill.
(setq match-extension (completing-read "Keep buffers suffix with: "
extension-names nil t))
;; Kill all buffers match extension in current group.
(awesome-tab-kill-buffer-match-rule
(lambda (buffer)
(let ((filename (buffer-file-name buffer)))
(and filename (not (string-equal (file-name-extension filename) match-extension)))
)))))
(defun awesome-tab-select-visible-nth-tab (tab-index)
"Select visible tab with `tab-index'.
Example, when `tab-index' is 1, this function will select the leftmost label in the visible area,
instead of the first label in the current group.
If `tab-index' more than length of visible tabs, selet the last tab.
If `tab-index' is 0, select last tab."
(let ((visible-tabs (awesome-tab-view awesome-tab-current-tabset)))
(switch-to-buffer
(car
(if (or (equal tab-index 0)
(> tab-index (length visible-tabs)))
(car (last visible-tabs))
(nth (- tab-index 1) visible-tabs))))))
(defun awesome-tab-select-visible-tab ()
"Bind this function with number keystroke, such as s-1, s-2, s-3 ... etc.
This function automatically recognizes the number at the end of the keystroke
and switches to the tab of the corresponding index.
Note that this function switches to the visible range,
not the actual logical index position of the current group."
(interactive)
(let* ((event last-command-event)
(key (make-vector 1 event))
(key-desc (key-description key))
(tab-index (string-to-number
;; Fix issue #81 that `last-command-event' is not keystroke.
(cond ((equal (length key-desc) 1)
key-desc)
(t
(nth 1 (split-string key-desc "-")))))))
(awesome-tab-select-visible-nth-tab tab-index)))
(defun awesome-tab-build-ace-strs (len key-number seqs)
"Build strings for `awesome-tab-ace-jump'.
LEN is the number of strings, should be the number of current visible
tabs. NKEYS should be 1 or 2."
(let ((i 0)
(str nil))
(when (>= key-number 3)
(error "NKEYS should be 1 or 2"))
(while (< i len)
(push (apply #'string (elt seqs i)) str)
(setq i (1+ i)))
(nreverse str)))
(defun awesome-tab-open-tab-in-current-group ()
"Open a tab in current group."
(interactive)
(let* ((generate-candidate (lambda (tab)
(propertize
(string-trim (awesome-tab-tab-name tab))
'awesome-tab-tab-entity tab)))
(candidates (mapcar generate-candidate
(awesome-tab-tabs (awesome-tab-current-tabset)))))
(awesome-tab-buffer-select-tab
(get-text-property 0 'awesome-tab-tab-entity
(completing-read "Open tab:" candidates nil t)))))
(defun awesome-tab-ace-jump ()
"Jump to a visible tab by 1 or 2 chars."
(interactive)
(catch 'quit
(let* ((visible-tabs (awesome-tab-view awesome-tab-current-tabset))
(visible-tabs-length (length visible-tabs))
done-flag
(lower-bound 0)
(upper-bound visible-tabs-length)
(ace-keys (length awesome-tab-ace-keys))
(key-number (cond
((<= visible-tabs-length ace-keys) 1)
((<= visible-tabs-length (* ace-keys ace-keys)) 2)
(t (error "Too many visible tabs. Put more keys into `awesome-tab-ace-keys'."))))
(visible-seqs
(cl-subseq
(symbol-value
(intern
(concat "awesome-tab-ace-" (number-to-string key-number) "-key-seqs")))
0 visible-tabs-length))
(ace-strs (awesome-tab-build-ace-strs visible-tabs-length key-number visible-seqs)))
(setq awesome-tab-ace-state t)
(awesome-tab-refresh-display)
(dotimes (i key-number)
(while (not done-flag)
(let ((char (with-local-quit (read-key (format "Awesome Tab Ace Jump (%d):" (1+ i))))))
(if (not (member char awesome-tab-ace-quit-keys))
(let ((current-chars (mapcar #'car visible-seqs)))
(when (member char current-chars)
(setq done-flag t)
(setq lower-bound (cl-position char current-chars))
(setq upper-bound (1- (- visible-tabs-length (cl-position char (nreverse current-chars)))))
(dotimes (lower-index lower-bound)
(setcar (nthcdr lower-index visible-seqs) nil))
(setq upper-index (1+ upper-bound))
(while (< upper-index visible-tabs-length)
(setcar (nthcdr upper-index visible-seqs) nil)
(setq upper-index (1+ upper-index)))
(setq upper-index 0)
))
;; Quit when user press Ctrl + g.
(setq awesome-tab-ace-state nil)
(awesome-tab-refresh-display)
(throw 'quit nil))))
(setq done-flag nil)
(setq visible-seqs (mapcar #'cdr visible-seqs))
(setq ace-strs (awesome-tab-build-ace-strs visible-tabs-length key-number visible-seqs))
(awesome-tab-refresh-display))
(setq awesome-tab-ace-state nil)
(awesome-tab-refresh-display)
(awesome-tab-buffer-select-tab (nth lower-bound visible-tabs)))))
;;;;;;;;;;;;;;;;;;;;;;; Utils functions ;;;;;;;;;;;;;;;;;;;;;;;
(defun awesome-tab-get-groups ()
;; Refresh groups.
(set awesome-tab-tabsets-tabset (awesome-tab-map-tabsets 'awesome-tab-selected-tab))
(mapcar #'(lambda (group)
(format "%s" (cdr group)))
(awesome-tab-tabs awesome-tab-tabsets-tabset)))
(defun awesome-tab-get-extensions ()
;; Refresh groups.
(set awesome-tab-tabsets-tabset (awesome-tab-map-tabsets 'awesome-tab-selected-tab))
(let ((extension-names '()))
(mapc #'(lambda (buffer)
(with-current-buffer buffer
(when (string-equal current-group-name (cdr (awesome-tab-selected-tab (awesome-tab-current-tabset t))))
(when (buffer-file-name buffer)
(add-to-list 'extension-names (file-name-extension (buffer-file-name buffer))))
)))
(buffer-list))
extension-names))
;;;;;;;;;;;;;;;;;;;;;;; Default configurations ;;;;;;;;;;;;;;;;;;;;;;;
;; Uniquify tab name when open multiple buffers with same filename.
(setq uniquify-separator "/")
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
(setq uniquify-after-kill-buffer-p t)
(dolist (hook awesometab-hide-tabs-hooks)
(add-hook hook '(lambda () (setq-local header-line-format nil))))
;; Rules to control buffer's group rules.
(defvar awesome-tab-groups-hash (make-hash-table :test 'equal))
(defvar awesome-tab-hide-hash (make-hash-table :test 'equal))
(defun awesome-tab-project-name ()
(let ((project-name (cdr (project-current))))
(if project-name
(format "Project: %s" (expand-file-name project-name))
awesome-tab-common-group-name)))
(defun awesome-tab-get-group-name (buf)
(let ((group-name (gethash buf awesome-tab-groups-hash)))
;; Return group name cache if it exists for improve performance.
(if group-name
group-name
;; Otherwise try get group name with `project-current'.
;; `project-current' is very slow, it will slow down Emacs if you call it when switch buffer.
(with-current-buffer buf
(let ((project-name (awesome-tab-project-name)))
(puthash buf project-name awesome-tab-groups-hash)
project-name)))))
(defun awesome-tab-buffer-groups ()
"`awesome-tab-buffer-groups' control buffers' group rules.
Group awesome-tab with mode if buffer is derived from `eshell-mode' `emacs-lisp-mode' `dired-mode' `org-mode' `magit-mode'.
All buffer name start with * will group to \"Emacs\".
Other buffer group by `awesome-tab-get-group-name' with project name."
(list
(cond
((or (string-equal "*" (substring (buffer-name) 0 1))
(memq major-mode '(magit-process-mode
magit-status-mode
magit-diff-mode
magit-log-mode
magit-file-mode
magit-blob-mode
magit-blame-mode
)))
"Emacs")
((derived-mode-p 'eshell-mode)
"EShell")
((derived-mode-p 'emacs-lisp-mode)
"Elisp")
((derived-mode-p 'dired-mode)
"Dired")
((memq major-mode '(org-mode org-agenda-mode diary-mode))
"OrgMode")
((derived-mode-p 'eaf-mode)
"EAF")
(t
(awesome-tab-get-group-name (current-buffer))))))
;; Helm source for switching group in helm.
(defvar helm-source-awesome-tab-group nil)
(defun awesome-tab-build-helm-source ()
(interactive)
(setq helm-source-awesome-tab-group
(when (ignore-errors (require 'helm))
(helm-build-sync-source "Awesome-Tab Group"
:candidates #'awesome-tab-get-groups
:action '(("Switch to group" . awesome-tab-switch-group))))))
;;;###autoload
(defun awesome-tab-counsel-switch-group ()
"Switch group of awesome-tab."
(interactive)
(when (ignore-errors (require 'ivy))
(ivy-read
"Awesome-Tab Groups:"
(awesome-tab-get-groups)
:action #'awesome-tab-switch-group
:caller 'awesome-tab-counsel-switch-group)))
(defun awesome-tab-hide-tab (x)
(let ((name (format "%s" x)))
(or
;; Current window is not dedicated window.
(window-dedicated-p (selected-window))
;; Buffer name not match below blacklist.
(string-prefix-p "*epc" name)
(string-prefix-p "*helm" name)
(string-prefix-p "*Compile-Log*" name)
(string-prefix-p "*lsp" name)
(string-prefix-p "*flycheck" name)
;; Is not magit buffer.
(and (string-prefix-p "magit" name)
(not (file-name-extension name)))
)))
(defun awesome-tab-hide-tab-cached (buf)
(let ((hide (gethash buf awesome-tab-hide-hash 'not-found)))
(when (eq hide 'not-found)
(setq hide (funcall awesome-tab-hide-tab-function buf))
(puthash buf hide awesome-tab-hide-hash))
hide))
(defvar awesome-tab-last-focus-buffer nil
"The last focus buffer.")
(defvar awesome-tab-last-focus-buffer-group nil
"The group name of last focus buffer.")
(defun awesome-tab-remove-nth-element (nth list)
(if (zerop nth) (cdr list)
(let ((last (nthcdr (1- nth) list)))
(setcdr last (cddr last))
list)))
(defun awesome-tab-insert-after (list aft-el el)
"Insert EL after AFT-EL in LIST."
(push el (cdr (member aft-el list)))
list)
(defun awesome-tab-insert-before (list bef-el el)
"Insert EL before BEF-EL in LIST."
(nreverse (awesome-tab-insert-after (nreverse list) bef-el el)))
(advice-add 'load-theme :around #'awesome-tab-load-theme)
(defun awesome-tab-load-theme (orig-fun &optional arg &rest args)
"Update tab after execute `load-theme'."
(apply orig-fun arg args)
(awesome-tab-refresh-display))
(provide 'awesome-tab)
;;; awesome-tab.el ends here