72

I have rebound a the 'd' key in gnus-article-mode, but its old behavior is still active when the point is on an attachment. I can see that the rebinding did not take effect there by doing C-h k d, but it is not telling me what keymap is in effect at that point, so that I can rebind it.

Is there a way to find it out?

Here is a precise example: I'm using evil, and I want articles to be in motion mode. For my keyboard layout I have 'd' configured as the key to go up.

(evil-mode 1)
(add-to-list 'evil-motion-state-modes 'gnus-article-mode)
(setq evil-emacs-state-modes (remove 'gnus-article-mode evil-emacs-state-modes))
(define-key evil-motion-state-map "d" 'evil-previous-line)

To make sure the evil keys are taken into account, I unset gnus key in the local map:

(defun as/alter-article-evil-map ()
  (local-unset-key "d"))
(add-hook 'gnus-article-mode-hook 'as/alter-article-evil-map)

Unfortunately, when the point is on an attachment, the 'd' key no longer goes up but it offers me to delete the attachment. I guess another binding is active at that point, hence the question.

Solution I used the keymaps-at-point below to find the keymap used was from a text property. I then looked at the code of the bound function to find the name of the keymap gnus-mime-button-map. The following code does what I want:

(defun as/alter-article-evil-map ()
  (define-key gnus-mime-button-map "d" nil))
(add-hook 'gnus-article-mode-hook 'as/alter-article-evil-map)
2

4 Answers 4

70

Emacs 25

As mentioned by @YoungFrog in the comments, starting with Emacs 25.1, the good-old C-h k method of describing key-binds will also tell you which keymap the key was found in.

Before Emacs 25

There’s some code here on this, but it’s incomplete as it does not cover everything. Below is an improved version of it.

Keys can be bound in 9(!) ways. Thanks to @Drew for this link (also supplemented by this) with the full list. By order of precedence, they are:

  1. A terminal-specific set of keys, overriding-terminal-local-map. This is defined by the set-transient-map function.
  2. A buffer-local override map, overriding-local-map. If this one is set, items 3–8 are skipped (probably why you don't see many of these).
  3. At point via the keymap text-propety (which could go on actual text or on overlays).
  4. A variable which essentially simulates different possible sets of enabled minor-modes, emulation-mode-map-alists.
  5. A variable where major-modes can override the keybinds of minor-modes, minor-mode-overriding-map-alist.
  6. The actual minor-modes, whose keybinds are stored in minor-mode-map-alist.
  7. At point (again), via the local-map text property. If this exists, item 8 is skipped.
  8. The standard buffer-local keymap (where major-mode or buffer-local keybinds go), returned by the function current-local-map.
  9. The global keymap, returned by current-global-map.

There's a also a semi-item 10. Whatever command was found through the above procedure might also have been remaped.

The following function queries some of these possibilities (the most likely ones), and returns or prints the result.

(defun locate-key-binding (key)
  "Determine in which keymap KEY is defined."
  (interactive "kPress key: ")
  (let ((ret
         (list
          (key-binding-at-point key)
          (minor-mode-key-binding key)
          (local-key-binding key)
          (global-key-binding key))))
    (when (called-interactively-p 'any)
      (message "At Point: %s\nMinor-mode: %s\nLocal: %s\nGlobal: %s"
               (or (nth 0 ret) "") 
               (or (mapconcat (lambda (x) (format "%s: %s" (car x) (cdr x)))
                              (nth 1 ret) "\n             ")
                   "")
               (or (nth 2 ret) "")
               (or (nth 3 ret) "")))
    ret))

There are built-in functions for each of these except the first, so we must create one (also an improved version of the code linked above).

(defun key-binding-at-point (key)
  (mapcar (lambda (keymap) (when (keymapp keymap)
                             (lookup-key keymap key)))
          (list
           ;; More likely
           (get-text-property (point) 'keymap)
           (mapcar (lambda (overlay)
                     (overlay-get overlay 'keymap))
                   (overlays-at (point)))
           ;; Less likely
           (get-text-property (point) 'local-map)
           (mapcar (lambda (overlay)
                     (overlay-get overlay 'local-map))
                   (overlays-at (point))))))

Since you’re saying the behaviour is active when point is on an attachment, there’s a good chance this keybind takes place on an overlay or text-property.

If that doesn't work, try the following command as well. Just place the cursor on the attachment, and do M-x keymaps-at-point.

(defun keymaps-at-point ()
  "List entire keymaps present at point."
  (interactive)
  (let ((map-list
         (list
          (mapcar (lambda (overlay)
                    (overlay-get overlay 'keymap))
                  (overlays-at (point)))
          (mapcar (lambda (overlay)
                    (overlay-get overlay 'local-map))
                  (overlays-at (point)))
          (get-text-property (point) 'keymap)
          (get-text-property (point) 'local-map))))
    (apply #'message
           (concat 
            "Overlay keymap: %s\n"
            "Overlay local-map: %s\n"
            "Text-property keymap: %s\n"
            "Text-property local-map: %s")
           map-list)))
6
  • 1
    This will tell me whether the key has a local binding, and what command it is bound to, but it still does not tell me the name of the keymap that it came from.
    – nispio
    Oct 7, 2014 at 15:34
  • @nispio if it has a local binding, it must be from the major-mode's keymap or from a call to local-set-key. Unfortunately, there's no foolproof way to tell the two cases apart.
    – Malabarba
    Oct 7, 2014 at 15:39
  • 1
    @Malabarba Do you mean "Unfortunately, there's no easy way to tell the two cases apart?" Surely the information is all present. Also, does every major mode have exactly one mode map? If not, is there a way to tell which major mode map is active?
    – nispio
    Oct 7, 2014 at 16:37
  • 1
    Starting from yet to be released emacs 25, "C-h k" will, in some (well, "in most" hopefully) cases tell you in which keymap (more precisely: a symbol which holds that keymap as its value) a given keybinding is defined. example output: k runs the command gnus-summary-kill-same-subject-and-select (found in gnus-summary-mode-map), which is (...)
    – YoungFrog
    Feb 12, 2015 at 13:01
  • 2
    For anyone interested, here is the relevant commit that displays the key binding's keymap in emacs 25+. Jul 11, 2015 at 18:46
3

What about:

(defun my-lookup-key (key)
  "Search for KEY in all known keymaps."
  (mapatoms (lambda (ob) (when (and (boundp ob) (keymapp (symbol-value ob)))
                      (when (functionp (lookup-key (symbol-value ob) key))
                        (message "%S" ob))))
            obarray))

For example for (my-lookup-key (kbd "C-c v v")) I get list in *Message* buffer:

global-map
term-pager-break-map
widget-global-map

This approach is useful for searching sub-keymap which included into higher level keymap, for example output from:

(my-lookup-key (kbd "d"))

is vc-prefix-map which is included into global-map:

(eq (lookup-key global-map (kbd "C-x v")) 'vc-prefix-map)

If you modify filtering condition to include keymapp - you will be able to search ever for prefixes:

(defun my-lookup-key-prefix (key)
  "Search for KEY as prefix in all known keymaps."
  (mapatoms (lambda (ob) (when (and (boundp ob) (keymapp (symbol-value ob)))
                      (when (let ((m (lookup-key (symbol-value ob) key)))
                              (and m (or (symbolp m) (keymapp m))))
                        (message "%S" ob))))
            obarray))

So rst-mode-map will be found on both:

(my-lookup-key-prefix (kbd "C-c C-t"))
(my-lookup-key-prefix (kbd "C-c C-t C-t"))
1
  • this is not correct. it ignores remappings.
    – alinsoar
    Nov 13, 2021 at 14:12
1

I'm aware that the following does not answer the question of How can I find out which keymap, however it deals with the underlying issue in this case of how to ensure that the d key behaves according to what the user wants. If preferred, I can remove this answer and convert into another question + answer regarding that issue.

As a method to override the text-property/overlay map, you should be able to use:

(let ((map (make-sparse-keymap)))
  (define-key map "d" 'evil-previous-line)
  (setq overriding-local-map map))

As per Controlling Active Maps, overriding-local-map takes precedence over every other active map other than overriding-terminal-local-map.

2
  • This breaks everything: I no longer can open a message in summary mode, and evil and icicles stop working.
    – brab
    Nov 2, 2014 at 18:07
  • This breaks everything: I no longer can open a message in summary mode, and evil and icicles stop working. Apr 18, 2016 at 16:34
1

emacs-buttons provides buttons-display, which with a prefix argument displaysa recursive visualization of all current bindings.

(Disclaimer: I am the author of the package)

https://github.com/erjoalgo/emacs-buttons/blob/master/doc/img/sample-visualization.png

Your Answer

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

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