Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| ;;; eyebrowse.el --- Easy window config switching -*- lexical-binding: t; -*- | |
| ;; Copyright (C) 2014-2016 Vasilij Schneidermann <v.schneidermann@gmail.com> | |
| ;; Author: Vasilij Schneidermann <v.schneidermann@gmail.com> | |
| ;; URL: https://github.com/wasamasa/eyebrowse | |
| ;; Version: 0.7.7 | |
| ;; Package-Requires: ((dash "2.7.0") (emacs "24.3.1")) | |
| ;; Keywords: convenience | |
| ;; This file is NOT part of GNU Emacs. | |
| ;; This file 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 file 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 GNU Emacs; see the file COPYING. If not, write to | |
| ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
| ;; Boston, MA 02111-1307, USA. | |
| ;;; Commentary: | |
| ;; This global minor mode provides a set of keybindings for switching | |
| ;; window configurations. It tries mimicking the tab behaviour of | |
| ;; `ranger`, a file manager. | |
| ;; See the README for more info: | |
| ;; https://github.com/wasamasa/eyebrowse | |
| ;;; Code: | |
| (require 'dash) | |
| (require 'format-spec) | |
| ;;; variables | |
| (defgroup eyebrowse nil | |
| "A window configuration switcher modeled after the ranger file | |
| manager." | |
| :group 'convenience | |
| :prefix "eyebrowse-") | |
| (defcustom eyebrowse-keymap-prefix (kbd "C-c C-w") | |
| "Prefix key for key-bindings." | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defface eyebrowse-mode-line-delimiters | |
| '((t (nil))) | |
| "Face for the mode line indicator delimiters." | |
| :group 'eyebrowse) | |
| (defface eyebrowse-mode-line-separator | |
| '((t (nil))) | |
| "Face for the mode line indicator separator." | |
| :group 'eyebrowse) | |
| (defface eyebrowse-mode-line-inactive | |
| '((t (nil))) | |
| "Face for the inactive items of the mode line indicator." | |
| :group 'eyebrowse) | |
| (defface eyebrowse-mode-line-active | |
| '((t (:inherit mode-line-emphasis))) | |
| "Face for the active items of the mode line indicator." | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-mode-line-separator ", " | |
| "Separator of the mode line indicator." | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-mode-line-left-delimiter "[" | |
| "Left delimiter of the mode line indicator." | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-mode-line-right-delimiter "]" | |
| "Right delimiter of the mode line indicator." | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-mode-line-style 'smart | |
| "The mode line indicator style may be one of the following: | |
| nil, 'hide: Don't show at all. | |
| 'smart: Hide when only one window config. | |
| t, 'always: Always show." | |
| :type '(choice (const :tag "Hide" hide) | |
| (const :tag "Smart" smart) | |
| (const :tag "Always" always)) | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-wrap-around nil | |
| "Wrap around when switching to the next/previous window config? | |
| If t, wrap around." | |
| :type 'boolean | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-switch-back-and-forth nil | |
| "Switch to the last window automatically? | |
| If t, switching to the same window config as | |
| `eyebrowse-current-window-config', switches to | |
| `eyebrowse-last-window-config'." | |
| :type 'boolean | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-new-workspace nil | |
| "Type of the new workspace. | |
| It may be one of the following: | |
| nil: Clone last workspace. | |
| string value: Clean up and display a buffer with that name. If | |
| necessary, create the buffer beforehand. | |
| symbol name: Clean up and call the specified function. | |
| t: Clean up and display the scratch buffer." | |
| :type '(choice (const :tag "Clone last workspace." nil) | |
| (string :tag "Switch to buffer name.") | |
| (function :tag "Initialize with function.") | |
| (const :tag "Switch to scratch buffer." t)) | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-pre-window-switch-hook nil | |
| "Hook run before switching to a window config." | |
| :type 'hook | |
| :group 'eyebrowse) | |
| (add-hook 'eyebrowse-pre-window-switch-hook 'deactivate-mark) | |
| (defcustom eyebrowse-post-window-switch-hook nil | |
| "Hook run after switching to a window config." | |
| :type 'hook | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-default-workspace-slot 1 | |
| "Slot number assigned to the default workspace." | |
| :type 'integer | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-slot-format "%s" | |
| "Format string for untagged slots. | |
| The following format codes are supported: | |
| %s: Current slot" | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-tagged-slot-format "%s:%t" | |
| "Format string for tagged slots. | |
| The following format codes are supported: | |
| %t: Tag | |
| %s: Current slot" | |
| :type 'string | |
| :group 'eyebrowse) | |
| (defcustom eyebrowse-close-window-config-prompt nil | |
| "Ask user for confirmation when closing a window config? | |
| If t, ask for confirmation." | |
| :type 'boolean | |
| :group 'eyebrowse) | |
| (defvar eyebrowse-mode-map | |
| (let ((map (make-sparse-keymap))) | |
| (let ((prefix-map (make-sparse-keymap))) | |
| (define-key prefix-map (kbd "<") 'eyebrowse-prev-window-config) | |
| (define-key prefix-map (kbd ">") 'eyebrowse-next-window-config) | |
| (define-key prefix-map (kbd "'") 'eyebrowse-last-window-config) | |
| (define-key prefix-map (kbd "\"") 'eyebrowse-close-window-config) | |
| (define-key prefix-map (kbd ",") 'eyebrowse-rename-window-config) | |
| (define-key prefix-map (kbd ".") 'eyebrowse-switch-to-window-config) | |
| (define-key prefix-map (kbd "0") 'eyebrowse-switch-to-window-config-0) | |
| (define-key prefix-map (kbd "1") 'eyebrowse-switch-to-window-config-1) | |
| (define-key prefix-map (kbd "2") 'eyebrowse-switch-to-window-config-2) | |
| (define-key prefix-map (kbd "3") 'eyebrowse-switch-to-window-config-3) | |
| (define-key prefix-map (kbd "4") 'eyebrowse-switch-to-window-config-4) | |
| (define-key prefix-map (kbd "5") 'eyebrowse-switch-to-window-config-5) | |
| (define-key prefix-map (kbd "6") 'eyebrowse-switch-to-window-config-6) | |
| (define-key prefix-map (kbd "7") 'eyebrowse-switch-to-window-config-7) | |
| (define-key prefix-map (kbd "8") 'eyebrowse-switch-to-window-config-8) | |
| (define-key prefix-map (kbd "9") 'eyebrowse-switch-to-window-config-9) | |
| (define-key prefix-map (kbd "c") 'eyebrowse-create-window-config) | |
| (define-key prefix-map (kbd "C-c") 'eyebrowse-create-window-config) | |
| (define-key map eyebrowse-keymap-prefix prefix-map)) | |
| map) | |
| "Initial key map for `eyebrowse-mode'.") | |
| ;;; functions | |
| (defun eyebrowse--get (type &optional frame) | |
| "Retrieve frame-specific value of TYPE. | |
| If FRAME is nil, use current frame. TYPE can be any of | |
| 'window-configs, 'current-slot, 'last-slot." | |
| (cond | |
| ((eq type 'window-configs) | |
| (frame-parameter frame 'eyebrowse-window-configs)) | |
| ((eq type 'current-slot) | |
| (frame-parameter frame 'eyebrowse-current-slot)) | |
| ((eq type 'last-slot) | |
| (frame-parameter frame 'eyebrowse-last-slot)))) | |
| (defun eyebrowse--set (type value &optional frame) | |
| "Set frame-specific value of TYPE to VALUE. | |
| If FRAME is nil, use current frame. TYPE can be any of | |
| 'window-configs, 'current-slot, 'last-slot." | |
| (cond | |
| ((eq type 'window-configs) | |
| (set-frame-parameter frame 'eyebrowse-window-configs value)) | |
| ((eq type 'current-slot) | |
| (set-frame-parameter frame 'eyebrowse-current-slot value)) | |
| ((eq type 'last-slot) | |
| (set-frame-parameter frame 'eyebrowse-last-slot value)))) | |
| (put 'eyebrowse--set 'lisp-indent-function 1) | |
| (defun eyebrowse-init (&optional frame) | |
| "Initialize Eyebrowse for the current frame." | |
| (unless (eyebrowse--get 'window-configs frame) | |
| (eyebrowse--set 'last-slot eyebrowse-default-workspace-slot frame) | |
| (eyebrowse--set 'current-slot eyebrowse-default-workspace-slot frame) | |
| (eyebrowse--insert-in-window-config-list | |
| (eyebrowse--current-window-config eyebrowse-default-workspace-slot "") | |
| frame))) | |
| (defun eyebrowse--update-window-config-element (new-element) | |
| "Replace the old element with NEW-ELEMENT in the window config list. | |
| The old element is identified by the first element of NEW-ELEMENT." | |
| (eyebrowse--set 'window-configs | |
| (--replace-where (= (car it) (car new-element)) | |
| new-element (eyebrowse--get 'window-configs)))) | |
| (defun eyebrowse--insert-in-window-config-list (element &optional frame) | |
| "Insert ELEMENT in the list of window configs. | |
| This function keeps the sortedness intact." | |
| (let* ((window-configs (eyebrowse--get 'window-configs frame)) | |
| (index (--find-last-index (< (car it) (car element)) window-configs))) | |
| (eyebrowse--set 'window-configs | |
| (-insert-at (if index (1+ index) 0) element window-configs) frame))) | |
| (defun eyebrowse--window-config-present-p (slot &optional frame) | |
| "Non-nil if there is a window config at SLOT." | |
| (assq slot (eyebrowse--get 'window-configs frame))) | |
| (defun eyebrowse--current-window-config (slot tag) | |
| "Returns a window config list appliable for SLOT." | |
| (list slot (window-state-get nil t) tag)) | |
| (defun eyebrowse--dotted-list-p (list) | |
| "Non-nil if LIST is terminated by a non-nil value." | |
| (cdr (last list))) | |
| (defun eyebrowse--walk-window-config (window-config function) | |
| "Walk through WINDOW-CONFIG and apply FUNCTION to each leaf." | |
| (dolist (item window-config) | |
| (when (consp item) | |
| (when (symbolp (car item)) | |
| (funcall function item)) | |
| (when (and (consp (cdr item)) | |
| (not (eyebrowse--dotted-list-p (cdr item)))) | |
| (eyebrowse--walk-window-config (cdr item) function))))) | |
| (defun eyebrowse--fixup-window-config (window-config) | |
| "Walk through WINDOW-CONFIG and fix it up destructively. | |
| If a no longer existent buffer is encountered, it is replaced | |
| with the scratch buffer." | |
| (eyebrowse--walk-window-config | |
| window-config | |
| (lambda (item) | |
| (when (eq (car item) 'buffer) | |
| (let* ((buffer-name (cadr item)) | |
| (buffer (get-buffer buffer-name))) | |
| (when (not buffer) | |
| (message "Replaced deleted %s buffer with *scratch*" buffer-name) | |
| (setf (cadr item) "*scratch*"))))))) | |
| (defun eyebrowse--rename-window-config-buffers (window-config old new) | |
| "Walk through WINDOW-CONFIG and rename buffers when appropriate. | |
| If a buffer name equal to OLD is found, it is replaced with NEW." | |
| (eyebrowse--walk-window-config | |
| window-config | |
| (lambda (item) | |
| (when (eq (car item) 'buffer) | |
| (let ((buffer-name (cadr item))) | |
| (when (equal buffer-name old) | |
| (setf (cadr item) new))))))) | |
| (defadvice rename-buffer (around eyebrowse-fixup-window-configs activate) | |
| "Replace buffer names in all window configs." | |
| (let ((old (buffer-name))) | |
| ad-do-it | |
| (let ((new ad-return-value)) | |
| (dolist (frame (frame-list)) | |
| (dolist (window-config (eyebrowse--get 'window-configs frame)) | |
| (eyebrowse--rename-window-config-buffers window-config old new)))) | |
| ad-return-value)) | |
| (defun eyebrowse--load-window-config (slot) | |
| "Restore the window config from SLOT." | |
| (-when-let (match (assq slot (eyebrowse--get 'window-configs))) | |
| ;; KLUDGE: workaround for #36 | |
| ;; see also http://debbugs.gnu.org/cgi/bugreport.cgi?bug=20848 | |
| (when (version< emacs-version "25") | |
| (delete-other-windows) | |
| (set-window-dedicated-p nil nil)) | |
| ;; KLUDGE: workaround for visual-fill-column foolishly | |
| ;; setting the split-window parameter | |
| (let ((ignore-window-parameters t) | |
| (window-config (cadr match))) | |
| (eyebrowse--fixup-window-config window-config) | |
| (window-state-put window-config (frame-root-window) 'safe)))) | |
| (defun eyebrowse--string-to-number (x) | |
| "Version of `string-to-number' that returns nil if not a number." | |
| (let ((result (string-to-number x))) | |
| (if (and (zerop result) | |
| (not (string-match-p (rx bos (* white) "0") x))) | |
| nil | |
| result))) | |
| (defun eyebrowse--read-slot () | |
| "Read in a window config SLOT to switch to. | |
| A formatted list of window configs is presented as candidates. | |
| If no match was found, the user input is interpreted as a new | |
| slot to switch to." | |
| (let* ((candidates (--map (cons (eyebrowse-format-slot it) | |
| (car it)) | |
| (eyebrowse--get 'window-configs))) | |
| (candidate (completing-read "Enter slot: " candidates)) | |
| (choice (cdr (assoc candidate candidates)))) | |
| (or choice (eyebrowse--string-to-number candidate) | |
| (user-error "Invalid slot number")))) | |
| (defun eyebrowse-switch-to-window-config (slot) | |
| "Switch to the window config SLOT. | |
| This will save the current window config to | |
| `eyebrowse-current-slot' first, then switch. If | |
| `eyebrowse-switch-back-and-forth' is t and | |
| `eyebrowse-current-slot' equals SLOT, this will switch to the | |
| last window config." | |
| (interactive (list (if (numberp current-prefix-arg) | |
| current-prefix-arg | |
| (eyebrowse--read-slot)))) | |
| (when slot | |
| (let* ((current-slot (eyebrowse--get 'current-slot)) | |
| (window-configs (eyebrowse--get 'window-configs)) | |
| (current-tag (nth 2 (assoc current-slot window-configs))) | |
| (last-slot (eyebrowse--get 'last-slot))) | |
| (when (and eyebrowse-switch-back-and-forth (= current-slot slot)) | |
| (setq slot last-slot)) | |
| (let ((new-window-config (not (eyebrowse--window-config-present-p slot)))) | |
| (when (/= current-slot slot) | |
| (run-hooks 'eyebrowse-pre-window-switch-hook) | |
| (eyebrowse--update-window-config-element | |
| (eyebrowse--current-window-config current-slot current-tag)) | |
| (when new-window-config | |
| (eyebrowse--insert-in-window-config-list | |
| (eyebrowse--current-window-config slot ""))) | |
| (eyebrowse--load-window-config slot) | |
| (eyebrowse--set 'last-slot current-slot) | |
| (eyebrowse--set 'current-slot slot) | |
| (when (and new-window-config eyebrowse-new-workspace) | |
| (delete-other-windows) | |
| (cond | |
| ((stringp eyebrowse-new-workspace) | |
| (switch-to-buffer (get-buffer-create eyebrowse-new-workspace))) | |
| ((functionp eyebrowse-new-workspace) | |
| (funcall eyebrowse-new-workspace)) | |
| (t (switch-to-buffer "*scratch*")))) | |
| (run-hooks 'eyebrowse-post-window-switch-hook)))))) | |
| (defun eyebrowse-next-window-config (count) | |
| "Switch to the next available window config. | |
| If `eyebrowse-wrap-around' is t, this will switch from the last | |
| to the first one. When used with a numerical argument, switch to | |
| window config COUNT." | |
| (interactive "P") | |
| (let* ((window-configs (eyebrowse--get 'window-configs)) | |
| (match (assq (eyebrowse--get 'current-slot) window-configs)) | |
| (index (-elem-index match window-configs))) | |
| (if count | |
| (eyebrowse-switch-to-window-config count) | |
| (when index | |
| (if (< (1+ index) (length window-configs)) | |
| (eyebrowse-switch-to-window-config | |
| (car (nth (1+ index) window-configs))) | |
| (when eyebrowse-wrap-around | |
| (eyebrowse-switch-to-window-config | |
| (caar window-configs)))))))) | |
| (defun eyebrowse-prev-window-config (count) | |
| "Switch to the previous available window config. | |
| If `eyebrowse-wrap-around' is t, this will switch from the | |
| first to the last one. When used with a numerical argument, | |
| switch COUNT window configs backwards and always wrap around." | |
| (interactive "P") | |
| (let* ((window-configs (eyebrowse--get 'window-configs)) | |
| (match (assq (eyebrowse--get 'current-slot) window-configs)) | |
| (index (-elem-index match window-configs))) | |
| (if count | |
| (let ((eyebrowse-wrap-around t)) | |
| (eyebrowse-prev-window-config | |
| (when (> count 1) | |
| (eyebrowse-prev-window-config (1- count))))) | |
| (when index | |
| (if (> index 0) | |
| (eyebrowse-switch-to-window-config | |
| (car (nth (1- index) window-configs))) | |
| (when eyebrowse-wrap-around | |
| (eyebrowse-switch-to-window-config | |
| (caar (last window-configs))))))))) | |
| (defun eyebrowse-last-window-config () | |
| "Switch to the last window config." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config (eyebrowse--get 'last-slot))) | |
| (defun eyebrowse--delete-window-config (slot) | |
| "Remove the window config at SLOT." | |
| (let ((window-configs (eyebrowse--get 'window-configs))) | |
| (eyebrowse--set 'window-configs | |
| (remove (assq slot window-configs) window-configs)))) | |
| (defun eyebrowse-close-window-config () | |
| "Close the current window config. | |
| This removes it from `eyebrowse-window-configs' and switches to | |
| another appropriate window config." | |
| (interactive) | |
| (let ((window-configs (eyebrowse--get 'window-configs))) | |
| (when (and (> (length window-configs) 1) | |
| (or (not eyebrowse-close-window-config-prompt) | |
| (yes-or-no-p "Close current window config?"))) | |
| (if (equal (assq (eyebrowse--get 'current-slot) window-configs) | |
| (car (last window-configs))) | |
| (eyebrowse-prev-window-config nil) | |
| (eyebrowse-next-window-config nil)) | |
| (eyebrowse--delete-window-config (eyebrowse--get 'last-slot))))) | |
| (defun eyebrowse-rename-window-config (slot tag) | |
| "Rename the window config at SLOT to TAG. | |
| When used interactively, default to the current window config, | |
| use the prefix argument to prompt for a slot or a numerical | |
| prefix argument to select a slot by its number." | |
| (interactive (list (cond | |
| ((consp current-prefix-arg) | |
| (eyebrowse--read-slot)) | |
| ((numberp current-prefix-arg) | |
| current-prefix-arg) | |
| (t (eyebrowse--get 'current-slot))) | |
| nil)) | |
| (let* ((window-configs (eyebrowse--get 'window-configs)) | |
| (window-config (assoc slot window-configs)) | |
| (current-tag (nth 2 window-config)) | |
| (tag (or tag (read-string "Tag: " current-tag)))) | |
| (setf (nth 2 window-config) tag))) | |
| ;; NOTE I've tried out generating the respective commands dynamically | |
| ;; with a macro, but this ended in unreadable code and Emacs not being | |
| ;; able to locate the generated commands, using lexical binding and a | |
| ;; loop resulted in very fun looking key bindings with closures in the | |
| ;; command description. That's why I gave up and just wrote out the | |
| ;; first ten commands instead. | |
| (defun eyebrowse-switch-to-window-config-0 () | |
| "Switch to window configuration 0." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 0)) | |
| (defun eyebrowse-switch-to-window-config-1 () | |
| "Switch to window configuration 1." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 1)) | |
| (defun eyebrowse-switch-to-window-config-2 () | |
| "Switch to window configuration 2." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 2)) | |
| (defun eyebrowse-switch-to-window-config-3 () | |
| "Switch to window configuration 3." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 3)) | |
| (defun eyebrowse-switch-to-window-config-4 () | |
| "Switch to window configuration 4." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 4)) | |
| (defun eyebrowse-switch-to-window-config-5 () | |
| "Switch to window configuration 5." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 5)) | |
| (defun eyebrowse-switch-to-window-config-6 () | |
| "Switch to window configuration 6." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 6)) | |
| (defun eyebrowse-switch-to-window-config-7 () | |
| "Switch to window configuration 7." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 7)) | |
| (defun eyebrowse-switch-to-window-config-8 () | |
| "Switch to window configuration 8." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 8)) | |
| (defun eyebrowse-switch-to-window-config-9 () | |
| "Switch to window configuration 9." | |
| (interactive) | |
| (eyebrowse-switch-to-window-config 9)) | |
| (defun eyebrowse-free-slot (slots) | |
| "Returns a yet unoccupied slot. | |
| The specific behaviour is tmux-like." | |
| (let ((min (car slots))) | |
| (if (> min 1) | |
| 1 | |
| (let (last cur done) | |
| (while (and slots (not done)) | |
| (setq last (car slots) | |
| cur (cadr slots)) | |
| (when (and last cur | |
| (> (- cur last) 1)) | |
| (setq done t)) | |
| (setq slots (cdr slots))) | |
| (1+ last))))) | |
| (defun eyebrowse-create-window-config () | |
| "Creates a window config at a yet unoccupied slot." | |
| (interactive) | |
| (let* ((window-configs (eyebrowse--get 'window-configs)) | |
| (slots (mapcar 'car window-configs)) | |
| (slot (eyebrowse-free-slot slots))) | |
| (eyebrowse-switch-to-window-config slot))) | |
| ;;;###autoload | |
| (defun eyebrowse-setup-evil-keys () | |
| "Set up key bindings specific to Evil. | |
| Currently only gt, gT, gc and zx are supported." | |
| (define-key evil-motion-state-map (kbd "gt") 'eyebrowse-next-window-config) | |
| (define-key evil-motion-state-map (kbd "gT") 'eyebrowse-prev-window-config) | |
| (define-key evil-motion-state-map (kbd "gc") 'eyebrowse-close-window-config) | |
| (define-key evil-motion-state-map (kbd "zx") 'eyebrowse-last-window-config)) | |
| ;;;###autoload | |
| (defun eyebrowse-setup-opinionated-keys () | |
| "Set up more opinionated key bindings for using eyebrowse. | |
| M-0..M-9, C-< / C->, C-'and C-\" are used for switching. If Evil | |
| is detected, extra key bindings will be set up with | |
| `eyebrowse-setup-evil-keys' as well." | |
| (let ((map eyebrowse-mode-map)) | |
| (when (bound-and-true-p evil-mode) | |
| (eyebrowse-setup-evil-keys)) | |
| (define-key map (kbd "C-<") 'eyebrowse-prev-window-config) | |
| (define-key map (kbd "C->") 'eyebrowse-next-window-config) | |
| (define-key map (kbd "C-'") 'eyebrowse-last-window-config) | |
| (define-key map (kbd "C-\"") 'eyebrowse-close-window-config) | |
| (define-key map (kbd "M-0") 'eyebrowse-switch-to-window-config-0) | |
| (define-key map (kbd "M-1") 'eyebrowse-switch-to-window-config-1) | |
| (define-key map (kbd "M-2") 'eyebrowse-switch-to-window-config-2) | |
| (define-key map (kbd "M-3") 'eyebrowse-switch-to-window-config-3) | |
| (define-key map (kbd "M-4") 'eyebrowse-switch-to-window-config-4) | |
| (define-key map (kbd "M-5") 'eyebrowse-switch-to-window-config-5) | |
| (define-key map (kbd "M-6") 'eyebrowse-switch-to-window-config-6) | |
| (define-key map (kbd "M-7") 'eyebrowse-switch-to-window-config-7) | |
| (define-key map (kbd "M-8") 'eyebrowse-switch-to-window-config-8) | |
| (define-key map (kbd "M-9") 'eyebrowse-switch-to-window-config-9))) | |
| (defun eyebrowse-format-slot (window-config) | |
| (let* ((slot (car window-config)) | |
| (tag (nth 2 window-config)) | |
| (format-string (if (and tag (> (length tag) 0)) | |
| eyebrowse-tagged-slot-format | |
| eyebrowse-slot-format)) | |
| ;; NOTE: `format-spec' sets `deactivate-mark' to t which | |
| ;; makes `eyebrowse-format-slot' usage in | |
| ;; `eyebrowse-mode-line-indicator' always deactivate the mark | |
| ;; after activating it as this triggers mode line updates... | |
| deactivate-mark) | |
| (format-spec format-string | |
| (format-spec-make ?s slot ?t tag)))) | |
| (defun eyebrowse-mode-line-indicator () | |
| "Return a string representation of the window configurations." | |
| (let* ((left-delimiter (propertize eyebrowse-mode-line-left-delimiter | |
| 'face 'eyebrowse-mode-line-delimiters)) | |
| (right-delimiter (propertize eyebrowse-mode-line-right-delimiter | |
| 'face 'eyebrowse-mode-line-delimiters)) | |
| (separator (propertize eyebrowse-mode-line-separator | |
| 'face 'eyebrowse-mode-line-separator)) | |
| (current-slot (eyebrowse--get 'current-slot)) | |
| (window-configs (eyebrowse--get 'window-configs))) | |
| (if (and eyebrowse-mode-line-style | |
| (not (eq eyebrowse-mode-line-style 'hide)) | |
| (or (and (not (eq eyebrowse-mode-line-style 'smart)) | |
| eyebrowse-mode-line-style) | |
| (and (eq eyebrowse-mode-line-style 'smart) | |
| (> (length window-configs) 1)))) | |
| (concat | |
| left-delimiter | |
| (mapconcat | |
| (lambda (window-config) | |
| (let* ((slot (car window-config)) | |
| (face (if (= slot current-slot) | |
| 'eyebrowse-mode-line-active | |
| 'eyebrowse-mode-line-inactive)) | |
| (keymap | |
| (let ((map (make-sparse-keymap))) | |
| (define-key map (kbd "<mode-line><mouse-1>") | |
| (lambda (e) | |
| (interactive "e") | |
| (eyebrowse-switch-to-window-config slot))) | |
| map)) | |
| (help-echo "mouse-1: Switch to indicated workspace") | |
| (caption (eyebrowse-format-slot window-config))) | |
| (propertize caption 'face face 'slot slot | |
| 'mouse-face 'mode-line-highlight | |
| 'local-map keymap | |
| 'help-echo help-echo))) | |
| window-configs separator) | |
| right-delimiter) | |
| ""))) | |
| ;;;###autoload | |
| (define-minor-mode eyebrowse-mode | |
| "Toggle `eyebrowse-mode'. | |
| This global minor mode provides a set of keybindings for | |
| switching window configurations. It tries mimicking the tab | |
| behaviour of `ranger`, a file manager." | |
| :keymap eyebrowse-mode-map | |
| :global t | |
| (if eyebrowse-mode | |
| (progn | |
| ;; for some reason it's necessary to init both after emacs | |
| ;; started and after frame creation to make it work for both | |
| ;; emacs and emacsclient | |
| (eyebrowse-init) | |
| (add-hook 'after-make-frame-functions 'eyebrowse-init) | |
| (unless (assoc 'eyebrowse-mode mode-line-misc-info) | |
| (push '(eyebrowse-mode (:eval (eyebrowse-mode-line-indicator))) | |
| (cdr (last mode-line-misc-info))))) | |
| (remove-hook 'after-make-frame-functions 'eyebrowse-init))) | |
| (provide 'eyebrowse) | |
| ;;; eyebrowse.el ends here |