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)
Drew
  • 75,699
  • 9
  • 109
  • 225
brab
  • 925
  • 1
  • 7
  • 8
  • 3
    Use this pseudo-code Lisp and description as your **high-level guide to how Emacs looks up a key**: [Elisp manual, node `Searching Keymaps`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Searching-Keymaps.html#Searching-Keymaps). See also nodes [`Functions for Key Lookup`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Functions-for-Key-Lookup.html#Functions-for-Key-Lookup) and [`Active Keymaps`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Active-Keymaps.html#Active-Keymaps). – Drew Oct 03 '14 at 15:24
  • There was a pretty big bug with my initial answer in the second function. I've just fixed it now, so it should be able to locate your keybind. Could you try again? – Malabarba Oct 30 '14 at 20:51

4 Answers4

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)))
Malabarba
  • 22,878
  • 6
  • 78
  • 163
  • 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 07 '14 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 07 '14 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 07 '14 at 16:37
  • @nispio AFAIK there's no foolproof way of determining the major keymap active in the current buffer. You can _guess_ it from the name of the major mode, but that isn't foolproof. Since you can't know the name of the variable, you can't lookup whether a given local bind comes from the major mode. – Malabarba Oct 07 '14 at 16:40
  • 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 '15 at 13:01
  • 2
    For anyone interested, here is the [relevant commit](http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=958d20d22a5e9b997de0bf7cc63436dc82486111) that displays the key binding's keymap in emacs 25+. – Kaushal Modi Jul 11 '15 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"))
gavenkoa
  • 3,352
  • 19
  • 36
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

erjoalgo
  • 853
  • 1
  • 5
  • 18
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.

Jonathan Leech-Pepin
  • 4,307
  • 1
  • 19
  • 32
  • This breaks everything: I no longer can open a message in summary mode, and evil and icicles stop working. – brab Nov 02 '14 at 18:07
  • This breaks everything: I no longer can open a message in summary mode, and evil and icicles stop working. – Emily Hoover Apr 18 '16 at 16:34