emacs/code/elpa/centaur-tabs-20230607.1501/centaur-tabs-interactive.el

628 lines
24 KiB
EmacsLisp

;;; centaur-tabs-interactive.el --- centaur-tabs interactive functions and plugins support lexical-binding: t; -*-
;; Copyright (C) 2019-2020 Emmanuel Bustos
;; 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 file contains centaur-tabs interactive functions and plugins support
;;; Code:
;;; Requires
(require 'centaur-tabs-elements)
(require 'centaur-tabs-functions)
;;;;;;;;;;;;;;;;;;;;;;; Interactive functions ;;;;;;;;;;;;;;;;;;;;;;;
(defun centaur-tabs-switch-group (&optional groupname)
"Switch tab groups using ido. GROUPNAME can optionaly be provided."
(interactive)
(let* ((tab-buffer-list (cl-mapcar
#'(lambda (b)
(with-current-buffer b
(list (current-buffer)
(buffer-name)
(funcall centaur-tabs-buffer-groups-function) )))
(funcall centaur-tabs-buffer-list-function)))
(groups (centaur-tabs-get-groups))
(group-name (or groupname (centaur-tabs-completing-read "Groups: " groups))) )
(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 centaur-tabs-select-end-tab ()
"Select end tab of current tabset."
(interactive)
(centaur-tabs-select-beg-tab t))
(defun centaur-tabs-select-beg-tab (&optional backward)
"Select beginning tab of current tabs.
If BACKWARD is non-nil, move backward, otherwise move forward.
TYPE is default option."
(interactive)
(let* ((tabset (centaur-tabs-current-tabset t))
(ttabset (centaur-tabs-get-tabsets-tabset))
(_cycle (if (and (eq centaur-tabs-cycle-scope 'groups)
(not (cdr (centaur-tabs-tabs ttabset))))
'tabs
centaur-tabs-cycle-scope))
_selected tab)
(when tabset
(setq tabset (centaur-tabs-tabs tabset)
tab (car (if backward (last tabset) tabset)))
(centaur-tabs-buffer-select-tab tab))))
(defun centaur-tabs-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
(centaur-tabs-forward-tab)
(centaur-tabs-backward-tab))
(other-window -1))
(defun centaur-tabs-forward-tab-other-window ()
"Move to right tab in other window."
(interactive)
(centaur-tabs-backward-tab-other-window t))
(defun centaur-tabs-move-current-tab-to-right ()
"Move current tab one place right, unless it's already the rightmost."
(interactive)
(let* ((bufset (centaur-tabs-current-tabset t))
(old-bufs (centaur-tabs-tabs bufset))
(new-bufs (list))
the-buffer)
(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 Centaur-Tabs's buffer list"))
(setq new-bufs (reverse new-bufs))
(setq new-bufs (append new-bufs (cdr old-bufs)))
(set bufset new-bufs)
(centaur-tabs-set-template bufset nil)
(centaur-tabs-display-update)))
(defun centaur-tabs-move-current-tab-to-left ()
"Move current tab one place left, unless it's already the leftmost."
(interactive)
(let* ((bufset (centaur-tabs-current-tabset t))
(old-bufs (centaur-tabs-tabs bufset))
(first-buf (car old-bufs))
(new-bufs (list))
not-yet-this-buf)
(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 Centaur-Tabs's buffer list"))
(set bufset new-bufs)
(centaur-tabs-set-template bufset nil)
(centaur-tabs-display-update))))
(defmacro centaur-tabs-kill-buffer-match-rule (match-rule)
"If buffer match MATCH-RULE, kill it."
`(save-excursion
(mapc #'(lambda (buffer)
(with-current-buffer buffer
(when (string-equal current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t))))
(when (funcall ,match-rule buffer)
(kill-buffer buffer))
)))
(buffer-list))))
(defun centaur-tabs-kill-all-buffers-in-current-group ()
"Kill all buffers in current group."
(interactive)
(let* ((current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t)))))
;; Kill all buffers in current group.
(centaur-tabs-kill-buffer-match-rule
(lambda (_buffer) t))
;; Switch to next group.
(centaur-tabs-forward-group)
))
(defun centaur-tabs-kill-other-buffers-in-current-group ()
"Kill all buffers except current buffer in current group."
(interactive)
(let* ((current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t))))
(currentbuffer (current-buffer)))
;; Kill all buffers in current group.
(centaur-tabs-kill-buffer-match-rule
(lambda (buffer) (not (equal buffer currentbuffer))))
))
(defun centaur-tabs-kill-unmodified-buffers-in-current-group ()
"Kill all unmodified buffer in current group."
(interactive)
(let* ((current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t))))
(currentbuffer (current-buffer)))
;; Kill all buffers in current group.
(centaur-tabs-kill-buffer-match-rule
(lambda (buffer) (not (buffer-modified-p buffer))))
))
(defun centaur-tabs-kill-match-buffers-in-current-group ()
"Kill all buffers match extension in current group."
(interactive)
(let* ((current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t))))
(extension-names (centaur-tabs-get-extensions))
match-extension)
;; Read extension need to kill.
(setq match-extension (centaur-tabs-completing-read "Kill buffers suffix with: " extension-names))
;; Kill all buffers match extension in current group.
(centaur-tabs-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)
(centaur-tabs-forward-group))
))
(defun centaur-tabs-keep-match-buffers-in-current-group ()
"Keep all buffers match extension in current group."
(interactive)
(let* ((current-group-name (cdr (centaur-tabs-selected-tab (centaur-tabs-current-tabset t))))
(extension-names (centaur-tabs-get-extensions))
match-extension)
;; Read extension need to kill.
(setq match-extension (centaur-tabs-completing-read "Just keep buffers suffix with: " extension-names))
;; Kill all buffers match extension in current group.
(centaur-tabs-kill-buffer-match-rule
(lambda (buffer)
(let ((filename (buffer-file-name buffer)))
(and filename (not (string-equal (file-name-extension filename) match-extension)))
)))
;; Switch to next group if last file killed.
(when (equal (length extension-names) 1)
(centaur-tabs-forward-group))
))
(defun centaur-tabs-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 (centaur-tabs-view centaur-tabs-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 centaur-tabs-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)))
(centaur-tabs-select-visible-nth-tab
(string-to-number (car (last (split-string key-desc "-")))))))
;; ace-jump style tab switching
(defvar centaur-tabs-ace-jump-active nil
"t if centaur-tabs-ace-jump is invoked.")
(defvar centaur-tabs-dim-overlay nil
"Holds the overlay for dimming buffer when invoking centaur-tabs-ace-jump.")
(defun centaur-tabs--dim-window ()
"Create a dim background overlay for the current window."
(when centaur-tabs-ace-jump-dim-buffer
(when centaur-tabs-dim-overlay
(delete-overlay centaur-tabs-dim-overlay))
(setq centaur-tabs-dim-overlay
(let ((ol (make-overlay (window-start) (window-end))))
(overlay-put ol 'face 'centaur-tabs-dim-buffer-face)
ol))))
(defun centaur-tabs-swap-tab (tab)
"Swap the position of current tab with TAB.
TAB has to be in the same group as the current tab."
(if (eq (centaur-tabs-tab-tabset tab) (centaur-tabs-current-tabset t))
(let* ((group (centaur-tabs-current-tabset t))
(tabs (cl-copy-list (centaur-tabs-tabs group)))
(current (centaur-tabs-selected-tab group))
(current-index (cl-position current tabs))
(target-index (cl-position tab tabs)))
(if (eq tab current)
(message "Can't swap with current tab itself.")
(setcar (nthcdr current-index tabs) tab)
(setcar (nthcdr target-index tabs) current)
(set group tabs)
(centaur-tabs-set-template (centaur-tabs-current-tabset t) nil)
(centaur-tabs-display-update)))
(message "Error: %s is not in the same group as the current tab." tab)))
(defun centaur-tabs-ace-action (action)
"Preform ACTION on a visible tab. Ace-jump style.
ACTION has to be one of value in `centuar-tabs-ace-dispatch-alist'"
(when (centaur-tabs-current-tabset t)
(when centaur-tabs-ace-jump-dim-buffer
(centaur-tabs--dim-window))
(cond ((eq action 'jump-to-tab)
(message "Jump to tab: "))
((eq action 'close-tab)
(message "Close tab: "))
((eq action 'swap-tab)
(message "Swap current tab with: ")))
(let ((centaur-tabs-ace-jump-active t))
(catch 'done
(while t
(centaur-tabs-set-template (centaur-tabs-current-tabset t) nil)
(centaur-tabs-display-update)
(let ((char (read-key)) (action-cache))
(cond
;; tab keys
((memq char centaur-tabs-ace-jump-keys)
(let ((sel (nth (cl-position char centaur-tabs-ace-jump-keys) (centaur-tabs-view (centaur-tabs-current-tabset t)))))
(cond ((eq sel nil)
(message "Tab %s does not exist" (key-description (vector char))))
((eq action 'jump-to-tab)
(centaur-tabs-buffer-select-tab sel))
((eq action 'close-tab)
(centaur-tabs-buffer-close-tab sel))
((eq action 'swap-tab)
(centaur-tabs-swap-tab sel))))
(throw 'done nil))
;; actions
((setq action-cache (assoc char centuar-tabs-ace-dispatch-alist))
(setq action-cache (cadr action-cache))
(cond ((eq action-cache 'exit) ; exit
(message "Quit")
(throw 'done nil))
((eq action-cache 'forward-group) ; forward group
(message "Forward group")
(centaur-tabs-forward-group)
(centaur-tabs--dim-window))
((eq action-cache 'backward-group) ; backward group
(message "Backward group")
(centaur-tabs-backward-group)
(centaur-tabs--dim-window))
((eq action-cache 'show-help) ; help menu
(message "%s" (mapconcat
(lambda (elem) (format "%s: %s"
(key-description (vector (car elem)))
(caddr elem)))
centuar-tabs-ace-dispatch-alist
"\n")))
(t (setq action action-cache) ; other actions
(cond ((eq action-cache 'jump-to-tab)
(message "Jump to tab: "))
((eq action-cache 'close-tab)
(message "Close tab: "))
((eq action-cache 'swap-tab)
(message "Swap current tab with: "))))))
;; no match, repeat
(t
(message "No such candidate: %s, hit ? for help." (key-description (vector char)))))))))
(centaur-tabs-set-template (centaur-tabs-current-tabset t) nil)
(when centaur-tabs-ace-jump-dim-buffer
(delete-overlay centaur-tabs-dim-overlay)
(setq centaur-tabs-dim-overlay nil))
(centaur-tabs-display-update)))
(defun centaur-tabs-ace-jump (&optional arg)
"Select a tab and perform an action. Ace-jump style.
If no ARG is provided, select that tab.
If prefixed with one `universal-argument', swap the current
tab with the selected tab.
If prefixed with two `universal-argument's, close
selected tab."
(interactive "p")
(cond ((eq arg 1)
(centaur-tabs-ace-action 'jump-to-tab))
((eq arg 4)
(centaur-tabs-ace-action 'swap-tab))
((eq arg 16)
(centaur-tabs-ace-action 'close-tab))
(t
(centaur-tabs-ace-action 'jump-to-tab))))
(defun centaur-tabs-group-buffer-groups ()
"Use centaur-tabs's own buffer grouping function."
(interactive)
(setq centaur-tabs-buffer-groups-function 'centaur-tabs-buffer-groups)
(centaur-tabs-display-update))
;; Projectile integration. Taken from tabbar-ruler
(defvar centaur-tabs-projectile-buffer-group-calc nil
"Set buffer groups for projectile.
Should be buffer local and speed up calculation of buffer groups.")
(defun centaur-tabs-projectile-buffer-groups ()
"Return the list of group names BUFFER belongs to."
(if centaur-tabs-projectile-buffer-group-calc
(symbol-value 'centaur-tabs-projectile-buffer-group-calc)
(set (make-local-variable 'centaur-tabs-projectile-buffer-group-calc)
(cond
((or (get-buffer-process (current-buffer)) (memq major-mode '(comint-mode compilation-mode))) '("Term"))
((string-equal "*" (substring (buffer-name) 0 1)) '("Misc"))
((condition-case _err
(projectile-project-root)
(error nil)) (list (projectile-project-name)))
((memq major-mode '(emacs-lisp-mode python-mode emacs-lisp-mode c-mode
c++-mode javascript-mode js-mode
js2-mode makefile-mode
lua-mode vala-mode)) '("Coding"))
((memq major-mode '(nxhtml-mode html-mode
mhtml-mode css-mode)) '("HTML"))
((memq major-mode '(org-mode calendar-mode diary-mode)) '("Org"))
((memq major-mode '(dired-mode)) '("Dir"))
(t '("Other"))))
(symbol-value 'centaur-tabs-projectile-buffer-group-calc)))
(defun centaur-tabs-group-by-projectile-project()
"Group by projectile project."
(interactive)
(setq centaur-tabs-buffer-groups-function 'centaur-tabs-projectile-buffer-groups)
(centaur-tabs-display-update))
;; Show groups instead of tabs
(defun centaur-tabs-toggle-groups ()
"Show group names on the tabs instead of buffer names."
(interactive)
(centaur-tabs-buffer-show-groups (not centaur-tabs--buffer-show-groups))
(centaur-tabs-display-update))
;; Helm source for switching group in helm.
(defun centaur-tabs-build-helm-source ()
"Display a list of current buffer groups in Helm."
(interactive)
(setq helm-source-centaur-tabs-group
(when (featurep 'helm)
(require 'helm)
(helm-build-sync-source "Centaur-Tabs Group"
:candidates #'centaur-tabs-get-groups
:action '(("Switch to group" . centaur-tabs-switch-group))))))
;; Ivy source for switching group in ivy.
;;;###autoload
(defun centaur-tabs-counsel-switch-group ()
"Display a list of current buffer groups using Counsel."
(interactive)
(when (featurep 'ivy)
(require 'ivy)
(ivy-read
"Centaur Tabs Groups:"
(centaur-tabs-get-groups)
:action #'centaur-tabs-switch-group
:caller 'centaur-tabs-counsel-switch-group)))
(defun centaur-tabs-extract-window-to-new-frame()
"Kill the current window in the current frame, and open the current buffer in a new frame."
(interactive)
(unless (centaur-tabs--one-window-p)
(let ((buffer (current-buffer)))
(delete-window)
(display-buffer-pop-up-frame buffer nil))))
(defun centaur-tabs--copy-file-name-to-clipboard ()
"Copy the current buffer file name to the clipboard."
;;; From https://emacsredux.com/blog/2013/03/27/copy-filename-to-the-clipboard/
(interactive)
(let* ((filename (if (equal major-mode 'dired-mode)
default-directory
(buffer-file-name)))
(filename (expand-file-name filename)))
(when filename
(kill-new filename)
(message "Copied buffer file name '%s' to the kill ring." filename))))
(defun centaur-tabs-open-directory-in-external-application ()
"Open the current directory in a external application."
(interactive)
(centaur-tabs--open-externally default-directory))
(defun centaur-tabs-open-in-external-application ()
"Open the file of the current buffer according to its mime type."
(interactive)
(let ((path (if (buffer-file-name) (buffer-file-name) default-directory)))
(centaur-tabs--open-externally path)))
(defun centaur-tabs--open-externally (file-or-path)
"Open FILE-OR-PATH according to its mime type in an external application.
FILE-OR-PATH is expanded with `expand-file-name`.
Modified copy of `treemacs-visit-node-in-external-application`."
(let ((path (expand-file-name file-or-path)))
(pcase system-type
('windows-nt
(declare-function w32-shell-execute "w32fns.c")
(w32-shell-execute "open" (replace-regexp-in-string "/" "\\" path t t)))
('darwin
(shell-command (format "open \"%s\"" path)))
('gnu/linux
(let ((process-connection-type nil))
(start-process "" nil "xdg-open" path)))
(_ (message "Don't know how to open files on %s." (symbol-name system-type))))))
(defun centaur-tabs--copy-directory-name-to-clipboard ()
"Copy the current directory name to the clipboard."
(interactive)
(when default-directory
(kill-new default-directory)
(message "Copied directory name '%s' to the kill ring." (expand-file-name default-directory))))
(defun centaur-tabs--tab-submenu-groups-definition ()
"Menu definition with a list of tab groups."
(mapcar (lambda (s) `[,s ,s]) (sort (centaur-tabs-get-groups) #'string<)))
(defun centaur-tabs--tab-submenu-tabs-definition ()
"Menu definition with a list of tabs for the current group."
(let* ((tabset (centaur-tabs-get-tabset centaur-tabs-last-focused-buffer-group))
(tabs-in-group (centaur-tabs-tabs tabset))
(buffers (mapcar #'centaur-tabs-tab-value tabs-in-group))
(sorted-tabnames (sort (mapcar #'buffer-name buffers) #'string<)))
(mapcar (lambda (s) `[,s ,s]) sorted-tabnames)))
(defvar centaur-tabs--groups-submenu-key "Tab groups")
(defvar centaur-tabs--tabs-submenu-key "Go to tab of group")
(defun centaur-tabs--kill-this-buffer-dont-ask()
"Kill the current buffer without confirmation."
(interactive)
(kill-buffer (current-buffer))
(centaur-tabs-display-update)
(redisplay t))
(defun centaur-tabs--tab-menu-definition ()
"Definition of the context menu of a tab."
`(["Kill this buffer" centaur-tabs--kill-this-buffer-dont-ask]
["Kill other buffers of group" centaur-tabs-kill-other-buffers-in-current-group]
["Kill unmodified buffers of group" centaur-tabs-kill-unmodified-buffers-in-current-group]
"----"
["Split below" split-window-below]
["Split right" split-window-right]
"----"
["Maximize tab" delete-other-windows
:active (null (centaur-tabs--one-window-p))]
["Extract to new frame" centaur-tabs-extract-window-to-new-frame
:active (null (centaur-tabs--one-window-p))]
["Duplicate in new frame" make-frame-command]
"----"
["Copy filepath" centaur-tabs--copy-file-name-to-clipboard
:active (buffer-file-name)]
["Copy directory path" centaur-tabs--copy-directory-name-to-clipboard
:active default-directory]
["Open in external application" centaur-tabs-open-in-external-application
:active (or (buffer-file-name) default-directory)]
["Open directory in dired" dired-jump
:active (not (eq major-mode 'dired-mode))]
["Open directory externally" centaur-tabs-open-directory-in-external-application
:active default-directory]
"----"
,( append (list centaur-tabs--groups-submenu-key) (centaur-tabs--tab-submenu-groups-definition))
,( append (list centaur-tabs--tabs-submenu-key) (centaur-tabs--tab-submenu-tabs-definition))
))
(defun centaur-tabs--one-window-p ()
"Like `one-window-p`, but taking into account side windows like treemacs."
(let* ((mainwindow (window-main-window))
(child-count (window-child-count mainwindow)))
(= 0 child-count)))
(defun centaur-tabs--get-tab-from-name (tabname)
"Get the tab from the current group given de TABNAME."
(let ((seq (centaur-tabs-tabs (centaur-tabs-get-tabset centaur-tabs-last-focused-buffer-group))))
(cl-find-if
(lambda (tab) (string= tabname (buffer-name (centaur-tabs-tab-value tab))))
seq)))
(defun centaur-tabs--tab-menu (event)
"Show a context menu for the clicked tab or button. The clicked tab, identified by EVENT, is selected."
(interactive "e" )
(let ((click-on-tab-p (ignore-errors (centaur-tabs-get-tab-from-event event))))
(when (not click-on-tab-p)
(centaur-tabs--groups-menu))
(when click-on-tab-p
(centaur-tabs-do-select event)
(redisplay t)
(let*
((menu (easy-menu-create-menu nil (centaur-tabs--tab-menu-definition)))
(choice (x-popup-menu t menu))
(action (lookup-key menu (apply 'vector choice)))
(action-is-command-p (and (commandp action) (functionp action))))
(when action-is-command-p
(call-interactively action))
(when (not action-is-command-p)
(let* ((menu-key (first choice))
(choice-is-group-p (string= centaur-tabs--groups-submenu-key (symbol-name menu-key)))
(name (car (last choice)))
(name-as-string (symbol-name name)))
(if choice-is-group-p
(centaur-tabs-switch-group name-as-string)
(switch-to-buffer name-as-string))))))))
(defun centaur-tabs--groups-menu ()
"Show a popup menu with the centaur tabs groups."
(interactive)
(let*
((sorted-groups (centaur-tabs--tab-submenu-groups-definition))
(menu (easy-menu-create-menu "Tab groups" (centaur-tabs--tab-submenu-groups-definition)))
(choice (x-popup-menu t menu))
(action (lookup-key menu (apply 'vector choice)))
(action-is-command-p (and (commandp action) (functionp action))))
(when action-is-command-p
(call-interactively action))
(when (not action-is-command-p)
(let ((group (car (last choice))))
(centaur-tabs-switch-group (format "%s" group))))))
(provide 'centaur-tabs-interactive)
;;; centaur-tabs-interactive.el ends here