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 current 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 (if (which-key--current-prefix)
                  (which-key--current-key-list)
                (butlast (append (this-command-keys-vector) nil))))
         (key (apply #'vector evs))
         (map (key-binding key)))
    (which-key--execute-binding+ map (key-description key))))

(defun which-key--execute-binding+ (map &optional prefix)
  "Completing read command from MAP and execute it.

If PREFIX is given it should be a key description which will be
included in the prompt."
  (let ((cmd (which-key--completing-read-cmd+ map prefix)))
    (when (commandp cmd)
      (which-key--execute-cmd+ cmd))))

(defun which-key--completing-read-cmd+ (map &optional prefix)
  "Completing read command from MAP.

Include PREFIX in prompt if given."
  (which-key--hide-popup-ignore-command)
  (let* ((desc
          (completing-read
           (if prefix
               (format "Execute (%s): " prefix)
             "Execute: ")
           (mapcar #'which-key--completing-read-format+
                   (which-key--get-keymap-bindings map 'all)))))
    (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.

See you in parendise!

Comments on Reddit