Subject: bug#60749: [PATCH] Allow describing commands when using :bind in use-package

From: Joel Pettersson <[email-masked]>
Date: Thu, 12 Jan 2023 03:38:30 +0100

Hello,

I would like to be able to provide descriptions of commands in key 
bindings when using `:bind' in use-package declarations. The `bind-key' 
macro from use-package, which `:bind' entries expand to, already 
supports that; this patch contains a suggestion on how to extend that 
support to `:bind'.

Here is an example usage:

     (use-package avy
       :bind ("C-:" ("Jump to char" . avy-goto-char)
              "M-g f" ("Jump to line" . avy-goto-line)))

Best regards,
Joel

From 15e4e45dbddb5fb387d1d3fda43b1b72d054fb41 Mon Sep 17 00:00:00 2001
From: Joel Pettersson <[email-masked]>
Date: Thu, 12 Jan 2023 02:50:46 +0100
Subject: [PATCH] Allow describing commands when using :bind in use-package

In `use-package' declarations, `:bind' entries turn into individual `bind-key'
forms when (partially) expanded; `bind-key' is a macro defined in the
`use-package' package that wraps `define-key'.  It accepts a cons `(STRING
. DEFN)' as its `COMMAND' argument---as an alternative to a plain command
name---where `STRING' is a description of the command named `DEFN'.  This
allows the user to provide a custom description of a command when binding it
to a key.  The description can be used by other code dealing with key
bindings.  For example, the GNU ELPA `which-key' package displays the
description when showing key bindings, instead of the plain command name.

Previously, however, `:bind' did not accept such description/command
conses---it only ever expected plain command names.  These changes teach
`:bind' how to handle description/command conses.  In addition to passing them
on to `bind-key', `:bind' is taught to look at the command names inside the
conses and deal with them the same way plain commands names are dealt with,
including having corresponding `:commands' entries set up so that autoloading
works as intended.

* doc/misc/use-package.texi (Key bindings): Document `:bind''s new ability to
handle description/command conses in key bindings.
* etc/NEWS: Mention this new feature of `use-package'.
* lisp/use-package/bind-key.el (bind-key): Update macro docs to reflect these
changes.
* lisp/use-package/use-package-bind-key.el (use-package-normalize-binder):
Allow description/command conses in `:bind' entries.
* lisp/use-package/use-package-core.el (use-package-autoloads-mode): Ensure
autoloads are set up for description/command conses.
* test/lisp/use-package/use-package-tests.el (use-package-test/:bind-1,
use-package-test/:bind-2): Include key bindings with description/command
conses in existing tests.
---
 doc/misc/use-package.texi                  | 34 ++++++++++++++++++----
 etc/NEWS                                   | 10 +++++++
 lisp/use-package/bind-key.el               |  3 +-
 lisp/use-package/use-package-bind-key.el   | 11 ++++++-
 lisp/use-package/use-package-core.el       | 14 +++++++--
 test/lisp/use-package/use-package-tests.el |  8 ++---
 6 files changed, 66 insertions(+), 14 deletions(-)

diff --git a/doc/misc/use-package.texi b/doc/misc/use-package.texi
index 87105c4db0..574dd0d965 100644
--- a/doc/misc/use-package.texi
+++ b/doc/misc/use-package.texi
@@ -869,11 +869,14 @@ Global keybindings
 @findex :bind
 To bind keys globally, the @code{:bind} keyword takes as its argument
 either a single cons or a list of conses.  Each cons has the form
-@w{@code{(@var{key} . @var{command})}}, where @var{key} is a string
-indicating the key to bind, and @var{command} is the name of a command
-(a symbol).  The syntax for the keys is similar to the syntax used by
-the @code{kbd} function (see @ref{Init Rebinding,,, emacs, GNU Emacs
-Manual}, for more information).
+@w{@code{(@var{key} . @var{definition})}}, where @var{key} is a string
+indicating the key to bind, and @var{definition} is the name of a
+command (a symbol).  Alternatively, @var{definition} may be a cons
+@w{@code{(@var{desc} . @var{command})}}, where @var{desc} is a string
+describing @var{command}, which is the name of a command to bind
+@var{key} to.  The syntax for the keys is similar to the syntax used
+by the @code{kbd} function (see @ref{Init Rebinding,,, emacs, GNU
+Emacs Manual}, for more information).
 
 @subheading Using @code{:bind} with a single cons
 
@@ -933,6 +936,27 @@ Global keybindings
 @end group
 @end lisp
 
+@subheading Providing custom descriptions of commands
+
+When binding keys to commands with @code{:bind}, custom descriptions
+of the commands may optionally be provided.
+
+Examples:
+
+@lisp
+@group
+(use-package avy
+  :bind ("C-:" ("Jump to char" . avy-goto-char)
+         "M-g f" ("Jump to line" . avy-goto-line)))
+@end group
+@end lisp
+
+@noindent
+These descriptions can be used by other code that deals with key
+bindings.  For example, the @acronym{GNU} @acronym{ELPA} package
+@file{which-key} displays them when showing key bindings, instead of
+the plain command names.
+
 @subheading Remapping commands
 @cindex remapping commands with @code{:bind}
 @cindex @code{:bind}, and remapping of commands
diff --git a/etc/NEWS b/etc/NEWS
index 90a6c6a052..251d55993d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -102,6 +102,16 @@ If you want to get back the old behavior, set the user option to the value
     (setopt gdb-locals-table-row-config
             `((type . 0) (name . 0) (value . ,gdb-locals-value-limit)))
 
+** Declarative package configuration
+
+*** Custom descriptions of commands can now be provided when binding keys.
+The 'bind-key' macro from 'use-package' already had support for
+providing a custom description of the command in a key binding.  Now,
+it is possible to do the same when defining key bindings with ':bind'.
+These descriptions can be used by other code that deals with key
+bindings.  For example, the GNU ELPA 'which-key' package displays them
+when showing key bindings, instead of the plain command names.
+
 ** VC
 
 ---
diff --git a/lisp/use-package/bind-key.el b/lisp/use-package/bind-key.el
index b216c668d8..178e377740 100644
--- a/lisp/use-package/bind-key.el
+++ b/lisp/use-package/bind-key.el
@@ -166,7 +166,8 @@ bind-key
 spelled-out keystrokes, e.g., \"C-c C-z\".  See the documentation
 of `edmacro-mode' for details.
 
-COMMAND must be an interactive function or lambda form.
+COMMAND must be an interactive function, lambda form, or a cons
+`(STRING . DEFN)'.
 
 KEYMAP, if present, should be a keymap variable or symbol.
 For example:
diff --git a/lisp/use-package/use-package-bind-key.el b/lisp/use-package/use-package-bind-key.el
index 47eb066eba..72ff3fdabd 100644
--- a/lisp/use-package/use-package-bind-key.el
+++ b/lisp/use-package/use-package-bind-key.el
@@ -69,8 +69,17 @@ use-package-normalize-binder
   (let ((arg args)
         args*)
     (while arg
-      (let ((x (car arg)))
+      (let ((x (car arg))
+            (y (cadr arg)))
         (cond
+         ;; (KEY DESC . COMMAND), i.e. (KEY . (DESC . COMMAND))
+         ((and (or (stringp x)
+                   (vectorp x))
+               (consp y)
+               (stringp (car y))
+               (or (use-package-recognize-function (cdr y) t #'stringp)))
+          (setq args* (nconc args* (list (cons x y))))
+          (setq arg (cddr arg)))
          ;; (KEY . COMMAND)
          ((and (consp x)
                (or (stringp (car x))
diff --git a/lisp/use-package/use-package-core.el b/lisp/use-package/use-package-core.el
index 379e119b60..ee3845652d 100644
--- a/lisp/use-package/use-package-core.el
+++ b/lisp/use-package/use-package-core.el
@@ -961,10 +961,18 @@ use-package-normalize-mode
 
 (defun use-package-autoloads-mode (_name _keyword args)
   (mapcar
-   #'(lambda (x) (cons (cdr x) 'command))
+   #'(lambda (x)
+       (cond
+        ((consp (cdr x))
+         (cons (cddr x) 'command))
+        ((consp x)
+         (cons (cdr x) 'command))))
    (cl-remove-if-not #'(lambda (x)
-                         (and (consp x)
-                              (use-package-non-nil-symbolp (cdr x))))
+                         (or (and (consp x)
+                                  (use-package-non-nil-symbolp (cdr x)))
+                             (and (consp x)
+                                  (consp (cdr x))
+                                  (use-package-non-nil-symbolp (cddr x)))))
                      args)))
 
 (defun use-package-handle-mode (name alist args rest state)
diff --git a/test/lisp/use-package/use-package-tests.el b/test/lisp/use-package/use-package-tests.el
index 6374a0d103..673d567592 100644
--- a/test/lisp/use-package/use-package-tests.el
+++ b/test/lisp/use-package/use-package-tests.el
@@ -575,7 +575,7 @@ use-package-test-normalize/:bind-3
 
 (ert-deftest use-package-test/:bind-1 ()
   (match-expansion
-   (use-package foo :bind ("C-k" . key1) ("C-u" . key2))
+   (use-package foo :bind ("C-k" . key1) ("C-u" ("Key 2" . key2)))
    `(progn
       (unless
           (fboundp 'key1)
@@ -585,11 +585,11 @@ use-package-test/:bind-1
         (autoload #'key2 "foo" nil t))
       (bind-keys :package foo
                  ("C-k" . key1)
-                 ("C-u" . key2)))))
+                 ("C-u" "Key 2" . key2)))))
 
 (ert-deftest use-package-test/:bind-2 ()
   (match-expansion
-   (use-package foo :bind (("C-k" . key1) ("C-u" . key2)))
+   (use-package foo :bind (("C-k" . key1) ("C-u" ("Key 2" . key2))))
    `(progn
       (unless (fboundp 'key1)
         (autoload #'key1 "foo" nil t))
@@ -597,7 +597,7 @@ use-package-test/:bind-2
         (autoload #'key2 "foo" nil t))
       (bind-keys :package foo
                  ("C-k" . key1)
-                 ("C-u" . key2)))))
+                 ("C-u" "Key 2" . key2)))))
 
 (ert-deftest use-package-test/:bind-3 ()
   (match-expansion
-- 
2.39.0