Every Emacs user will want to (re)bind keys at some point, but keybindings can be a complicated affair in Emacs, what with all the keymaps and hierarchy of precedence. This guide walks you through the concepts and how to bind keys in a number of scenarios.
Concepts
Key sequences
Emacs represents a sequence of key presses in Elisp a number of ways:
- As a character code:
Key Emacs Lisp Tab ?\t
x ?x
Ctrlc ?\C-c
- As a vector of character codes and/or events:
Key Emacs Lisp Tab [?\t]
x [?x]
Ctrlc, c [?\C-c ?c]
Tab, Left Arrow, Left Mouse [tab left down-mouse-1]
Shift+Tab [backtab]
- Or as a key string:
Key Emacs Lisp Tab (kbd "TAB")
or(kbd "<tab>")
Ctrlc, c (kbd "C-c c")
SPC, x, y, z (kbd "SPC x y z")
Emacs can’t understand key strings directly, so they must be converted to an internal representation using the kbd
function:
(global-set-key (kbd "TAB") #'some-command)
(global-set-key (kbd "C-c C-c") #'another-command)
Doom’s
map!
macro implicitly wraps its keys inkbd
so you don’t have to.
Some keys have multiple representations, like
"TAB"
and"<tab>"
.
(kbd "TAB")
evaluates to"\t"
(kbd "<tab>")
evaluates to[tab]
.They are not interchangeable. e.g. Terminal emacs won’t understand
[tab]
(but GUI Emacs will). If you are binding a key to tab it’s best to bind to both for maximum coverage.
Prefixes
A prefix is a key that begins a key sequence. For example, the key sequence C-xC-kb is comprised of three keypresses. Both C-x and C-xC-k are prefixes.
Emacs will wait indefinitely for you to complete a key sequence. This may be unusual to vim users, where the editor will time out after a moment. To “abort” a key sequence, press C-g.
C-g isn’t actually an abort procedure in the formal sense. We’re exploiting the fact that Emacs will throw a harmless error when you type a key sequence that has no command bound to it. The error stops Emacs from waiting for your next key.
By unwritten convention Emacs, Doom, and third party packages avoid binding to
C-g
anywhere, so it’s safe to exploit for this purpose.
Keymaps
Emacs reads keymaps to determine what to do when you type in a key sequence. A keymap is a mapping of key sequences to commands (and each key=>command mapping is a keybind). At any time Emacs has a hierarchy of active keymaps, all vying for precedence.
Keymaps with higher precedence will override keymaps with lower precedence. i.e. If you press a key, Emacs will travel down the list of active keymaps from highest to lowest precedence until it finds a matching keybind.
Below I describe the most common keymaps you will encounter, but this is not an exhaustive list.
Look up keymaps with
M-x helpful-variable
(SPChv for evil users, C-hv for vanilla users).
-
global-map
is the global keymap and is always active, but has the lowest precedence.Unless you are targeting buffers where evil is disabled — or in emacs state — it is rarely wise for evil users to bind keys to this keymap, because evil’s global keymaps have higher precedence (see “Evil States” in the next section).
(map! "C-x C-f" #'counsel-find-file "C-x C-c" #'save-buffers-kill-terminal)
(global-set-key (kbd "C-x C-f") #'counsel-find-file) (global-set-key (kbd "C-x C-c") #'save-buffers-kill-terminal)
(define-key global-map (kbd "C-x C-f") #'counsel-find-file) (define-key global-map (kbd "C-x C-c") #'save-buffers-kill-terminal)
-
Major modes have their own keymap
{MAJOR-MODE}-map
, and should be used for keybinds that you want restricted to a specific major mode (i.e. mode-local keybinds).Major mode keymaps have higher precedence than
global-map
, but lower than minor mode keymaps, and are only active when the major mode is.(map! :after python :map python-mode-map :prefix "C-x C-p" "f" #'python-pytest-file "r" #'python-pytest-repeat)
(with-eval-after-load 'python (define-key python-mode-map (kbd "C-x C-p f") #'python-pytest-file) (define-key python-mode-map (kbd "C-x C-p r") #'python-pytest-repeat))
-
Minor modes have their own keymaps as well
{MINOR-MODE}-map
, and some packages may define more than one (for example, helm hashelm-map
,helm-grep-map
,helm-bookmark-map
, etc). These are only active when their associated minor mode is active.Minor mode keymaps have higher precedence than major mode keymaps. Their priority relative to other minor modes is sorted by activation order, with recent minor modes having higher precedence.
-
The
general.el
package providesgeneral-override-mode-map
, which is a special keymap that tries to have precedence over most other keymaps (including minor modes’). Doom only binds two things to this keymap:- Its leader keys (not its localleader keys)
-
M-x
(so it doesn’t get overwritten by a careless package/minor mode)
Use this keymap if you want to bind a key you want nothing else to override. e.g. another leader key:
;; Binds a second leader key to C-x C-x (original key is still ;; on C-c for vanilla users and SPC for evil users) (map! :map 'override "C-x C-x" #'doom/leader)
;; Binds a second leader key to C-x C-x (original key is still ;; on C-c for vanilla users and SPC for evil users) (general-def 'normal 'override "C-x C-x" #'doom/leader)
;; Binds a second leader key to C-x C-x (original key is still ;; on C-c for vanilla users and SPC for evil users) (define-key general-override-mode-map (kbd "C-x C-x") #'doom/leader)
Evil states
Evil users have yet another layer of keymaps to contend with: for states.
What evil calls “states”, vim users know as “modes”. e.g. the normal/visual/insert modes are referred to as normal/visual/insert states in evil’s internals.
Evil’s state keymaps come in three flavors:
- The global keymaps that evil defines for each of its states:
evil-normal-state-map
,evil-visual-state-map
,evil-insert-state-map
, etc. The bindings in these maps are visible in all buffers that are in the corresponding state. - The buffer-local state keymaps:
evil-normal-state-local-map
,evil-visual-state-local-map
, etc. Keys bound here are restricted to the buffer they belong to (i.e. they are buffer-local keybinds), and will be available when that buffer is in the corresponding state. - The special nested ones evil creates in existing keymaps for each of its states (which it calls auxiliary keymaps). For instance,
(evil-define-key* 'normal python-mode-map "x" #'do-something)
creates an auxiliary keymap insidepython-mode-map
that is only active when bothpython-mode
is active and you are in evil’s normal mode.
Precedence with evil keymaps is more complicated. In an attempt to over-simplify, these are listed in order of lowest to highest precedence:
global-map
python-mode-map
evil-normal-state-map
evil-normal-state-local-map
-
normal
auxiliary map onpython-mode-map
Which-key
Doom installs the which-key plugin, which presents these popups while Emacs is waiting for you to complete a key sequence:
How to define your own labels or change these are described in How to bind > Which-key labels below.
How to bind
Global keys
(map! "C-x C-r" #'git-gutter:revert-hunk
"C-x C-b" #'ibuffer
"C-x C-l" #'+lookup/file)
;; or
(map! :prefix "C-x"
"C-r" #'git-gutter:revert-hunk
"C-b" #'ibuffer
"C-l" #'+lookup/file)
(general-define-key :prefix "C-x"
"C-r" #'git-gutter:revert-hunk
"C-b" #'ibuffer
"C-l" #'+lookup/file)
(global-set-key (kbd "C-x C-r") #'git-gutter:revert-hunk)
(global-set-key (kbd "C-x C-b") #'ibuffer)
(global-set-key (kbd "C-x C-l") #'+lookup/file)
;; or
(let ((map global-map))
(define-key map (kbd "C-x C-r") #'git-gutter:revert-hunk)
(define-key map (kbd "C-x C-b") #'ibuffer)
(define-key map (kbd "C-x C-l") #'+lookup/file))
Mode or buffer-local keys
It is important to bind your keys after the keymap is defined, for two reasons:
- You will get
void-variable
errors if you try to bind keys to a keymap that isn’t defined. - Doom or other packages may bind their own default keys to that keymap – running yours after ensures yours override the defaults and not the other way around.
The key to achieving this is one of: the after!
macro, map!
's :after
keyword, or with-eval-after-load
:
(map! :after python
:map python-mode-map
"C-x C-r" #'python-shell-send-region)
;; or
(after! python
(map! :map python-mode-map "C-x C-r" #'python-shell-send-region))
(general-define-key :package 'python
:prefix "C-x"
"C-r" #'python-shell-send-region)
(with-eval-after-load 'python
(define-key python-mode-map (kbd "C-x C-r") #'python-shell-send-region))
Evil state-local keys
Which-key labels
Defining a prefix
Overriding deferred keys
Defining new evil text objects
How to unbind keys
Common pitfalls
Avoid :prefix-map
Binding to SPC
instead of :leader
Key sequence ... starts with non-prefix key ...
errors
Special cases
Binding keys to key sequences
-
general-simulate-key
, vectors, or string key sequences