Skip to main content

Prefix command completion

As a reader of this blog you probably know about which-key. It is a great package which helps discovering and remembering keybindings. One thing I miss, is the option to choose candidates via completion, which is the goal of this post.

When you press C-h after a prefix key, Emacs displays a buffer containing all the bindings under that prefix. The command which gets called is determined by the variable prefix-help-command. Which-key makes use of this variable, too. It uses the initial value as a fallback so the variable needs to be set before which-key-mode is activated:

(setq prefix-help-command #'which-key-M-x-prefix+)
(which-key-mode 1)

The code of this post makes use of internal which-key functions and is tightly related to it, so I will keep the which-key- prefix and add a + suffix. This approach is what I’m using for all my personal package extensions. Using the same "name space" has some nice benefits for search and completion and the unusual suffix avoids potential name clashes.

(defun which-key-M-x-prefix+ (&optional _)
  "Completing read and execute command from prefix-map.

This command can be used as `prefix-help-command'. The optional
argument is ignored and only for compatability with
`which-key-C-h-dispatch' so this command can be bound in
`which-key-C-h-map', too."
  (interactive)
  (let* ((evs (cond ((which-key--current-prefix)
                     (which-key--current-key-list))
                    (t
                     (butlast (append (this-command-keys-vector) nil)))))
         (key  (and evs (apply #'vector evs))))
    (which-key-M-x+ key)))

(defun which-key-M-x+ (&optional key)
  "Completing read command and execute it.

Only commands which are bound to keys are considered. If KEY is
given it should be the prefix-key for which commands should be
completed. Otherwise read command from top-level. "
  (interactive)
  (let ((cmd (which-key--completing-read-cmd+ key)))
    (when (commandp cmd)
      (which-key--execute-cmd+ cmd))))

(defun which-key--completing-read-cmd+ (&optional prefix)
  "Completing read command for PREFIX.

Read commands for PREFIX or top-level if PREFIX not given."
  (which-key--hide-popup-ignore-command)
  (let ((desc
         (completing-read
          (if prefix
              (format "Execute (%s): " (key-description prefix))
            "Execute: ")
          (mapcar #'which-key--completing-read-format+
                  (which-key--get-current-bindings prefix)))))
    (intern (car (split-string desc)))))

(defun which-key--execute-cmd+ (cmd)
  "Execute command CMD as if invoked by key sequence."
  (setq prefix-arg current-prefix-arg)
  (setq this-command cmd)
  (setq real-this-command cmd)
  (command-execute cmd 'record))

(defun which-key--completing-read-format+ (bnd)
  "Format binding BND for `completing-read'."
  (let* ((key (car bnd))
         (cmd (cdr bnd))
         (desc (format "%s (%s)" cmd
                       (propertize key 'face 'which-key-key-face))))
    (which-key--maybe-add-docstring
     (format "%-50s" desc) cmd)))

After adding the above code to your Emacs, you should be able to press a prefix key and invoke the command completion with C-h. If the which-key popup is already showing you need to press it twice, because it uses C-h for its own paging commands, too. You can also complete commands from top-level by calling the which-key-M-x+ command.