helpful

a fork of Wilfred's helpful
git clone https://git.trogloxene.org/helpful.git
Log | Files | Refs | README

commit 937ab3e1d97426ea344510104172bac7bff6f71f
parent 800cd18ec8ec417c7338eed6c5d5a158af8f0458
Author: Wilfred Hughes <me@wilfred.me.uk>
Date:   Wed, 30 May 2018 08:38:38 +0100

Extract keybindings from minor-mode-map-alist too

Fixes #112.

Diffstat:
MCHANGELOG.md | 3++-
Mhelpful.el | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mtest/unit-test.el | 25+++++++++++++++++++++++++
3 files changed, 82 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,6 +1,7 @@ # v0.11 -No changes yet. +Fixed an issue where Helpful didn't find keybindings if the command +was only referenced in `minor-mode-map-alist`. # v0.10 diff --git a/helpful.el b/helpful.el @@ -1134,36 +1134,68 @@ buffer." (push sym keymaps)))) keymaps)) +(defun helpful--key-sequences (command-sym keymap) + "Return all the key sequences of COMMAND-SYM in KEYMAP." + (let* ((keycodes + ;; Look up this command in the keymap, its parent and the + ;; global map. We need to include the global map to find + ;; remapped commands. + (where-is-internal command-sym keymap nil t)) + ;; Look up this command in the parent keymap. + (parent-keymap (keymap-parent keymap)) + (parent-keycodes + (when parent-keymap + (where-is-internal + command-sym (list parent-keymap) nil t))) + ;; Look up this command in the global map. + (global-keycodes + (unless (eq keymap global-map) + (where-is-internal + command-sym (list global-map) nil t)))) + (->> keycodes + ;; Ignore keybindings from the parent or global map. + (--remove (-contains-p parent-keycodes it)) + (--remove (-contains-p global-keycodes it)) + ;; Convert raw keycode vectors into human-readable strings. + (-map #'key-description)))) + (defun helpful--keymaps-containing (command-sym) - "Return a list of pairs listing keymap symbols that contain COMMAND-SYM, + "Return a list of pairs listing keymap names that contain COMMAND-SYM, along with the keybindings in each keymap. +Keymap names are typically variable names, but may also be +descriptions of values in `minor-mode-map-alist'. + We ignore keybindings that are menu items, and ignore keybindings from parent keymaps. `widget-global-map' is also ignored as it generally contains the same bindings as `global-map'." - (let (matching-keymaps) - ;; Look for this command in all keymaps. - (dolist (keymap-sym (helpful--all-keymap-syms)) - (let* ((keymap (symbol-value keymap-sym)) - (keycodes - (where-is-internal - command-sym (list keymap) nil t)) - ;; Look up this command in the parent keymap. - (parent-keymap (keymap-parent keymap)) - (parent-keycodes - (when parent-keymap - (where-is-internal - command-sym (list parent-keymap) nil t)))) - (setq keycodes - ;; Ignore keybindings that we've just inherited from the - ;; parent. - (-difference keycodes parent-keycodes)) - (when (and keycodes (not (eq keymap-sym 'widget-global-map))) - (push (cons keymap-sym - (-map #'key-description keycodes)) - matching-keymaps)))) + (let* ((keymap-syms (helpful--all-keymap-syms)) + (keymap-sym-vals (-map #'symbol-value keymap-syms)) + matching-keymaps) + ;; Look for this command in all keymaps bound to variables. + (-map + (-lambda ((keymap-sym . keymap)) + (let ((key-sequences (helpful--key-sequences command-sym keymap))) + (when (and key-sequences (not (eq keymap-sym 'widget-global-map))) + (push (cons (symbol-name keymap-sym) key-sequences) + matching-keymaps)))) + (-zip keymap-syms keymap-sym-vals)) + + ;; Look for this command in keymaps used by minor modes that + ;; aren't bound to variables. + (-map + (-lambda ((minor-mode . keymap)) + ;; Only consider this keymap if we didn't find it bound to a variable. + (unless (memq keymap keymap-sym-vals) + (let ((key-sequences (helpful--key-sequences command-sym keymap))) + (when key-sequences + (push (cons (format "minor-mode-map-alist (%s)" minor-mode) + key-sequences) + matching-keymaps))))) + minor-mode-map-alist) + matching-keymaps)) (defun helpful--format-keys (command-sym) @@ -1175,7 +1207,7 @@ same bindings as `global-map'." (dolist (key keys) (push (format "%s %s" - (propertize (symbol-name map) 'face 'font-lock-variable-name-face) + (propertize map 'face 'font-lock-variable-name-face) key) (if (eq map 'global-map) global-lines mode-lines))))) (setq global-lines (-sort #'string< global-lines)) diff --git a/test/unit-test.el b/test/unit-test.el @@ -468,6 +468,31 @@ associated a lambda with a keybinding." ([17] forward-line) ([97] forward-char)))))) +(defun helpful--dummy-command () + (interactive)) + +(ert-deftest helpful--keymaps-containing () + "Ensure that we find keymaps for variables with bindings." + ;; This is defined in the global map. + (should + (helpful--keymaps-containing #'where-is)) + ;; This is only defined in `minor-mode-map-alist'. + (should + (helpful--keymaps-containing #'ido-display-buffer)) + ;; Create a keybinding that is very unlikely to clobber actually + ;; defined keybindings in the current emacs instance. + (global-set-key (kbd "C-c M-S-c") #'helpful--dummy-command) + + ;; This command should only be in `global-map' and + ;; `mode-specific-map'. + (should + (equal + (length (helpful--keymaps-containing #'helpful--dummy-command)) + 2)) + + ;; Undo keybinding. + (global-set-key (kbd "C-c M-S-c") nil)) + (ert-deftest helpful--source () (let* ((source (helpful--source #'helpful--source t))) (should