10

I use use-package to manage installed packages and bind-key to assign actions to custom keys that I like.

I override most of the default Emacs keybinding (e.g. C-n becomes M-k, C-p becomes M-i), but I'm OK with other modes overriding my keybinding scheme. Sometimes, I want my keybinding persist, however. I want M-k mean something else, than in default Gnus or Helm.

However they all conflict with each other on Emacs startup, because I can't add a binding to a keymap, if it doesn't exist (because use-package sometimes defers loading of a package). E.g., the following commands throw errors (e.g. (void-variable helm-map)), because Helm and Gnus aren't yet fully loaded.

(bind-key "M-Y" 'helm-end-of-buffer helm-map)
(bind-key "M-k" 'helm-next-line helm-find-files-map)
(bind-key "M-s" 'other-window gnus-summary-mode-map)

I have all my use-package invocations in one file and bind-key for custom keybindings in another file. I don't want to put bindings into use-package calls, because maybe I want to publish my custom keybinding scheme as a standalone package. What if I want someone installing my scheme had Helm and Gnus local keybindings overriden too?

How do I manage mode-local keybindings using bind-key, so that all keys are set even if packages are loaded lately, and all key settings are inside one file?

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
Mirzhan Irkegulov
  • 1,088
  • 1
  • 9
  • 16

4 Answers4

21

You can use with-eval-after-load to defer the key binding until after a certain module has been loaded (and thus defined the keymap):

(with-eval-after-load "helm"
  (bind-key "M-Y" #'helm-end-of-buffer helm-map))

Use C-h v helm-map to find which module the keymap is defined in, and thus what to put in the string on the first line.


with-eval-after-load was introduced in Emacs 24.4. If you have an earlier Emacs version, you need to use eval-after-load instead, and put a single quote in front of the bind-key call:

(eval-after-load "helm"
  '(bind-key "M-Y" #'helm-end-of-buffer helm-map))

If you want to put several bind-key calls in this form, with with-eval-after-load you just put them one after another, but with eval-after-load you need to wrap them all in a progn:

(eval-after-load "helm"
  '(progn
     (bind-key "M-Y" #'helm-end-of-buffer helm-map)
     (bind-key "M-k" #'helm-next-line helm-find-files-map)))
Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
legoscia
  • 6,012
  • 29
  • 54
9

Solution

In order to execute stuff after a given package is loaded, you need to put that after :config in use-package.

Here's an example using the snippet in your question:

Snippet # 1

(use-package helm
  :config
  (progn
    (bind-key "M-Y" #'helm-end-of-buffer helm-map)
    (bind-key "M-k" #'helm-next-line helm-find-files-map)))

(use-package gnus
  :config
  (bind-key "M-s" #'other-window gnus-summary-mode-map))

Explanation

It is OK to have the below 2 snippets at different places in your emacs init.el or in any of the nested files loaded/required.

Snippet # 2

(use-package gnus)

Snippet # 3

(use-package gnus
  :config
  (bind-key "M-s" #'other-window gnus-summary-mode-map))

The reason is that it does not matter which of the above 2 snippets is executed first.

Here is why.. below is what Snippet # 3 expands to.

You get the below by doing M-x pp-macroexpand-last-sexp when the point (cursor) is after the last closing parenthesis of that snippet.

Snippet # 4

(if (not (require 'gnus nil t))
    (ignore (message (format "Could not load %s" 'gnus)))
  (condition-case-unless-debug err
      (bind-key "M-s" #'other-window gnus-summary-mode-map)
    (error
     (ignore
      (display-warning 'use-package
                       (format "%s %s: %s" "gnus" ":config"
                               (error-message-string err))
                       :error))))
  t)

The above snippet basically means that

  • gnus is required first and then the bind-key form is executed.
  • If gnus is not found then you will see a message in the *Messages* buffer saying that that package couldn't be loaded.
  • It will throw error if there is any problem in the executing (bind-key "M-s" #'other-window gnus-summary-mode-map)

Also if gnus is already required by Snippet # 2 above and it is required again by Snippet # 3, it does not matter because require does not load a package again if it is already loaded.


Reference

From the use-package basics on its github,

:config can be used to execute code after a package is loaded. In cases where loading is done lazily (see more about autoloading below), this execution is deferred until after the autoload occurs:

Snippet # 5

(use-package foo
  :init
  (setq foo-variable t)
  :config
  (foo-mode 1))

Above executes the :init section ( (setq foo-variable t) ) before foo package is loaded. But (foo-mode 1) in the :config section is executed after foo is loaded.

Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179
3

On the opposite of the other answers, I've always used hooks for this:

(defun my-company-maps()
  (define-key company-active-map "\C-x\M-h" 'company-show-doc-buffer)
  (define-key company-active-map "\C-n" 'company-select-next)
  (define-key company-active-map "\C-p" 'company-select-previous)
  (define-key company-active-map "\C-h" 'delete-backward-char))

(add-hook 'company-mode-hook 'my-company-maps)
Jesse
  • 1,984
  • 11
  • 19
2

Since you are already using bind-key, straight from documentation of bind-key.el:

If you want the keybinding to override all minor modes that may also bind the same key, use the `bind-key*' form:

(bind-key* "<C-return>" 'other-window)

To unbind a key within a keymap (for example, to stop your favorite major mode from changing a binding that you don't want to override everywhere), use unbind-key:

(unbind-key "C-c x" some-other-mode-map)

The last form breaks down if the keymap is currently undefined because the file defining some-other-mode-maphas not yet be loaded. So you could put this in a use-package for some-other-mode(the package defining some-other-mode-map), or using with-eval-after-load:

(with-eval-after-load 'some-other-mode
  (unbind-key "C-c x" some-other-mode-map))

An other alternative would be defining your own minor mode containing all bindings which should not be overridden by major modes:

(defvar my-very-own-keymap (make-keymap) "my very own keymap.")

(define-key my-very-own-keymap (kbd "M-i") 'my-foo)

(define-minor-mode my-very-own-keys-minor-mode
  "Minor mode with my very own keybindings."
  t " my-own-keys" my-very-own-keymap)
cogsci
  • 53
  • 3