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- and H- maps.

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” :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.

  1. It manages keybindings for Evil and non-evil use cases.
  2. It provides a quick way to define multiple keybindings at once.
  3. It respects keymaps.
  4. It allow for really simple :prefix definition, being C-c, C-§ if you want, or SPC to emulate spacemacs leader.
  5. It has a nice integration with which-key. One can define a keybinding and describe it in the same statement.
  6. 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 normal and visual 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 delete unused buffer or save 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 !