From 10f4c070302a2b2bc0cb2115a0dd91921208b9ac Mon Sep 17 00:00:00 2001
From: Stefan Kangas <stefankangas@gmail.com>
Date: Sat, 24 Aug 2019 01:02:04 +0200
Subject: [PATCH] Add new help function describe-keymap

This code was written by Drew Adams.  Stefan Kangas adapted it for
inclusion in Emacs, added unit tests and documentation.

* lisp/help-fns.el (describe-keymap): New function to show key
bindings for a given keymap.  (Bug#30660)
* doc/lispref/keymaps.texi (Scanning Keymaps): Document it.
* etc/NEWS: Announce it.

* test/lisp/help-fns-tests.el (help-fns-test-describe-keymap/symbol)
(help-fns-test-describe-keymap/value)
(help-fns-test-describe-keymap/value+search-symbols)
(help-fns-test-describe-keymap/symbol+search-symbols)
(help-fns-test-describe-keymap/not-keymap)
(help-fns-test-describe-keymap/let-bound)
(help-fns-test-describe-keymap/dynamically-bound-no-file): New tests.
---
 doc/lispref/keymaps.texi    | 12 ++++++
 etc/NEWS                    |  3 ++
 lisp/help-fns.el            | 73 +++++++++++++++++++++++++++++++++++++
 test/lisp/help-fns-tests.el | 41 +++++++++++++++++++++
 4 files changed, 129 insertions(+)

diff --git a/doc/lispref/keymaps.texi b/doc/lispref/keymaps.texi
index 532e537026..569e6137e6 100644
--- a/doc/lispref/keymaps.texi
+++ b/doc/lispref/keymaps.texi
@@ -2004,6 +2004,18 @@ Scanning Keymaps
 instead of the current buffer's.
 @end deffn
 
+@deffn Command describe-keymap keymap &optional search-symbols
+This function creates a listing of all key bindings in variable
+@var{keymap}, and displays it in a buffer named @file{*Help*}.  When
+called interactively, prompt for a variable that has a keymap value.
+
+If optional argument @var{search-symbols} is non-nil, and @var{keymap}
+is not a symbol, search all variables for one whose value is KEYMAP
+and use that to describe the key bindings.  This makes it possible to
+not only see the bindings but also to identify the name of a keymap
+when you have its value.
+@end deffn
+
 @node Menu Keymaps
 @section Menu Keymaps
 @cindex menu keymaps
diff --git a/etc/NEWS b/etc/NEWS
index e1eb74f86e..05c2940a92 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1337,6 +1337,9 @@ directories in the destination.
 
 ** Help
 
++++
+*** New command 'describe-keymap' describes keybindings in a keymap.
+
 ---
 *** Description of variables and functions give an estimated first release.
 
diff --git a/lisp/help-fns.el b/lisp/help-fns.el
index 235aa9a6e1..276d44958b 100644
--- a/lisp/help-fns.el
+++ b/lisp/help-fns.el
@@ -1557,6 +1557,79 @@ describe-categories
 	  (insert "\nThe parent category table is:")
 	  (describe-vector table 'help-describe-category-set))))))
 
+;;;###autoload
+(defun describe-keymap (keymap &optional search-symbols)
+  "Describe key bindings in KEYMAP.
+When called interactively, prompt for a variable that has a
+keymap value.  Completion is available for the variable name.
+
+If optional argument SEARCH-SYMBOLS is non-nil, and KEYMAP is
+not a symbol, search all variables for one whose value is KEYMAP
+and use that to describe the key bindings.  This makes it
+possible to not only see the bindings but also to identify the
+name of the keymap in question."
+  (interactive (list
+                (intern (completing-read "Keymap: " obarray
+                                         (lambda (m)
+                                           (and (boundp m)
+                                                (keymapp (symbol-value m))))
+                                         t nil 'variable-name-history))))
+  (let (used-gentemp)
+    (unless (and (symbolp keymap)
+                 (boundp keymap)
+                 (keymapp (symbol-value keymap)))
+      (when (not (keymapp keymap))
+        (if (symbolp keymap)
+            (error "Not a keymap variable: %S" keymap)
+          (error "Not a keymap")))
+      (let ((sym nil))
+        (when search-symbols
+          (setq sym (catch 'describe-keymap
+                      (mapatoms (lambda (symb)
+                                  (when (and (boundp symb)
+                                             (eq (symbol-value symb) keymap)
+                                             (not (eq symb 'keymap))
+                                             (throw 'describe-keymap symb)))))
+                      nil)))
+        (unless sym
+          (setq sym (cl-gentemp "KEYMAP OBJECT (no variable) "))
+          (setq used-gentemp t)
+          (set sym keymap))
+        (setq keymap sym)))
+    ;; Follow aliasing.
+    (setq keymap (or (ignore-errors (indirect-variable keymap)) keymap))
+    (help-setup-xref (list #'describe-keymap keymap)
+                     (called-interactively-p 'interactive))
+    (let* ((name (symbol-name keymap))
+           (doc (documentation-property keymap 'variable-documentation))
+           (file-name (find-lisp-object-file-name keymap 'defvar)))
+      (with-help-window (help-buffer)
+        (with-current-buffer standard-output
+          (unless used-gentemp
+            (princ (format-message "%S is a keymap variable" keymap))
+            (if (not file-name)
+                (princ ".\n\n")
+              (princ (format-message
+                      " defined in `%s'.\n\n"
+                      (if (eq file-name 'C-source)
+                          "C source code"
+                        (file-name-nondirectory file-name))))
+              (save-excursion
+                (re-search-backward (substitute-command-keys
+                                     "`\\([^`']+\\)'")
+                                    nil t)
+                (help-xref-button 1 'help-variable-def
+                                  keymap file-name))))
+          (when (and (not (equal "" doc)) doc)
+            (princ "Documentation:\n")
+            (princ (format-message "%s\n\n" doc)))
+          ;; Use `insert' instead of `princ', so control chars (e.g. \377)
+          ;; insert correctly.
+          (insert (substitute-command-keys (concat "\\{" name "}"))))))
+    ;; Cleanup.
+    (when used-gentemp
+      (makunbound keymap))))
+
 
 ;;; Replacements for old lib-src/ programs.  Don't seem especially useful.
 
diff --git a/test/lisp/help-fns-tests.el b/test/lisp/help-fns-tests.el
index da4d25d0a6..0bdfd8c39c 100644
--- a/test/lisp/help-fns-tests.el
+++ b/test/lisp/help-fns-tests.el
@@ -123,4 +123,45 @@ help-fns-test-describe-symbol
     (goto-char (point-min))
     (should (looking-at "^font-lock-comment-face is "))))
 
+
+
+;;; Tests for describe-keymap
+(ert-deftest help-fns-test-describe-keymap/symbol ()
+  (describe-keymap 'minibuffer-local-must-match-map)
+  (with-current-buffer "*Help*"
+    (should (looking-at "^minibuffer-local-must-match-map is"))))
+
+(ert-deftest help-fns-test-describe-keymap/value ()
+  (describe-keymap minibuffer-local-must-match-map)
+  (with-current-buffer "*Help*"
+    (should (looking-at "^key"))))
+
+(ert-deftest help-fns-test-describe-keymap/value+search-symbols ()
+  (describe-keymap minibuffer-local-must-match-map t)
+  (with-current-buffer "*Help*"
+    (should (looking-at "^minibuffer-local-must-match-map is"))))
+
+(ert-deftest help-fns-test-describe-keymap/symbol+search-symbols ()
+  (describe-keymap 'minibuffer-local-must-match-map t)
+  (with-current-buffer "*Help*"
+    (should (looking-at "^minibuffer-local-must-match-map is"))))
+
+(ert-deftest help-fns-test-describe-keymap/not-keymap ()
+  (should-error (describe-keymap nil))
+  (should-error (describe-keymap emacs-version))
+  (should-error (describe-keymap nil t))
+  (should-error (describe-keymap emacs-version t)))
+
+(ert-deftest help-fns-test-describe-keymap/let-bound ()
+  (let ((foobar minibuffer-local-must-match-map))
+    (describe-keymap foobar)
+    (with-current-buffer "*Help*"
+      (should (looking-at "^key")))))
+
+(ert-deftest help-fns-test-describe-keymap/dynamically-bound-no-file ()
+  (setq help-fns-test--describe-keymap-foo minibuffer-local-must-match-map)
+  (describe-keymap 'help-fns-test--describe-keymap-foo)
+  (with-current-buffer "*Help*"
+    (should (looking-at "^help-fns-test--describe-keymap-foo is"))))
+
 ;;; help-fns-tests.el ends here
-- 
2.23.0

