]> git.eshelyaron.com Git - emacs.git/commitdiff
Merge remote-tracking branch 'origin/feature/which-key-in-core'
authorPhilip Kaludercic <philipk@posteo.net>
Tue, 18 Jun 2024 19:45:58 +0000 (21:45 +0200)
committerEshel Yaron <me@eshelyaron.com>
Wed, 19 Jun 2024 08:57:13 +0000 (10:57 +0200)
(cherry picked from commit fa4203300fde6820a017bf1089652fb95759d68c)

etc/NEWS
lisp/which-key.el [new file with mode: 0644]
test/lisp/which-key-tests.el [new file with mode: 0644]

index 18de3c9040d362988b89934ff865a8a8a6b27627..5abcf88847d61b68f0b97b9df115bc00c5ed8d82 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2160,6 +2160,13 @@ mode is enabled, a tool bar is displayed at the top of a window.  To
 conserve space, no tool bar is shown if 'tool-bar-map' is nil.  The
 global minor mode 'global-window-tool-bar-mode' enables this minor mode
 in all buffers.
+
++++
+** New package Which-Key
+The 'which-key' package from GNU ELPA is now included in Emacs.  It
+implements the 'which-key-mode' that displays a table of key bindings
+upon entering a partial key chord and waiting for a moment.
+
 \f
 * Incompatible Lisp Changes in Emacs 30.1
 
diff --git a/lisp/which-key.el b/lisp/which-key.el
new file mode 100644 (file)
index 0000000..1de599e
--- /dev/null
@@ -0,0 +1,2814 @@
+;;; which-key.el --- Display available keybindings in popup  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2024  Free Software Foundation, Inc.
+
+;; Author: Justin Burkett <justin@burkett.cc>
+;; Maintainer: Justin Burkett <justin@burkett.cc>
+;; Version: 3.6.0
+;; Package-Requires: ((emacs "25.1"))
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The `which-key' mode 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.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'button)
+(require 'regexp-opt)
+
+;; For compiler
+(defvar evil-operator-shortcut-map)
+(defvar evil-operator-state-map)
+(defvar evil-motion-state-map)
+(defvar golden-ratio-mode)
+(declare-function evil-get-command-property "ext:evil-common.el")
+
+;;; Options
+
+(defgroup which-key nil
+  "Customization options for `which-key-mode'."
+  :group 'help
+  :prefix "which-key-")
+
+(defcustom which-key-idle-delay 1.0
+  "Delay (in seconds) for which-key buffer to popup.
+This variable should be set before activating `which-key-mode'.
+
+A value of zero might lead to issues, so a non-zero value is
+recommended
+(see https://github.com/justbur/emacs-which-key/issues/134)."
+  :type 'float
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-idle-secondary-delay nil
+  "Seconds to wait for which-key to pop up after initial display.
+This makes it possible to shorten the delay for subsequent popups
+in the same key sequence.  The default is for this value to be
+nil, which disables this behavior."
+  :type '(choice float (const :tag "Disabled" nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-echo-keystrokes (if (and echo-keystrokes
+                                              (> (+ echo-keystrokes 0.01)
+                                                 which-key-idle-delay))
+                                         (/ (float which-key-idle-delay) 4)
+                                       echo-keystrokes)
+  "Value to use for `echo-keystrokes'.
+This only applies if `which-key-popup-type' is minibuffer or
+`which-key-show-prefix' is echo.  It needs to be less than
+`which-key-idle-delay' or else the keystroke echo will erase the
+which-key popup."
+  :type 'float
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-max-description-length 27
+  "Truncate the description of keys to this length.
+Either nil (no truncation), an integer (truncate after that many
+characters), a float (use that fraction of the available width),
+or a function, which takes one argument, the available width in
+characters, and whose return value has one of the types mentioned
+before.  Truncation is done using `which-key-ellipsis'."
+  :type '(choice (const :tag "Disable truncation" nil)
+                (integer :tag "Width in characters")
+                (float :tag "Use fraction of available width")
+                function)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-min-column-description-width 0
+  "Every column should at least have this width."
+  :type 'natnum
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-add-column-padding 0
+  "Additional spaces to add to the left of each key column."
+  :type 'integer
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-unicode-correction 3
+  "Correction for wide unicode characters.
+Since we measure width in terms of the number of characters,
+Unicode characters that are wider than ASCII characters throw off
+the calculation for available width in the which-key buffer.  This
+variable allows you to adjust for the wide unicode characters by
+artificially reducing the available width in the buffer.
+
+The default of 3 means allow for the total extra width
+contributed by any wide unicode characters to be up to one
+additional ASCII character in the which-key buffer.  Increase this
+number if you are seeing characters get cutoff on the right side
+of the which-key popup."
+  :type 'integer
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-dont-use-unicode t
+  "If non-nil, don't use any unicode characters in default setup.
+For affected settings, see `which-key-replacement-alist', `which-key-ellipsis'
+`which-key-separator'."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-separator
+  (if which-key-dont-use-unicode " : " " → ")
+  "Separator to use between key and description.
+Default is \" → \", unless `which-key-dont-use-unicode' is non
+nil, in which case the default is \" : \"."
+  :type 'string
+  :set-after '(which-key-dont-use-unicode)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-ellipsis
+  (if which-key-dont-use-unicode ".." "…")
+  "Ellipsis to use when truncating.
+Default is \"…\", unless `which-key-dont-use-unicode' is non nil,
+in which case the default is \"..\".  This can also be the empty
+string to truncate without using any ellipsis."
+  :type 'string
+  :set-after '(which-key-dont-use-unicode)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-prefix-prefix "+"
+  "Prefix string to indicate a key bound to a keymap.
+Default is \"+\"."
+  :type 'string
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-compute-remaps nil
+  "If non-nil, show remapped commands.
+This applies to commands that have been remapped given the
+currently active keymaps."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-replacement-alist
+  `(((nil . "which-key-show-next-page-no-cycle") . (nil . "wk next pg"))
+    ,@(unless which-key-dont-use-unicode
+        '((("<left>") . ("←"))
+          (("<right>") . ("→"))))
+    (("<\\([[:alnum:]-]+\\)>") . ("\\1")))
+  "ALIST for manipulating display of binding descriptions.
+Each element of the list is a nested cons cell with the format
+
+\(MATCH CONS . REPLACEMENT\).
+
+The MATCH CONS determines when a replacement should occur and
+REPLACEMENT determines how the replacement should occur.  Each may
+have the format \(KEY REGEXP . BINDING REGEXP\).  For the
+replacement to apply the key binding must match both the KEY
+REGEXP and the BINDING REGEXP.  A value of nil in either position
+can be used to match every possibility.  The replacement is
+performed by using `replace-regexp-in-string' on the KEY REGEXP
+from the MATCH CONS and REPLACEMENT when it is a cons cell, and
+then similarly for the BINDING REGEXP.  A nil value in the BINDING
+REGEXP position cancels the replacement.  For example, the entry
+
+\(\(nil . \"Prefix Command\"\) . \(nil . \"prefix\"\)\)
+
+matches any binding with the descriptions \"Prefix Command\" and
+replaces the description with \"prefix\", ignoring the
+corresponding key.
+
+REPLACEMENT may also be a function taking a cons cell
+\(KEY . BINDING\) and producing a new corresponding cons cell.
+
+If REPLACEMENT is anything other than a cons cell \(and non nil\)
+the key binding is ignored by which-key.
+
+Finally, you can multiple replacements to occur for a given key
+binding by setting `which-key-allow-multiple-replacements' to a
+non-nil value."
+  :type '(alist :key-type (cons (choice regexp (const nil))
+                                (choice regexp (const nil)))
+                :value-type (cons (choice string (const nil))
+                                  (choice string (const nil))))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-allow-multiple-replacements nil
+  "Allow a key binding to be modified by multiple elements.
+When non-nil, this allows a single key binding to match multiple
+patterns in `which-key-replacement-alist'.  When nil, only the
+first match is used to perform replacements from
+`which-key-replacement-alist'."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-docstrings nil
+  "If non-nil, show each command's docstring in the which-key popup.
+This will only display the docstring up to the first line
+break.  If you set this variable to the symbol docstring-only,
+then the command's name with be omitted.  You probably also want
+to adjust `which-key-max-description-length' at the same time if
+you use this feature."
+  :type '(radio
+          (const :tag "Do not show docstrings" nil)
+          (const :tag "Add docstring to command names" t)
+          (const :tag "Replace command name with docstring" docstring-only))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-highlighted-command-list '()
+  "Rules used to highlight certain commands.
+If the element is a string, assume it is a regexp pattern for
+matching command names and use
+`which-key-highlighted-command-face' for any matching names.  If
+the element is a cons cell, it should take the form (regexp .
+face to apply)."
+  :type  '(repeat (choice string (cons regexp face)))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-special-keys '()
+  "These keys will automatically be truncated to one character.
+They also have `which-key-special-key-face' applied to them.  This
+is disabled by default.  An example configuration is
+
+\(setq which-key-special-keys \\='(\"SPC\" \"TAB\" \"RET\" \"ESC\" \"DEL\")\)"
+  :type '(repeat string)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-buffer-name " *which-key*"
+  "Name of which-key buffer."
+  :type 'string
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-prefix 'echo
+  "Whether to and where to display the current prefix sequence.
+Possible choices are echo for echo area (the default), left, top
+and nil.  nil turns the feature off."
+  :type '(radio (const :tag "Left of the keys" left)
+                (const :tag "In the first line" top)
+                (const :tag "In the last line" bottom)
+                (const :tag "In the echo area" echo)
+                (const :tag "In the mode-line" mode-line)
+                (const :tag "Hide" nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-popup-type 'side-window
+  "Supported types are minibuffer, side-window, frame, and custom."
+  :type '(radio (const :tag "Show in minibuffer" minibuffer)
+                (const :tag "Show in side window" side-window)
+                (const :tag "Show in popup frame" frame)
+                (const :tag "Use your custom display functions" custom))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-min-display-lines 1
+  "Minimum number of horizontal lines to display in the which-key buffer."
+  :type 'integer
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-max-display-columns nil
+  "Maximum number of columns to display in the which-key buffer.
+A value of nil means don't impose a maximum."
+  :type '(choice integer (const :tag "Unbounded" nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-side-window-location 'bottom
+  "Location of which-key popup when `which-key-popup-type' is side-window.
+Should be one of top, bottom, left or right.  You can also specify
+a list of two locations, like (right bottom).  In this case, the
+first location is tried.  If there is not enough room, the second
+location is tried."
+  :type '(radio (const right)
+                (const bottom)
+                (const left)
+                (const top)
+                (const (right bottom))
+                (const (bottom right)))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-side-window-slot 0
+  "The `slot' to use for `display-buffer-in-side-window'.
+This applies when `which-key-popup-type' is `side-window'.
+Quoting from the docstring of `display-buffer-in-side-window',
+
+`slot' if non-nil, specifies the window slot where to display
+BUFFER.  A value of zero or nil means use the middle slot on the
+specified side.  A negative value means use a slot
+preceding (that is, above or on the left of) the middle slot.  A
+positive value means use a slot following (that is, below or on
+the right of) the middle slot.  The default is zero."
+  :type 'integer
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-side-window-max-width 0.333
+  "Maximum width of which-key popup when type is side-window.
+This variable can also be a number between 0 and 1. In that case,
+it denotes a percentage out of the frame's width."
+  :type 'float
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-side-window-max-height 0.25
+  "Maximum height of which-key popup when type is side-window.
+This variable can also be a number between 0 and 1. In that case, it denotes
+a percentage out of the frame's height."
+  :type 'float
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-frame-max-width 60
+  "Maximum width of which-key popup when type is frame."
+  :type 'natnum
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-frame-max-height 20
+  "Maximum height of which-key popup when type is frame."
+  :type 'natnum
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-allow-imprecise-window-fit (not (display-graphic-p))
+  "Allow which-key to use a simpler method for resizing the popup.
+If you are noticing lag when the which-key popup displays turning
+this on may help.
+
+See https://github.com/justbur/emacs-which-key/issues/130
+and https://github.com/justbur/emacs-which-key/issues/225."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-remaining-keys nil
+  "Show remaining keys in last slot, when keys are hidden."
+  :type '(radio (const :tag "Yes" t)
+                (const :tag "No" nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-sort-order #'which-key-key-order
+  "Order in which the key bindings are sorted.
+If nil, do not resort the output from `describe-buffer-bindings'
+which groups by mode.  Ordering options are:
+
+1. `which-key-key-order': by key (default)
+2. `which-key-key-order-alpha': by key using alphabetical order
+3. `which-key-description-order': by description
+4. `which-key-prefix-then-key-order': prefix (no prefix first) then key
+5. `which-key-local-then-key-order': local binding then key
+
+See the README and the docstrings for those functions for more
+information."
+  :type '(choice (function-item which-key-key-order)
+                 (function-item which-key-key-order-alpha)
+                 (function-item which-key-description-order)
+                 (function-item which-key-prefix-then-key-order)
+                 (function-item which-key-local-then-key-order))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-sort-uppercase-first t
+  "If non-nil, uppercase comes before lowercase in sorting.
+This applies to the function chosen in
+`which-key-sort-order'.  Otherwise, the order is reversed."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-paging-prefixes '()
+  "Enable paging for these prefixes."
+  :type '(repeat string)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-paging-key "<f5>"
+  "Key to use for changing pages.
+Bound after each of the prefixes in `which-key-paging-prefixes'"
+  :type 'string
+  :package-version "1.0" :version "30.1")
+
+;; (defcustom which-key-undo-key nil
+;;   "Key (string) to use for undoing keypresses. Bound recursively
+;; in each of the maps in `which-key-undo-keymaps'."
+;;   :group 'which-key
+;;   :type 'string)
+
+;; (defcustom which-key-undo-keymaps '()
+;;   "Keymaps in which to bind `which-key-undo-key'"
+;;   :group 'which-key
+;;   :type '(repeat symbol))
+
+(defcustom which-key-use-C-h-commands t
+  "Use \\`C-h' (`help-char') for paging if non-nil.
+Normally `help-char' after a prefix calls
+`describe-prefix-bindings'.  This changes that command to a
+which-key paging command when `which-key-mode' is active."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-early-on-C-h nil
+  "Allow \\`C-h' (`help-char') to trigger which-key popup before timer.
+Show the which-key buffer if `help-char' is pressed in the middle
+of a prefix before the which-key buffer would normally be
+triggered by the time.  If combined with the following settings,
+which-key will effectively only show when triggered \"manually\"
+using \\`C-h'.
+
+\(setq `which-key-idle-delay' 10000)
+\(setq `which-key-idle-secondary-delay' 0.05)
+
+Note that `which-key-idle-delay' should be set before turning on
+`which-key-mode'."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-is-verbose nil
+  "Whether to warn about potential mistakes in configuration."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-preserve-window-configuration nil
+  "Save and restore window configuration around which-key popup display.
+If non-nil, save window configuration before which-key buffer is
+shown and restore it after which-key buffer is hidden.  It
+prevents which-key from changing window position of visible
+buffers.  Only takken into account when popup type is
+side-window."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defvar which-key-C-h-map-prompt
+  (concat " \\<which-key-C-h-map>"
+          " \\[which-key-show-next-page-cycle]"
+          which-key-separator "next-page,"
+          " \\[which-key-show-previous-page-cycle]"
+          which-key-separator "previous-page,"
+          " \\[which-key-undo-key]"
+          which-key-separator "undo-key,"
+          " \\[which-key-toggle-docstrings]"
+          which-key-separator "toggle-docstrings,"
+          " \\[which-key-show-standard-help]"
+          which-key-separator "help,"
+          " \\[which-key-abort]"
+          which-key-separator "abort"
+          " 1..9"
+          which-key-separator "digit-arg")
+  "Prompt to display when invoking `which-key-C-h-map'.
+This string is fed into `substitute-command-keys'")
+
+(defvar which-key-C-h-map
+  (let ((map (make-sparse-keymap)))
+    (dolist (bind `(("\C-a" . which-key-abort)
+                    ("a" . which-key-abort)
+                    ("\C-d" . which-key-toggle-docstrings)
+                    ("d" . which-key-toggle-docstrings)
+                    (,(vector help-char) . which-key-show-standard-help)
+                    ("h" . which-key-show-standard-help)
+                    ("\C-n" . which-key-show-next-page-cycle)
+                    ("n" . which-key-show-next-page-cycle)
+                    ("\C-p" . which-key-show-previous-page-cycle)
+                    ("p" . which-key-show-previous-page-cycle)
+                    ("\C-u" . which-key-undo-key)
+                    ("u" . which-key-undo-key)
+                    ("1" . which-key-digit-argument)
+                    ("2" . which-key-digit-argument)
+                    ("3" . which-key-digit-argument)
+                    ("4" . which-key-digit-argument)
+                    ("5" . which-key-digit-argument)
+                    ("6" . which-key-digit-argument)
+                    ("7" . which-key-digit-argument)
+                    ("8" . which-key-digit-argument)
+                    ("9" . which-key-digit-argument)))
+      (define-key map (car bind) (cdr bind)))
+    map)
+  "Keymap for \\`C-h' commands.")
+
+(defvar which-key--paging-functions
+  (list #'which-key-C-h-dispatch
+        #'which-key-turn-page
+        #'which-key-show-next-page-cycle
+        #'which-key-show-next-page-no-cycle
+        #'which-key-show-previous-page-cycle
+        #'which-key-show-previous-page-no-cycle
+        #'which-key-undo-key
+        #'which-key-undo))
+
+(defvar which-key-persistent-popup nil
+  "Whether or not to disable `which-key--hide-popup'.")
+
+(defcustom which-key-hide-alt-key-translations t
+  "Hide key translations using Alt key if non nil.
+These translations are not relevant most of the times since a lot
+of terminals issue META modifier for the Alt key.
+
+See Info node `(emacs)Modifier Keys'."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-delay-functions nil
+  "List of functions that may delay the which-key popup.
+A list of functions that may decide whether to delay the
+which-key popup based on the current incomplete key
+sequence.  Each function in the list is run with two arguments,
+the current key sequence as produced by `key-description' and the
+length of the key sequence.  If the popup should be delayed based
+on that key sequence, the function should return the delay time
+in seconds.  Returning nil means no delay.  The first function in
+this list to return a value is the value that is used.
+
+The delay time is effectively added to the normal
+`which-key-idle-delay'."
+  :type '(repeat function)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-allow-regexps nil
+  "A list of regexp strings to use to filter key sequences.
+When non-nil, for a key sequence to trigger the which-key popup
+it must match one of the regexps in this list.  The format of the
+key sequences is what is produced by `key-description'."
+  :type '(repeat regexp)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-inhibit-regexps nil
+  "A list of regexp strings to use to filter key sequences.
+When non-nil, for a key sequence to trigger the which-key popup
+it cannot match one of the regexps in this list.  The format of
+the key sequences is what is produced by `key-description'."
+  :type '(repeat regexp)
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-transient-maps nil
+  "Show keymaps created by `set-transient-map' when applicable.
+
+More specifically, detect when `overriding-terminal-local-map' is
+set (this is the keymap used by `set-transient-map') and display
+it."
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(make-obsolete-variable
+ 'which-key-enable-extended-define-key
+ "which-key-enable-extended-define-key is obsolete and has no effect."
+ "2021-06-21")
+
+;; Hooks
+(defcustom which-key-init-buffer-hook '()
+  "Hook run when which-key buffer is initialized."
+  :type 'hook
+  :package-version "1.0" :version "30.1")
+
+;;;; Faces
+
+(defgroup which-key-faces nil
+  "Faces for `which-key-mode'."
+  :group 'which-key
+  :prefix "which-key-")
+
+(defface which-key-key-face
+  '((t . (:inherit font-lock-constant-face)))
+  "Face for which-key keys."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-separator-face
+  '((t . (:inherit font-lock-comment-face)))
+  "Face for the separator (default separator is an arrow)."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-note-face
+  '((t . (:inherit which-key-separator-face)))
+  "Face for notes or hints occasionally provided."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-command-description-face
+  '((t . (:inherit font-lock-function-name-face)))
+  "Face for the key description when it is a command."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-local-map-description-face
+  '((t . (:inherit which-key-command-description-face)))
+  "Face for the key description when it is found in `current-local-map'."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-highlighted-command-face
+  '((t . (:inherit (which-key-command-description-face highlight))))
+  "Default face for highlighted command descriptions.
+A command is highlighted, when it matches a string in
+`which-key-highlighted-command-list'."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-group-description-face
+  '((t . (:inherit font-lock-keyword-face)))
+  "Face for the key description when it is a group or prefix."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-special-key-face
+  '((t . (:inherit which-key-key-face :inverse-video t :weight bold)))
+  "Face for special keys (\\`SPC', \\`TAB', \\`RET')."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+(defface which-key-docstring-face
+  '((t . (:inherit which-key-note-face)))
+  "Face for docstrings."
+  :group 'which-key-faces
+  :package-version "1.0" :version "30.1")
+
+;;;; Custom popup
+
+(defcustom which-key-custom-popup-max-dimensions-function nil
+  "Set a custom max-dimensions function.
+Will be passed the width of the active window and is expected to
+return the maximum height in lines and width in characters of the
+which-key popup in the form a cons cell (height . width)."
+  :group 'which-key
+  :type '(choice function (const nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-custom-hide-popup-function nil
+  "Set a custom hide-popup function.
+It takes no arguments and the return value is ignored."
+  :group 'which-key
+  :type '(choice function (const nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-custom-show-popup-function nil
+  "Set a custom show-popup function.
+Will be passed the required dimensions in the form (height .
+width) in lines and characters respectively.  The return value is
+ignored."
+  :group 'which-key
+  :type '(choice function (const nil))
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-lighter " WK"
+  "Minor mode lighter to use in the mode-line."
+  :group 'which-key
+  :type 'string
+  :package-version "1.0" :version "30.1")
+
+(defvar which-key-inhibit nil
+  "Prevent which-key from popping up momentarily.
+This can be used by setting this to a non-nil value for the
+execution of a command, as in
+
+\(let \(\(which-key-inhibit t\)\)
+...\)")
+
+(defcustom which-key-inhibit-display-hook nil
+  "Hook run before display of which-key popup.
+Each function in the hook is run before displaying the which-key
+popup.  If any function returns a non-nil value, the popup will
+not display."
+  :group 'which-key
+  :type 'hook
+  :package-version "1.0" :version "30.1")
+
+(defvar which-key-keymap-history nil
+  "History of keymap selections.
+Used in functions like `which-key-show-keymap'.")
+
+;;; Internal Vars
+
+(defvar which-key--buffer nil
+  "Holds reference to which-key buffer.")
+(defvar which-key--timer nil
+  "Holds reference to open window timer.")
+(defvar which-key--secondary-timer-active nil
+  "Non-nil if the secondary timer is active.")
+(defvar which-key--paging-timer nil
+  "Holds reference to timer for paging.")
+(defvar which-key--frame nil
+  "Holds reference to which-key frame.
+Used when `which-key-popup-type' is frame.")
+(defvar which-key--echo-keystrokes-backup nil
+  "Backup the initial value of `echo-keystrokes'.")
+(defvar which-key--prefix-help-cmd-backup nil
+  "Backup the value of `prefix-help-command'.")
+(defvar which-key--last-try-2-loc nil
+  "Last location of side-window when two locations used.")
+(defvar which-key--automatic-display nil
+  "Non-nil if popup was triggered with automatic update.")
+(defvar which-key--debug-buffer-name nil
+  "If non-nil, use this buffer for debug messages.")
+(defvar which-key--multiple-locations nil)
+(defvar which-key--inhibit-next-operator-popup nil)
+(defvar which-key--prior-show-keymap-args nil)
+(defvar which-key--previous-frame-size nil)
+(defvar which-key--prefix-title-alist nil)
+(defvar which-key--evil-keys-regexp (eval-when-compile
+                                      (regexp-opt '("-state"))))
+(defvar which-key--ignore-non-evil-keys-regexp
+  (regexp-opt '("mouse-" "wheel-" "remap" "drag-" "scroll-bar"
+                "select-window" "switch-frame" "which-key")))
+(defvar which-key--ignore-keys-regexp
+  (regexp-opt '("mouse-" "wheel-" "remap" "drag-" "scroll-bar"
+                "select-window" "switch-frame" "-state"
+                "which-key")))
+
+(defvar which-key--pages-obj nil)
+(cl-defstruct which-key--pages
+  pages
+  height
+  widths
+  keys/page
+  page-nums
+  num-pages
+  total-keys
+  prefix
+  prefix-title)
+
+(defvar which-key--saved-window-configuration nil)
+
+(defun which-key--rotate (list n)
+  (let* ((len (length list))
+         (n (- len (mod n len))))
+    (append (last list n) (butlast list n))))
+
+(defun which-key--pages-set-current-page (pages-obj n)
+  (setf (which-key--pages-pages pages-obj)
+        (which-key--rotate (which-key--pages-pages pages-obj) n))
+  (setf (which-key--pages-widths pages-obj)
+        (which-key--rotate (which-key--pages-widths pages-obj) n))
+  (setf (which-key--pages-keys/page pages-obj)
+        (which-key--rotate (which-key--pages-keys/page pages-obj) n))
+  (setf (which-key--pages-page-nums pages-obj)
+        (which-key--rotate (which-key--pages-page-nums pages-obj) n))
+  pages-obj)
+
+(defsubst which-key--on-first-page ()
+  (= (which-key--pages-page-nums which-key--pages-obj) 1))
+
+(defsubst which-key--on-last-page ()
+  (= (which-key--pages-page-nums which-key--pages-obj)
+     (which-key--pages-num-pages which-key--pages-obj)))
+
+(defsubst which-key--current-prefix ()
+  (and which-key--pages-obj
+       (which-key--pages-prefix which-key--pages-obj)))
+
+(defmacro which-key--debug-message (&rest msg)
+  `(when which-key--debug-buffer-name
+     (let ((buf (get-buffer-create which-key--debug-buffer-name))
+           (fmt-msg (format ,@msg)))
+       (with-current-buffer buf
+         (goto-char (point-max))
+         (insert "\n" fmt-msg "\n")))))
+
+(defsubst which-key--safe-lookup-key (keymap key)
+  "Version of `lookup-key' that allows KEYMAP to be nil.
+Also convert numeric results of `lookup-key' to nil.
+KEY is not checked."
+  (when (keymapp keymap)
+    (let ((result (lookup-key keymap key)))
+      (when (and result (not (numberp result)))
+        result))))
+
+(defsubst which-key--safe-lookup-key-description (keymap key)
+  "Version of `lookup-key' that allows KEYMAP to be nil.
+Also convert numeric results of `lookup-key' to nil.
+KEY should be formatted as an input for `kbd'."
+  (let ((key (ignore-errors (kbd key))))
+    (when (and key (keymapp keymap))
+      (let ((result (lookup-key keymap key)))
+        (when (and result (not (numberp result)))
+          result)))))
+
+;;; Third-party library support
+
+(defun which-key--this-command-keys ()
+  "Version of `this-single-command-keys' corrected for key-chords."
+  (let ((this-command-keys (this-single-command-keys)))
+    (when (and (vectorp this-command-keys)
+               (> (length this-command-keys) 0)
+               (eq (aref this-command-keys 0) 'key-chord)
+               (bound-and-true-p key-chord-mode))
+      (setq this-command-keys (this-single-command-raw-keys)))
+    this-command-keys))
+
+(defcustom which-key-this-command-keys-function #'which-key--this-command-keys
+  "Function used to retrieve current key sequence.
+The purpose of allowing this variable to be customized is to
+allow which-key to support packages that insert non-standard
+`keys' into the key sequence being read by Emacs."
+  :group 'which-key
+  :type 'function
+  :package-version "1.0" :version "30.1")
+
+
+;;;; Evil
+
+(defvar evil-state nil)
+
+(defcustom which-key-allow-evil-operators (boundp 'evil-this-operator)
+  "Allow popup to show for evil operators.
+The popup is normally inhibited in the middle of commands, but
+setting this to non-nil will override this behavior for evil
+operators."
+  :group 'which-key
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defcustom which-key-show-operator-state-maps nil
+  "Show the keys following an evil command that reads a motion.
+These are commands typically mapped to keys such as \"y\", \"d\"
+and \"c\" from normal state. This is experimental, because there
+might be some valid keys missing and it might be showing some
+invalid keys."
+  :group 'which-key
+  :type 'boolean
+  :package-version "1.0" :version "30.1")
+
+(defun which-key-evil-this-operator-p ()
+  (and which-key-allow-evil-operators
+       (bound-and-true-p evil-this-operator)))
+
+(add-hook 'which-key-inhibit-display-hook
+          #'which-key-evil-this-operator-p)
+
+;;;; God-mode
+
+(defvar which-key--god-mode-support-enabled nil
+  "Support god-mode if non-nil.")
+
+(defvar which-key--god-mode-key-string nil
+  "String to use for god-mode support.")
+
+(defun which-key--god-mode-lookup-command-advice (orig-fun arg1 &rest args)
+  (setq which-key--god-mode-key-string arg1)
+  (unwind-protect
+      (apply orig-fun arg1 args)
+    (when (bound-and-true-p which-key-mode)
+      (which-key--hide-popup))))
+
+(defun which-key--god-mode-this-command-keys ()
+  "Version of `this-single-command-keys' corrected for god-mode."
+  (let ((this-command-keys (this-single-command-keys)))
+    (when (and which-key--god-mode-support-enabled
+               (bound-and-true-p god-local-mode)
+               (eq this-command 'god-mode-self-insert))
+      (setq this-command-keys (when which-key--god-mode-key-string
+                                (kbd which-key--god-mode-key-string))))
+    this-command-keys))
+
+(defun which-key-god-mode-self-insert-p ()
+  (and which-key--god-mode-support-enabled
+       (bound-and-true-p god-local-mode)
+       (eq this-command 'god-mode-self-insert)))
+
+(defun which-key-enable-god-mode-support (&optional disable)
+  "Enable support for god-mode if non-nil.
+This is experimental, so you need to explicitly opt-in for
+now.  Please report any problems at github.  If DISABLE is non-nil
+disable support."
+  (interactive "P")
+  (setq which-key--god-mode-support-enabled (null disable))
+  (if disable
+      (progn
+        (advice-remove 'god-mode-lookup-command
+                       #'which-key--god-mode-lookup-command-advice)
+        (remove-function which-key-this-command-keys-function
+                         #'which-key--god-mode-this-command-keys)
+        (remove-hook 'which-key-inhibit-display-hook
+                     #'which-key-god-mode-self-insert-p))
+    (advice-add 'god-mode-lookup-command :around
+                #'which-key--god-mode-lookup-command-advice)
+    (add-function :override which-key-this-command-keys-function
+                  #'which-key--god-mode-this-command-keys)
+    (add-hook 'which-key-inhibit-display-hook
+              #'which-key-god-mode-self-insert-p)))
+
+;;; Mode
+
+;;;###autoload
+(define-minor-mode which-key-mode
+  "Toggle `which-key-mode'."
+  :global t
+  :group 'which-key
+  :lighter which-key-lighter
+  :keymap (let ((map (make-sparse-keymap)))
+            (mapc
+             (lambda (prefix)
+               (define-key map
+                 (kbd (concat prefix " " which-key-paging-key))
+                 #'which-key-C-h-dispatch))
+             which-key-paging-prefixes)
+            map)
+  (if which-key-mode
+      (progn
+        (setq which-key--echo-keystrokes-backup echo-keystrokes)
+        (when (or (eq which-key-show-prefix 'echo)
+                  (eq which-key-popup-type 'minibuffer))
+          (which-key--setup-echo-keystrokes))
+        (unless (member prefix-help-command which-key--paging-functions)
+          (setq which-key--prefix-help-cmd-backup prefix-help-command))
+        (when (or which-key-use-C-h-commands
+                  which-key-show-early-on-C-h)
+          (setq prefix-help-command #'which-key-C-h-dispatch))
+        (when which-key-show-remaining-keys
+          (add-hook 'pre-command-hook #'which-key--lighter-restore))
+        (add-hook 'pre-command-hook #'which-key--hide-popup)
+        (add-hook 'window-size-change-functions
+                  #'which-key--hide-popup-on-frame-size-change)
+        (which-key--start-timer))
+    (setq echo-keystrokes which-key--echo-keystrokes-backup)
+    (when which-key--prefix-help-cmd-backup
+      (setq prefix-help-command which-key--prefix-help-cmd-backup))
+    (when which-key-show-remaining-keys
+      (remove-hook 'pre-command-hook #'which-key--lighter-restore))
+    (remove-hook 'pre-command-hook #'which-key--hide-popup)
+    (remove-hook 'window-size-change-functions
+                 #'which-key--hide-popup-on-frame-size-change)
+    (which-key--stop-timer)))
+
+(defun which-key--init-buffer ()
+  "Initialize which-key buffer."
+  (unless (buffer-live-p which-key--buffer)
+    (setq which-key--buffer (get-buffer-create which-key-buffer-name))
+    (with-current-buffer which-key--buffer
+      ;; suppress confusing minibuffer message
+      (let (message-log-max)
+        (toggle-truncate-lines 1)
+        (message ""))
+      (setq-local cursor-type nil)
+      (setq-local cursor-in-non-selected-windows nil)
+      (setq-local mode-line-format nil)
+      (setq-local header-line-format nil)
+      (setq-local word-wrap nil)
+      (setq-local show-trailing-whitespace nil)
+      (run-hooks 'which-key-init-buffer-hook))))
+
+(defun which-key--setup-echo-keystrokes ()
+  "Reduce `echo-keystrokes' if necessary.
+It will interfere if set too high."
+  (when (and echo-keystrokes
+             (> (abs (- echo-keystrokes which-key-echo-keystrokes)) 0.000001))
+    (if (> which-key-idle-delay which-key-echo-keystrokes)
+        (setq echo-keystrokes which-key-echo-keystrokes)
+      (setq which-key-echo-keystrokes (/ (float which-key-idle-delay) 4)
+            echo-keystrokes which-key-echo-keystrokes))))
+
+(defun which-key-remove-default-unicode-chars ()
+  "Remove default unicode chars from settings.
+Use of `which-key-dont-use-unicode' is preferred to this
+function, but it's included here in case someone cannot set that
+variable early enough in their configuration, if they are using a
+starter kit for example."
+  (when (string-equal which-key-separator " → ")
+    (setq which-key-separator " : ")))
+
+;;; Default configuration functions for use by users.
+
+;;;###autoload
+(defun which-key-setup-side-window-right ()
+  "Set up side-window on right."
+  (interactive)
+  (setq which-key-popup-type 'side-window
+        which-key-side-window-location 'right
+        which-key-show-prefix 'top))
+
+;;;###autoload
+(defun which-key-setup-side-window-right-bottom ()
+  "Set up side-window on right if space allows.
+Otherwise, use bottom."
+  (interactive)
+  (setq which-key-popup-type 'side-window
+        which-key-side-window-location '(right bottom)
+        which-key-show-prefix 'top))
+
+;;;###autoload
+(defun which-key-setup-side-window-bottom ()
+  "Set up side-window that opens on bottom."
+  (interactive)
+  (which-key--setup-echo-keystrokes)
+  (setq which-key-popup-type 'side-window
+        which-key-side-window-location 'bottom
+        which-key-show-prefix 'echo))
+
+;;;###autoload
+(defun which-key-setup-minibuffer ()
+  "Set up minibuffer display.
+Do not use this setup if you use the paging commands.  Instead use
+`which-key-setup-side-window-bottom', which is nearly identical
+but more functional."
+  (interactive)
+  (which-key--setup-echo-keystrokes)
+  (setq which-key-popup-type 'minibuffer
+        which-key-show-prefix 'left))
+
+;;; Helper functions to modify replacement lists.
+
+;;;###autoload
+(defun which-key-add-keymap-based-replacements (keymap key replacement &rest more)
+  "Replace the description of KEY using REPLACEMENT in KEYMAP.
+KEY should take a format suitable for use in `kbd'.  REPLACEMENT
+should be a cons cell of the form \(STRING . COMMAND\) for each
+REPLACEMENT, where STRING is the replacement string and COMMAND
+is a symbol corresponding to the intended command to be
+replaced.  COMMAND can be nil if the binding corresponds to a key
+prefix.  An example is
+
+\(which-key-add-keymap-based-replacements global-map
+  \"C-x w\" \\='\(\"Save as\" . write-file\)\).
+
+For backwards compatibility, REPLACEMENT can also be a string,
+but the above format is preferred, and the option to use a string
+for REPLACEMENT will eventually be removed."
+  (declare (indent defun))
+  (while key
+    (let ((def
+           (cond
+            ((consp replacement) replacement)
+            ((stringp replacement)
+             (cons replacement
+                   (or (which-key--safe-lookup-key-description keymap key)
+                       (make-sparse-keymap))))
+            (t
+             (user-error "Replacement is neither a cons cell or a string")))))
+      (define-key keymap (kbd key) def))
+    (setq key (pop more)
+          replacement (pop more))))
+
+;;;###autoload
+(defun which-key-add-key-based-replacements
+    (key-sequence replacement &rest more)
+  "Replace the description of KEY-SEQUENCE with REPLACEMENT.
+KEY-SEQUENCE is a string suitable for use in `kbd'.
+REPLACEMENT may either be a string, as in
+
+\(which-key-add-key-based-replacements \"C-x 1\" \"maximize\"\)
+
+a cons of two strings as in
+
+\(which-key-add-key-based-replacements \"C-x 8\"
+                                        \\='(\"unicode\" . \"Unicode keys\")\)
+
+or a function that takes a \(KEY . BINDING\) cons and returns a
+replacement.
+
+In the second case, the second string is used to provide a longer
+name for the keys under a prefix.
+
+MORE allows you to specifcy additional KEY REPLACEMENT pairs.  All
+replacements are added to `which-key-replacement-alist'."
+  ;; TODO: Make interactive
+  (while key-sequence
+    ;; normalize key sequences before adding
+    (let ((key-seq (key-description (kbd key-sequence)))
+          (replace (or (and (functionp replacement) replacement)
+                       (car-safe replacement)
+                       replacement)))
+      (push (cons (cons (concat "\\`" (regexp-quote key-seq) "\\'") nil)
+                  (if (functionp replace) replace (cons nil replace)))
+            which-key-replacement-alist)
+      (when (and (not (functionp replacement)) (consp replacement))
+        (push (cons key-seq (cdr-safe replacement))
+              which-key--prefix-title-alist)))
+    (setq key-sequence (pop more) replacement (pop more))))
+(put 'which-key-add-key-based-replacements 'lisp-indent-function 'defun)
+
+;;;###autoload
+(defun which-key-add-major-mode-key-based-replacements
+    (mode key-sequence replacement &rest more)
+  "Functions like `which-key-add-key-based-replacements'.
+The difference is that MODE specifies the `major-mode' that must
+be active for KEY-SEQUENCE and REPLACEMENT (MORE contains
+addition KEY-SEQUENCE REPLACEMENT pairs) to apply."
+  (declare (indent defun))
+  ;; TODO: Make interactive
+  (when (not (symbolp mode))
+    (error "`%S' should be a symbol corresponding to a value of major-mode" mode))
+  (let ((mode-alist
+         (or (cdr-safe (assq mode which-key-replacement-alist)) (list)))
+        (title-mode-alist
+         (or (cdr-safe (assq mode which-key--prefix-title-alist)) (list))))
+    (while key-sequence
+      ;; normalize key sequences before adding
+      (let ((key-seq (key-description (kbd key-sequence)))
+            (replace (or (and (functionp replacement) replacement)
+                         (car-safe replacement)
+                         replacement)))
+        (push (cons (cons (concat "\\`" (regexp-quote key-seq) "\\'") nil)
+                    (if (functionp replace) replace (cons nil replace)))
+              mode-alist)
+        (when (and (not (functionp replacement)) (consp replacement))
+          (push (cons key-seq (cdr-safe replacement))
+                title-mode-alist)))
+      (setq key-sequence (pop more) replacement (pop more)))
+    (if (assq mode which-key-replacement-alist)
+        (setcdr (assq mode which-key-replacement-alist) mode-alist)
+      (push (cons mode mode-alist) which-key-replacement-alist))
+    (if (assq mode which-key--prefix-title-alist)
+        (setcdr (assq mode which-key--prefix-title-alist) title-mode-alist)
+      (push (cons mode title-mode-alist) which-key--prefix-title-alist))))
+
+(defun which-key-define-key-recursively (map key def &optional at-root)
+  "Recursively bind KEY in MAP to DEF on every level of MAP except the first.
+If AT-ROOT is non-nil the binding is also placed at the root of MAP."
+  (when at-root (define-key map key def))
+  (map-keymap
+   (lambda (_ev df)
+     (when (keymapp df)
+       (which-key-define-key-recursively df key def t)))
+   map))
+
+;;; Functions for computing window sizes
+
+(defun which-key--text-width-to-total (text-width)
+  "Convert window TEXT-WIDTH to window total-width.
+TEXT-WIDTH is the desired text width of the window.  The function
+calculates what total width is required for a window in the
+selected to have a text-width of TEXT-WIDTH columns.  The
+calculation considers possible fringes and scroll bars.  This
+function assumes that the desired window has the same character
+width as the frame."
+  (let ((char-width (frame-char-width)))
+    (+ text-width
+       (/ (frame-fringe-width) char-width)
+       (/ (frame-scroll-bar-width) char-width)
+       (if (which-key--char-enlarged-p) 1 0)
+       ;; add padding to account for possible wide (unicode) characters
+       3)))
+
+(defun which-key--total-width-to-text (total-width)
+  "Convert window TOTAL-WIDTH to window text-width.
+TOTAL-WIDTH is the desired total width of the window.  The function calculates
+what text width fits such a window.  The calculation considers possible fringes
+and scroll bars.  This function assumes that the desired window has the same
+character width as the frame."
+  (let ((char-width (frame-char-width)))
+    (- total-width
+       (/ (frame-fringe-width) char-width)
+       (/ (frame-scroll-bar-width) char-width)
+       (if (which-key--char-enlarged-p) 1 0)
+       ;; add padding to account for possible wide (unicode) characters
+       3)))
+
+(defun which-key--char-enlarged-p (&optional _frame)
+  (> (frame-char-width)
+     (/ (float (frame-pixel-width)) (window-total-width (frame-root-window)))))
+
+(defun which-key--char-reduced-p (&optional _frame)
+  (< (frame-char-width)
+     (/ (float (frame-pixel-width)) (window-total-width (frame-root-window)))))
+
+(defun which-key--char-exact-p (&optional _frame)
+  (= (frame-char-width)
+     (/ (float (frame-pixel-width)) (window-total-width (frame-root-window)))))
+
+(defun which-key--width-or-percentage-to-width (width-or-percentage)
+  "Return window total width.
+If WIDTH-OR-PERCENTAGE is a whole number, return it unchanged.  Otherwise, it
+should be a percentage (a number between 0 and 1) out of the frame's width.
+More precisely, it should be a percentage out of the frame's root window's
+total width."
+  (if (natnump width-or-percentage)
+      width-or-percentage
+    (round (* width-or-percentage (window-total-width (frame-root-window))))))
+
+(defun which-key--height-or-percentage-to-height (height-or-percentage)
+  "Return window total height.
+If HEIGHT-OR-PERCENTAGE is a whole number, return it unchanged.  Otherwise, it
+should be a percentage (a number between 0 and 1) out of the frame's height.
+More precisely, it should be a percentage out of the frame's root window's
+total height."
+  (if (natnump height-or-percentage)
+      height-or-percentage
+    (round (* height-or-percentage (window-total-height (frame-root-window))))))
+
+(defun which-key--frame-size-changed-p ()
+  "Non-nil if a change in frame size is detected."
+  (let ((new-size (cons (frame-width) (frame-height))))
+    (cond ((null which-key--previous-frame-size)
+           (setq which-key--previous-frame-size new-size)
+           nil)
+          ((not (equal which-key--previous-frame-size new-size))
+           (setq which-key--previous-frame-size new-size)))))
+
+;;; Show/hide which-key buffer
+
+(defun which-key--hide-popup ()
+  "This function is called to hide the which-key buffer."
+  (unless (or which-key-persistent-popup
+              (member real-this-command which-key--paging-functions))
+    (setq which-key--last-try-2-loc nil)
+    (setq which-key--pages-obj nil)
+    (setq which-key--automatic-display nil)
+    (setq which-key--prior-show-keymap-args nil)
+    (when (and which-key-idle-secondary-delay which-key--secondary-timer-active)
+      (which-key--start-timer))
+    (which-key--lighter-restore)
+    (which-key--hide-popup-ignore-command)))
+
+(defun which-key--hide-popup-ignore-command ()
+  "`which-key--hide-popup' without the check of `real-this-command'."
+  (cl-case which-key-popup-type
+    ;; Not necessary to hide minibuffer
+    ;; (minibuffer (which-key--hide-buffer-minibuffer))
+    (side-window (which-key--hide-buffer-side-window))
+    (frame (which-key--hide-buffer-frame))
+    (custom (funcall which-key-custom-hide-popup-function))))
+
+(defun which-key--hide-popup-on-frame-size-change (&optional _)
+  "Hide which-key popup if the frame is resized (to trigger a new popup)."
+  (when (which-key--frame-size-changed-p)
+    (which-key--hide-popup)))
+
+(defun which-key--hide-buffer-side-window ()
+  "Hide which-key buffer when side-window popup is used."
+  (when (buffer-live-p which-key--buffer)
+    ;; in case which-key buffer was shown in an existing window, `quit-window'
+    ;; will re-show the previous buffer, instead of closing the window
+    (quit-windows-on which-key--buffer)
+    (when (and which-key-preserve-window-configuration
+               which-key--saved-window-configuration)
+      (set-window-configuration which-key--saved-window-configuration)
+      (setq which-key--saved-window-configuration nil))))
+
+(defun which-key--hide-buffer-frame ()
+  "Hide which-key buffer when frame popup is used."
+  (when (frame-live-p which-key--frame)
+    (delete-frame which-key--frame)))
+
+(defun which-key--popup-showing-p ()
+  (and (bufferp which-key--buffer)
+       (or (window-live-p (get-buffer-window which-key--buffer))
+           (let ((window (get-buffer-window which-key--buffer t)))
+             (and (window-live-p window)
+                  (frame-visible-p (window-frame window)))))))
+
+(defun which-key--show-popup (act-popup-dim)
+  "Show the which-key buffer.
+ACT-POPUP-DIM includes the dimensions, (height . width) of the
+buffer text to be displayed in the popup.  Return nil if no window
+is shown, or if there is no need to start the closing timer."
+  (when (and (> (car act-popup-dim) 0)
+             (> (cdr act-popup-dim) 0))
+    (cl-case which-key-popup-type
+      ;; Not called for minibuffer
+      ;; (minibuffer (which-key--show-buffer-minibuffer act-popup-dim))
+      (side-window (which-key--show-buffer-side-window act-popup-dim))
+      (frame (which-key--show-buffer-frame act-popup-dim))
+      (custom (funcall which-key-custom-show-popup-function act-popup-dim)))))
+
+(defun which-key--fit-buffer-to-window-horizontally
+    (&optional window &rest params)
+  "Slightly modified version of `fit-buffer-to-window'.
+Use &rest params because `fit-buffer-to-window' has a different
+call signature in different Emacs versions"
+  (let ((fit-window-to-buffer-horizontally t)
+        (window-min-height 1))
+    (apply #'fit-window-to-buffer window params)))
+
+(defun which-key--show-buffer-side-window (act-popup-dim)
+  "Show which-key buffer when popup type is side-window."
+  (when (and which-key-preserve-window-configuration
+             (not which-key--saved-window-configuration))
+    (setq which-key--saved-window-configuration (current-window-configuration)))
+  (let* ((height (car act-popup-dim))
+         (width (cdr act-popup-dim))
+         (alist
+          (if which-key-allow-imprecise-window-fit
+              `((window-width .  ,(which-key--text-width-to-total width))
+                (window-height . ,height)
+                (side . ,which-key-side-window-location)
+                (slot . ,which-key-side-window-slot))
+            `((window-width . which-key--fit-buffer-to-window-horizontally)
+              (window-height . (lambda (w) (fit-window-to-buffer w nil 1)))
+              (side . ,which-key-side-window-location)
+              (slot . ,which-key-side-window-slot)))))
+    (which-key--debug-message "Allow imprecise fit: %s
+Display window alist: %s"
+                              which-key-allow-imprecise-window-fit
+                              alist)
+    ;; Previously used `display-buffer-in-major-side-window' here, but
+    ;; apparently that is meant to be an internal function. See emacs bug #24828
+    ;; and advice given there.
+    (cond
+     ((eq which-key--multiple-locations t)
+      ;; possibly want to switch sides in this case so we can't reuse the window
+      (delete-windows-on which-key--buffer)
+      (display-buffer-in-side-window which-key--buffer alist))
+     ((get-buffer-window which-key--buffer)
+      (display-buffer-reuse-window which-key--buffer alist))
+     (t
+      (display-buffer-in-side-window which-key--buffer alist)))))
+
+(defun which-key--show-buffer-frame (act-popup-dim)
+  "Show which-key buffer when popup type is frame."
+  (let* (;(orig-window (selected-window))
+         (frame-height (+ (car act-popup-dim)
+                          (if (with-current-buffer which-key--buffer
+                                mode-line-format)
+                              1
+                            0)))
+         ;; without adding 2, frame sometimes isn't wide enough for the buffer.
+         ;; this is probably because of the fringes. however, setting fringes
+         ;; sizes to 0 (instead of adding 2) didn't always make the frame wide
+         ;; enough. don't know why it is so.
+         (frame-width (+ (cdr act-popup-dim) 2))
+         (new-window (if (and (frame-live-p which-key--frame)
+                              (eq which-key--buffer
+                                  (window-buffer
+                                   (frame-root-window which-key--frame))))
+                         (which-key--show-buffer-reuse-frame
+                          frame-height frame-width)
+                       (which-key--show-buffer-new-frame
+                        frame-height frame-width))))
+    (when new-window
+      ;; display successful
+      (setq which-key--frame (window-frame new-window))
+      new-window)))
+
+(defun which-key--show-buffer-new-frame (frame-height frame-width)
+  "Helper for `which-key--show-buffer-frame'."
+  (let* ((frame-params `((height . ,frame-height)
+                         (width . ,frame-width)
+                         ;; tell the window manager to respect the given sizes
+                         (user-size . t)
+                         ;; which-key frame doesn't need a minibuffer
+                         (minibuffer . nil)
+                         (name . "which-key")
+                         ;; no need for scroll bars in which-key frame
+                         (vertical-scroll-bars . nil)
+                         ;; (left-fringe . 0)
+                         ;; (right-fringe . 0)
+                         ;; (right-divider-width . 0)
+                         ;; make sure frame is visible
+                         (visibility . t)))
+         (alist `((pop-up-frame-parameters . ,frame-params)))
+         (orig-frame (selected-frame))
+         (new-window (display-buffer-pop-up-frame which-key--buffer alist)))
+    (when new-window
+      ;; display successful
+      (redirect-frame-focus (window-frame new-window) orig-frame)
+      new-window)))
+
+(defun which-key--show-buffer-reuse-frame (frame-height frame-width)
+  "Helper for `which-key--show-buffer-frame'."
+  (let ((window
+         (display-buffer-reuse-window
+          which-key--buffer `((reusable-frames . ,which-key--frame)))))
+    (when window
+      ;; display successful
+      (set-frame-size (window-frame window) frame-width frame-height)
+      window)))
+
+;;; Max dimension of available window functions
+
+(defun which-key--popup-max-dimensions ()
+  "Return maximum dimension available for popup.
+Dimension functions should return the maximum possible (height
+. width) of the intended popup.  SELECTED-WINDOW-WIDTH is the
+width of currently active window, not the which-key buffer
+window."
+  (cl-ecase which-key-popup-type
+    (minibuffer (which-key--minibuffer-max-dimensions))
+    (side-window (which-key--side-window-max-dimensions))
+    (frame (which-key--frame-max-dimensions))
+    (custom (funcall which-key-custom-popup-max-dimensions-function
+                     (window-width)))))
+
+(defun which-key--minibuffer-max-dimensions ()
+  "Return max-dimensions of minibuffer (height . width).
+Measured in lines and characters respectively."
+  (cons
+   ;; height
+   (if (floatp max-mini-window-height)
+       (floor (* (frame-text-lines)
+                 max-mini-window-height))
+     max-mini-window-height)
+   ;; width
+   (max 0 (- (frame-text-cols) which-key-unicode-correction))))
+
+(defun which-key--side-window-max-dimensions ()
+  "Return max-dimensions of the side-window popup.
+The return value should be (height . width) in lines and
+characters respectively."
+  (cons
+   ;; height
+   (if (member which-key-side-window-location '(left right))
+       ;; 1 is a kludge to make sure there is no overlap
+       (- (frame-height) (window-text-height (minibuffer-window)) 1)
+     ;; (window-mode-line-height which-key--window))
+     ;; FIXME: change to something like
+     ;; (min which-*-height (calculate-max-height))
+     (which-key--height-or-percentage-to-height
+      which-key-side-window-max-height))
+   ;; width
+   (max 0
+        (- (if (memq which-key-side-window-location '(left right))
+               (which-key--total-width-to-text
+                (which-key--width-or-percentage-to-width
+                 which-key-side-window-max-width))
+             (which-key--total-width-to-text
+              (which-key--width-or-percentage-to-width
+               1.0)))
+           which-key-unicode-correction))))
+
+(defun which-key--frame-max-dimensions ()
+  "Return max-dimensions of the frame popup.
+The return value should be (height . width) in lines and
+characters respectively."
+  (cons which-key-frame-max-height which-key-frame-max-width))
+
+;;; Sorting functions
+
+(defun which-key--string< (a b &optional alpha)
+  (let ((da (downcase a))
+        (db (downcase b)))
+    (cond
+     ((and alpha (not which-key-sort-uppercase-first))
+      (if (string-equal da db)
+          (not (string-lessp a b))
+        (string-lessp da db)))
+     ((and alpha which-key-sort-uppercase-first)
+      (if (string-equal da db)
+          (string-lessp a b)
+        (string-lessp da db)))
+     ((not which-key-sort-uppercase-first)
+      (let ((aup (not (string-equal da a)))
+            (bup (not (string-equal db b))))
+        (if (eq aup bup)
+            (string-lessp a b)
+          bup)))
+     (t (string-lessp a b)))))
+
+(defun which-key--key-description< (a b &optional alpha)
+  "Key sorting function.
+Used for `which-key-key-order' and `which-key-key-order-alpha'."
+  (save-match-data
+    (let* ((a (which-key--extract-key a))
+           (b (which-key--extract-key b))
+           (rngrgxp "^\\([^ ]+\\) \\.\\. [^ ]+")
+           (a (if (string-match rngrgxp a) (match-string 1 a) a))
+           (b (if (string-match rngrgxp b) (match-string 1 b) b))
+           (aem? (string-equal a ""))
+           (bem? (string-equal b ""))
+           (a1? (= 1 (length a)))
+           (b1? (= 1 (length b)))
+           (srgxp "^\\(RET\\|SPC\\|TAB\\|DEL\\|LFD\\|ESC\\|NUL\\)")
+           (asp? (string-match-p srgxp a))
+           (bsp? (string-match-p srgxp b))
+           (prrgxp "^\\(M\\|C\\|S\\|A\\|H\\|s\\)-")
+           (apr? (string-match-p prrgxp a))
+           (bpr? (string-match-p prrgxp b))
+           (afn? (string-match-p "<f[0-9]+>" a))
+           (bfn? (string-match-p "<f[0-9]+>" b)))
+      (cond ((or aem? bem?) (and aem? (not bem?)))
+            ((and asp? bsp?)
+             (if (string-equal (substring a 0 3) (substring b 0 3))
+                 (which-key--key-description<
+                  (substring a 3) (substring b 3) alpha)
+               (which-key--string< a b alpha)))
+            ((or asp? bsp?) asp?)
+            ((and a1? b1?) (which-key--string< a b alpha))
+            ((or a1? b1?) a1?)
+            ((and afn? bfn?)
+             (< (string-to-number
+                 (replace-regexp-in-string "<f\\([0-9]+\\)>" "\\1" a))
+                (string-to-number
+                 (replace-regexp-in-string "<f\\([0-9]+\\)>" "\\1" b))))
+            ((or afn? bfn?) afn?)
+            ((and apr? bpr?)
+             (if (string-equal (substring a 0 2) (substring b 0 2))
+                 (which-key--key-description<
+                  (substring a 2) (substring b 2) alpha)
+               (which-key--string< a b alpha)))
+            ((or apr? bpr?) apr?)
+            (t (which-key--string< a b alpha))))))
+
+(defsubst which-key-key-order-alpha (acons bcons)
+  "Order key descriptions A and B.
+Order is lexicographic within a \"class\", where the classes and
+the ordering of classes are listed below.
+
+special (SPC,TAB,...) < single char < mod (C-,M-,...) < other.
+Sorts single characters alphabetically with lowercase coming
+before upper."
+  (which-key--key-description< (car acons) (car bcons) t))
+
+(defsubst which-key-key-order (acons bcons)
+  "Order key descriptions A and B.
+Order is lexicographic within a \"class\", where the classes and
+the ordering of classes are listed below.
+
+special (SPC,TAB,...) < single char < mod (C-,M-,...) < other."
+  (which-key--key-description< (car acons) (car bcons)))
+
+(defsubst which-key-description-order (acons bcons)
+  "Order descriptions of A and B.
+Uses `string-lessp' after applying lowercase."
+  (string-lessp (downcase (cdr acons)) (downcase (cdr bcons))))
+
+(defsubst which-key--group-p (description)
+  (or (string-equal description "prefix")
+      (string-match-p "^group:" description)
+      (keymapp (intern description))))
+
+(defun which-key-prefix-then-key-order (acons bcons)
+  "Order prefixes before non-prefixes.
+Within these categories order using `which-key-key-order'."
+  (let ((apref? (which-key--group-p (cdr acons)))
+        (bpref? (which-key--group-p (cdr bcons))))
+    (if (not (eq apref? bpref?))
+        (and (not apref?) bpref?)
+      (which-key-key-order acons bcons))))
+
+(defun which-key-prefix-then-key-order-reverse (acons bcons)
+  "Order prefixes before non-prefixes.
+Within these categories order using `which-key-key-order'."
+  (let ((apref? (which-key--group-p (cdr acons)))
+        (bpref? (which-key--group-p (cdr bcons))))
+    (if (not (eq apref? bpref?))
+        (and apref? (not bpref?))
+      (which-key-key-order acons bcons))))
+
+(defun which-key-local-then-key-order (acons bcons)
+  "Order local bindings before non-local ones.
+Within these categories order using `which-key-key-order'."
+  (let ((aloc? (which-key--local-binding-p acons))
+        (bloc? (which-key--local-binding-p bcons)))
+    (if (not (eq aloc? bloc?))
+        (and aloc? (not bloc?))
+      (which-key-key-order acons bcons))))
+
+;;; Functions for retrieving and formatting keys
+
+(defsubst which-key--string-width (maybe-string)
+  "If MAYBE-STRING is a string use `which-key--string-width' o/w return 0."
+  (if (stringp maybe-string) (string-width maybe-string) 0))
+
+(defsubst which-key--butlast-string (str)
+  (string-join (butlast (split-string str)) " "))
+
+(defun which-key--match-replacement (key-binding replacement)
+  ;; these are mode specific ones to ignore. The mode specific case is
+  ;; handled in the selection of alist
+  (when (and (consp key-binding) (not (symbolp (car replacement))))
+    (let ((key-regexp (caar replacement))
+          (binding-regexp (cdar replacement))
+          (case-fold-search nil))
+      (and (or (null key-regexp)
+               (string-match-p key-regexp
+                               (car key-binding)))
+           (or (null binding-regexp)
+               (string-match-p binding-regexp
+                               (cdr key-binding)))))))
+
+(defsubst which-key--replace-in-binding (key-binding repl)
+  (cond ((or (not (consp repl)) (null (cdr repl)))
+         key-binding)
+        ((functionp (cdr repl))
+         (funcall (cdr repl) key-binding))
+        ((consp (cdr repl))
+         (cons
+          (cond ((and (caar repl) (cadr repl))
+                 (replace-regexp-in-string
+                  (caar repl) (cadr repl) (car key-binding) t))
+                ((cadr repl) (cadr repl))
+                (t (car key-binding)))
+          (cond ((and (cdar repl) (cddr repl))
+                 (replace-regexp-in-string
+                  (cdar repl) (cddr repl) (cdr key-binding) t))
+                ((cddr repl) (cddr repl))
+                (t (cdr key-binding)))))))
+
+(defun which-key--replace-in-repl-list-once (key-binding repls)
+  (cl-dolist (repl repls)
+    (when (which-key--match-replacement key-binding repl)
+      (cl-return `(replaced . ,(which-key--replace-in-binding key-binding repl))))))
+
+(defun which-key--replace-in-repl-list-many (key-binding repls)
+  (let (found)
+    (dolist (repl repls)
+      (when (which-key--match-replacement key-binding repl)
+        (setq found t)
+        (setq key-binding (which-key--replace-in-binding key-binding repl))))
+    (when found `(replaced . ,key-binding))))
+
+(defun which-key--maybe-replace (key-binding)
+  "Use `which-key--replacement-alist' to maybe replace KEY-BINDING.
+KEY-BINDING is a cons cell of the form \(KEY . BINDING\) each of
+which are strings.  KEY is of the form produced by `key-binding'."
+  (let* ((replacer (if which-key-allow-multiple-replacements
+                       #'which-key--replace-in-repl-list-many
+                     #'which-key--replace-in-repl-list-once)))
+    (pcase
+        (apply replacer
+               (list key-binding
+                     (cdr-safe (assq major-mode which-key-replacement-alist))))
+      (`(replaced . ,repl)
+       (if which-key-allow-multiple-replacements
+           (pcase (apply replacer (list repl which-key-replacement-alist))
+             (`(replaced . ,repl) repl)
+             ('() repl))
+         repl))
+      ('()
+       (pcase (apply replacer (list key-binding which-key-replacement-alist))
+         (`(replaced . ,repl) repl)
+         ('() key-binding))))))
+
+(defsubst which-key--current-key-list (&optional key-str)
+  (append (listify-key-sequence (which-key--current-prefix))
+          (when key-str
+            (listify-key-sequence (kbd key-str)))))
+
+(defsubst which-key--current-key-string (&optional key-str)
+  (key-description (which-key--current-key-list key-str)))
+
+(defun which-key--local-binding-p (keydesc)
+  (eq (which-key--safe-lookup-key-description
+       (current-local-map)
+       (which-key--current-key-string (car keydesc)))
+      (intern (cdr keydesc))))
+
+(defun which-key--map-binding-p (map keydesc)
+  "Does MAP contain KEYDESC = (key . binding)?"
+  (or
+   (when (bound-and-true-p evil-state)
+     (let ((lookup
+            (which-key--safe-lookup-key-description
+             map
+             (which-key--current-key-string
+              (format "<%s-state> %s" evil-state (car keydesc))))))
+       (or (eq lookup (intern (cdr keydesc)))
+           (and (keymapp lookup) (string= (cdr keydesc) "Prefix Command")))))
+   (let ((lookup
+          (which-key--safe-lookup-key-description
+           map (which-key--current-key-string (car keydesc)))))
+     (or (eq lookup (intern (cdr keydesc)))
+         (and (keymapp lookup) (string= (cdr keydesc) "Prefix Command"))))))
+
+(defun which-key--maybe-get-prefix-title (keys)
+  "KEYS is a string produced by `key-description'.
+A title is possibly returned using
+`which-key--prefix-title-alist'.  An empty string is returned if
+no title exists."
+  (cond
+   ((not (string-equal keys ""))
+    (let* ((title-res
+            (cdr-safe (assoc-string keys which-key--prefix-title-alist)))
+           (repl-res
+            (cdr-safe (which-key--maybe-replace (cons keys ""))))
+           (binding (key-binding (kbd keys)))
+           (alternate (when (and binding (symbolp binding))
+                        (symbol-name binding))))
+      (cond (title-res title-res)
+            ((not (string-empty-p repl-res)) repl-res)
+            ((and (eq which-key-show-prefix 'echo) alternate)
+             alternate)
+            ((and (member which-key-show-prefix '(bottom top mode-line))
+                  (eq which-key-side-window-location 'bottom)
+                  echo-keystrokes)
+             (if alternate alternate
+               (concat "Following " keys)))
+            (t ""))))
+   (t "")))
+
+(defun which-key--propertize (string &rest properties)
+  "Version of `propertize' that checks type of STRING."
+  (when (stringp string)
+    (apply #'propertize string properties)))
+
+(defun which-key--propertize-key (key)
+  "Add a face to KEY.
+If KEY contains any \"special keys\" defined in
+`which-key-special-keys' then truncate and add the corresponding
+`which-key-special-key-face'."
+  (let ((key-w-face (which-key--propertize key 'face 'which-key-key-face))
+        (regexp (concat "\\("
+                        (mapconcat #'identity which-key-special-keys
+                                   "\\|")
+                        "\\)"))
+        (case-fold-search nil))
+    (save-match-data
+      (if (and which-key-special-keys
+               (string-match regexp key))
+          (let ((beg (match-beginning 0)) (end (match-end 0)))
+            (concat (substring key-w-face 0 beg)
+                    (which-key--propertize (substring key-w-face beg (1+ beg))
+                                           'face 'which-key-special-key-face)
+                    (substring key-w-face end
+                               (which-key--string-width key-w-face))))
+        key-w-face))))
+
+(defsubst which-key--truncate-description (desc avl-width)
+  "Truncate DESC description to `which-key-max-description-length'."
+  (let* ((max which-key-max-description-length)
+         (max (cl-etypecase max
+                (null nil)
+                (integer max)
+                (float (truncate (* max avl-width)))
+                (function (let ((val (funcall max avl-width)))
+                            (if (floatp val) (truncate val) val))))))
+    (if (and max (> (length desc) max))
+        (let ((dots (and (not (equal which-key-ellipsis ""))
+                         (which-key--propertize
+                          which-key-ellipsis 'face
+                          (get-text-property (1- (length desc)) 'face desc)))))
+          (if dots
+              (concat (substring desc 0 (- max (length dots))) dots)
+            (substring desc 0 max)))
+      desc)))
+
+(defun which-key--highlight-face (description)
+  "Return the highlight face for DESCRIPTION if it has one."
+  (let (face)
+    (dolist (el which-key-highlighted-command-list)
+      (unless face
+        (cond ((consp el)
+               (when (string-match-p (car el) description)
+                 (setq face (cdr el))))
+              ((stringp el)
+               (when (string-match-p el description)
+                 (setq face 'which-key-highlighted-command-face)))
+              (t
+               (message "which-key: warning: element %s of \
+which-key-highlighted-command-list is not a string or a cons
+cell" el)))))
+    face))
+
+(defun which-key--propertize-description
+    (description group local hl-face &optional original-description)
+  "Add face to DESCRIPTION.
+The face chosen depends on whether the description represents a
+group or a command.  Also make some minor adjustments to the
+description string, like removing a \"group:\" prefix.
+
+ORIGINAL-DESCRIPTION is the description given by
+`describe-buffer-bindings'."
+  (when description
+    (let* ((desc description)
+           (desc (if (string-match-p "^group:" desc)
+                     (substring desc 6) desc))
+           (desc (if group (concat which-key-prefix-prefix desc) desc)))
+      (make-text-button
+       desc nil
+       'face (cond (hl-face hl-face)
+                   (group 'which-key-group-description-face)
+                   (local 'which-key-local-map-description-face)
+                   (t 'which-key-command-description-face))
+       'help-echo (cond
+                   ((and original-description
+                         (fboundp (intern original-description))
+                         (documentation (intern original-description))
+                         ;; tooltip-mode doesn't exist in emacs-nox
+                         (boundp 'tooltip-mode) tooltip-mode)
+                    (documentation (intern original-description)))
+                   ((and original-description
+                         (fboundp (intern original-description))
+                         (documentation (intern original-description))
+                         (let* ((doc (documentation
+                                      (intern original-description)))
+                                (str (replace-regexp-in-string "\n" " " doc))
+                                (max (floor (* (frame-width) 0.8))))
+                           (if (> (length str) max)
+                               (concat (substring str 0 max) "...")
+                             str)))))))))
+
+(defun which-key--extract-key (key-str)
+  "Pull the last key (or key range) out of KEY-STR."
+  (save-match-data
+    (let ((key-range-regexp "\\`.*\\([^ \t]+ \\.\\. [^ \t]+\\)\\'"))
+      (if (string-match key-range-regexp key-str)
+          (match-string 1 key-str)
+        (car (last (split-string key-str " ")))))))
+
+(defun which-key--maybe-add-docstring (current original)
+  "Maybe concat a docstring to CURRENT and return result.
+Specifically, do this if ORIGINAL is a command with a docstring
+and `which-key-show-docstrings' is non-nil.  If
+`which-key-show-docstrings' is the symbol docstring-only, just
+return the docstring."
+  (let* ((orig-sym (intern original))
+         (doc (when (commandp orig-sym)
+                (documentation orig-sym)))
+         (doc (when doc
+                (replace-regexp-in-string
+                 (concat "^\\(?::"
+                         (regexp-opt '("around" "override"
+                                       "after" "after-until" "after-while"
+                                       "before" "before-until" "before-while"
+                                       "filter-args" "filter-return"))
+                         " advice: [^\n]+\n"
+                         "\\)+\n")
+                 "" doc)))
+         (docstring (when doc
+                      (which-key--propertize (car (split-string doc "\n"))
+                                             'face 'which-key-docstring-face))))
+    (cond ((not (and which-key-show-docstrings docstring))
+           current)
+          ((eq which-key-show-docstrings 'docstring-only)
+           docstring)
+          (t
+           (format "%s %s" current docstring)))))
+
+(defun which-key--format-and-replace (unformatted &optional preserve-full-key)
+  "Make list of key bindings with separators and descriptions.
+Take a list of (key . desc) cons cells in UNFORMATTED, add
+faces and perform replacements according to the three replacement
+alists.  Return a list (key separator description)."
+  (let ((sep-w-face
+         (which-key--propertize which-key-separator
+                                'face 'which-key-separator-face))
+        (local-map (current-local-map))
+       (avl-width (cdr (which-key--popup-max-dimensions)))
+        new-list)
+    (dolist (key-binding unformatted)
+      (let* ((keys (car key-binding))
+             (orig-desc (cdr key-binding))
+             (group (which-key--group-p orig-desc))
+             (local (eq (which-key--safe-lookup-key-description
+                         local-map keys)
+                        (intern orig-desc)))
+             (hl-face (which-key--highlight-face orig-desc))
+             (key-binding (which-key--maybe-replace key-binding))
+             (final-desc (which-key--propertize-description
+                          (cdr key-binding) group local hl-face orig-desc)))
+        (when final-desc
+          (setq final-desc
+                (which-key--truncate-description
+                 (which-key--maybe-add-docstring final-desc orig-desc)
+                avl-width)))
+        (when (consp key-binding)
+          (push
+           (list (which-key--propertize-key
+                  (if preserve-full-key
+                      (car key-binding)
+                    (which-key--extract-key (car key-binding))))
+                 sep-w-face
+                 final-desc)
+           new-list))))
+    (nreverse new-list)))
+
+(defun which-key--compute-binding (binding)
+  "Replace BINDING with remapped binding if it exists.
+Requires `which-key-compute-remaps' to be non-nil."
+  (copy-sequence (symbol-name
+                  (or (and which-key-compute-remaps
+                           (command-remapping binding))
+                      binding))))
+
+(defun which-key--get-menu-item-binding (def)
+  "Retrieve binding for menu-item."
+  ;; see `keymap--menu-item-binding'
+  (let* ((binding (nth 2 def))
+         (plist (nthcdr 3 def))
+         (filter (plist-get plist :filter)))
+    (if filter (funcall filter binding) binding)))
+
+(defun which-key--get-keymap-bindings-1
+    (keymap start &optional prefix filter all ignore-commands)
+  "See `which-key--get-keymap-bindings'."
+  (let ((bindings start)
+        (prefix-map (if prefix (lookup-key keymap prefix) keymap)))
+    (when (keymapp prefix-map)
+      (map-keymap
+       (lambda (ev def)
+         (let* ((key (vconcat prefix (list ev)))
+                (key-desc (key-description key)))
+           (cond
+            ((assoc key-desc bindings))
+            ((and (listp ignore-commands) (symbolp def) (memq def ignore-commands)))
+            ((or (string-match-p
+                  which-key--ignore-non-evil-keys-regexp key-desc)
+                 (eq ev 'menu-bar)))
+            ((and (keymapp def)
+                  (string-match-p which-key--evil-keys-regexp key-desc)))
+            ((and (keymapp def)
+                  (or all
+                      ;; event 27 is escape, so this will pick up meta
+                      ;; bindings and hopefully not too much more
+                      (eql ev 27)))
+             (setq bindings
+                   (which-key--get-keymap-bindings-1
+                    keymap bindings key nil all ignore-commands)))
+            (def
+             (let* ((def (if (eq 'menu-item (car-safe def))
+                             (which-key--get-menu-item-binding def)
+                           def))
+                    (binding
+                     (cons key-desc
+                           (cond
+                            ((symbolp def) (which-key--compute-binding def))
+                            ((keymapp def) "prefix")
+                            ((functionp def)
+                             (cond
+                              ((eq 'lambda (car-safe def)) "lambda")
+                              ((eq 'closure (car-safe def)) "closure")
+                              (t "function")))
+                            ((stringp def) def)
+                            ((vectorp def) (key-description def))
+                            ((and (consp def)
+                                  ;; looking for (STRING . DEFN)
+                                  (stringp (car def)))
+                             (concat (when (keymapp (cdr-safe def))
+                                       "group:")
+                                     (car def)))
+                            (t "unknown")))))
+               (when (or (null filter)
+                         (and (functionp filter)
+                              (funcall filter binding)))
+                 (push binding bindings)))))))
+       prefix-map))
+    bindings))
+
+(defun which-key--get-keymap-bindings
+    (keymap &optional start prefix filter all evil)
+  "Retrieve top-level bindings from KEYMAP.
+PREFIX limits bindings to those starting with this key
+sequence.  START is a list of existing bindings to add to.  If ALL
+is non-nil, recursively retrieve all bindings below PREFIX.  If
+EVIL is non-nil, extract active evil bidings."
+  (let ((bindings start)
+        (ignore '(self-insert-command ignore ignore-event company-ignore))
+        (evil-map
+         (when (and evil (bound-and-true-p evil-local-mode))
+           (lookup-key keymap (kbd (format "<%s-state>" evil-state))))))
+    (when (keymapp evil-map)
+      (setq bindings (which-key--get-keymap-bindings-1
+                      evil-map bindings prefix filter all ignore)))
+    (which-key--get-keymap-bindings-1
+     keymap bindings prefix filter all ignore)))
+
+(defun which-key--get-current-bindings (&optional prefix filter)
+  "Generate a list of current active bindings."
+  (let (bindings)
+    (dolist (map (current-active-maps t) bindings)
+      (when (cdr map)
+        (setq bindings
+              (which-key--get-keymap-bindings
+               map bindings prefix filter))))))
+
+(defun which-key--get-bindings (&optional prefix keymap filter recursive)
+  "Collect key bindings.
+If KEYMAP is nil, collect from current buffer using the current
+key sequence as a prefix.  Otherwise, collect from KEYMAP.  FILTER
+is a function to use to filter the bindings.  If RECURSIVE is
+non-nil, then bindings are collected recursively for all prefixes."
+  (let* ((unformatted
+          (cond ((keymapp keymap)
+                 (which-key--get-keymap-bindings
+                  keymap nil prefix filter recursive))
+                (keymap
+                 (error "%s is not a keymap" keymap))
+                (t
+                 (which-key--get-current-bindings prefix filter)))))
+    (when which-key-sort-order
+      (setq unformatted
+            (sort unformatted which-key-sort-order)))
+    (which-key--format-and-replace unformatted recursive)))
+
+;;; Functions for laying out which-key buffer pages
+
+(defun which-key--normalize-columns (columns)
+  "Pad COLUMNS to the same length using empty strings."
+  (let ((max-len (cl-reduce (lambda (a x) (max a (length x))) columns
+                            :initial-value 0)))
+    (mapcar
+     (lambda (c)
+       (if (< (length c) max-len)
+           (append c (make-list (- max-len (length c)) ""))
+         c))
+     columns)))
+
+(defsubst which-key--join-columns (columns)
+  "Transpose columns into rows, concat rows into lines and rows into page."
+  (let* ((padded (which-key--normalize-columns (nreverse columns)))
+         (rows (apply #'cl-mapcar #'list padded)))
+    (mapconcat (lambda (row) (mapconcat #'identity row " ")) rows "\n")))
+
+(defsubst which-key--max-len (keys index &optional initial-value)
+  "Find the max length of the INDEX element in each of KEYS."
+  (cl-reduce
+   (lambda (x y) (max x (which-key--string-width (nth index y))))
+   keys :initial-value (if initial-value initial-value 0)))
+
+(defun which-key--pad-column (col-keys avl-width)
+  "Pad cells of COL-KEYS to AVL-WIDTH.
+Take a column of (key separator description) COL-KEYS,
+calculate the max width in the column and pad all cells out to
+that width."
+  (let* ((col-key-width  (+ which-key-add-column-padding
+                            (which-key--max-len col-keys 0)))
+         (col-sep-width  (which-key--max-len col-keys 1))
+         (avl-width      (- avl-width col-key-width col-sep-width))
+         (col-desc-width (min avl-width
+                              (which-key--max-len
+                               col-keys 2
+                               which-key-min-column-description-width)))
+         (col-width      (+ col-key-width col-sep-width col-desc-width))
+         (col-format     (concat "%" (int-to-string col-key-width) "s%s%s")))
+    (cons col-width
+          (mapcar (pcase-lambda (`(,key ,sep ,desc ,_doc))
+                    (concat
+                     (format col-format key sep desc)
+                     (make-string (- col-desc-width (length desc)) ?\s)))
+                  col-keys))))
+
+(defun which-key--partition-list (n list)
+  "Partition LIST into N-sized sublists."
+  (let (res)
+    (while list
+      (setq res (cons (cl-subseq list 0 (min n (length list))) res)
+            list (nthcdr n list)))
+    (nreverse res)))
+
+(defun which-key--list-to-pages (keys avl-lines avl-width)
+  "Convert list of KEYS to columns based on dimensions AVL-LINES and AVL-WIDTH.
+Return a `which-key--pages' object that holds the page strings,
+as well as metadata."
+  (let ((cols-w-widths (mapcar (lambda (c) (which-key--pad-column c avl-width))
+                              (which-key--partition-list avl-lines keys)))
+        (page-width 0) (n-pages 0) (n-keys 0) (n-columns 0)
+        page-cols pages page-widths keys/page col)
+    (if (> (apply #'max (mapcar #'car cols-w-widths)) avl-width)
+        ;; give up if no columns fit
+        nil
+      (while cols-w-widths
+        ;; start new page
+        (cl-incf n-pages)
+        (setq col (pop cols-w-widths))
+        (setq page-cols (list (cdr col)))
+        (setq page-width (car col))
+        (setq n-keys (length (cdr col)))
+        (setq n-columns 1)
+        ;; add additional columns as long as they fit
+        (while (and cols-w-widths
+                    (or (null which-key-max-display-columns)
+                        (< n-columns which-key-max-display-columns))
+                    (<= (+ page-width 1 (caar cols-w-widths)) avl-width))
+          (setq col (pop cols-w-widths))
+          (push (cdr col) page-cols)
+          (cl-incf page-width (1+ (car col)))
+          (cl-incf n-keys (length (cdr col)))
+          (cl-incf n-columns))
+        (push (which-key--join-columns page-cols) pages)
+        (push n-keys keys/page)
+        (push page-width page-widths))
+      (make-which-key--pages
+       :pages (nreverse pages)
+       :height (if (> n-pages 1) avl-lines (min avl-lines n-keys))
+       :widths (nreverse page-widths)
+       :keys/page (reverse keys/page)
+       :page-nums (number-sequence 1 n-pages)
+       :num-pages n-pages
+       :total-keys (apply #'+ keys/page)))))
+
+(defun which-key--create-pages-1
+    (keys available-lines available-width &optional min-lines vertical)
+  "Create page strings using `which-key--list-to-pages'.
+Will try to find the best number of rows and columns using the
+given dimensions and the length and widths of ITEMS.  Use VERTICAL
+if the ITEMS are laid out vertically and the number of columns
+should be minimized."
+  (let ((result (which-key--list-to-pages
+                 keys available-lines available-width))
+        (min-lines (or min-lines 0))
+        found prev-result)
+    (if (or (null result)
+            vertical
+            (> (which-key--pages-num-pages result) 1)
+            (= 1 available-lines))
+        result
+      ;; simple search for a fitting page
+      (while (and (> available-lines min-lines)
+                  (not found))
+        (setq available-lines (cl-decf available-lines)
+              prev-result result
+              result (which-key--list-to-pages
+                      keys available-lines available-width)
+              found (> (which-key--pages-num-pages result) 1)))
+      (if found prev-result result))))
+
+(defun which-key--create-pages (keys &optional prefix-keys prefix-title)
+  "Create page strings using `which-key--list-to-pages'.
+Will try to find the best number of rows and columns using the
+given dimensions and the length and wdiths of KEYS.  SEL-WIN-WIDTH
+is the width of the live window."
+  (let* ((max-dims (which-key--popup-max-dimensions))
+         (max-lines (car max-dims))
+         (max-width (cdr max-dims))
+         (prefix-desc (key-description prefix-keys))
+         (full-prefix (which-key--full-prefix prefix-desc))
+         (prefix (when (eq which-key-show-prefix 'left)
+                   (+ 2 (which-key--string-width full-prefix))))
+         (prefix-top-bottom (member which-key-show-prefix '(bottom top)))
+         (avl-lines (if prefix-top-bottom (- max-lines 1) max-lines))
+         (min-lines (min avl-lines which-key-min-display-lines))
+         (avl-width (if prefix (- max-width prefix) max-width))
+         (vertical (or (and (eq which-key-popup-type 'side-window)
+                            (member which-key-side-window-location '(left right)))
+                       (eq which-key-max-display-columns 1)))
+         result)
+    (setq result
+          (which-key--create-pages-1
+           keys avl-lines avl-width min-lines vertical))
+    (when (and result
+               (> (which-key--pages-num-pages result) 0))
+      (setf (which-key--pages-prefix result) prefix-keys)
+      (setf (which-key--pages-prefix-title result)
+            (or prefix-title
+                (which-key--maybe-get-prefix-title
+                 (key-description prefix-keys))))
+      (when prefix-top-bottom
+        ;; Add back the line earlier reserved for the page information.
+        (setf (which-key--pages-height result) max-lines))
+      (when (and (= (which-key--pages-num-pages result) 1)
+                 (> which-key-min-display-lines
+                    (which-key--pages-height result)))
+        ;; result is shorter than requested, so we artificially increase the
+        ;; height. See #325. Note this only has an effect if
+        ;; `which-key-allow-imprecise-window-fit' is non-nil.
+        (setf (which-key--pages-height result) which-key-min-display-lines))
+      (which-key--debug-message "Frame height: %s
+Frame pixel width: %s
+Frame char width: %s
+Frame width: %s
+Which-key initial width: %s
+Which-key adjusted width: %s
+Minibuffer height: %s
+Max dimensions: (%s, %s)
+Available for bindings: (%s, %s)
+Popup type info: (%s, %s, %s)
+Computed page widths: %s
+Actual lines: %s"
+                                (frame-height)
+                                (frame-pixel-width)
+                                (frame-char-width)
+                                (window-total-width (frame-root-window))
+                                (which-key--width-or-percentage-to-width
+                                  which-key-side-window-max-width)
+                                (which-key--total-width-to-text
+                                 (which-key--width-or-percentage-to-width
+                                  which-key-side-window-max-width))
+                                (window-text-height (minibuffer-window))
+                                max-lines
+                                max-width
+                                avl-lines
+                                avl-width
+                                which-key-popup-type
+                                which-key-side-window-location
+                                which-key-side-window-max-width
+                                (which-key--pages-widths result)
+                                (which-key--pages-height result))
+      result)))
+
+(defun which-key--lighter-status ()
+  "Possibly show number of keys and total in the mode line."
+  (when which-key-show-remaining-keys
+    (let ((n-shown (car (which-key--pages-keys/page which-key--pages-obj)))
+          (n-tot (which-key--pages-total-keys which-key--pages-obj)))
+      (setcar (cdr (assq 'which-key-mode minor-mode-alist))
+              (format " WK: %s/%s keys" n-shown n-tot)))))
+
+(defun which-key--lighter-restore ()
+  "Restore the lighter for which-key."
+  (when which-key-show-remaining-keys
+    (setcar (cdr (assq 'which-key-mode minor-mode-alist))
+            which-key-lighter)))
+
+(defun which-key--echo (text)
+  "Echo TEXT to minibuffer without logging."
+  (let (message-log-max)
+    (message "%s" text)))
+
+(defun which-key--next-page-hint (prefix-keys)
+  "Return string for next page hint."
+  (let* ((paging-key (concat prefix-keys " " which-key-paging-key))
+         (paging-key-bound (eq 'which-key-C-h-dispatch
+                               (key-binding (kbd paging-key))))
+         (key (key-description (vector help-char)))
+         (key (if paging-key-bound
+                  (concat key " or " which-key-paging-key)
+                key)))
+    (when (and which-key-use-C-h-commands
+               (not (equal (vector help-char)
+                           (vconcat (kbd prefix-keys)))))
+      (which-key--propertize (format "[%s paging/help]" key)
+                             'face 'which-key-note-face))))
+
+(defun which-key--full-prefix (prefix-keys &optional -prefix-arg dont-prop-keys)
+  "Return a description of the full key sequence up to now.
+Include prefix arguments."
+  (let* ((left (eq which-key-show-prefix 'left))
+         (prefix-arg (if -prefix-arg -prefix-arg prefix-arg))
+         (str (concat
+               (universal-argument--description)
+               (when prefix-arg " ")
+               prefix-keys))
+         (dash (if (and (not (string= prefix-keys ""))
+                        (null left)) "-" "")))
+    (if (or (eq which-key-show-prefix 'echo) dont-prop-keys)
+        (concat str dash)
+      (concat (which-key--propertize-key str)
+              (which-key--propertize dash 'face 'which-key-key-face)))))
+
+(defun which-key--get-popup-map ()
+  "Generate transient map for use in the top level binding display."
+  (unless which-key--automatic-display
+    (let ((map (make-sparse-keymap)))
+      (define-key map (kbd which-key-paging-key) #'which-key-C-h-dispatch)
+      (when which-key-use-C-h-commands
+        ;; Show next page even when C-h is pressed
+        (define-key map (vector help-char) #'which-key-C-h-dispatch))
+      map)))
+
+(defun which-key--process-page (pages-obj)
+  "Add information to the basic list of key bindings.
+Include, if applicable, the current prefix, the name of the current
+prefix, and a page count."
+  (let* ((page (car (which-key--pages-pages pages-obj)))
+         (height (which-key--pages-height pages-obj))
+         (n-pages (which-key--pages-num-pages pages-obj))
+         (page-n (car (which-key--pages-page-nums pages-obj)))
+         (prefix-desc (key-description (which-key--pages-prefix pages-obj)))
+         (prefix-title (which-key--pages-prefix-title pages-obj))
+         (full-prefix (which-key--full-prefix prefix-desc))
+         (nxt-pg-hint (which-key--next-page-hint prefix-desc))
+         ;; not used in left case
+         (status-line
+          (concat (which-key--propertize prefix-title 'face 'which-key-note-face)
+                  (when (< 1 n-pages)
+                    (which-key--propertize (format " (%s of %s)" page-n n-pages)
+                                           'face 'which-key-note-face)))))
+    (pcase which-key-show-prefix
+      (`left
+       (let* ((page-cnt (which-key--propertize (format "%s/%s" page-n n-pages)
+                                               'face 'which-key-separator-face))
+              (first-col-width (+ 2 (max (which-key--string-width full-prefix)
+                                         (which-key--string-width page-cnt))))
+              (prefix (format (concat "%-" (int-to-string first-col-width) "s")
+                              full-prefix))
+              (page-cnt (if (> n-pages 1)
+                            (format
+                             (concat "%-" (int-to-string first-col-width) "s")
+                             page-cnt)
+                          (make-string first-col-width ?\s)))
+              lines first-line new-end)
+         (if (= 1 height)
+             (cons (concat prefix page) nil)
+           (setq lines (split-string page "\n")
+                 first-line (concat prefix (car lines) "\n" page-cnt)
+                 new-end (concat "\n" (make-string first-col-width ?\s)))
+           (cons
+            (concat first-line (mapconcat #'identity (cdr lines) new-end))
+            nil))))
+      (`top
+       (cons
+        (concat (when (or (= 0 echo-keystrokes)
+                          (not (eq which-key-side-window-location 'bottom)))
+                  (concat full-prefix " "))
+                status-line " " nxt-pg-hint "\n" page)
+        nil))
+      (`bottom
+       (cons
+        (concat page "\n"
+                (when (or (= 0 echo-keystrokes)
+                          (not (eq which-key-side-window-location 'bottom)))
+                  (concat full-prefix " "))
+                status-line " " nxt-pg-hint)
+        nil))
+      (`echo
+       (cons page
+             (lambda ()
+               (which-key--echo
+                (concat full-prefix (when prefix-desc " ")
+                        status-line (when status-line " ")
+                        nxt-pg-hint)))))
+      (`mode-line
+       (cons page
+             (lambda ()
+               (with-current-buffer which-key--buffer
+                 (setq-local mode-line-format
+                             (concat " " full-prefix
+                                     " " status-line
+                                     " " nxt-pg-hint))))))
+      (_ (cons page nil)))))
+
+(defun which-key--show-page (&optional n)
+  "Show current page.
+N changes the current page to the Nth page relative to the
+current one."
+  (which-key--init-buffer) ;; in case it was killed
+  (let ((prefix-keys (which-key--current-key-string))
+        golden-ratio-mode)
+    (if (null which-key--pages-obj)
+        (message "%s- which-key can't show keys: There is not \
+enough space based on your settings and frame size." prefix-keys)
+      (when n
+        (setq which-key--pages-obj
+              (which-key--pages-set-current-page which-key--pages-obj n)))
+      (let ((page-echo (which-key--process-page which-key--pages-obj))
+            (height (which-key--pages-height which-key--pages-obj))
+            (width (car (which-key--pages-widths which-key--pages-obj))))
+        (which-key--lighter-status)
+        (if (eq which-key-popup-type 'minibuffer)
+            (which-key--echo (car page-echo))
+          (with-current-buffer which-key--buffer
+            (erase-buffer)
+            (insert (car page-echo))
+            (goto-char (point-min)))
+          (when (cdr page-echo) (funcall (cdr page-echo)))
+          (which-key--show-popup (cons height width)))))
+    ;; used for paging at top-level
+    (if (fboundp 'set-transient-map)
+        (set-transient-map (which-key--get-popup-map))
+      (with-no-warnings
+        (set-temporary-overlay-map (which-key--get-popup-map))))))
+
+;;; Paging functions
+
+;;;###autoload
+(defun which-key-reload-key-sequence (&optional key-seq)
+  "Simulate entering the key sequence KEY-SEQ.
+KEY-SEQ should be a list of events as produced by
+`listify-key-sequence'.  If nil, KEY-SEQ defaults to
+`which-key--current-key-list'.  Any prefix arguments that were
+used are reapplied to the new key sequence."
+  (let* ((key-seq (or key-seq (which-key--current-key-list)))
+         (next-event (mapcar (lambda (ev) (cons t ev)) key-seq)))
+    (setq prefix-arg current-prefix-arg
+          unread-command-events next-event)))
+
+(defun which-key-turn-page (delta)
+  "Show the next page of keys."
+  (which-key-reload-key-sequence)
+  (if which-key--last-try-2-loc
+      (let ((which-key-side-window-location which-key--last-try-2-loc)
+            (which-key--multiple-locations t))
+        (which-key--show-page delta))
+    (which-key--show-page delta))
+  (which-key--start-paging-timer))
+
+;;;###autoload
+(defun which-key-show-standard-help (&optional _)
+  "Call the command in `which-key--prefix-help-cmd-backup'.
+Usually this is `describe-prefix-bindings'."
+  (interactive)
+  (let ((which-key-inhibit t)
+        (popup-showing (which-key--popup-showing-p)))
+    (which-key--hide-popup-ignore-command)
+    (cond ((and (eq which-key--prefix-help-cmd-backup
+                    'describe-prefix-bindings)
+                ;; If the popup is not showing, we call
+                ;; `describe-prefix-bindings' directly.
+                popup-showing)
+           ;; This is essentially what `describe-prefix-bindings' does. We can't
+           ;; use this function directly, because the prefix will not be correct
+           ;; when we enter using `which-key-C-h-dispatch'.
+           (describe-bindings (kbd (which-key--current-key-string))))
+          ((functionp which-key--prefix-help-cmd-backup)
+           (funcall which-key--prefix-help-cmd-backup)))))
+
+;;;###autoload
+(defun which-key-show-next-page-no-cycle ()
+  "Show next page of keys or `which-key-show-standard-help'."
+  (interactive)
+  (let ((which-key-inhibit t))
+    (if (which-key--on-last-page)
+        (which-key-show-standard-help)
+      (which-key-turn-page 1))))
+
+;;;###autoload
+(defun which-key-show-previous-page-no-cycle ()
+  "Show previous page of keys if one exists."
+  (interactive)
+  (let ((which-key-inhibit t))
+    (unless (which-key--on-first-page)
+      (which-key-turn-page -1))))
+
+;;;###autoload
+(defun which-key-show-next-page-cycle (&optional _)
+  "Show the next page of keys, cycling from end to beginning."
+  (interactive)
+  (let ((which-key-inhibit t))
+    (which-key-turn-page 1)))
+
+;;;###autoload
+(defun which-key-show-previous-page-cycle (&optional _)
+  "Show the previous page of keys, cycling from beginning to end."
+  (interactive)
+  (let ((which-key-inhibit t))
+    (which-key-turn-page -1)))
+
+;;;###autoload
+(defun which-key-show-top-level (&optional _)
+  "Show top-level bindings."
+  (interactive)
+  (which-key--create-buffer-and-show nil nil nil "Top-level bindings"))
+
+;;;###autoload
+(defun which-key-show-major-mode (&optional all)
+  "Show top-level bindings in the map of the current major mode.
+This function will also detect evil bindings made using
+`evil-define-key' in this map.  These bindings will depend on the
+current evil state."
+  (interactive "P")
+  (let ((map-sym (intern (format "%s-map" major-mode))))
+    (if (and (boundp map-sym) (keymapp (symbol-value map-sym)))
+        (which-key--show-keymap
+         "Major-mode bindings"
+         (symbol-value map-sym)
+         (apply-partially #'which-key--map-binding-p (symbol-value map-sym))
+         all)
+      (message "which-key: No map named %s" map-sym))))
+
+;;;###autoload
+(defun which-key-show-full-major-mode ()
+  "Show all bindings in the map of the current major mode.
+This function will also detect evil bindings made using
+`evil-define-key' in this map.  These bindings will depend on the
+current evil state."
+  (interactive)
+  (which-key-show-major-mode t))
+
+;;;###autoload
+(defun which-key-dump-bindings (prefix buffer-name)
+  "Dump bindings from PREFIX into buffer named BUFFER-NAME.
+PREFIX should be a string suitable for `kbd'."
+  (interactive "sPrefix: \nB")
+  (let* ((buffer (get-buffer-create buffer-name))
+         (keys (which-key--get-bindings (kbd prefix))))
+    (with-current-buffer buffer
+      (point-max)
+      (save-excursion
+        (dolist (key keys)
+          (insert (apply #'format "%s%s%s\n" key)))))
+    (switch-to-buffer-other-window buffer)))
+
+;;;###autoload
+(defun which-key-undo-key (&optional _)
+  "Undo last keypress and force which-key update."
+  (interactive)
+  (let* ((key-lst (butlast (which-key--current-key-list)))
+         (which-key-inhibit t))
+    (cond (which-key--prior-show-keymap-args
+           (if (keymapp (cdr (car-safe which-key--prior-show-keymap-args)))
+               (let ((args (pop which-key--prior-show-keymap-args)))
+                 (which-key--show-keymap (car args) (cdr args)))
+             (which-key--hide-popup)))
+          (key-lst
+           (which-key-reload-key-sequence key-lst)
+           (which-key--create-buffer-and-show (apply #'vector key-lst)))
+          (t (setq which-key--automatic-display nil)
+             (which-key-show-top-level)))))
+(defalias 'which-key-undo #'which-key-undo-key)
+
+(defun which-key-abort (&optional _)
+  "Abort key sequence."
+  (interactive)
+  (let ((which-key-inhibit t))
+    (which-key--hide-popup-ignore-command)
+    (keyboard-quit)))
+
+(defun which-key-digit-argument (key)
+  "Version of `digit-argument' for use in `which-key-C-h-map'."
+  (interactive)
+  (let ((last-command-event (string-to-char key)))
+    (digit-argument key))
+  (let ((current-prefix-arg prefix-arg))
+    (which-key-reload-key-sequence)))
+
+(defun which-key-toggle-docstrings (&optional _)
+  "Toggle the display of docstrings."
+  (interactive)
+  (unless (eq which-key-show-docstrings 'docstring-only)
+    (setq which-key-show-docstrings (null which-key-show-docstrings)))
+  (which-key-reload-key-sequence)
+  (which-key--create-buffer-and-show (which-key--current-prefix)))
+
+;;;###autoload
+(defun which-key-C-h-dispatch ()
+  "Dispatch \\`C-h' commands by looking up key in `which-key-C-h-map'.
+This command is always accessible (from any prefix) if
+`which-key-use-C-h-commands' is non nil."
+  (interactive)
+  (cond ((and (not (which-key--popup-showing-p))
+              which-key-show-early-on-C-h)
+         (let ((current-prefix
+                (butlast
+                 (listify-key-sequence
+                  (funcall which-key-this-command-keys-function)))))
+           (which-key-reload-key-sequence current-prefix)
+           (if which-key-idle-secondary-delay
+               (which-key--start-timer which-key-idle-secondary-delay t)
+             (which-key--start-timer 0.05 t))))
+        ((not (which-key--popup-showing-p))
+         (which-key-show-standard-help))
+        (t
+         (if (not (which-key--popup-showing-p))
+             (which-key-show-standard-help)
+           (let* ((prefix-keys (which-key--current-key-string))
+                  (full-prefix (which-key--full-prefix prefix-keys current-prefix-arg t))
+                  (prompt (concat (when (string-equal prefix-keys "")
+                                    (which-key--propertize
+                                     (concat " "
+                                             (which-key--pages-prefix-title
+                                              which-key--pages-obj))
+                                     'face 'which-key-note-face))
+                                  full-prefix
+                                  (which-key--propertize
+                                   (substitute-command-keys
+                                    which-key-C-h-map-prompt)
+                                   'face 'which-key-note-face)))
+                  (key (let ((key (read-key prompt)))
+                         (if (numberp key)
+                             (string key)
+                           (vector key))))
+                  (cmd (lookup-key which-key-C-h-map key))
+                  (which-key-inhibit t))
+             (if cmd (funcall cmd key) (which-key-turn-page 0)))))))
+
+;;; Update
+
+(defun which-key--any-match-p (regexps string)
+  "Non-nil if any of REGEXPS match STRING."
+  (catch 'match
+    (dolist (regexp regexps)
+      (when (string-match-p regexp string)
+        (throw 'match t)))))
+
+(defun which-key--try-2-side-windows
+    (bindings prefix-keys prefix-title loc1 loc2 &rest _ignore)
+  "Try to show BINDINGS (PAGE-N) in LOC1 first.
+Only if no bindings fit fallback to LOC2."
+  (let (pages1)
+    (let ((which-key-side-window-location loc1)
+          (which-key--multiple-locations t))
+      (setq pages1 (which-key--create-pages
+                    bindings prefix-keys prefix-title)))
+    (if pages1
+        (progn
+          (setq which-key--pages-obj pages1)
+          (let ((which-key-side-window-location loc1)
+                (which-key--multiple-locations t))
+            (which-key--show-page))
+          loc1)
+      (let ((which-key-side-window-location loc2)
+            (which-key--multiple-locations t))
+        (setq which-key--pages-obj
+              (which-key--create-pages bindings prefix-keys prefix-title))
+        (which-key--show-page)
+        loc2))))
+
+(defun which-key--read-keymap ()
+  "Read keymap symbol from minibuffer."
+  (intern
+   (completing-read "Keymap: " obarray
+                    (lambda (m)
+                      (and (boundp m)
+                           (keymapp (symbol-value m))
+                           (not (equal (symbol-value m)
+                                       (make-sparse-keymap)))))
+                    t
+                    (let ((sym (symbol-at-point)))
+                      (and (boundp sym)
+                           (keymapp (symbol-value sym))
+                           (symbol-name sym)))
+                    'which-key-keymap-history)))
+
+;;;###autoload
+(defun which-key-show-keymap (keymap &optional no-paging)
+  "Show the top-level bindings in KEYMAP using which-key.
+KEYMAP is selected interactively from all available keymaps.
+
+If NO-PAGING is non-nil, which-key will not intercept subsequent
+keypresses for the paging functionality."
+  (interactive (list (which-key--read-keymap)))
+  (which-key--show-keymap (symbol-name keymap)
+                          (symbol-value keymap)
+                          nil nil no-paging))
+
+;;;###autoload
+(defun which-key-show-full-keymap (keymap)
+  "Show all bindings in KEYMAP using which-key.
+KEYMAP is selected interactively from all available keymaps."
+  (interactive (list (which-key--read-keymap)))
+  (which-key--show-keymap (symbol-name keymap)
+                          (symbol-value keymap)
+                          nil t))
+
+;;;###autoload
+(defun which-key-show-minor-mode-keymap (&optional all)
+  "Show the top-level bindings in KEYMAP using which-key.
+KEYMAP is selected interactively by mode in
+`minor-mode-map-alist'."
+  (interactive)
+  (let ((mode-sym
+         (intern
+          (completing-read
+           "Minor Mode: "
+           (mapcar #'car
+                   (cl-remove-if-not
+                    (lambda (entry)
+                      (and (symbol-value (car entry))
+                           (not (equal (cdr entry) (make-sparse-keymap)))))
+                    minor-mode-map-alist))
+           nil t nil 'which-key-keymap-history))))
+    (which-key--show-keymap (symbol-name mode-sym)
+                            (cdr (assq mode-sym minor-mode-map-alist))
+                            all)))
+;;;###autoload
+(defun which-key-show-full-minor-mode-keymap ()
+  "Show all bindings in KEYMAP using which-key.
+KEYMAP is selected interactively by mode in
+`minor-mode-map-alist'."
+  (interactive)
+  (which-key-show-minor-mode-keymap t))
+
+(defun which-key--show-keymap
+    (keymap-name keymap &optional prior-args all no-paging filter)
+  (when prior-args (push prior-args which-key--prior-show-keymap-args))
+  (let ((bindings (which-key--get-bindings nil keymap filter all)))
+    (if (null bindings)
+        (message "which-key: No bindings found in %s" keymap-name)
+      (cond ((listp which-key-side-window-location)
+             (setq which-key--last-try-2-loc
+                   (apply #'which-key--try-2-side-windows
+                          bindings nil keymap-name
+                          which-key-side-window-location)))
+            (t (setq which-key--pages-obj
+                     (which-key--create-pages bindings nil keymap-name))
+               (which-key--show-page)))
+      (unless no-paging
+        (let* ((key (read-key))
+               (key-desc (key-description (list key)))
+               (next-def (lookup-key keymap (vector key))))
+          (cond ((and which-key-use-C-h-commands
+                      (numberp key) (= key help-char))
+                 (which-key-C-h-dispatch))
+                ((keymapp next-def)
+                 (which-key--hide-popup-ignore-command)
+                 (which-key--show-keymap
+                  (concat keymap-name " " key-desc)
+                  next-def
+                  (cons keymap-name keymap)))
+                (t (which-key--hide-popup))))))))
+
+(defun which-key--evil-operator-filter (binding)
+  (let ((def (intern (cdr binding))))
+    (and (functionp def)
+         (not (evil-get-command-property def :suppress-operator)))))
+
+(defun which-key--show-evil-operator-keymap ()
+  (if which-key--inhibit-next-operator-popup
+      (setq which-key--inhibit-next-operator-popup nil)
+    (let ((keymap
+           (make-composed-keymap (list evil-operator-shortcut-map
+                                       evil-operator-state-map
+                                       evil-motion-state-map))))
+      (when (keymapp keymap)
+        (let ((formatted-keys
+               (which-key--get-bindings
+                nil keymap #'which-key--evil-operator-filter)))
+          (cond ((null formatted-keys)
+                 (message "which-key: Keymap empty"))
+                ((listp which-key-side-window-location)
+                 (setq which-key--last-try-2-loc
+                       (apply #'which-key--try-2-side-windows
+                              formatted-keys nil "evil operator/motion keys"
+                              which-key-side-window-location)))
+                (t (setq which-key--pages-obj
+                         (which-key--create-pages
+                          formatted-keys
+                          nil "evil operator/motion keys"))
+                   (which-key--show-page)))))
+      (let ((key (read-key)))
+        (when (memq key '(?f ?F ?t ?T ?`))
+          ;; these keys trigger commands that read the next char manually
+          (setq which-key--inhibit-next-operator-popup t))
+        (cond ((and which-key-use-C-h-commands (numberp key) (= key help-char))
+               (which-key-C-h-dispatch))
+              ((and (numberp key) (= key ?\C-\[))
+               (which-key--hide-popup)
+               (keyboard-quit))
+              (t
+               (which-key--hide-popup)
+               (setq unread-command-events (vector key))))))))
+
+(defun which-key--create-buffer-and-show
+    (&optional prefix-keys from-keymap filter prefix-title)
+  "Fill `which-key--buffer' with key descriptions and reformat.
+Finally, show the buffer."
+  (let ((start-time (current-time))
+        (formatted-keys (which-key--get-bindings
+                         prefix-keys from-keymap filter))
+        (prefix-desc (key-description prefix-keys)))
+    (cond ((null formatted-keys)
+           (message "%s-  which-key: There are no keys to show" prefix-desc))
+          ((listp which-key-side-window-location)
+           (setq which-key--last-try-2-loc
+                 (apply #'which-key--try-2-side-windows
+                        formatted-keys prefix-keys prefix-title
+                        which-key-side-window-location)))
+          (t (setq which-key--pages-obj
+                   (which-key--create-pages
+                    formatted-keys prefix-keys prefix-title))
+             (which-key--show-page)))
+    (which-key--debug-message
+     "On prefix \"%s\" which-key took %.0f ms." prefix-desc
+     (* 1000 (float-time (time-since start-time))))))
+
+(defun which-key--update ()
+  "Function run by timer to possibly trigger `which-key--create-buffer-and-show'."
+  (let ((prefix-keys (funcall which-key-this-command-keys-function))
+        delay-time)
+    (cond ((and (> (length prefix-keys) 0)
+                (or (keymapp (key-binding prefix-keys))
+                    ;; Some keymaps are stored here like iso-transl-ctl-x-8-map
+                    (keymapp (which-key--safe-lookup-key
+                              key-translation-map prefix-keys))
+                    ;; just in case someone uses one of these
+                    (keymapp (which-key--safe-lookup-key
+                              function-key-map prefix-keys)))
+                (not which-key-inhibit)
+                (or (null which-key-allow-regexps)
+                    (which-key--any-match-p
+                     which-key-allow-regexps (key-description prefix-keys)))
+                (or (null which-key-inhibit-regexps)
+                    (not
+                     (which-key--any-match-p
+                      which-key-inhibit-regexps (key-description prefix-keys))))
+                ;; Do not display the popup if a command is currently being
+                ;; executed
+                (or (run-hook-with-args-until-success
+                     'which-key-inhibit-display-hook)
+                    (null this-command))
+                (let ((max-dim (which-key--popup-max-dimensions)))
+                  (> (min (car-safe max-dim) (cdr-safe max-dim)) 0)))
+           (when (and (not (equal prefix-keys (which-key--current-prefix)))
+                      (or (null which-key-delay-functions)
+                          (null (setq delay-time
+                                      (run-hook-with-args-until-success
+                                       'which-key-delay-functions
+                                       (key-description prefix-keys)
+                                       (length prefix-keys))))
+                          (sit-for delay-time)))
+             (setq which-key--automatic-display t)
+             (which-key--create-buffer-and-show prefix-keys)
+             (when (and which-key-idle-secondary-delay
+                        (not which-key--secondary-timer-active))
+               (which-key--start-timer which-key-idle-secondary-delay t))))
+          ((and which-key-show-transient-maps
+                ;; Assuming that if this is not true we're in
+                ;; `which-key-show-top-level', which would then be overwritten.
+                (> (length prefix-keys) 0)
+                (keymapp overriding-terminal-local-map)
+                ;; basic test for it being a hydra
+                (not (eq (lookup-key overriding-terminal-local-map "\C-u")
+                         'hydra--universal-argument)))
+           (which-key--create-buffer-and-show
+            nil overriding-terminal-local-map))
+          ((and which-key-show-operator-state-maps
+                (bound-and-true-p evil-state)
+                (eq evil-state 'operator)
+                (not (which-key--popup-showing-p)))
+           (which-key--show-evil-operator-keymap))
+          (which-key--automatic-display
+           (which-key--hide-popup)))))
+
+;;; Timers
+
+(defun which-key--start-timer (&optional delay secondary)
+  "Activate idle timer to trigger `which-key--update'."
+  (which-key--stop-timer)
+  (setq which-key--secondary-timer-active secondary)
+  (setq which-key--timer
+        (run-with-idle-timer (or delay which-key-idle-delay)
+                             t #'which-key--update)))
+
+(defun which-key--stop-timer ()
+  "Deactivate idle timer for `which-key--update'."
+  (when which-key--timer (cancel-timer which-key--timer)))
+
+(defun which-key--start-paging-timer ()
+  "Activate timer to restart which-key after paging."
+  (when which-key--paging-timer (cancel-timer which-key--paging-timer))
+  (which-key--stop-timer)
+  (setq which-key--paging-timer
+        (run-with-idle-timer
+         0.2 t (lambda ()
+                 (when (or (not (member real-last-command
+                                        which-key--paging-functions))
+                           (and (< 0 (length (this-single-command-keys)))
+                                (not (equal (which-key--current-prefix)
+                                            (funcall which-key-this-command-keys-function)))))
+                   (cancel-timer which-key--paging-timer)
+                   (if which-key-idle-secondary-delay
+                       ;; we haven't executed a command yet so the secandary
+                       ;; timer is more relevant here
+                       (which-key--start-timer which-key-idle-secondary-delay t)
+                     (which-key--start-timer)))))))
+
+(provide 'which-key)
+;;; which-key.el ends here
diff --git a/test/lisp/which-key-tests.el b/test/lisp/which-key-tests.el
new file mode 100644 (file)
index 0000000..1f2b196
--- /dev/null
@@ -0,0 +1,267 @@
+;;; which-key-tests.el --- Tests for which-key.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017-2021  Free Software Foundation, Inc.
+
+;; Author: Justin Burkett <justin@burkett.cc>
+;; Maintainer: Justin Burkett <justin@burkett.cc>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for which-key.el
+
+;;; Code:
+
+(require 'which-key)
+(require 'ert)
+
+(ert-deftest which-key-test--keymap-based-bindings ()
+  (let ((map (make-sparse-keymap))
+        (prefix-map (make-sparse-keymap)))
+    (define-key prefix-map "x" #'ignore)
+    (define-key map "\C-a" 'complete)
+    (define-key map "\C-b" prefix-map)
+    (which-key-add-keymap-based-replacements map
+      "C-a" '("mycomplete" . complete)
+      "C-b" "mymap"
+      "C-c" "mymap2")
+    (define-key map "\C-ca" 'foo)
+    (should (equal
+             (which-key--get-keymap-bindings map)
+             '(("C-a" . "mycomplete")
+               ("C-b" . "group:mymap")
+               ("C-c" . "group:mymap2"))))))
+
+(ert-deftest which-key-test--named-prefix-keymap ()
+  (define-prefix-command 'which-key-test--named-map)
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\C-a" 'which-key-test--named-map)
+    (should (equal
+             (which-key--get-keymap-bindings map)
+             '(("C-a" . "which-key-test--named-map"))))))
+
+(ert-deftest which-key-test--prefix-declaration ()
+  "Test `which-key-declare-prefixes' and
+`which-key-declare-prefixes-for-mode'. See Bug #109."
+  (let* ((major-mode 'test-mode)
+         which-key-replacement-alist)
+    (which-key-add-key-based-replacements
+      "SPC C-c" '("complete" . "complete title")
+      "SPC C-k" "cancel")
+    (which-key-add-major-mode-key-based-replacements 'test-mode
+      "C-c C-c" '("complete" . "complete title")
+      "C-c C-k" "cancel")
+    (should (equal
+             (which-key--maybe-replace '("SPC C-k" . ""))
+             '("SPC C-k" . "cancel")))
+    (should (equal
+             (which-key--maybe-replace '("C-c C-c" . ""))
+             '("C-c C-c" . "complete")))))
+
+(ert-deftest which-key-test--maybe-replace ()
+  "Test `which-key--maybe-replace'. See #154"
+  (let ((which-key-replacement-alist
+         '((("C-c [a-d]" . nil) . ("C-c a" . "c-c a"))
+           (("C-c .+" . nil) . ("C-c *" . "c-c *"))))
+        (test-mode-1 't)
+        (test-mode-2 'nil)
+        which-key-allow-multiple-replacements)
+    (which-key-add-key-based-replacements
+      "C-c ." "test ."
+      "SPC ." "SPC ."
+      "C-c \\" "regexp quoting"
+      "C-c [" "bad regexp"
+      "SPC t1" (lambda (kb)
+                 (cons (car kb)
+                       (if test-mode-1
+                           "[x] test mode"
+                         "[ ] test mode")))
+      "SPC t2" (lambda (kb)
+                 (cons (car kb)
+                       (if test-mode-2
+                           "[x] test mode"
+                         "[ ] test mode"))))
+    (should (equal
+             (which-key--maybe-replace '("C-c g" . "test"))
+             '("C-c *" . "c-c *")))
+    (should (equal
+             (which-key--maybe-replace '("C-c b" . "test"))
+             '("C-c a" . "c-c a")))
+    (should (equal
+             (which-key--maybe-replace '("C-c ." . "not test ."))
+             '("C-c ." . "test .")))
+    (should (not
+             (equal
+              (which-key--maybe-replace '("C-c +" . "not test ."))
+              '("C-c ." . "test ."))))
+    (should (equal
+             (which-key--maybe-replace '("C-c [" . "orig bad regexp"))
+             '("C-c [" . "bad regexp")))
+    (should (equal
+             (which-key--maybe-replace '("C-c \\" . "pre quoting"))
+             '("C-c \\" . "regexp quoting")))
+    ;; see #155
+    (should (equal
+             (which-key--maybe-replace '("SPC . ." . "don't replace"))
+             '("SPC . ." . "don't replace")))
+    (should (equal
+             (which-key--maybe-replace '("SPC t 1" . "test mode"))
+             '("SPC t 1" . "[x] test mode")))
+    (should (equal
+             (which-key--maybe-replace '("SPC t 2" . "test mode"))
+             '("SPC t 2" . "[ ] test mode")))))
+
+(ert-deftest which-key-test--maybe-replace-multiple ()
+  "Test `which-key-allow-multiple-replacements'. See #156."
+  (let ((which-key-replacement-alist
+         '(((nil . "helm") . (nil . "HLM"))
+           ((nil . "projectile") . (nil . "PRJTL"))))
+        (which-key-allow-multiple-replacements t))
+    (should (equal
+             (which-key--maybe-replace '("C-c C-c" . "helm-x"))
+             '("C-c C-c" . "HLM-x")))
+    (should (equal
+             (which-key--maybe-replace '("C-c C-c" . "projectile-x"))
+             '("C-c C-c" . "PRJTL-x")))
+    (should (equal
+             (which-key--maybe-replace '("C-c C-c" . "helm-projectile-x"))
+             '("C-c C-c" . "HLM-PRJTL-x")))))
+
+(ert-deftest which-key-test--key-extraction ()
+  "Test `which-key--extract-key'. See #161."
+  (should (equal (which-key--extract-key "SPC a") "a"))
+  (should (equal (which-key--extract-key "C-x a") "a"))
+  (should (equal (which-key--extract-key "<left> b a") "a"))
+  (should (equal (which-key--extract-key "<left> a .. c") "a .. c"))
+  (should (equal (which-key--extract-key "M-a a .. c") "a .. c")))
+
+(ert-deftest which-key-test--get-keymap-bindings ()
+  (skip-unless (require 'evil nil t))
+  (defvar evil-local-mode)
+  (defvar evil-state)
+  (declare-function evil-define-key* "ext:evil")
+  (let ((map (make-sparse-keymap))
+        (evil-local-mode t)
+        (evil-state 'normal)
+        which-key-replacement-alist)
+    (define-key map [which-key-a] '(which-key "blah"))
+    (define-key map "b" #'ignore)
+    (define-key map "c" "c")
+    (define-key map "dd" "dd")
+    (define-key map "eee" "eee")
+    (define-key map "f" [123 45 6])
+    (define-key map (kbd "M-g g") "M-gg")
+    (evil-define-key* 'normal map (kbd "C-h") "C-h-normal")
+    (evil-define-key* 'insert map (kbd "C-h") "C-h-insert")
+    (should (equal
+             (sort (which-key--get-keymap-bindings map)
+                   (lambda (a b) (string-lessp (car a) (car b))))
+             '(("M-g" . "prefix")
+               ("c" . "c")
+               ("d" . "prefix")
+               ("e" . "prefix")
+               ("f" . "{ - C-f"))))
+    (should (equal
+             (sort (which-key--get-keymap-bindings map nil nil nil nil t)
+                   (lambda (a b) (string-lessp (car a) (car b))))
+             '(("C-h" . "C-h-normal")
+               ("M-g" . "prefix")
+               ("c" . "c")
+               ("d" . "prefix")
+               ("e" . "prefix")
+               ("f" . "{ - C-f"))))
+    (should (equal
+             (sort (which-key--get-keymap-bindings map nil nil nil t)
+                   (lambda (a b) (string-lessp (car a) (car b))))
+             '(("M-g g" . "M-gg")
+               ("c" . "c")
+               ("d d" . "dd")
+               ("e e e" . "eee")
+               ("f" . "{ - C-f"))))))
+
+(ert-deftest which-key-test--nil-replacement ()
+  (let ((which-key-replacement-alist
+         '(((nil . "winum-select-window-[1-9]") . t))))
+    (should (equal
+             (which-key--maybe-replace '("C-c C-c" . "winum-select-window-1"))
+             '()))))
+
+(ert-deftest which-key-test--key-sorting ()
+  (let ((keys '(("a" . "z")
+                ("A" . "Z")
+                ("b" . "y")
+                ("B" . "Y")
+                ("p" . "prefix")
+                ("SPC" . "x")
+                ("C-a" . "w"))))
+    (let ((which-key-sort-uppercase-first t))
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys) #'which-key-key-order))
+        '("SPC" "A" "B" "a" "b" "p" "C-a"))))
+    (let (which-key-sort-uppercase-first)
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys) #'which-key-key-order))
+        '("SPC" "a" "b" "p" "A" "B" "C-a"))))
+    (let ((which-key-sort-uppercase-first t))
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys) #'which-key-key-order-alpha))
+        '("SPC" "A" "a" "B" "b" "p" "C-a"))))
+    (let (which-key-sort-uppercase-first)
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys) #'which-key-key-order-alpha))
+        '("SPC" "a" "A" "b" "B" "p" "C-a"))))
+    (let ((which-key-sort-uppercase-first t))
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys)
+                            #'which-key-prefix-then-key-order))
+        '("SPC" "A" "B" "a" "b" "C-a" "p"))))
+    (let (which-key-sort-uppercase-first)
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys)
+                            #'which-key-prefix-then-key-order))
+        '("SPC" "a" "b" "A" "B" "C-a" "p"))))
+    (let ((which-key-sort-uppercase-first t))
+      (should
+       (equal
+        (mapcar 'car (sort (copy-sequence keys)
+                           #'which-key-prefix-then-key-order-reverse))
+        '("p" "SPC" "A" "B" "a" "b" "C-a"))))
+    (let (which-key-sort-uppercase-first)
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys)
+                            #'which-key-prefix-then-key-order-reverse))
+        '("p" "SPC" "a" "b" "A" "B" "C-a"))))
+    (let ((which-key-sort-uppercase-first t))
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys)
+                            #'which-key-description-order))
+        '("p" "C-a" "SPC" "b" "B" "a" "A"))))
+    (let (which-key-sort-uppercase-first)
+      (should
+       (equal
+        (mapcar #'car (sort (copy-sequence keys)
+                            #'which-key-description-order))
+        '("p" "C-a" "SPC" "b" "B" "a" "A"))))))
+
+(provide 'which-key-tests)
+;;; which-key-tests.el ends here