emacs-evil / evil-collection

A set of keybindings for evil-mode
GNU General Public License v3.0
1.2k stars 275 forks source link
bindings emacs evil evil-mode

+TITLE: Evil Collection

+STARTUP: noindent

[[https://github.com/emacs-evil/evil-collection/actions][file:https://github.com/emacs-evil/evil-collection/workflows/CI/badge.svg?branch=master]] [[https://melpa.org/#/evil-collection][file:https://melpa.org/packages/evil-collection-badge.svg]] [[https://stable.melpa.org/#/evil-collection][file:https://stable.melpa.org/packages/evil-collection-badge.svg]]

This is a collection of [[https://github.com/emacs-evil/evil][Evil]] bindings for /the parts of Emacs/ that Evil does not cover properly by default, such as ~help-mode~, ~M-x calendar~, Eshell and more.

Warning: Expect some default bindings to change in the future.

** Preliminaries

  1. ~evil-overriding-maps~ is assumed as ~nil~ to reduce redundant ~w/W/l/f/t~ etc evil bindings. See [[https://github.com/emacs-evil/evil-collection/pull/501][Fixup Info-mode]] for example.

** Goals

  1. Reduce context switching: As soon as "moving around" gets hardwired to ~~, it becomes frustratingly inefficient not to have it everywhere.

  2. Community work: setting up bindings is tremendous work and joining force can only save hours for all of Evil users out there. While not everyone may agree on the chosen bindings, it helps to have something to start with rather than nothing at all. In the end, users are free to override a subset of the proposed bindings to best fit their needs.

  3. Consistency: Having all bindings defined in one place allows for enforcing consistency across special modes and coordinating the community work to define a reference implementation.

** Installation

~evil-collection~ assumes ~evil-want-keybinding~ is set to ~nil~ and ~evil-want-integration~ is set to ~t~ before loading ~evil~ and ~evil-collection~. Note some other packages may load evil (e.g. evil-leader) so bear that in mind when determining when to set the variables.

See https://github.com/emacs-evil/evil-collection/issues/60 and https://github.com/emacs-evil/evil/pull/1087 for more details.

For example:

+begin_src emacs-lisp :tangle yes

(setq evil-want-integration t) ;; This is optional since it's already set to t by default. (setq evil-want-keybinding nil) (require 'evil) (when (require 'evil-collection nil t) (evil-collection-init))

+end_src

Here's another full TLDR ~use-package~ example.

+begin_src emacs-lisp :tangle yes

(use-package evil :ensure t :init (setq evil-want-integration t) ;; This is optional since it's already set to t by default. (setq evil-want-keybinding nil) :config (evil-mode 1))

(use-package evil-collection :after evil :ensure t :config (evil-collection-init))

+end_src

NOTE: If you don't like surprises but still want to use ~evil-collection-init~, setting ~evil-collection-mode-list~ to nil and adding each mode manually might be a better option.

** Configuration Modify ~evil-collection-mode-list~ to disable or add any modes that should be evilified by ~evil-collection~.

| Variable | Default | Description | |--------------------------------------------+---------+-------------------------------------------------------------------| | evil-collection-calendar-want-org-bindings | nil | Set up Org functions in calendar keymap. | | evil-collection-outline-bind-tab-p | nil | Enable -based bindings in Outline mode. | | evil-collection-term-sync-state-and-mode-p | t | Synchronize insert/normal state with char/line-mode in term-mode. | | evil-collection-setup-minibuffer | nil | Set up Vim style bindings in the minibuffer. | | evil-collection-setup-debugger-keys | t | Set up debugger keys for certain modes. | | evil-collection-want-unimpaired-p | t | Set up unimpaired bindings globally. | | evil-collection-want-find-usages-bindings | t | Bind -find references-, etc to various modes. | | evil-collection-config | * | List of mode specific configurations. | | evil-collection-key-whitelist | nil | List of keys Evil Collection is allowed to bind to. | | evil-collection-key-blacklist | nil | List of keys Evil Collection is not allowed to bind to. | | evil-collection-state-passlist | nil | List of Evil States Evil Collection is allowed to bind to. | | evil-collection-state-denylist | nil | List of Evil States Evil Collection is not allowed to bind to. |

For example, if you want to enable Evil in the minibuffer, you'll have to turn it on explicitly by customizing ~evil-collection-setup-minibuffer~ to ~t~. Some minibuffer-related packages such as Helm rely on this option.

~use-package~ example:

+begin_src emacs-lisp :tangle yes

(use-package evil-collection :custom (evil-collection-setup-minibuffer t) :init (evil-collection-init))

+end_src

~evil-collection-config~ can also be modified to configure specific modes. At the moment, it can be used to defer binding keys to those specific modes in order to improve startup time.

** Guidelines

The following rules serve as guiding principles to define the set of standard Evil bindings for various modes. Since special modes are by definition structurally incomparable, those rules cannot be expected to be applied universally.

The rules are more-or-less sorted by priority.

  1. Don't bind anything to ~:~ nor ~~.

  2. Keep the movement keys when possible and sensible.

    • ~h~, ~j~, ~k~, ~l~
    • ~w~, ~W~, ~b~, ~B~, ~e~, ~E~, ~ge~, ~gE~
    • ~f~, ~F~, ~t~, ~T~, ~;~, =,=
    • ~gg~, ~G~
    • ~|~
    • ~(~, ~)~
    • ~{~, ~}~
    • ~%~
    • ~+~, ~-~, ~0~, ~^~, ~$~
    • ~C-i~, ~C-o~
  3. Keep the yanking and register keys when possible and sensible.

    • ~y~, ~Y~
    • ="=
  4. Keep the search keys when possible and sensible.

    • ~/~, ~?~
    • ~#~, ~*~
  5. Keep the mark keys when possible and sensible.

    • ~m~
    • ='=, =~=
  6. Keep the windowing keys when possible and sensible.

    • ~H~, ~L~, ~M~
    • ~C-e~, ~C-y~
    • ~C-f~, ~C-b~
    • ~C-d~, ~C-u~
    • ~C-w~-prefixed bindings.
    • Some ~z~-prefixed bindings (see below).
  7. The following keys are free when insert state does not make sense in the current mode:

    • ~a~, ~A~, ~i~, ~I~
    • ~c~, ~C~, ~r~, ~R~, ~s~, ~S~
    • ~d~, ~D~, ~x~, ~X~
    • ~o~, ~O~
    • ~p~, ~P~
    • ~=~, ~<~, ~>~
    • ~J~
    • =~=

    Any of those keys can be set to be a prefix key.

  8. Prefix keys: ~g~ and ~z~ are the ubiquitous prefix keys.

    • ~g~ generally stands for "go" and is best used for movements.
    • ~z~ is used for scrolling, folding, spell-checking and more.
  9. Macro and action keys

    • ~@~, ~q~
    • ~.~
  10. Ensure terminal compatibility without sacrificing GUI key bindings.

    • Tab key
      • Tab key is recognized as ~~ in GUI and ~TAB~ in terminal. ~TAB~ equals ~C-i~.
      • ~C-i~ is bound to jumping forward for vim compatibility. If Shift+Tab is not relevant, just bind ~g TAB~ to the function that Tab is bound to. If Shift+Tab is relevant, bind ~g]~ and ~g TAB~ to the function that Tab is bound to, and bind ~g[~ to the function that Shift+Tab is bound to for terminal compatibility.
    • Enter key
      • Enter key is recognized as ~~ in GUI and ~RET~ in terminal. ~RET~ equals ~Ctrl+m~.
      • Bind only ~RET~ and ~M-RET~. Or, bind ~RET~ and ~M-RET~ to the same functions ~~ and ~~ are bound to.
      • ~S-RET~ is impossible on terminal. Bind ~~ and a vacant key to the same function for terminal compatibility.

** Rationale

Many special modes share the same set of similar actions. Those actions should share the same bindings across all modes whenever feasible.

*** Motion (~[~, ~]~, ~{~, ~}~, ~(~, ~)~, ~gj~, ~gk~, ~C-j~, ~C-k~)

*** Quitting (~q~, ~ZQ~, ~ZZ~)

In Vim, ~q~ is for recording macros. Vim quits with ~ZZ~ or ~ZQ~. In most Emacs special modes, it stands for quitting while macros are recorded/played with ~~ and ~~.

A good rule of thumb would be:

*** Refreshing / Reverting (~gr~)

*** Marking

~m~ defaults to ~evil-set-marker~ which might not be very useful in special modes. ='= can still be used as it can jump to other buffers.

If ~*~ is used for marking, then ~#~ is free.

Also note that Emacs inconsistently uses ~u~ and ~U~ to unmark.

*** Selecting / Filtering / Narrowing / Searching

*** Sorting

*** Go to definition (~gd~, ~gD~)

*** Go to references, etc (~gr~, ~gA~) When ~evil-collection-want-find-usages-bindings~ is set to t:

*** Go to current entity

*** Open thing at point (~RET~, ~S-RET~, ~M-RET~, ~go~, ~gO~)

*** Emacs-style jumping (~J~)

*** Browse URL (~gx~)

~gx~: go to URL. This is a default Vim binding.

*** Help (~?~)

*** History browsing (~C-n~, ~C-p~)

~C-n~ and ~C-p~ are standard bindings to browse the history elements.

*** Bookmarking

?

*** REPL (~gz~)

If the mode has a Go To REPL-type command, set it to ~gz~.

*** Zooming (~+~, ~-~, ~=~, ~0~)

*** Debugging

When debugging is on, debugger keys takes the most precedence.

These keys will be set when there's an available command for them.

For debugging outside of debugger being on (e.g. setting initial breakpoints), we use similar keys to [[https://github.com/realgud/realgud][realgud]].

*** Editable Buffers

For buffers where insert-state doesn't make sense but buffer can be edited, (e.g. wdired or wgrep), pressing ~i~ will change into editable state.

When this editable state is turned on,

~ZQ~ will abort and clear any changes. ~ZZ~ will finish and save any changes. ~ESC~ will exit editable state.

*** :q/:wq/etc

Modes with commands that can be bound to :q/:wq/etc will have those keys remapped.

** Key Translation

~evil-collection-translate-key~ allows binding a key to the definition of another key in the same keymap (comparable to how Vim's keybindings work). Its arguments are the ~states~ and ~keymaps~ to bind/look up the key(s) in followed optionally by keyword arguments (currently only ~:destructive~) and key/replacement pairs. ~states~ should be nil for non-evil keymaps, and both ~states~ and ~keymaps~ can be a single symbol or a list of symbols.

This function can be useful for making key swaps/cycles en masse. For example, someone who uses an alternate keyboard layout may want to retain the ~hjkl~ positions for directional movement in dired, the calendar, etc.

Here's an example for Colemak of making swaps in a single keymap:

+begin_src emacs-lisp

(evil-collection-translate-key nil 'evil-motion-state-map ;; colemak hnei is qwerty hjkl "n" "j" "e" "k" "i" "l" ;; add back nei "j" "e" "k" "n" "l" "i")

+end_src

Here's an example of using ~evil-collection-setup-hook~ to cycle the keys for all modes in ~evil-collection-mode-list~:

+begin_src emacs-lisp

(defun my-hjkl-rotation (_mode mode-keymaps &rest _rest) (evil-collection-translate-key 'normal mode-keymaps "n" "j" "e" "k" "i" "l" "j" "e" "k" "n" "l" "i"))

;; called after evil-collection makes its keybindings (add-hook 'evil-collection-setup-hook #'my-hjkl-rotation)

(evil-collection-init)

+end_src

A more common use case of ~evil-collection-translate-key~ would be for keeping the functionality of some keys that users may bind globally. For example, ~SPC~, ~[~, and ~]~ are bound in some modes. If you use these keys as global prefix keys that you never want to be overridden, you'll want to give them higher priority than other evil keybindings (e.g. those made by ~(evil-define-key 'normal some-map ...)~). To do this, you can create an "intercept" map and bind your prefix keys in it instead of in ~evil-normal-state-map~:

+begin_src emacs-lisp

(defvar my-intercept-mode-map (make-sparse-keymap) "High precedence keymap.")

(define-minor-mode my-intercept-mode "Global minor mode for higher precedence evil keybindings." :global t)

(my-intercept-mode)

(dolist (state '(normal visual insert)) (evil-make-intercept-map ;; NOTE: This requires an evil version from 2018-03-20 or later (evil-get-auxiliary-keymap my-intercept-mode-map state t t) state))

(evil-define-key 'normal my-intercept-mode-map (kbd "SPC f") 'find-file) ;; ...

+end_src

You can then define replacement keys:

+begin_src emacs-lisp

(defun my-prefix-translations (_mode mode-keymaps &rest _rest) (evil-collection-translate-key 'normal mode-keymaps "C-SPC" "SPC" ;; these need to be unbound first; this needs to be in same statement "[" nil "]" nil "[[" "[" "]]" "]"))

(add-hook 'evil-collection-setup-hook #'my-prefix-translations)

(evil-collection-init)

+end_src

By default, the first invocation of ~evil-collection-translate-key~ will make a backup of the keymap. Each subsequent invocation will look up keys in the backup instead of the original. This means that a call to ~evil-collection-translate-key~ will always have the same behavior even if evaluated multiple times. When ~:destructive t~ is specified, keys are looked up in the keymap as it is currently. This means that a call to ~evil-collection-translate-key~ that swapped two keys would continue to swap/unswap them with each call. Therefore when ~:destructive t~ is used, all cycles/swaps must be done within a single call to ~evil-collection-translate-key~. To make a comparison to Vim keybindings, ~:destructive t~ is comparable to Vim's ~map~, and ~:destructive nil~ is comparable to Vim's ~noremap~ (where the "original" keybindings are those that existed in the keymap when ~evil-collection-translate-key~ was first called). You'll almost always want to use the default behavior (especially in your init file). The limitation of ~:destructive nil~ is that you can't translate a key to another key that was defined after the first ~evil-collection-translate-key~, so ~:destructive t~ may be useful for interactive experimentation.

~evil-collection-swap-key~ is also provided as a wrapper around ~evil-collection-translate-key~ that allows swapping keys:

+begin_src emacs-lisp

(evil-collection-swap-key nil 'evil-motion-state-map ";" ":") ;; is equivalent to (evil-collection-translate-key nil 'evil-motion-state-map ";" ":" ":" ";")

+end_src

In some cases, keys are bound through evil-define-minor-mode-key and may need to be translated using ~evil-collection-translate-minor-mode-key~ and/or ~evil-collection-swap-minor-mode-key~.

+begin_src emacs-lisp

(evil-collection-swap-minor-mode-key '(normal motion) '(evil-snipe-local-mode evil-snipe-override-local-mode) "k" "s" ;; Set this to t to make this swap the keys everytime ;; this expression is evaluated. :destructive nil)

(evil-collection-translate-minor-mode-key '(normal motion) '(evil-snipe-local-mode evil-snipe-override-local-mode) "k" "s" "s" "k" ;; Set this to t to make this swap the keys everytime ;; this expression is evaluated. :destructive nil)

+end_src

** Third-party packages

Third-party packages are provided by several parties:

| Major mode | Evil bindings | |------------+--------------------------| | ledger | [[https://github.com/atheriel/evil-ledger][evil-ledger]] | | lispy | [[https://github.com/noctuid/lispyville][lispyville]] or [[https://github.com/sp3ctum/evil-lispy][evil-lispy]] | | org | [[https://github.com/GuiltyDolphin/org-evil][org-evil]] or [[https://github.com/Somelauw/evil-org-mode][evil-org]] | | markdown | [[https://github.com/Somelauw/evil-markdown][evil-markdown]] |

Also ~evil-collection~ has minimal support (~TAB~, ~S-TAB~ and sentence/paragraph forwarding) for ~markdown~ and ~org~ if you prefer less packages installed.

Should you know any suitable package not mentioned in this list, let us know and file an issue.

Other references:

** FAQ

*** Making SPC work similarly to [[https://github.com/syl20bnr/spacemacs][spacemacs]].

~evil-collection~ binds over SPC in many packages. To use SPC as a leader key with the [[https://github.com/noctuid/general.el][general]] library:

+begin_src emacs-lisp :tangle yes

(use-package general :ensure t :init (setq general-override-states '(insert emacs hybrid normal visual motion operator replace)) :config (general-define-key :states '(normal visual motion) :keymaps 'override "SPC" 'hydra-space/body)) ;; Replace 'hydra-space/body with your leader function.

+end_src

See [[https://github.com/noctuid/evil-guide][noctuid's evil guide]] for other approaches.

+begin_src emacs-lisp :tangle yes

(add-hook 'evil-collection-setup-hook (lambda (_mode keymaps) (add-hook 'ediff-mode-hook (lambda () (... keymaps ...)))))

+end_src

View [[https://github.com/emacs-evil/evil-collection/issues/196][196]] for more info. *** Don't allow Evil-Collection to bind some keys. Look into ~evil-collection-key-whitelist~ and ~evil-collection-key-blacklist~.

For example:

+begin_src emacs-lisp :tangle yes

;; Don't allow Evil Collection to bind to gfu and gfp. (setq evil-collection-key-blacklist '("gfu" "gfp"))

+end_src

*** Modes left behind

Some modes might still remain unsupported by this package. Should you be missing your ~~, please feel free to do a pull request.

*** Writing a new binding

This [[template][yasnippet template]] can be used to bootstrap a new binding.

For example, if we were to want to add ~evil-collection~ support to ~eldoc~. (e.g.) There is a package that contains:

+begin_src emacs-lisp :tangle yes

(provide 'eldoc)

+end_src

Create a directory named eldoc under [[modes-directory][modes/]]. Create a file named evil-collection-eldoc.el under the newly created eldoc directory. Then use the above template as an example or, using [[yasnippet][yasnippet]], ~yas-expand~ the above template which will result in something like below:

+begin_src emacs-lisp :tangle yes

;;; evil-collection-eldoc.el --- Bindings for `eldoc' -- lexical-binding: t --

;; Copyright (C) 2022 James Nguyen

;; Author: James Nguyen james@jojojames.com ;; Maintainer: James Nguyen james@jojojames.com ;; URL: https://github.com/emacs-evil/evil-collection ;; Version: 0.0.2 ;; Package-Requires: ((emacs "27.1")) ;; Keywords: evil, emacs, convenience, tools

;; 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 http://www.gnu.org/licenses/.

;;; Commentary: ;;; Bindings for eldoc.

;;; Code: (require 'evil-collection) (require 'eldoc nil t)

(defvar eldoc-mode-map) (defconst evil-collection-eldoc-maps '(eldoc-mode-map))

(defun evil-collection-eldoc-setup () "Set up `evil' bindings for eldoc." (evil-collection-define-key 'normal 'eldoc-mode-map ))

(provide 'evil-collection-eldoc) ;;; evil-collection-eldoc.el ends here

+end_src

Finally, add ~eldoc~ to ~evil-collection--supported-modes~.

+begin_src emacs-lisp :tangle yes

(defvar evil-collection--supported-modes ( ;; ... eldoc ;; ... ) "List of modes supported by evil-collection. Elements are either target mode symbols or lists whichcar' is the mode symbol and `cdr' the packages to register.")

+end_src

** Submitting Issues

When reproducing issues, you can use this emacs -Q recipe.

+begin_src emacs-lisp :tangle yes

(setq user-emacs-directory "~/.emacs.1.d") (setq package-user-dir (format "%s/elpa/%s/" user-emacs-directory emacs-major-version))

(setq package-enable-at-startup nil package-archives '(("melpa" . "https://melpa.org/packages/") ("gnu" . "http://elpa.gnu.org/packages/")))

(require 'package) (package-initialize) (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (require 'use-package) (setq use-package-always-ensure t)

(use-package evil :ensure t :init (setq evil-want-keybinding nil) :config (evil-mode 1))

(use-package evil-collection :after evil :ensure t :config (evil-collection-init))

+end_src

** Contributing We welcome any additional modes that are not already supported.

All bindings in ~evil-collection~ are open to change so if there's a better or more consistent binding, please [[https://github.com/emacs-evil/evil-collection/issues][open an issue]] or [[https://github.com/emacs-evil/evil-collection/pulls][submit a pull request]].

Follow [[https://github.com/bbatsov/emacs-lisp-style-guide/][The Emacs Lisp Style Guide]] for coding conventions.

[[https://github.com/erlang/otp/wiki/writing-good-commit-messages][Erlang/OTP]] has a good read for helpful commit messages.

+LINK: EMMS https://www.gnu.org/software/emms/

+LINK: evilmagit https://github.com/emacs-evil/evil-magit

+LINK: evilmu4e https://github.com/JorisE/evil-mu4e

+LINK: mu4e https://www.djcbsoftware.nl/code/mu/mu4e.html

+LINK: yasnippet https://github.com/joaotavora/yasnippet

+LINK: template https://github.com/emacs-evil/evil-collection/blob/master/yasnippet_evil-collection

+LINK: modes-directory https://github.com/emacs-evil/evil-collection/tree/master/modes