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?

0

4 Answers 4

22

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)))
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.

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)
1
  • Me too, I thought this was the preferred way of doing it. Commented May 23, 2015 at 20:09
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)

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.