3

I'm a new Emacs user so its possible that I've misunderstood something. I'm looking for a function that automatically changes my current key bindings depending on the current major mode.

My approach was the following:

(add-hook 'org-mode-hook
  (lambda ()
    (define-key evil-normal-state-map (kbd "C-c r") 'mark-done-and-archive))

(add-hook 'clojure-mode-hook
  (lambda ()
    (define-key evil-normal-state-map (kbd "C-c r") 'cider-restart))

Consider the following scenario:

  1. Open a new org-file in emacs, "C-c r" is now bound to mark-done-and-archive.
  2. Open a new clj-file in emacs, "C-c r" is now bound to cider-restart.
  3. Switch back to my org file using for example ivy-switch-buffer or windmove-right, "C-c r" is still bound to cider-restart, even though the current major mode is org-mode.

I'm looking for something that redefines the key bindings on every window/buffer switch to the ones I defined for that particular mode.

6
  • Mode hooks are run whenever you create a buffer with a certain mode or switch modes. You're setting a global keymap instead of the keymap for the mode you're in. I don't know how normal mode in evil works, so I don't know if commands starting with C-c are taken from the major mode keymap instead of the evil's major mode keymap.
    – user12563
    Oct 12, 2017 at 9:42
  • 2
    I think what you're looking for is local keymaps. Every well-behaved Emacs major mode, as well as some minor modes, come with their own keymap. The key to your solution is modifying the mode's local keymap, not evil-normal-state-map, which is active globally. See the FAQ for some examples.
    – Basil
    Oct 12, 2017 at 9:50
  • This indeed did the trick, thank you both for clarifying this.
    – lsund
    Oct 12, 2017 at 10:28
  • @Basil: Please consider posting your comment as an answer, so it can be accepted. Comments can be deleted anytime, and this good question has a good answer, which can help people.
    – Drew
    Oct 12, 2017 at 15:47
  • Thanks @Drew, will write something up later. I often opt for a comment first because a) I don't always have the time for what I expect of an answer; b) it helps clarify what OP is looking for.
    – Basil
    Oct 12, 2017 at 15:53

1 Answer 1

5

Disclaimer:

  • I am not an evil user so my understanding of evil-normal-state-map may be inaccurate
  • There exist naughty Emacs modes in the wild which do not follow all of the standard conventions described in this answer; such cases can be considered bugs and ought to be disciplined

I'm looking for a function that automatically changes my current keybindings depending on the current major mode.

You're in luck! This is a fundamental design feature of Emacs modes[1] and their interplay with keymaps[2, 3]. The key to achieving your desired behaviour simply lies in knowing which keymap to customise[4] and when / how[5].

In summary of the manual nodes linked in the footnotes, it is useful to note:

  • Every buffer has a single major-mode and any number of minor modes enabled at a time
  • All modes are (generally) independent of one another and of any other buffer's modes
  • Enabling a major mode in a buffer automatically enables its associated keymap for that buffer[6]; this is also true of some, but not all, minor modes
  • Enabling any mode runs its dedicated mode hook[7]

AIUI, evil-normal-state-map is the keymap of a global minor mode, meaning it can be (and usually is) active in multiple buffers, irrespective of their major-mode. Furthermore, though keymaps of both major and minor modes may not always be active, the variable that holds them (in this case evil-normal-state-map) is a global definition. This means that modifying evil-normal-state-map (from any buffer) will affect all buffers which have that minor mode enabled. This explains your reported behaviour when switching between buffers. See (elisp) Active Keymaps for a more detailed and technical explanation of this.

So, in order to bind the same key to different commands in org-mode and clojure-mode buffers, you should instead be customising the local (major mode) keymap in the corresponding mode hook:

(add-hook 'org-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c r") #'mark-done-and-archive)))

(add-hook 'clojure-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c r") #'cider-restart)))

which is equivalent to

(add-hook 'org-mode-hook
          (lambda ()
            (define-key org-mode-map (kbd "C-c r") #'mark-done-and-archive)))

(add-hook 'clojure-mode-hook
          (lambda ()
            (define-key clojure-mode-map (kbd "C-c r") #'cider-restart)))

This, however, will cause the same (redundant) operation to be performed every time one of these major modes is enabled. Since the two mode maps are global variables, a single modification after they are defined and before they are used for the first time will suffice:

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c r") #'mark-done-and-archive))

(with-eval-after-load 'clojure-mode
  (define-key clojure-mode-map (kbd "C-c r") #'cider-restart))

Footnotes

  1. (emacs) Major Modes
  2. (eintr) Keymaps
  3. (elisp) Keymap Basics
  4. (emacs) Rebinding
  5. (emacs) Init Rebinding
  6. (emacs) Local Keymaps
  7. (emacs) Hooks
1
  • Awesome answer. Just what a emacs-newbie needs!
    – lsund
    Oct 16, 2017 at 14:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.