This is keymap-popup.info, produced by makeinfo version 7.1.1 from
keymap-popup.texi.

INFO-DIR-SECTION Emacs misc features
START-INFO-DIR-ENTRY
* Keymap Popup: (keymap-popup). Define a keymap once, dispatch keys and show a popup menu.
END-INFO-DIR-ENTRY


File: keymap-popup.info,  Node: Top,  Next: Introduction,  Up: (dir)

Keymap Popup User Manual
************************

* Menu:

* Introduction::
* Installation::
* Quick start::
* Defining keymaps::
* Showing the popup::
* Special bindings::
* Prompting and re-entry::
* Annotating existing keymaps::
* Customization::
* When to use this vs transient::
* Contributing::

-- The Detailed Node Listing --

Installation

* From source::
* With use-package::

Defining keymaps

* Macro syntax::
* Groups and rows::
* Dynamic descriptions::
* Sharing bindings with parent::
* Adding and removing entries at runtime::

Showing the popup

* Display backends::
* Persistent popups::
* Dismissing programmatically::

Special bindings

* Sub-menus with keymap::
* Toggles with switch::
* Stay-open commands with stay-open::
* Disabling entries with inapt-if::
* Hiding entries with if::
* Prefix-arg variants with c-u::

Annotating existing keymaps

* Attaching descriptions built from data::

Customization

* User options::
* Faces::



File: keymap-popup.info,  Node: Introduction,  Next: Installation,  Prev: Top,  Up: Top

1 Introduction
**************

‘keymap-popup’ defines a normal Emacs keymap with embedded descriptions,
and renders it as a popup menu on demand.  One definition, two uses:
direct key dispatch and an interactive menu.

   Requires Emacs 29.1 or later (for ‘defvar-keymap’).  No external
dependencies.


File: keymap-popup.info,  Node: Installation,  Next: Quick start,  Prev: Introduction,  Up: Top

2 Installation
**************

* Menu:

* From source::
* With use-package::


File: keymap-popup.info,  Node: From source,  Next: With use-package,  Up: Installation

2.1 From source
===============

     (add-to-list 'load-path "/path/to/keymap-popup")
     (require 'keymap-popup)


File: keymap-popup.info,  Node: With use-package,  Prev: From source,  Up: Installation

2.2 With ‘use-package’
======================

     (use-package keymap-popup
       :ensure t)


File: keymap-popup.info,  Node: Quick start,  Next: Defining keymaps,  Prev: Installation,  Up: Top

3 Quick start
*************

     (keymap-popup-define my-commands-map
       "My commands."
       :group "Edit"
       "c" ("Comment" comment-dwim)
       "r" ("Rename" rename-file)
       :group "View"
       "g" ("Refresh" revert-buffer)
       "b" ("Bury buffer" bury-buffer))

     (keymap-set some-mode-map "C-c m" my-commands-map)

   The macro auto-binds ‘h’ inside the keymap, so ‘C-c m h’ opens the
popup.  ‘q’ or ‘C-g’ dismisses it.


File: keymap-popup.info,  Node: Defining keymaps,  Next: Showing the popup,  Prev: Quick start,  Up: Top

4 Defining keymaps
******************

* Menu:

* Macro syntax::
* Groups and rows::
* Dynamic descriptions::
* Sharing bindings with parent::
* Adding and removing entries at runtime::


File: keymap-popup.info,  Node: Macro syntax,  Next: Groups and rows,  Up: Defining keymaps

4.1 Macro syntax
================

     (keymap-popup-define MAP-NAME
       DOCSTRING
       :popup-key KEY  :exit-key KEY
       :parent KEYMAP  :description STRING-OR-FN
       :persistent BOOL
       BINDING...)

   Only ‘MAP-NAME’ and the bindings are required; every keyword above is
optional and may appear in any order.  ‘keymap-popup-annotate’ takes the
same keywords but does _not_ apply defaults for ‘:popup-key’ or
‘:exit-key’ - it never binds keys unless asked.

   A binding takes one of three forms:

     KEY (DESC COMMAND     [PROP VALUE...])    ; ordinary command
     KEY (DESC :switch VAR [PROP VALUE...])    ; toggle
     KEY (DESC :keymap MAP [PROP VALUE...])    ; sub-menu

   • ‘KEY’ - ‘key-valid-p’ string.
   • ‘DESC’ - string or zero-arg function (called at render time in the
     user's buffer, so it sees buffer-local state).
   • ‘COMMAND’ - command symbol or a ‘(lambda () (interactive) ...)’.
   • ‘PROP’ - one of ‘:if’, ‘:inapt-if’, ‘:stay-open’, ‘:c-u’ (see *note
     Special bindings::).


File: keymap-popup.info,  Node: Groups and rows,  Next: Dynamic descriptions,  Prev: Macro syntax,  Up: Defining keymaps

4.2 Groups and rows
===================

     (keymap-popup-define my-feed-map
       :group "Navigation"
       "n" ("Next" next-line :stay-open t)
       "p" ("Previous" previous-line :stay-open t)
       :group "Tags"
       "r" ("Toggle read" my-feed-toggle-read :stay-open t)
       :row
       :group "Feeds"
       "A" ("Add feed" my-feed-add)
       "D" ("Remove feed" my-feed-remove))

   ‘:row’ between groups starts a fresh row of columns beneath the
first.  Groups may attach their own predicates: ‘:group ("Bulk" :if PRED
:inapt-if PRED)’.


File: keymap-popup.info,  Node: Dynamic descriptions,  Next: Sharing bindings with parent,  Prev: Groups and rows,  Up: Defining keymaps

4.3 Dynamic descriptions
========================

     (keymap-popup-define my-encryption-map
       :description (lambda ()
                      (format "Encryption: %s" my-chat-encryption))
       "o" ("OMEMO" my-chat-encryption-set-omemo)
       "g" ("OpenPGP" my-chat-encryption-set-openpgp)
       "p" ("Plaintext" my-chat-encryption-set-plaintext))

   The header is re-rendered after each command.


File: keymap-popup.info,  Node: Sharing bindings with parent,  Next: Adding and removing entries at runtime,  Prev: Dynamic descriptions,  Up: Defining keymaps

4.4 Sharing bindings with ‘:parent’
===================================

Same semantics as ‘defvar-keymap’'s ‘:parent’.  The popup walks the
parent chain, so parent bindings appear in the child's menu under their
original group.

   If a key is bound in both parent and child, the child wins for both
dispatch and display.  Shadowed parent entries are dropped from the
popup, and a parent group whose entries are all shadowed disappears
entirely.

     (keymap-popup-define my-items-mode-map
       :parent my-dashboard-common-map
       :group "Items"
       "RET" ("Open" my-items-open)
       "q" ("Back" my-items-back))


File: keymap-popup.info,  Node: Adding and removing entries at runtime,  Prev: Sharing bindings with parent,  Up: Defining keymaps

4.5 Adding and removing entries at runtime
==========================================

     (keymap-popup-add-entry my-commands-map
                             "l" "Log message" #'my-log-message
                             "Edit")

     (keymap-popup-remove-entry my-commands-map "l")

   Signatures:

   • ‘(keymap-popup-add-entry KEYMAP KEY DESC COMMAND &optional GROUP)’
   • ‘(keymap-popup-remove-entry KEYMAP KEY)’

   Both mutate the keymap and its descriptions in place.  Re-adding a
key replaces the prior entry (in whatever group it was in), matching
‘keymap-set’'s semantics.


File: keymap-popup.info,  Node: Showing the popup,  Next: Special bindings,  Prev: Defining keymaps,  Up: Top

5 Showing the popup
*******************

     ;;;###autoload
     (defun yeetube ()
       (interactive)
       (keymap-popup yeetube-mode-map))

   ‘C-g’ always dismisses, regardless of the configured ‘:exit-key’.

* Menu:

* Display backends::
* Persistent popups::
* Dismissing programmatically::


File: keymap-popup.info,  Node: Display backends,  Next: Persistent popups,  Up: Showing the popup

5.1 Display backends
====================

‘keymap-popup-backend’ selects ‘keymap-popup-backend-side-window’
(default) or ‘keymap-popup-backend-child-frame’.  Bind it locally for a
one-off floating popup:

     (defun my-commands-floating ()
       (interactive)
       (let ((keymap-popup-backend #'keymap-popup-backend-child-frame))
         (keymap-popup my-commands-map)))


File: keymap-popup.info,  Node: Persistent popups,  Next: Dismissing programmatically,  Prev: Display backends,  Up: Showing the popup

5.2 Persistent popups
=====================

By default the popup dismisses after a suffix command runs.
‘:persistent t’ keeps it open until the exit key or ‘C-g’:

     (keymap-popup-define my-map :persistent t ...)
     (keymap-popup-annotate dired-mode-map :popup-key "?" :persistent t ...)
     (setopt keymap-popup-persistent t)        ; global default

   Per-keymap setting wins over the global one.


File: keymap-popup.info,  Node: Dismissing programmatically,  Prev: Persistent popups,  Up: Showing the popup

5.3 Dismissing programmatically
===============================

Call ‘keymap-popup-dismiss’ from a command to close the popup before
transitioning elsewhere:

     (defun my-open-detail ()
       (interactive)
       (keymap-popup-dismiss)
       (pop-to-buffer "*detail*"))


File: keymap-popup.info,  Node: Special bindings,  Next: Prompting and re-entry,  Prev: Showing the popup,  Up: Top

6 Special bindings
******************

Predicate quick reference:

   • ‘:if’ - _hide_ the entry (or group) when the predicate returns nil.
   • ‘:inapt-if’ - _grey out_ the entry and block its key when the
     predicate returns non-nil.

* Menu:

* Sub-menus with keymap::
* Toggles with switch::
* Stay-open commands with stay-open::
* Disabling entries with inapt-if::
* Hiding entries with if::
* Prefix-arg variants with c-u::


File: keymap-popup.info,  Node: Sub-menus with keymap,  Next: Toggles with switch,  Up: Special bindings

6.1 Sub-menus with ‘:keymap’
============================

     (keymap-popup-define my-dashboard-mode-map
       :group "Navigate"
       "n" ("Nodes" :keymap my-dashboard-nodes-map)
       "t" ("Themata" :keymap my-dashboard-themata-map)
       :group "More"
       "x" ("Import/Export" :keymap my-dashboard-io-map))

   Sub-menu entries use the ‘keymap-popup-submenu’ face.  Nesting is
unlimited; the exit key pops one level.


File: keymap-popup.info,  Node: Toggles with switch,  Next: Stay-open commands with stay-open,  Prev: Sub-menus with keymap,  Up: Special bindings

6.2 Toggles with ‘:switch’
==========================

     (keymap-popup-define yeetube-mode-map
       :group "Actions"
       "o" ("Settings" :switch yeetube--show-settings)
       :group "Settings"
       "V" ("No video" :switch yeetube-mpv-no-video
            :if (lambda () yeetube--show-settings)))

   The variable is auto-declared via ‘defvar’ +
‘make-variable-buffer-local’; each buffer gets its own value on first
set.  Switches always keep the popup open - no need to add ‘:stay-open
t’.


File: keymap-popup.info,  Node: Stay-open commands with stay-open,  Next: Disabling entries with inapt-if,  Prev: Toggles with switch,  Up: Special bindings

6.3 Stay-open commands with ‘:stay-open’
========================================

     (keymap-popup-define my-mark-map
       :group "Mark"
       "m" ("Mark" dired-mark :stay-open t)
       "u" ("Unmark" dired-unmark :stay-open t)
       "U" ("Unmark all" dired-unmark-all-marks))

   For an entire popup, prefer ‘:persistent t’ (see *note Persistent
popups::).


File: keymap-popup.info,  Node: Disabling entries with inapt-if,  Next: Hiding entries with if,  Prev: Stay-open commands with stay-open,  Up: Special bindings

6.4 Disabling entries with ‘:inapt-if’
======================================

     (keymap-popup-define yeetube-mode-map
       :group "Settings"
       "p" ("Toggle pause" yeetube-mpv-toggle-pause
            :inapt-if (lambda () (not (get-process yeetube-mpv--process-name)))))

     ;; Group-level applies to every entry in the group:
     (keymap-popup-define my-bulk-map
       :group ("Bulk" :inapt-if (lambda () (not (my-marked-files))))
       "c" ("Chmod" my-do-chmod)
       "z" ("Compress" my-do-compress))

   Inapt entries render in ‘keymap-popup-inapt’ and print "Command
unavailable" when pressed.


File: keymap-popup.info,  Node: Hiding entries with if,  Next: Prefix-arg variants with c-u,  Prev: Disabling entries with inapt-if,  Up: Special bindings

6.5 Hiding entries with ‘:if’
=============================

     (keymap-popup-define yeetube-mode-map
       "o" ("Settings" :switch yeetube--show-settings)
       :group ("Playback" :if (lambda () yeetube--show-settings))
       "p" ("Toggle pause" yeetube-mpv-toggle-pause)
       "v" ("Toggle video" yeetube-mpv-toggle-video))


File: keymap-popup.info,  Node: Prefix-arg variants with c-u,  Prev: Hiding entries with if,  Up: Special bindings

6.6 Prefix-arg variants with ‘:c-u’
===================================

     (keymap-popup-define my-greet-map
       :group "Actions"
       "a" ("Greet" my-greet :c-u "SHOUT (C-u)"))

   The ‘:c-u’ label renders in ‘shadow’ alongside the description as a
hint.  Pressing ‘C-u’ enters prefix-arg mode: ‘:c-u’ entries switch to
‘warning’, all other entries dim with ‘shadow’, and
‘universal-argument-map’ takes over so further ‘C-u’, digits, and ‘-’
compose the prefix as usual.  Pressing the key then runs the command
with ‘current-prefix-arg’ set, exactly like any ‘interactive "P"’
command.


File: keymap-popup.info,  Node: Prompting and re-entry,  Next: Annotating existing keymaps,  Prev: Special bindings,  Up: Top

7 Prompting and re-entry
************************

Suffix commands dismiss the popup when they return.  For commands that
prompt (‘read-string’, ‘completing-read’, ...) and should leave the
popup visible, either set ‘:persistent t’ on the keymap or re-invoke
‘keymap-popup’ at the end of the command:

     (defun my-edit-set-title ()
       (interactive)
       (let ((new (read-string "Title: ")))
         (my-db-set-title new))
       (keymap-popup my-edit-map))

     (keymap-popup-define my-edit-map
       :group "Edit"
       "t" ("Title" my-edit-set-title))

   The same pattern works with a lambda in the binding itself:

     (keymap-popup-define my-settings-map
       :group "Settings"
       "n" ((lambda () (format "Limit: %d" my--limit))
            (lambda ()
              (interactive)
              (setq-local my--limit (read-number "Limit: "))
              (keymap-popup my-settings-map))))


File: keymap-popup.info,  Node: Annotating existing keymaps,  Next: Customization,  Prev: Prompting and re-entry,  Up: Top

8 Annotating existing keymaps
*****************************

‘keymap-popup-annotate’ attaches descriptions to a keymap you don't own
(like ‘dired-mode-map’) without rebinding its keys.  Keys are resolved
at popup time via ‘where-is-internal’, so the popup tracks the user's
current bindings.

     (keymap-popup-annotate dired-mode-map
       :popup-key "?"
       :persistent t
       :description (lambda ()
                      (format "Dired: %s" (abbreviate-file-name default-directory)))
       :group "File"
       dired-find-file "Open"
       dired-do-copy "Copy"
       dired-do-rename "Rename"
       dired-do-delete "Delete"
       :group "Navigate"
       dired-up-directory "Up"
       dired-previous-line "Prev"
       dired-next-line "Next"
       :group "More"
       "M" ("Mark operations" :keymap my-dired-mark-map))

   Entries are ‘COMMAND-SYMBOL DESCRIPTION’ (the key comes from the
user's binding).  String-key entries are allowed too - that's how
sub-menus attach to an annotated keymap.

* Menu:

* Attaching descriptions built from data::


File: keymap-popup.info,  Node: Attaching descriptions built from data,  Up: Annotating existing keymaps

8.1 Attaching descriptions built from data
==========================================

Both macros take bindings written literally in source.  When the menu
itself is computed at runtime - an anonymous keymap, entries generated
from a list - use the function ‘keymap-popup-attach’:

     (defun my-project-popup ()
       "Pick a recent project from a popup."
       (interactive)
       (let* ((projects (seq-take (project-known-project-roots) 9))
              (map (make-sparse-keymap))
              (bindings
               (cl-loop for root in projects
                        for key from ?1
                        for cmd = (let ((r root))
                                    (lambda () (interactive)
                                      (project-switch-project r)))
                        do (keymap-set map (char-to-string key) cmd)
                        append (list (char-to-string key)
                                     (list (abbreviate-file-name root) cmd)))))
         (keymap-popup (keymap-popup-attach
                        map (append '(:group "Projects") bindings)))))

   Signature:

   • ‘(keymap-popup-attach KEYMAP BINDINGS &rest OPTS)’

   BINDINGS is an evaluated list in the same shape as
‘keymap-popup-annotate’'s body; commands and predicates are actual
objects rather than forms.  OPTS accepts ‘:exit-key’, ‘:description’,
and ‘:persistent’.  Unlike the macros, no keys are bound and no commands
are defined - keyed entries should already be bound in KEYMAP.  Returns
KEYMAP.


File: keymap-popup.info,  Node: Customization,  Next: When to use this vs transient,  Prev: Annotating existing keymaps,  Up: Top

9 Customization
***************

* Menu:

* User options::
* Faces::


File: keymap-popup.info,  Node: User options,  Next: Faces,  Up: Customization

9.1 User options
================

   • ‘keymap-popup-backend’ - display function; built-ins are
     ‘keymap-popup-backend-side-window’ (default) and
     ‘keymap-popup-backend-child-frame’.
   • ‘keymap-popup-display-action’ - ‘display-buffer’ action for the
     side-window backend.
   • ‘keymap-popup-buffer-parameters’ - buffer-local parameters applied
     to the popup buffer.
   • ‘keymap-popup-child-frame-parameters’ - frame parameters for the
     child-frame backend.
   • ‘keymap-popup-persistent’ - global default for ‘:persistent’.
   • ‘keymap-popup-default-popup-key’ - default ‘:popup-key’ (‘h’).
   • ‘keymap-popup-default-exit-key’ - default ‘:exit-key’ (‘q’).


File: keymap-popup.info,  Node: Faces,  Prev: User options,  Up: Customization

9.2 Faces
=========

   • ‘keymap-popup-key’ - key string (inherits ‘help-key-binding’).
   • ‘keymap-popup-group-header’ - group header.
   • ‘keymap-popup-value’ - ‘[on]’ / ‘[off]’ and inline values.
   • ‘keymap-popup-submenu’ - sub-menu entries.
   • ‘keymap-popup-inapt’ - disabled entries (inherits ‘shadow’).


File: keymap-popup.info,  Node: When to use this vs transient,  Next: Contributing,  Prev: Customization,  Up: Top

10 When to use this vs ‘transient’
**********************************

‘transient’ is its own abstraction: a class hierarchy built on EIEIO,
with prefix/suffix/infix objects and a custom dispatch loop.  The menu
is not an Emacs keymap.  It fits codebases that already lean on EIEIO
and benefit from typed, extensible class machinery (magit being the
canonical example).

   ‘keymap-popup’ is just a ‘defvar-keymap’ plus metadata, displayed via
the built-in ‘set-transient-map’ primitive (which predates the
‘transient’ package by years and is unrelated to it, despite the naming
collision).  Use it when you want a real keymap that also knows how to
draw itself: bindings show up in ‘where-is’ and ‘describe-key’, and the
popup does not block ‘M-x’.  There is no class hierarchy to learn, and
state lives wherever you choose to put it.


File: keymap-popup.info,  Node: Contributing,  Prev: When to use this vs transient,  Up: Top

11 Contributing
***************

Source is a single file, ‘keymap-popup.el’, with tests in
‘tests/keymap-popup-tests.el’.  Run them with:

     emacs --batch -l ert -l keymap-popup.el \
                   -l tests/keymap-popup-tests.el \
                   -f ert-run-tests-batch-and-exit



Tag Table:
Node: Top256
Node: Introduction1288
Node: Installation1689
Node: From source1866
Node: With use-package2074
Node: Quick start2266
Node: Defining keymaps2831
Node: Macro syntax3126
Node: Groups and rows4311
Node: Dynamic descriptions4997
Node: Sharing bindings with parent5545
Node: Adding and removing entries at runtime6345
Node: Showing the popup7084
Node: Display backends7506
Node: Persistent popups7998
Node: Dismissing programmatically8552
Node: Special bindings8946
Node: Sub-menus with keymap9511
Node: Toggles with switch10057
Node: Stay-open commands with stay-open10725
Node: Disabling entries with inapt-if11259
Node: Hiding entries with if12045
Node: Prefix-arg variants with c-u12540
Node: Prompting and re-entry13302
Node: Annotating existing keymaps14362
Node: Attaching descriptions built from data15571
Node: Customization17222
Node: User options17425
Node: Faces18264
Node: When to use this vs transient18709
Node: Contributing19700

End Tag Table


Local Variables:
coding: utf-8
End:
