/emacs-which-key

Emacs package that displays available keybindings in popup

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

which-key

http://melpa.org/packages/which-key-badge.svg

What’s new

Local map face

  • The face which-key-local-map-description-face is now available. This face will be applied to any commands that are found using (current-local-map) (commands defined for the major mode are usually here). This allows you to distinguish between local and global bindings visually. See Face Customization for more information.

Paging

  • Paging is now turned on by default, using any prefix plus C-h (this doesn’t affect key sequences that start with C-h and will not override any key sequences that end will C-h). See the Paging Section for more details and for other options on using and/or disabling paging.
  • This makes which-key function as a replacement for the default behavior of pressing C-h after a prefix which shows the key bindings for any prefix (this default command is describe-prefix-bindings). It will also save the prefix that you just entered. So C-x C-h C-h C-x will popup the which-key buffer for the prefix C-x change the page twice and then execute the command bound to C-x C-x.
  • C-h will also now popup the which-key buffer to the first page if it is pressed before which-key-idle-delay takes effect. This means you can set a long idle delay if you like and just use C-h when you want to see which-key.
  • Note that this behavior is easily disabled, and you have the ability to choose another binding of course.

Introduction

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup. For example, after enabling the minor mode if you enter C-x and wait for the default of 1 second the minibuffer will expand with all of the available key bindings that follow C-x (or as many as space allows given your settings). This includes prefixes like C-x 8 which are shown in a different face. Screenshots of what the popup will look like are included below. which-key started as a rewrite of guide-key-mode, but the feature sets have diverged to a certain extent.

With respect to guide-key, the intention is to provide the following features:

  1. A different polling mechanism to make it lighter on resources than guide-key
  2. An improved display of keys with more keys being shown by default and a nicer presentation
  3. Customization options that allow for the rewriting of command names on the fly through easily modifiable alists
  4. Good default configurations that work well with most themes
  5. A well configured back-end for displaying keys (removing the popwin dependency) that can be easily customized by writing new display functions

Many of these have been implemented and are described below.

Table of Contents

Install

MELPA

After setting up MELPA as a repository, use M-x package-install which-key or your preferred method. You will need to call which-key-mode to enable the minor mode of course.

Manually

Add which-key.el to your load-path and require. Something like

(add-to-list 'load-path "path/to/which-key.el")
(require 'which-key)
(which-key-mode)

Initial Setup

No further setup is required if you are happy with the default setup. To try other options, there are 3 choices of default configs that are preconfigured (then customize to your liking). The main choice is where you want the which-key buffer to display. Screenshots of the default options are shown in the next sections.

In each case, we show as many key bindings as we can fit in the buffer within the constraints. The constraints are determined by several factors, including your Emacs settings, the size of the current Emacs frame, and the which-key settings, most of which are described below.

By default which-key makes substitutions for text all with the aim of saving space. The most noticeable are the “special keys” like SPC, TAB, RET, etc. This can be turned off (see Other Options), but the default is to truncate these keys to one character and display them using :inverse-video (flips foreground and background colors). You can see the effect in the screenshots.

There are other substitution abilities included, which are quite flexible (ability to use regexp for example). This makes which-key very customizable. This functionality is targeting spacemacs.

Side Window Bottom Option

Popup side window on bottom. This is the current default. To restore this setup use

(which-key-setup-side-window-bottom)

./img/which-key-bottom.png

Side Window Right Option

Popup side window on right. For defaults use

(which-key-setup-side-window-right)

Note the defaults are fairly conservative and will tend to not display on narrower frames. If you get a message saying which-key can’t display the keys, try making your frame wider or adjusting the defaults related to the maximum width (see M-x customize-group which-key).

./img/which-key-right.png

Side Window Right then Bottom

This is a combination of the previous two choices. It will try to use the right side, but if there is no room it will switch to using the bottom, which is usually easier to fit keys into. This setting can be helpful if the size of the Emacs frame changes frequently, which might be the case if you are using a dynamic/tiling window manager.

(which-key-setup-side-window-right-bottom)

Minibuffer Option

Take over the minibuffer. For the recommended configuration use

(which-key-setup-minibuffer)

./img/which-key-minibuffer.png

Note the maximum height of the minibuffer is controlled through the built-in variable max-mini-window-height.

Special Features and Configuration Options

There are more options than the ones described here. All of the configurable variables are available through M-x customize-group which-key.

Popup Type Options

There are three different popup types that which-key can use by default to display the available keys. The variable which-key-popup-type decides which one is used.

minibuffer

(setq which-key-popup-type 'minibuffer)

Show keys in the minibuffer.

side window

(setq which-key-popup-type 'side-window)

Show keys in a side window. This popup type has further options:

;; location of which-key window. valid values: top, bottom, left, right, 
;; or a list of any of the two. If it's a list, which-key will always try
;; the first location first. It will go to the second location if there is
;; not enough room to display any keys in the first location
(setq which-key-side-window-location 'bottom)

;; max width of which-key window, when displayed at left or right.
;; valid values: number of columns (integer), or percentage out of current
;; frame's width (float larger than 0 and smaller than 1)
(setq which-key-side-window-max-width 0.33)

;; max height of which-key window, when displayed at top or bottom.
;; valid values: number of lines (integer), or percentage out of current
;; frame's height (float larger than 0 and smaller than 1)
(setq which-key-side-window-max-height 0.25)

frame

(setq which-key-popup-type 'frame)

Show keys in a popup frame. This popup won’t work very well in a terminal, where only one frame can be shown at any given moment. This popup type has further options:

;; max width of which-key frame: number of columns (an integer)
(setq which-key-frame-max-width 60)

;; max height of which-key frame: number of lines (an integer)
(setq which-key-frame-max-height 20)

custom

Write your own display functions! This requires you to write three functions, which-key-custom-popup-max-dimensions-function, which-key-custom-show-popup-function, and which-key-custom-hide-popup-function. Refer to the documentation for those variables for more information, but here is a working example (this is the current implementation of side-window bottom).

(setq which-key-popup-type 'custom)
(defun which-key-custom-popup-max-dimensions-function (ignore)
  (cons
   (which-key-height-or-percentage-to-height which-key-side-window-max-height)
   (frame-width)))
(defun fit-horizonatally ()
  (let ((fit-window-to-buffer-horizontally t))
    (fit-window-to-buffer)))
(defun which-key-custom-show-popup-function (act-popup-dim)
  (let* ((alist '((window-width . fit-horizontally)
                  (window-height . fit-window-to-buffer))))
    (if (get-buffer-window which-key--buffer)
        (display-buffer-reuse-window which-key--buffer alist)
      (display-buffer-in-major-side-window which-key--buffer 'bottom 0 alist))))
(defun which-key-custom-hide-popup-function ()
  (when (buffer-live-p which-key--buffer)
    (quit-windows-on which-key--buffer)))

Custom String Replacement Options

You can customize the way the keys show in the buffer using three different replacement methods, each of which corresponds replacement alist. The basic idea of behind each alist is that you specify a selection string in the car of each cons cell and the replacement string in the cdr.

“Key-Based” replacement

The relevant variable is the awkwardly named which-key-key-based-description-replacement-alist. In this alist you can have cons cells of two types. An example of the first type is

("C-x C-f" . "find files")

where the string on the left is the key combination whose description you want to replace. For that key combination, which-key overwrites the description with the second string, “find files”. In the second type of entry you can restrict the replacements to a major-mode. For example,

(org-mode . (("C-c C-c" . "Org C-c C-c") ("C-c C-a" . "Org Attach"))

Here the first entry is the major-mode and the second is a list of the first type of entries. In case the same key combination is listed under a major-mode and by itself, the major-mode version will take precedence.

There are two helper functions to add entries to this list, which-key-add-key-based-replacements and which-key-add-major-mode-key-based-replacements. You can modify the alist directly or use these.

Key and Description replacement

The second and third methods target the text used for the keys and the descriptions directly. The relevant variables are which-key-key-replacement-alist and which-key-description-replacement-alist. Here’s an example of one of the default key replacements

("<\\([[:alnum:]-]+\\)>" . "\\1")

The car takes a string which may use Emacs regexp and the cdr takes a string with the replacement text. As shown, you can specify a sub-expression of the match. The replacements do not need to use regexp and can be as simple as

("left" . "lft")

You can add this element to the key list with (there are no helper functions for these alists)

(add-to-list 'which-key-key-replacement-alist '("left" . "lft"))

Sorting Options

By default the output is sorted by the key in a custom order. The default order is to sort lexicographically within each “class” of key, where the classes and their order are

Special (SPC, TAB, ...) < Single Character (a, ...) < Modifier (C-, M-, ...) < Other

You can control the order by setting this variable.

(setq which-key-sort-order 'which-key-key-order)
;; or (setq which-key-sort-order 'which-key-description-order)

The only other built-in option at the moment (besides using nil to turn off sorting completely) is which-key-description-order, which orders by the key’s description based on the usual ordering of strings after applying downcase.

Paging Options

There are at least several prefixes that have many keys bound to them, like C-x. which-key displays as many keys as it can given your settings, but for these prefixes this may not be enough. The paging feature gives you the ability to bind a key to the function which-key-show-next-page which will cycle through the pages without changing the key sequence you were in the middle of typing. There are two slightly different ways of doing this.

Method 1 (default): Using C-h (or help-char)

This is the easiest way, and is turned on by default. Use

(setq which-key-use-C-h-for-paging nil)

to disable the behavior (this will only take effect after toggling which-key-mode if it is already enabled). C-h can be used with any prefix to switch pages when there are multiple pages of keys. This changes the default behavior of Emacs which is to show a list of key bindings that apply to a prefix. For example, if you were to type C-x C-h you would get a list of commands that follow C-x. This uses which-key instead to show those keys, and unlike the Emacs default saves the incomplete prefix that you just entered so that the next keystroke can complete the command. As a bonus you can type C-x C-h and the which-key buffer will pop up immediately (i.e., before which-key-idle-delay kicks in).

Method 2: Bind your own keys

Essentially, all you need to do for a prefix like C-x is the following which will bind <f5> to the relevant command.

(define-key which-key-mode-map (kbd "C-x <f5>") 'which-key-show-next-page)

This is completely equivalent to

(setq which-key-paging-prefixes '("C-x"))
(setq which-key-paging-key "<f5>")

where the latter are provided for convenience if you have a lot of prefixes.

Face Customization Options

The faces that which-key uses are

FaceApplied ToDefault Definition
which-key-key-faceEvery key sequence:inherit font-lock-constant-face
which-key-separator-faceThe separator (→):inherit font-lock-comment-face
which-key-note-faceHints and notes:inherit which-key-separator-face
which-key-special-key-faceUser-defined special keys:inherit which-key-key-face :inverse-video t :weight bold
which-key-group-description-faceCommand groups (i.e, keymaps):inherit font-lock-keyword-face
which-key-command-description-faceCommands not in local-map:inherit font-lock-function-name-face
which-key-local-map-description-faceCommands in local-map:inherit which-key-command-description-face

The last two deserve some explanation. A command lives in one of many possible keymaps. You can distinguish between local maps, which depend on the buffer you are in, which modes are active, etc., and the global map which applies everywhere. It might be useful for you to distinguish between the two. One way to do this is to remove the default face from which-key-command-description-face like this

(set-face-attribute 'which-key-command-description-face nil :inherit nil)

another is to make the local map keys appear in bold

(set-face-attribute 'which-key-local-map-description-face nil :weight 'bold)

You can also use M-x customize-face to customize any of the above faces to your liking.

Other Options

The options below are also available through customize. Their defaults are shown.

;; Set the time delay (in seconds) for the which-key popup to appear.
(setq which-key-idle-delay 1.0) 

;; Set the maximum length (in characters) for key descriptions (commands or
;; prefixes). Descriptions that are longer are truncated and have ".." added
(setq which-key-max-description-length 27)

;; Set the separator used between keys and descriptions. Change this setting to
;; an ASCII character if your font does not show the default arrow. The second
;; setting here allows for extra padding for Unicode characters. which-key uses
;; characters as a means of width measurement, so wide Unicode characters can
;; throw off the calculation.
(setq which-key-separator "" )
(setq which-key-unicode-correction 3)

;; Set the special keys. These are automatically truncated to one character and
;; have which-key-special-key-face applied. Set this variable to nil to disable
;; the feature
(setq which-key-special-keys '("SPC" "TAB" "RET" "ESC" "DEL"))

;; Show the key prefix on the left or top (nil means hide the prefix). The
;; prefix consists of the keys you have typed so far. which-key also shows the
;; page information along with the prefix.
(setq which-key-show-prefix 'left)

;; Set to t to show the count of keys shown vs. total keys in the mode line.
(setq which-key-show-remaining-keys nil)

More Examples

Nice Display with Split Frame

Unlike guide-key, which-key looks good even if the frame is split into several windows.

./img/which-key-right-split.png

./img/which-key-bottom-split.png

Status

It requires testing on different platforms with different configurations, which is beyond my capabilities. The default configuration has been reasonably stable for me.

Thanks

Thanks to @bmag for helping with the initial development and finding many bugs.