Emacs is all keybindings. Escape Meta Alt Control Shift as they say. When you add another layer of possible keybindings with Evil, you end up being pretty limitless in your keybindings choices.
Here I want to describe how I bind keys in Emacs. I do this for two reasons: because writing helps me to formalise it, and to get feedbacks from you and your own keybindings strategies.
The outline is the following.
- I bind keys under the
SPCprefix using General.
- I remap awkward Emacs keybindings on the
- I place common functions in normal state on
M-map, which is unused by Evil.
- I use Key-seq to declare short key-sequences bound to very often used functions.
- And finally I bind keys available in all states to
I use Evil, so my main tool to define keybindings is General.el, which
allow me to place functions under
:prefixes. I have one main
:prefix defined on the
SPC key, and two “minor”
on keys specific to my keyboard layout and not used by Evil. Every
function that I use a lot is defined under the
SPC prefix, to
emulate a Spacemacs behavior that I really like. In insert state, I
tend to use the default Emacs keybindings.
general-define-key from the General package is great. Here’s why.
- It manages keybindings for Evil and non-evil use cases.
- It provides a quick way to define multiple keybindings at once.
- It respects keymaps.
- It allow for really simple
C-§if you want, or
SPCto emulate spacemacs leader.
- It has a nice integration with
which-key. One can define a keybinding and describe it in the same statement.
- When you don’t remember your keybindings,
general-describe-keybindingsprovide a nicely formatted org-mode buffer with every keybindings defined using General, sorted by major mode.
In normal and visual states
In normal state, I bind key under a
SPC prefix like so:
(general-define-key :states '(normal visual insert emacs) :prefix "SPC" :non-normal-prefix "M-SPC" "'" '(iterm-focus :which-key "iterm") "?" '(iterm-goto-filedir-or-home :which-key "iterm - goto dir") "/" '(counsel-ag :wich-key "ag") "TAB" '(ivy-switch-buffer :which-key "prev buffer") "." '(avy-goto-word-or-subword-1 :which-key "go to word") "SPC" '(counsel-M-x :which-key "M-x") "a" '(hydra-launcher/body :which-key "Applications") "b" '(hydra-buffer/body t :which-key "Buffer") "c" '(:ignore t :which-key "Comment") "cl" '(comment-or-uncomment-region-or-line :which-key "comment line") "w" '(hydra-window/body :which-key "Window") "f" '(:ignore t :which-key "Files") "fd" '(counsel-git :which-key "find in git dir") ;; ... )
I can access those functions with
SPC in normal and visual state,
M-SPC in emacs and insert state. The
:which-key keyword allow
me to describe the role of prefixes like
SPC f, attached to file
I remap the
M- map functions in normal state, since I usually need
them only in insert mode. They does not conflict with Evil
keybindings, since Vim has no Meta key.
(general-nvmap "'" (general-simulate-keys "C-c") "M-'" 'evil-goto-mark "M-b" 'ivy-switch-buffer ;; ... )
general-nvmap defines keybindings for the
state only. The
general-simulate-keys allows me to press
C-c. So commonly defined prefixes are accessible using two key
Another hidden treasure of General is the
general-omap function. It
is a wrapper that allow for very convenient Vim-like operator
(general-omap :prefix "SPC" "." 'avy-goto-word-or-subword-1 "l" 'evil-avy-goto-line "é" 'evil-avy-goto-subword-0 )
Here’s how I can use it.
d in normal mode (
delete from here to the line selected with avy, then
t to select the
target line. This is awesome. It’s a great wrapper around
evil-define-motion-like definitions. Plus I don’t have to use a
prefix, I can just bind
evil-avy-goto-line in operator state.
In insert and emacs state
In insert and emacs state, I use Emacs default keybindings. By default, Evil attributes Vim-like keybindings in insert state, but I found that Emacs keybindings can go a long way before moving to normal state.
(setq evil-disable-insert-state-bindings t)
In insert state, I typically use the default keybindings, provided by
package authors or Emacs default. When I find that a keybinding is
really awkward, I remap it using
general-iemap, but I tend to keep
default Emacs in insert state. I found this to be the quickest way to
learn new packages, keybindings corresponds to the package
documentation for example. Plus packages authors generally know best
which keybindings are good to use.
In insert mode, it is quicker to have a quick sequence trigger a
commonly used function, without going back to normal state and reach
For example, I save compulsively like every minutes. So using my
prefix map, I should escape with
xq — my escape key sequence, then
s.. That’s five keypress. This is how I save
buffers in normal state. Using
Key-seq, I can stay in insert
(or emacs) state and use two keypress to save the buffer. For quick
saving, I use:
(use-package key-seq :ensure t :config (key-seq-define evil-insert-state-map "qs" #'save-buffer))
Now when I press
q, Key-seq waits for the second keypress for a delay
corresponding to the
key-chord-two-keys-delay, otherwise it inserts
To me, Key-seq is superior to Key-chord since it provides binding only in a defined key order, while Key-chord does not. I do not want to notice lags for every key strokes involved in a sequence. I only want a lag on the first key.
I have thus constructed a short list of function that I use really
often. Here is my whole setup for Key-seq and Key-chord. I use the
key for the first key stroke because it is always followed by
French and right under my index finger.
(use-package key-chord :ensure t :defer 1 ; do not load right at startup :config (setq key-chord-two-keys-delay 0.2) ;; need to use key-seq. otherwise key order does not matter. that's bad. ;; i want latency only on x. (use-package key-seq :ensure t :config (key-seq-define evil-insert-state-map "qf" #'ivy-switch-buffer) (key-seq-define evil-insert-state-map "qv" #'git-gutter:stage-hunk) (key-seq-define evil-insert-state-map "qc" #'avy-goto-word-1) (key-seq-define evil-insert-state-map "ql" #'avy-goto-line) (key-seq-define evil-insert-state-map "qs" #'save-buffer) (key-seq-define evil-insert-state-map "qp" #'hydra-projectile/body) (key-seq-define evil-insert-state-map "QV" #'magit-status)))
How do I remember keybindings ?
Short answer: I don’t.
keybindings.el file is around 700 lines of elisp, I can’t
remember it all.
Some keybindings are so deeply ingrained in muscle memory that I do
not have to think about them, like
SPC s. to save a buffer or
to escape insert state.
But the vast majority of keybindings defined by Emacs, packages developpers or me is hard to get used to. For those use cases, I cannot recommend enough to use Which-Key and Hydra.
Which-key is one of the best package to learn another. Its main
role is to describe functions associated to prefixed keybindings, like
C-x. For example, the Markdown package in Emacs is pretty
C-c oriented, every keybindings it defines are associated to a
C-c prefix, like
C-c C-u (
markdown-up-heading). I just can’t
remember them. So in my
(use-package markdown-mode) declaration, I’ve
added the following chunk:
(which-key-add-major-mode-key-based-replacements 'markdown-mode "C-c C-a" "insert" "C-c C-c" "export" "C-c TAB" "images" "C-c C-s" "text" "C-c C-t" "header" "C-c C-x" "move" )
Now when I press
C-c in a markdown buffer, I got:
The second best package to learn another is Hydra. I know it’s not its primary use case, but I found it to be very convenient to learn a package. For those of you who do not know Hydra, it is a package that allow you to define “sticky” keybindings, ie keybindings that you can press any number of times to trigger the same effect. Here is an example that I use to switch buffers.
(defhydra hydra-buffer (:color blue :columns 3) " Buffers : " ("n" next-buffer "next" :color red) ("b" ivy-switch-buffer "switch") ("B" ibuffer "ibuffer") ("p" previous-buffer "prev" :color red) ("C-b" buffer-menu "buffer menu") ("N" evil-buffer-new "new") ("d" kill-this-buffer "delete" :color red) ;; don't come back to previous buffer after delete ("D" (progn (kill-this-buffer) (next-buffer)) "Delete" :color red) ("s" save-buffer "save" :color red))
When I evaluate the previous chunk, the macro is expanded to
hydra-buffer/body, which I bound to
SPC b. So now when I press
SPC b, I got:
From now on, until I press a blue key like
N, this hydra will
stick. It allows me to switch between buffers with
delete unused buffer or
It seems to me that hydra is a great tool to learn a new package, because it allows you to attach a description to common function. Just take a look at this one, borrowed from here.
(defhydra hydra-projectile (:color teal :hint nil) " PROJECTILE: %(projectile-project-root) ^Find File^ ^Search/Tags^ ^Buffers^ ^Cache^ ^Project^ ^---------^ ^-----------^ ^-------^ ^-----^ ^-------^ _f_: file _a_: ag _i_: Ibuffer _c_: cache clear _p_: switch proj _F_: file dwim _g_: update gtags _b_: switch to _x_: remove known project _C-f_: file pwd _o_: multi-occur _s-k_: Kill all _X_: cleanup non-existing _r_: recent file ^ ^ ^ ^ _z_: cache current _d_: dir " ("a" projectile-ag) ("b" projectile-switch-to-buffer) ("c" projectile-invalidate-cache) ("d" projectile-find-dir) ("f" projectile-find-file) ("F" projectile-find-file-dwim) ("C-f" projectile-find-file-in-directory) ("g" ggtags-update-tags) ("s-g" ggtags-update-tags) ("i" projectile-ibuffer) ("K" projectile-kill-buffers) ("s-k" projectile-kill-buffers) ("m" projectile-multi-occur) ("o" projectile-multi-occur) ("p" projectile-switch-project) ("r" projectile-recentf) ("x" projectile-remove-known-project) ("X" projectile-cleanup-known-projects) ("z" projectile-cache-current-file) ("q" nil "cancel" :color blue))
I bind it to
SPC p, it expands to that, and that’s really cool:
None of those keybindings use the “sticky” behavior of the previously
described red keys. But this way, I do not have to worry about the
exact Projectile function that I want to use, I just press
SPC p and
read the Hydra etiquette. To me this is a great way of learning to
use a package.
So finally how do I bind keys in Emacs ? I make this easy on my thumb
using General.el and a
SPC prefix. For common functions, I bind them
using Key-seq to short two key-strokes definitions. And for those cases
that I do not precisely know the keybinding I want to reach, there is
Which-key and Hydra. For everything else, I just call
Sometimes it is shorter to just type the function name with Ivy and
Smex, the result is here really quickly.
That’s how I bind key in Emacs. But I am sure there are totally different or better strategies out there !