diff --git a/spacemacs/extensions.el b/spacemacs/extensions.el index 4a2529e052bc..18fda1c994d6 100644 --- a/spacemacs/extensions.el +++ b/spacemacs/extensions.el @@ -13,6 +13,7 @@ centered-cursor dos emoji-cheat-sheet + evil-escape evil-org-mode evil-plugins helm-rcirc @@ -51,6 +52,13 @@ (use-package emoji-cheat-sheet :commands emoji-cheat-sheet)) +(defun spacemacs/init-evil-escape () + (use-package evil-escape + :init + (evil-escape-mode) + :config + (spacemacs//hide-lighter evil-escape-mode))) + (defun spacemacs/init-evil-org-mode () (use-package evil-org :commands evil-org-mode diff --git a/spacemacs/extensions/evil-escape/README.md b/spacemacs/extensions/evil-escape/README.md new file mode 100644 index 000000000000..7058e7bd5f56 --- /dev/null +++ b/spacemacs/extensions/evil-escape/README.md @@ -0,0 +1,48 @@ +# evil-escape + +Customizable key sequence to escape from insert state and everything else in +Emacs. + +Press `fd` quickly to: + +- escape from all evil states to normal state +- escape from evil-lisp-state to normal state +- abort evil ex command +- quit minibuffer +- abort isearch +- quit magit buffers +- quit help buffers +- hide neotree buffer + +And more to come ! + +Contributions to support more buffers are _very welcome_: +**Escape Everything !** + +## Install + +The package _will be available soon_ in [MELPA][]. + +If you have MELPA in `package-archives`, use + + M-x package-install RET evil-escape RET + +If you don't, open `evil-escape.el` in Emacs and call +`package-install-from-buffer`. + +## Usage + +To toggle the `evil-escape` mode globally: + + M-x evil-escape-mode + +## Customization + +Open the customization group buffer: + + M-x customize-group RET evil-escape RET + +There you can change the key sequence to your desire. +The default value is `fd`. + +[MELPA]: http://melpa.org/ diff --git a/spacemacs/extensions/evil-escape/evil-escape.el b/spacemacs/extensions/evil-escape/evil-escape.el new file mode 100644 index 000000000000..b248a9f12d38 --- /dev/null +++ b/spacemacs/extensions/evil-escape/evil-escape.el @@ -0,0 +1,217 @@ +;;; evil-escape.el --- Customizable key sequence to escape from insert state and everything else. + +;; Copyright (C) 2014 syl20bnr +;; +;;;; Author: Sylvain Benner +;; Keywords: convenience editing evil +;; Created: 22 Oct 2014 +;; Version: 1.0 +;; Package-Requires: ((emacs "24") (evil "1.0.9") (key-chord "0.6")) +;; URL: https://github.com/syl20bnr/evil-escape + +;; 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 3 of the License, 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. If not, see . + +;;; Commentary: + +;; Press `fd` quickly to: +;; - escape from all evil states to normal state +;; - escape from evil-lisp-state to normal state +;; - abort evil ex command +;; - quit minibuffer +;; - abort isearch +;; - quit magit buffers +;; - quit help buffers +;; - hide neotree buffer +;; And more to come ! + +;; The key sequence can be customized with the variable +;; `evil-escape-key-sequence` + +;; More information in the readme of the repository: +;; https://github.com/syl20bnr/evil-escape + +;;; Code: + +(require 'evil) +(require 'key-chord) + +(defgroup evil-escape nil + "Key sequence to escape insert state and everything else." + :prefix "evil-escape-" + :group 'evil) + +(defvar evil-escape-key-sequence (kbd "fd") + "Two keys sequence to return to normal state.") +(defcustom evil-escape-key-sequence (kbd "fd") + "Two keys sequence to escape from insert state." + :type 'key-sequence + :group 'evil-escape) + +;;;###autoload +(define-minor-mode evil-escape-mode + "Buffer-local minor mode to escape insert state and everythin else +with a key sequence." + :lighter (:eval (concat " " evil-escape-key-sequence)) + :group 'evil + :global t + (if evil-escape-mode + (progn + (key-chord-mode 1) + (evil-escape--define-keys) + (message "evil-escape enabled, press \"%s\" to escape from anything." + evil-escape-key-sequence)) + (evil-escape--undefine-keys))) + +(defun evil-escape--define-keys () + "Set the key bindings to escape _everything!_" + ;; use key-chord whenever it is possible + ;; evil states + (key-chord-define evil-insert-state-map evil-escape-key-sequence 'evil-normal-state) + (key-chord-define evil-emacs-state-map evil-escape-key-sequence + '(lambda () (interactive) + (cond ((string-match "magit" (symbol-name major-mode)) + (setq unread-command-events (listify-key-sequence "q"))) + (t evil-normal-state)))) + (key-chord-define evil-visual-state-map evil-escape-key-sequence 'evil-exit-visual-state) + (key-chord-define evil-motion-state-map evil-escape-key-sequence + '(lambda () (interactive) + (cond ((eq 'help-mode major-mode) (quit-window)) + ((eq 'neotree-mode major-mode) (neotree-hide)) + (t (evil-normal-state))))) + ;; lisp state if installed + (eval-after-load 'evil-lisp-state + '(key-chord-define evil-lisp-state-map evil-escape-key-sequence 'evil-normal-state)) + ;; mini-buffer + (key-chord-define minibuffer-local-map evil-escape-key-sequence 'abort-recursive-edit) + ;; evil ex command + (key-chord-define evil-ex-completion-map evil-escape-key-sequence 'abort-recursive-edit) + ;; key-chord does not work with isearch, use evil-escape implementation + (evil-escape-define-escape isearch-mode-map isearch-abort + :insert t + :delete t + :insert-func evil-escape--isearch-insert-func + :delete-func isearch-delete-char)) + +(defun evil-escape--undefine-keys () + "Unset the key bindings defined in `evil-escape--define-keys'." + ;; bulk undefine + (dolist (map '(evil-insert-state-map + evil-emacs-state-map + evil-visual-state-map + evil-motion-state-map + minibuffer-local-map + evil-ex-completion-map)) + (key-chord-define (eval map) evil-escape-key-sequence nil)) + ;; lisp state if installed + (eval-after-load 'evil-lisp-state + '(key-chord-define evil-lisp-state-map evil-escape-key-sequence nil)) + ;; isearch + (define-key isearch-mode-map (kbd "f") 'isearch-printing-char)) + +(defmacro evil-escape-define-escape (map command &rest properties) + "Define an escape in MAP keymap by executing COMMAND. + +`:insert BOOL' + If BOOL is not nil the first character of the escape sequence is inserted + in the buffer using `:insert-func' if the buffer is not read-only. + +`:delete BOOL' + If BOOL is not nil the first character is deleted using `:delete-func' if + the escape sequence succeeded. + +`:shadowed BOOL' + BOOL not nil indicates that the first key of the sequence shadows a + function. This function is looked-up from `evil-motion-state-map'. + Whenever the escape sequence does not succeed and BOOL is not nil + the shadowed function is called. + +`:insert-func FUNCTION' + Specify the insert function to call when inserting the first key. + +`:delete-func FUNCTION' + Specify the delete function to call when deleting the first key." + (let* ((first-key (elt evil-escape-key-sequence 0)) + (fkeystr (char-to-string first-key)) + (insertp (plist-get properties :insert)) + (deletep (plist-get properties :delete)) + (insert-func (plist-get properties :insert-func)) + (delete-func (plist-get properties :delete-func))) + `(progn + (define-key ,map ,fkeystr + (lambda () (interactive) + (evil-escape--escape + ,evil-escape-key-sequence + ',(if (plist-get properties :shadowed) (lookup-key evil-motion-state-map fkeystr)) + ,insertp + ,deletep + ',command + ',insert-func + ',delete-func)))))) + +(defun evil-escape--default-insert-func (key) + "Insert KEY in current buffer if not read only." + (let* ((insertp (not buffer-read-only))) + (insert key))) + +(defun evil-escape--isearch-insert-func (key) + "Insert KEY in current buffer if not read only." + (isearch-printing-char)) + +(defun evil-escape--default-delete-func () + "Delete char in current buffer if not read only." + (let* ((insertp (not buffer-read-only))) + (delete-char -1))) + +(evil-define-command evil-escape--escape + (keys shadowed-func insert? delete? callback &optional insert-func delete-func) + "Execute the passed CALLBACK using KEYS. KEYS is a cons cell of 2 characters. + +If the first key insertion shadowed a function then pass the shadowed function +in SHADOWED-FUNC and it will be executed if the key sequence was not + successfull. + +If INSERT? is not nil then the first key pressed is inserted using the function +INSERT-FUNC. + +If DELETE? is not nil then the first key is deleted using the function +DELETE-FUNC when calling CALLBACK. " + :repeat nil + (let* ((modified (buffer-modified-p)) + (insertf (if insert-func + insert-func 'evil-escape--default-insert-func)) + (deletef (if delete-func + delete-func 'evil-escape--default-delete-func)) + (fkey (elt keys 0)) + (fkeystr (char-to-string fkey)) + (skey (elt keys 1))) + (if insert? (funcall insertf fkey)) + (let* ((evt (read-event nil nil key-chord-two-keys-delay))) + (cond + ((null evt) + (unless (eq 'insert evil-state) + (if shadowed-func (call-interactively shadowed-func)))) + ((and (integerp evt) + (char-equal evt skey)) + ;; remove the f character + (if delete? (funcall deletef)) + (set-buffer-modified-p modified) + (funcall callback)) + (t ; otherwise + (setq unread-command-events + (append unread-command-events (list evt))) + (if shadowed-func (call-interactively shadowed-func))))))) + +(provide 'evil-escape) diff --git a/spacemacs/packages.el b/spacemacs/packages.el index 05f350f62892..235f154eb442 100644 --- a/spacemacs/packages.el +++ b/spacemacs/packages.el @@ -77,6 +77,7 @@ json-mode ledger-mode less-css-mode + key-chord magit magit-gitflow markdown-mode @@ -219,118 +220,6 @@ determine the state to enable when escaping from the insert state.") (defadvice evil-lisp-state (before spacemacs/evil-lisp-state activate) "Advice to keep track of the last base state." (setq spacemacs-last-base-state 'lisp)) - - (defun spacemacs/escape-state-default-insert-func (key) - "Insert KEY in current isearch minibuffer." - (let* ((insertp (not buffer-read-only))) - (insert key))) - - (defun spacemacs/escape-state-isearch-insert-func (key) - "Insert KEY in current buffer if not read only." - (isearch-printing-char)) - - (defun spacemacs/escape-state-term-insert-func (key) - "Insert KEY in current term buffer." - (term-send-raw)) - - (defun spacemacs/escape-state-default-delete-func () - "Delete char in current buffer if not read only." - (let* ((insertp (not buffer-read-only))) - (delete-char -1))) - - (evil-define-command spacemacs/escape-state - (keys shadowed insert? delete? callback &optional insert-func delete-func) - "Allows to execute the passed CALLBACK using KEYS. KEYS is a cons cell -of 2 characters. - -If INSERT? is not nil then the first key pressed is inserted using the function -INSERT-FUNC. - -If DELETE? is not nil then the first key is deleted using the function -DELETE-FUNC when calling CALLBACK. -" - :repeat change - (let* ((modified (buffer-modified-p)) - (insertf - (if insert-func - insert-func 'spacemacs/escape-state-default-insert-func)) - (deletef - (if delete-func - delete-func 'spacemacs/escape-state-default-delete-func)) - (fkey (car keys)) - (fkeystr (char-to-string fkey)) - (skey (cdr keys))) - (if insert? (funcall insertf fkey)) - (let* ((evt (read-event nil nil spacemacs-normal-state-sequence-delay))) - (cond - ((null evt) - (unless (eq 'insert evil-state) - (if shadowed (call-interactively shadowed)))) - ((and (integerp evt) - (char-equal evt skey)) - ;; remove the f character - (if delete? (funcall deletef)) - (set-buffer-modified-p modified) - (funcall callback)) - (t ; otherwise - (setq unread-command-events - (append unread-command-events (list evt))) - (if shadowed (call-interactively shadowed))))))) - ;; easier toggle for emacs-state - (evil-set-toggle-key "s-`") - ;; escape state with a better key sequence than ESC - (let* ((seq spacemacs-normal-state-sequence) - (key (char-to-string (car spacemacs-normal-state-sequence))) - (shadowed (lookup-key evil-motion-state-map key))) - ;; 'fd' triggers to escape from a state to the base state - (global-set-key key `(lambda () (interactive) - (spacemacs/escape-state ',seq nil nil nil 'keyboard-quit))) - (mapc (lambda (map) - (define-key (eval map) key - `(lambda () (interactive) - (spacemacs/escape-state ',seq nil t nil 'abort-recursive-edit)))) - '(minibuffer-local-map - minibuffer-local-ns-map - minibuffer-local-completion-map - minibuffer-local-must-match-map - minibuffer-local-isearch-map)) - (define-key isearch-mode-map key - `(lambda () (interactive) - (spacemacs/escape-state - ',seq nil t t 'isearch-abort 'spacemacs/escape-state-isearch-insert-func - 'isearch-delete-char))) - (define-key evil-ex-completion-map key - `(lambda () (interactive) - (spacemacs/escape-state - ',seq nil t nil 'abort-recursive-edit nil 'evil-ex-delete-backward-char))) - ;; Note: we keep emacs state untouched in order to always have a - ;; fallback for modes that uses the `f' key (ie. magit-gitflow) - (define-key evil-insert-state-map key - `(lambda () (interactive) - (let ((insertf (if (eq 'term-mode major-mode) - 'spacemacs/escape-state-term-insert-func))) - (spacemacs/escape-state - ',seq nil t t (intern (format "evil-%s-state" spacemacs-last-base-state)) insertf)))) - (define-key evil-visual-state-map key - `(lambda () (interactive) - (spacemacs/escape-state ',seq ',shadowed nil nil 'evil-exit-visual-state))) - (define-key evil-motion-state-map key - `(lambda () (interactive) - (let ((exit-func (cond ((eq 'help-mode major-mode) - 'quit-window) - ((eq 'neotree-mode major-mode) - 'neotree-hide) - (t 'evil-normal-state)))) - (spacemacs/escape-state ',seq ',shadowed nil nil exit-func)))) - (eval-after-load 'evil-lisp-state - `(define-key evil-lisp-state-map ,key - (lambda () (interactive) - (spacemacs/escape-state ',seq ',shadowed nil nil 'evil-normal-state)))) - (eval-after-load "helm-mode" - `(define-key helm-map ,key - (lambda () (interactive) - (spacemacs/escape-state ',seq nil t nil 'helm-keyboard-quit))))) - ;; manage the base state target when leaving the insert state (define-key evil-insert-state-map [escape] (lambda () (interactive)