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
SPC
prefix using General. - I remap awkward Emacs keybindings on the
C-
map. - 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
S-
andH-
maps.
I use Evil, so my main tool to define keybindings is General.el, which
allow me to place functions under :prefix
es. I have one main
:prefix
defined on the SPC
key, and two “minor” :prefix
defined
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
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
:prefix
definition, beingC-c
,C-§
if you want, orSPC
to 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-keybindings
provide 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,
and 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
related functions.
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
;; ...
)
The general-nvmap
defines keybindings for the n
ormal and v
isual
state only. The general-simulate-keys
allows me to press '
instead
of C-c
. So commonly defined prefixes are accessible using two key
strokes.
Another hidden treasure of General is the general-omap
function. It
is a wrapper that allow for very convenient Vim-like operator
definitions.
(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.
I press d
in normal mode (evil-delete
), then SPC
and l
to
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 l
to 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
the SPC
prefix.
For example, I save compulsively like every minutes. So using my
prefix map, I should escape with xq
— my escape key sequence, then
press SPC
, 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
q
.
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 q
key for the first key stroke because it is always followed by u
in
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.
My 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 xq
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-c
or C-x
. For example, the Markdown package in Emacs is pretty
much 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 b
or N
, this hydra will
stick. It allows me to switch between buffers with n
or p
, to
d
elete unused buffer or s
ave buffers.
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.
Conclusion
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 M-x
!
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 !