Skip to content

Instantly share code, notes, and snippets.

@hlissner
Created August 31, 2026 02:58
Show Gist options
  • Select an option

  • Save hlissner/7c28034ebd8cf91a22ef9a6318dca1bd to your computer and use it in GitHub Desktop.

Select an option

Save hlissner/7c28034ebd8cf91a22ef9a6318dca1bd to your computer and use it in GitHub Desktop.
Doom Emacs: how to rebind keys

< back to index

Important

This gist is temporary until the new documentation (docs.doomemacs.org) and community wiki (wiki.doomemacs.org) have been published.

How to (re)bind keys

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
    Ctrl c ?\C-c
  • As a vector of character codes and/or events:
    Key Emacs Lisp
    Tab [?\t]
    x [?x]
    Ctrl c, 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)

Tip

Doom's map! macro implicitly wraps its keys in kbd so you don't have to.

Tip

ℹ️ 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.

Tip

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.

Tip

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.

    [!WARNING] 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!
    (map! "C-x C-f" #'counsel-find-file
          "C-x C-c" #'save-buffers-kill-terminal)
    • global-set-key
    (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
    (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)
    • keymap-set
    (keymap-set global-map "C-x C-f" #'counsel-find-file)
    (keymap-set global-map "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.

    • With Doom's map! macro:
    (map! :after python
          :map python-mode-map
          :prefix "C-x C-p"
          "f" #'python-pytest-file
          "r" #'python-pytest-repeat)
    • In vanilla Emacs
    (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 has helm-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 provides general-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:

    1. Its leader keys (not its localleader keys)
    2. 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:

    • With Doom's map! macro:
    ;; 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)
    • With general.el:
    ;; 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)
    • In vanilla Emacs:
    ;; 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.

Warning

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 inside python-mode-map that is only active when both python-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 on python-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:

image|690x102

How to define your own labels or change these are described in How to bind > Which-key labels below.

How to bind

Global keys

  • With Doom's map! macro
    (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)
  • With general.el
    (general-define-key :prefix "C-x"
                        "C-r" #'git-gutter:revert-hunk
                        "C-b" #'ibuffer
                        "C-l" #'+lookup/file)
  • In vanilla Emacs:
    (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:

  • With Doom's map! macro
    (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))
  • With general.el:
    (general-define-key :package 'python
                        :prefix "C-x"
                        "C-r" #'python-shell-send-region)
  • In vanilla Emacs:
    (with-eval-after-load 'python
      (define-key python-mode-map (kbd "C-x C-r") #'python-shell-send-region)
      ;; There's also `keymap-set' in Emacs 29.1+
      (keymap-set python-mode-map "C-x C-r" #'python-shell-send-region))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment