emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master f972634 45/45: Merge commit '742d66a63e86ac740e610faa5abba


From: Oleh Krehel
Subject: [elpa] master f972634 45/45: Merge commit '742d66a63e86ac740e610faa5abba97e7f8ad5c2' from hydra
Date: Thu, 16 Apr 2015 12:45:58 +0000

branch: master
commit f9726342d0783bb3442acb69eb850650fc186bfb
Merge: 22139ae 742d66a
Author: Oleh Krehel <address@hidden>
Commit: Oleh Krehel <address@hidden>

    Merge commit '742d66a63e86ac740e610faa5abba97e7f8ad5c2' from hydra
    
    Conflicts:
        packages/hydra/hydra-test.el
        packages/hydra/hydra.el
---
 packages/hydra/.travis.yml   |    6 +-
 packages/hydra/Makefile      |    1 +
 packages/hydra/README.md     |   68 +-
 packages/hydra/hydra-test.el | 1518 ++++++++++++++++++++++--------------------
 packages/hydra/hydra.el      |  783 +++++++++++------------
 5 files changed, 1202 insertions(+), 1174 deletions(-)

diff --git a/packages/hydra/.travis.yml b/packages/hydra/.travis.yml
index 1f5dbc7..e97acdb 100644
--- a/packages/hydra/.travis.yml
+++ b/packages/hydra/.travis.yml
@@ -1,12 +1,14 @@
 language: emacs-lisp
 env:
   matrix:
-    - EMACS=emacs24
+    - emacs=emacs24
+    - emacs=emacs-snapshot
 
 before_install:
   - sudo add-apt-repository -y ppa:cassou/emacs
+  - sudo add-apt-repository -y ppa:ubuntu-elisp
   - sudo apt-get update -qq
-  - sudo apt-get install -qq $EMACS
+  - sudo apt-get install -qq $emacs
 
 script:
   - make test
diff --git a/packages/hydra/Makefile b/packages/hydra/Makefile
index 35709e1..43bcb5a 100644
--- a/packages/hydra/Makefile
+++ b/packages/hydra/Makefile
@@ -8,6 +8,7 @@ LOAD = -l lv.el -l hydra.el -l hydra-test.el
 all: test
 
 test:
+       @echo "Using $(shell which $(emacs))..."
        $(emacs) -batch $(LOAD) -f ert-run-tests-batch-and-exit
 
 compile:
diff --git a/packages/hydra/README.md b/packages/hydra/README.md
index 172524e..d2237d8 100644
--- a/packages/hydra/README.md
+++ b/packages/hydra/README.md
@@ -5,6 +5,8 @@ bindings with a common prefix - a Hydra.
 
 ![hydra](http://oremacs.com/download/Hydra.jpg)
 
+## Description for Poets
+
 Once you summon the Hydra through the prefixed binding (the body + any one 
head), all heads can be
 called in succession with only a short extension.
 
@@ -13,6 +15,22 @@ Hercules, besides vanquishing the Hydra, will still serve 
his original purpose,
 command.  This makes the Hydra very seamless, it's like a minor mode that 
disables itself
 auto-magically.
 
+## Description for Pragmatics
+
+Imagine that you have bound <kbd>C-c j</kbd> and <kbd>C-c k</kbd> in your
+config.  You want to call <kbd>C-c j</kbd> and <kbd>C-c k</kbd> in some
+(arbitrary) sequence. Hydra allows you to:
+
+- Bind your functions in a way that pressing <kbd>C-c jjkk3j5k</kbd> is
+equivalent to pressing <kbd>C-c j C-c j C-c k C-c k M-3 C-c j M-5 C-c
+k</kbd>. Any key other than <kbd>j</kbd> or <kbd>k</kbd> exits this state.
+
+- Assign a custom hint to this group of functions, so that you know immediately
+after pressing <kbd>C-c</kbd> that you can follow up with <kbd>j</kbd> or
+<kbd>k</kbd>.
+
+If you want to quickly understand the concept, see [the video 
demo](https://www.youtube.com/watch?v=_qZliI1BKzI).
+
 <!-- markdown-toc start - Don't edit this section. Run M-x 
markdown-toc/generate-toc again -->
 **Table of Contents**
 
@@ -158,41 +176,21 @@ Here's what `hydra-zoom/body` looks like, if you're 
interested:
 
 The body can be accessed via `hydra-zoom/body'."
   (interactive)
-  (hydra-disable)
-  (catch (quote hydra-disable)
-    (when hydra-is-helpful (hydra-zoom/hint))
-    (setq hydra-last
-          (hydra-set-transient-map
-           (setq hydra-curr-map
-                 (quote
-                  (keymap (7 . hydra-keyboard-quit)
-                          (108 . hydra-zoom/text-scale-decrease)
-                          (103 . hydra-zoom/text-scale-increase)
-                          (kp-subtract . hydra--negative-argument)
-                          (kp-9 . hydra--digit-argument)
-                          (kp-8 . hydra--digit-argument)
-                          (kp-7 . hydra--digit-argument)
-                          (kp-6 . hydra--digit-argument)
-                          (kp-5 . hydra--digit-argument)
-                          (kp-4 . hydra--digit-argument)
-                          (kp-3 . hydra--digit-argument)
-                          (kp-2 . hydra--digit-argument)
-                          (kp-1 . hydra--digit-argument)
-                          (kp-0 . hydra--digit-argument)
-                          (57 . hydra--digit-argument)
-                          (56 . hydra--digit-argument)
-                          (55 . hydra--digit-argument)
-                          (54 . hydra--digit-argument)
-                          (53 . hydra--digit-argument)
-                          (52 . hydra--digit-argument)
-                          (51 . hydra--digit-argument)
-                          (50 . hydra--digit-argument)
-                          (49 . hydra--digit-argument)
-                          (48 . hydra--digit-argument)
-                          (45 . hydra--negative-argument)
-                          (21 . hydra--universal-argument))))
-           t (lambda nil (hydra-cleanup))))
-    (setq prefix-arg current-prefix-arg)))
+  (hydra-default-pre)
+  (when hydra-is-helpful
+    (if hydra-lv
+        (lv-message
+         (eval hydra-zoom/hint))
+      (message
+       (eval hydra-zoom/hint))))
+  (hydra-set-transient-map
+   hydra-zoom/keymap
+   (lambda nil
+     (hydra-keyboard-quit)
+     nil)
+   nil)
+  (setq prefix-arg
+        current-prefix-arg))
 ```
 
 ## `awesome-map` and `awesome-binding`
diff --git a/packages/hydra/hydra-test.el b/packages/hydra/hydra-test.el
index f876e36..15eabcf 100644
--- a/packages/hydra/hydra-test.el
+++ b/packages/hydra/hydra-test.el
@@ -26,6 +26,7 @@
 
 (require 'ert)
 (require 'hydra)
+(message "Emacs version: %s" emacs-version)
 
 (ert-deftest hydra-red-error ()
   (should
@@ -38,7 +39,61 @@
        ("k" previous-error "prev")
        ("SPC" hydra-repeat "rep" :bind nil)))
     '(progn
-      (defun hydra-error/first-error nil "Create a hydra with a \"M-g\" body 
and the heads:
+      (set
+       (defvar hydra-error/keymap nil
+         "Keymap for hydra-error.")
+       (quote
+        (keymap
+         (32 . hydra-repeat)
+         (107 . hydra-error/previous-error)
+         (106 . hydra-error/next-error)
+         (104 . hydra-error/first-error)
+         (kp-subtract . hydra--negative-argument)
+         (kp-9 . hydra--digit-argument)
+         (kp-8 . hydra--digit-argument)
+         (kp-7 . hydra--digit-argument)
+         (kp-6 . hydra--digit-argument)
+         (kp-5 . hydra--digit-argument)
+         (kp-4 . hydra--digit-argument)
+         (kp-3 . hydra--digit-argument)
+         (kp-2 . hydra--digit-argument)
+         (kp-1 . hydra--digit-argument)
+         (kp-0 . hydra--digit-argument)
+         (57 . hydra--digit-argument)
+         (56 . hydra--digit-argument)
+         (55 . hydra--digit-argument)
+         (54 . hydra--digit-argument)
+         (53 . hydra--digit-argument)
+         (52 . hydra--digit-argument)
+         (51 . hydra--digit-argument)
+         (50 . hydra--digit-argument)
+         (49 . hydra--digit-argument)
+         (48 . hydra--digit-argument)
+         (45 . hydra--negative-argument)
+         (21 . hydra--universal-argument))))
+      (set
+       (defvar hydra-error/heads nil
+         "Heads for hydra-error.")
+       (quote
+        (("h"
+          first-error
+          "first"
+          :exit nil)
+         ("j"
+          next-error
+          "next"
+          :exit nil)
+         ("k"
+          previous-error
+          "prev"
+          :exit nil)
+         ("SPC"
+          hydra-repeat
+          "rep"
+          :bind nil
+          :exit nil))))
+      (defun hydra-error/first-error nil
+        "Create a hydra with a \"M-g\" body and the heads:
 
 \"h\":    `first-error',
 \"j\":    `next-error',
@@ -48,50 +103,33 @@
 The body can be accessed via `hydra-error/body'.
 
 Call the head: `first-error'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
first-error)))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-error/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (32 . hydra-repeat)
-                                           (107 . hydra-error/previous-error)
-                                           (106 . hydra-error/next-error)
-                                           (104 . hydra-error/first-error)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-error/next-error nil "Create a hydra with a \"M-g\" body 
and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (progn
+              (setq this-command
+                    (quote first-error))
+              (call-interactively
+               (function first-error)))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-error/hint))
+            (message
+             (eval hydra-error/hint))))
+        (hydra-set-transient-map
+         hydra-error/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil))
+      (defun hydra-error/next-error nil
+        "Create a hydra with a \"M-g\" body and the heads:
 
 \"h\":    `first-error',
 \"j\":    `next-error',
@@ -101,50 +139,33 @@ Call the head: `first-error'."
 The body can be accessed via `hydra-error/body'.
 
 Call the head: `next-error'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
next-error)))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-error/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (32 . hydra-repeat)
-                                           (107 . hydra-error/previous-error)
-                                           (106 . hydra-error/next-error)
-                                           (104 . hydra-error/first-error)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-error/previous-error nil "Create a hydra with a \"M-g\" 
body and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (progn
+              (setq this-command
+                    (quote next-error))
+              (call-interactively
+               (function next-error)))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-error/hint))
+            (message
+             (eval hydra-error/hint))))
+        (hydra-set-transient-map
+         hydra-error/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil))
+      (defun hydra-error/previous-error nil
+        "Create a hydra with a \"M-g\" body and the heads:
 
 \"h\":    `first-error',
 \"j\":    `next-error',
@@ -154,68 +175,58 @@ Call the head: `next-error'."
 The body can be accessed via `hydra-error/body'.
 
 Call the head: `previous-error'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
previous-error)))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-error/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (32 . hydra-repeat)
-                                           (107 . hydra-error/previous-error)
-                                           (106 . hydra-error/next-error)
-                                           (104 . hydra-error/first-error)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (unless (keymapp (lookup-key global-map (kbd "M-g")))
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (progn
+              (setq this-command
+                    (quote previous-error))
+              (call-interactively
+               (function previous-error)))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-error/hint))
+            (message
+             (eval hydra-error/hint))))
+        (hydra-set-transient-map
+         hydra-error/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil))
+      (unless (keymapp
+               (lookup-key
+                global-map
+                (kbd "M-g")))
         (define-key global-map (kbd "M-g")
           nil))
       (define-key global-map [134217831 104]
-        (function hydra-error/first-error))
+       (function
+        hydra-error/first-error))
       (define-key global-map [134217831 106]
-        (function hydra-error/next-error))
+       (function
+        hydra-error/next-error))
       (define-key global-map [134217831 107]
-        (function hydra-error/previous-error))
-      (defun hydra-error/hint nil
-        (if hydra-lv (lv-message (format #("error: [h]: first, [j]: next, [k]: 
prev, [SPC]: rep." 8 9 (face hydra-face-red)
-                                           20 21 (face hydra-face-red)
-                                           31 32 (face hydra-face-red)
-                                           42 45 (face hydra-face-red))))
-          (message (format #("error: [h]: first, [j]: next, [k]: prev, [SPC]: 
rep." 8 9 (face hydra-face-red)
-                             20 21 (face hydra-face-red)
-                             31 32 (face hydra-face-red)
-                             42 45 (face hydra-face-red))))))
-      (defun hydra-error/body nil "Create a hydra with a \"M-g\" body and the 
heads:
+       (function
+        hydra-error/previous-error))
+      (set
+       (defvar hydra-error/hint nil
+         "Dynamic hint for hydra-error.")
+       (quote
+        (format
+         #("error: [h]: first, [j]: next, [k]: prev, [SPC]: rep."
+           8 9 (face hydra-face-red)
+           20 21 (face hydra-face-red)
+           31 32 (face hydra-face-red)
+           42 45 (face hydra-face-red)))))
+      (defun hydra-error/body nil
+        "Create a hydra with a \"M-g\" body and the heads:
 
 \"h\":    `first-error',
 \"j\":    `next-error',
@@ -223,45 +234,24 @@ Call the head: `previous-error'."
 \"SPC\":    `hydra-repeat'
 
 The body can be accessed via `hydra-error/body'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (when hydra-is-helpful (hydra-error/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (32 . hydra-repeat)
-                                           (107 . hydra-error/previous-error)
-                                           (106 . hydra-error/next-error)
-                                           (104 . hydra-error/first-error)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))
-               (setq prefix-arg current-prefix-arg)))))))
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore nil))
+          (hydra-keyboard-quit))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-error/hint))
+            (message
+             (eval hydra-error/hint))))
+        (hydra-set-transient-map
+         hydra-error/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil)
+        (setq prefix-arg
+              current-prefix-arg))))))
 
 (ert-deftest hydra-blue-toggle ()
   (should
@@ -274,7 +264,57 @@ The body can be accessed via `hydra-error/body'."
        ("a" abbrev-mode "abbrev")
        ("q" nil "cancel")))
     '(progn
-      (defun hydra-toggle/toggle-truncate-lines-and-exit nil "Create a hydra 
with no body and the heads:
+      (set
+       (defvar hydra-toggle/keymap nil
+         "Keymap for hydra-toggle.")
+       (quote
+        (keymap
+         (113 . hydra-toggle/nil)
+         (97 . hydra-toggle/abbrev-mode-and-exit)
+         (102 . hydra-toggle/auto-fill-mode-and-exit)
+         (116 . hydra-toggle/toggle-truncate-lines-and-exit)
+         (kp-subtract . hydra--negative-argument)
+         (kp-9 . hydra--digit-argument)
+         (kp-8 . hydra--digit-argument)
+         (kp-7 . hydra--digit-argument)
+         (kp-6 . hydra--digit-argument)
+         (kp-5 . hydra--digit-argument)
+         (kp-4 . hydra--digit-argument)
+         (kp-3 . hydra--digit-argument)
+         (kp-2 . hydra--digit-argument)
+         (kp-1 . hydra--digit-argument)
+         (kp-0 . hydra--digit-argument)
+         (57 . hydra--digit-argument)
+         (56 . hydra--digit-argument)
+         (55 . hydra--digit-argument)
+         (54 . hydra--digit-argument)
+         (53 . hydra--digit-argument)
+         (52 . hydra--digit-argument)
+         (51 . hydra--digit-argument)
+         (50 . hydra--digit-argument)
+         (49 . hydra--digit-argument)
+         (48 . hydra--digit-argument)
+         (45 . hydra--negative-argument)
+         (21 . hydra--universal-argument))))
+      (set
+       (defvar hydra-toggle/heads nil
+         "Heads for hydra-toggle.")
+       (quote
+        (("t"
+          toggle-truncate-lines
+          "truncate"
+          :exit t)
+         ("f"
+          auto-fill-mode
+          "fill"
+          :exit t)
+         ("a"
+          abbrev-mode
+          "abbrev"
+          :exit t)
+         ("q" nil "cancel" :exit t))))
+      (defun hydra-toggle/toggle-truncate-lines-and-exit nil
+        "Create a hydra with no body and the heads:
 
 \"t\":    `toggle-truncate-lines',
 \"f\":    `auto-fill-mode',
@@ -284,13 +324,17 @@ The body can be accessed via `hydra-error/body'."
 The body can be accessed via `hydra-toggle/body'.
 
 Call the head: `toggle-truncate-lines'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function toggle-truncate-lines))))
-      (defun hydra-toggle/auto-fill-mode-and-exit nil "Create a hydra with no 
body and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit)
+        (progn
+          (setq this-command
+                (quote toggle-truncate-lines))
+          (call-interactively
+           (function
+            toggle-truncate-lines))))
+      (defun hydra-toggle/auto-fill-mode-and-exit nil
+        "Create a hydra with no body and the heads:
 
 \"t\":    `toggle-truncate-lines',
 \"f\":    `auto-fill-mode',
@@ -300,13 +344,16 @@ Call the head: `toggle-truncate-lines'."
 The body can be accessed via `hydra-toggle/body'.
 
 Call the head: `auto-fill-mode'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function auto-fill-mode))))
-      (defun hydra-toggle/abbrev-mode-and-exit nil "Create a hydra with no 
body and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit)
+        (progn
+          (setq this-command
+                (quote auto-fill-mode))
+          (call-interactively
+           (function auto-fill-mode))))
+      (defun hydra-toggle/abbrev-mode-and-exit nil
+        "Create a hydra with no body and the heads:
 
 \"t\":    `toggle-truncate-lines',
 \"f\":    `auto-fill-mode',
@@ -316,13 +363,16 @@ Call the head: `auto-fill-mode'."
 The body can be accessed via `hydra-toggle/body'.
 
 Call the head: `abbrev-mode'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function abbrev-mode))))
-      (defun hydra-toggle/nil nil "Create a hydra with no body and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit)
+        (progn
+          (setq this-command
+                (quote abbrev-mode))
+          (call-interactively
+           (function abbrev-mode))))
+      (defun hydra-toggle/nil nil
+        "Create a hydra with no body and the heads:
 
 \"t\":    `toggle-truncate-lines',
 \"f\":    `auto-fill-mode',
@@ -332,21 +382,21 @@ Call the head: `abbrev-mode'."
 The body can be accessed via `hydra-toggle/body'.
 
 Call the head: `nil'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)))
-      (defun hydra-toggle/hint nil
-        (if hydra-lv (lv-message (format #("toggle: [t]: truncate, [f]: fill, 
[a]: abbrev, [q]: cancel." 9 10 (face hydra-face-blue)
-                                           24 25 (face hydra-face-blue)
-                                           35 36 (face hydra-face-blue)
-                                           48 49 (face hydra-face-blue))))
-          (message (format #("toggle: [t]: truncate, [f]: fill, [a]: abbrev, 
[q]: cancel." 9 10 (face hydra-face-blue)
-                             24 25 (face hydra-face-blue)
-                             35 36 (face hydra-face-blue)
-                             48 49 (face hydra-face-blue))))))
-      (defun hydra-toggle/body nil "Create a hydra with no body and the heads:
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit))
+      (set
+       (defvar hydra-toggle/hint nil
+         "Dynamic hint for hydra-toggle.")
+       (quote
+        (format
+         #("toggle: [t]: truncate, [f]: fill, [a]: abbrev, [q]: cancel."
+           9 10 (face hydra-face-blue)
+           24 25 (face hydra-face-blue)
+           35 36 (face hydra-face-blue)
+           48 49 (face hydra-face-blue)))))
+      (defun hydra-toggle/body nil
+        "Create a hydra with no body and the heads:
 
 \"t\":    `toggle-truncate-lines',
 \"f\":    `auto-fill-mode',
@@ -354,45 +404,24 @@ Call the head: `nil'."
 \"q\":    `nil'
 
 The body can be accessed via `hydra-toggle/body'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (when hydra-is-helpful (hydra-toggle/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (113 . hydra-toggle/nil)
-                                           (97 . 
hydra-toggle/abbrev-mode-and-exit)
-                                           (102 . 
hydra-toggle/auto-fill-mode-and-exit)
-                                           (116 . 
hydra-toggle/toggle-truncate-lines-and-exit)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))
-               (setq prefix-arg current-prefix-arg)))))))
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore nil))
+          (hydra-keyboard-quit))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-toggle/hint))
+            (message
+             (eval hydra-toggle/hint))))
+        (hydra-set-transient-map
+         hydra-toggle/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil)
+        (setq prefix-arg
+              current-prefix-arg))))))
 
 (ert-deftest hydra-amaranth-vi ()
   (should
@@ -409,27 +438,50 @@ The body can be accessed via `hydra-toggle/body'."
        ("k" previous-line)
        ("q" nil "quit")))
     '(progn
-      (defun hydra-vi/hydra-keyboard-quit-and-exit nil "Create a hydra with no 
body and the heads:
+      (set
+       (defvar hydra-vi/keymap nil
+         "Keymap for hydra-vi.")
+       (quote
+        (keymap
+         (113 . hydra-vi/nil)
+         (107 . hydra-vi/previous-line)
+         (106 . hydra-vi/next-line)
+         (kp-subtract . hydra--negative-argument)
+         (kp-9 . hydra--digit-argument)
+         (kp-8 . hydra--digit-argument)
+         (kp-7 . hydra--digit-argument)
+         (kp-6 . hydra--digit-argument)
+         (kp-5 . hydra--digit-argument)
+         (kp-4 . hydra--digit-argument)
+         (kp-3 . hydra--digit-argument)
+         (kp-2 . hydra--digit-argument)
+         (kp-1 . hydra--digit-argument)
+         (kp-0 . hydra--digit-argument)
+         (57 . hydra--digit-argument)
+         (56 . hydra--digit-argument)
+         (55 . hydra--digit-argument)
+         (54 . hydra--digit-argument)
+         (53 . hydra--digit-argument)
+         (52 . hydra--digit-argument)
+         (51 . hydra--digit-argument)
+         (50 . hydra--digit-argument)
+         (49 . hydra--digit-argument)
+         (48 . hydra--digit-argument)
+         (45 . hydra--negative-argument)
+         (21 . hydra--universal-argument))))
+      (set
+       (defvar hydra-vi/heads nil
+         "Heads for hydra-vi.")
+       (quote
+        (("j" next-line "" :exit nil)
+         ("k"
+          previous-line
+          ""
+          :exit nil)
+         ("q" nil "quit" :exit t))))
+      (defun hydra-vi/next-line nil
+        "Create a hydra with no body and the heads:
 
-\"\":    `hydra-keyboard-quit',
-\"j\":    `next-line',
-\"k\":    `previous-line',
-\"q\":    `nil'
-
-The body can be accessed via `hydra-vi/body'.
-
-Call the head: `hydra-keyboard-quit'."
-             (interactive)
-             (hydra-default-pre)
-             (set-cursor-color "#e52b50")
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function hydra-keyboard-quit))
-               (set-cursor-color "#ffffff")))
-      (defun hydra-vi/next-line nil "Create a hydra with no body and the heads:
-
-\"\":    `hydra-keyboard-quit',
 \"j\":    `next-line',
 \"k\":    `previous-line',
 \"q\":    `nil'
@@ -437,57 +489,34 @@ Call the head: `hydra-keyboard-quit'."
 The body can be accessed via `hydra-vi/body'.
 
 Call the head: `next-line'."
-             (interactive)
-             (hydra-default-pre)
-             (set-cursor-color "#e52b50")
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
next-line)))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-vi/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (t lambda nil (interactive)
-                                              (message "An amaranth Hydra can 
only exit through a blue head")
-                                              (hydra-set-transient-map 
hydra-curr-map t)
-                                              (when hydra-is-helpful (unless 
hydra-lv (sit-for 0.8))
-                                                    (hydra-vi/hint)))
-                                           (113 . hydra-vi/nil)
-                                           (107 . hydra-vi/previous-line)
-                                           (106 . hydra-vi/next-line)
-                                           (7 . 
hydra-vi/hydra-keyboard-quit-and-exit)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-vi/previous-line nil "Create a hydra with no body and the 
heads:
-
-\"\":    `hydra-keyboard-quit',
+        (interactive)
+        (hydra-default-pre)
+        (set-cursor-color "#e52b50")
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (progn
+              (setq this-command
+                    (quote next-line))
+              (call-interactively
+               (function next-line)))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-vi/hint))
+            (message (eval hydra-vi/hint))))
+        (hydra-set-transient-map
+         hydra-vi/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           (set-cursor-color "#ffffff"))
+         (quote warn)))
+      (defun hydra-vi/previous-line nil
+        "Create a hydra with no body and the heads:
+
 \"j\":    `next-line',
 \"k\":    `previous-line',
 \"q\":    `nil'
@@ -495,57 +524,34 @@ Call the head: `next-line'."
 The body can be accessed via `hydra-vi/body'.
 
 Call the head: `previous-line'."
-             (interactive)
-             (hydra-default-pre)
-             (set-cursor-color "#e52b50")
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
previous-line)))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-vi/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (t lambda nil (interactive)
-                                              (message "An amaranth Hydra can 
only exit through a blue head")
-                                              (hydra-set-transient-map 
hydra-curr-map t)
-                                              (when hydra-is-helpful (unless 
hydra-lv (sit-for 0.8))
-                                                    (hydra-vi/hint)))
-                                           (113 . hydra-vi/nil)
-                                           (107 . hydra-vi/previous-line)
-                                           (106 . hydra-vi/next-line)
-                                           (7 . 
hydra-vi/hydra-keyboard-quit-and-exit)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-vi/nil nil "Create a hydra with no body and the heads:
-
-\"\":    `hydra-keyboard-quit',
+        (interactive)
+        (hydra-default-pre)
+        (set-cursor-color "#e52b50")
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (progn
+              (setq this-command
+                    (quote previous-line))
+              (call-interactively
+               (function previous-line)))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-vi/hint))
+            (message (eval hydra-vi/hint))))
+        (hydra-set-transient-map
+         hydra-vi/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           (set-cursor-color "#ffffff"))
+         (quote warn)))
+      (defun hydra-vi/nil nil
+        "Create a hydra with no body and the heads:
+
 \"j\":    `next-line',
 \"k\":    `previous-line',
 \"q\":    `nil'
@@ -553,72 +559,335 @@ Call the head: `previous-line'."
 The body can be accessed via `hydra-vi/body'.
 
 Call the head: `nil'."
-             (interactive)
-             (hydra-default-pre)
-             (set-cursor-color "#e52b50")
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (set-cursor-color "#ffffff")))
-      (defun hydra-vi/hint nil
-        (if hydra-lv (lv-message (format #("vi: j, k, [q]: quit." 4 5 (face 
hydra-face-amaranth)
-                                           7 8 (face hydra-face-amaranth)
-                                           11 12 (face hydra-face-blue))))
-          (message (format #("vi: j, k, [q]: quit." 4 5 (face 
hydra-face-amaranth)
-                             7 8 (face hydra-face-amaranth)
-                             11 12 (face hydra-face-blue))))))
-      (defun hydra-vi/body nil "Create a hydra with no body and the heads:
-
-\"\":    `hydra-keyboard-quit',
+        (interactive)
+        (hydra-default-pre)
+        (set-cursor-color "#e52b50")
+        (hydra-keyboard-quit))
+      (set
+       (defvar hydra-vi/hint nil
+         "Dynamic hint for hydra-vi.")
+       (quote
+        (format
+         #("vi: j, k, [q]: quit."
+           4 5 (face hydra-face-amaranth)
+           7 8 (face hydra-face-amaranth)
+           11 12 (face hydra-face-teal)))))
+      (defun hydra-vi/body nil
+        "Create a hydra with no body and the heads:
+
 \"j\":    `next-line',
 \"k\":    `previous-line',
 \"q\":    `nil'
 
 The body can be accessed via `hydra-vi/body'."
-             (interactive)
-             (hydra-default-pre)
-             (set-cursor-color "#e52b50")
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (when hydra-is-helpful (hydra-vi/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (t lambda nil (interactive)
-                                              (message "An amaranth Hydra can 
only exit through a blue head")
-                                              (hydra-set-transient-map 
hydra-curr-map t)
-                                              (when hydra-is-helpful (unless 
hydra-lv (sit-for 0.8))
-                                                    (hydra-vi/hint)))
-                                           (113 . hydra-vi/nil)
-                                           (107 . hydra-vi/previous-line)
-                                           (106 . hydra-vi/next-line)
-                                           (7 . 
hydra-vi/hydra-keyboard-quit-and-exit)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra--digit-argument)
-                                           (48 . hydra--digit-argument)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))
-               (setq prefix-arg current-prefix-arg)))))))
+        (interactive)
+        (hydra-default-pre)
+        (set-cursor-color "#e52b50")
+        (let ((hydra--ignore nil))
+          (hydra-keyboard-quit))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-vi/hint))
+            (message (eval hydra-vi/hint))))
+        (hydra-set-transient-map
+         hydra-vi/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           (set-cursor-color "#ffffff"))
+         (quote warn))
+        (setq prefix-arg
+              current-prefix-arg))))))
+
+(ert-deftest hydra-zoom-duplicate-1 ()
+  (should
+   (equal
+    (macroexpand
+     '(defhydra hydra-zoom ()
+       "zoom"
+       ("r" (text-scale-set 0) "reset")
+       ("0" (text-scale-set 0) :bind nil :exit t)
+       ("1" (text-scale-set 0) nil :bind nil :exit t)))
+    '(progn
+      (set
+       (defvar hydra-zoom/keymap nil
+         "Keymap for hydra-zoom.")
+       (quote
+        (keymap
+         (114 . hydra-zoom/lambda-r)
+         (kp-subtract . hydra--negative-argument)
+         (kp-9 . hydra--digit-argument)
+         (kp-8 . hydra--digit-argument)
+         (kp-7 . hydra--digit-argument)
+         (kp-6 . hydra--digit-argument)
+         (kp-5 . hydra--digit-argument)
+         (kp-4 . hydra--digit-argument)
+         (kp-3 . hydra--digit-argument)
+         (kp-2 . hydra--digit-argument)
+         (kp-1 . hydra--digit-argument)
+         (kp-0 . hydra--digit-argument)
+         (57 . hydra--digit-argument)
+         (56 . hydra--digit-argument)
+         (55 . hydra--digit-argument)
+         (54 . hydra--digit-argument)
+         (53 . hydra--digit-argument)
+         (52 . hydra--digit-argument)
+         (51 . hydra--digit-argument)
+         (50 . hydra--digit-argument)
+         (49 . hydra-zoom/lambda-0-and-exit)
+         (48 . hydra-zoom/lambda-0-and-exit)
+         (45 . hydra--negative-argument)
+         (21 . hydra--universal-argument))))
+      (set
+       (defvar hydra-zoom/heads nil
+         "Heads for hydra-zoom.")
+       (quote
+        (("r"
+          (text-scale-set 0)
+          "reset"
+          :exit nil)
+         ("0"
+          (text-scale-set 0)
+          ""
+          :bind nil
+          :exit t)
+         ("1"
+          (text-scale-set 0)
+          nil
+          :bind nil
+          :exit t))))
+      (defun hydra-zoom/lambda-r nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'.
+
+Call the head: `(text-scale-set 0)'."
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (call-interactively
+             (function
+              (lambda nil
+               (interactive)
+               (text-scale-set 0))))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-zoom/hint))
+            (message
+             (eval hydra-zoom/hint))))
+        (hydra-set-transient-map
+         hydra-zoom/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil))
+      (defun hydra-zoom/lambda-0-and-exit nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'.
+
+Call the head: `(text-scale-set 0)'."
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit)
+        (call-interactively
+         (function
+          (lambda nil
+           (interactive)
+           (text-scale-set 0)))))
+      (set
+       (defvar hydra-zoom/hint nil
+         "Dynamic hint for hydra-zoom.")
+       (quote
+        (format
+         #("zoom: [r 0]: reset."
+           7 8 (face hydra-face-red)
+           9 10 (face hydra-face-blue)))))
+      (defun hydra-zoom/body nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'."
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore nil))
+          (hydra-keyboard-quit))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-zoom/hint))
+            (message
+             (eval hydra-zoom/hint))))
+        (hydra-set-transient-map
+         hydra-zoom/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil)
+        (setq prefix-arg
+              current-prefix-arg))))))
+
+(ert-deftest hydra-zoom-duplicate-2 ()
+  (should
+   (equal
+    (macroexpand
+     '(defhydra hydra-zoom ()
+       "zoom"
+       ("r" (text-scale-set 0) "reset")
+       ("0" (text-scale-set 0) :bind nil :exit t)
+       ("1" (text-scale-set 0) nil :bind nil)))
+    '(progn
+      (set
+       (defvar hydra-zoom/keymap nil
+         "Keymap for hydra-zoom.")
+       (quote
+        (keymap
+         (114 . hydra-zoom/lambda-r)
+         (kp-subtract . hydra--negative-argument)
+         (kp-9 . hydra--digit-argument)
+         (kp-8 . hydra--digit-argument)
+         (kp-7 . hydra--digit-argument)
+         (kp-6 . hydra--digit-argument)
+         (kp-5 . hydra--digit-argument)
+         (kp-4 . hydra--digit-argument)
+         (kp-3 . hydra--digit-argument)
+         (kp-2 . hydra--digit-argument)
+         (kp-1 . hydra--digit-argument)
+         (kp-0 . hydra--digit-argument)
+         (57 . hydra--digit-argument)
+         (56 . hydra--digit-argument)
+         (55 . hydra--digit-argument)
+         (54 . hydra--digit-argument)
+         (53 . hydra--digit-argument)
+         (52 . hydra--digit-argument)
+         (51 . hydra--digit-argument)
+         (50 . hydra--digit-argument)
+         (49 . hydra-zoom/lambda-r)
+         (48 . hydra-zoom/lambda-0-and-exit)
+         (45 . hydra--negative-argument)
+         (21 . hydra--universal-argument))))
+      (set
+       (defvar hydra-zoom/heads nil
+         "Heads for hydra-zoom.")
+       (quote
+        (("r"
+          (text-scale-set 0)
+          "reset"
+          :exit nil)
+         ("0"
+          (text-scale-set 0)
+          ""
+          :bind nil
+          :exit t)
+         ("1"
+          (text-scale-set 0)
+          nil
+          :bind nil
+          :exit nil))))
+      (defun hydra-zoom/lambda-r nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'.
+
+Call the head: `(text-scale-set 0)'."
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore t))
+          (hydra-keyboard-quit))
+        (condition-case err
+            (call-interactively
+             (function
+              (lambda nil
+               (interactive)
+               (text-scale-set 0))))
+          ((quit error)
+           (message "%S" err)
+           (unless hydra-lv (sit-for 0.8))))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-zoom/hint))
+            (message
+             (eval hydra-zoom/hint))))
+        (hydra-set-transient-map
+         hydra-zoom/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil))
+      (defun hydra-zoom/lambda-0-and-exit nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'.
+
+Call the head: `(text-scale-set 0)'."
+        (interactive)
+        (hydra-default-pre)
+        (hydra-keyboard-quit)
+        (call-interactively
+         (function
+          (lambda nil
+           (interactive)
+           (text-scale-set 0)))))
+      (set
+       (defvar hydra-zoom/hint nil
+         "Dynamic hint for hydra-zoom.")
+       (quote
+        (format
+         #("zoom: [r 0]: reset."
+           7 8 (face hydra-face-red)
+           9 10 (face hydra-face-blue)))))
+      (defun hydra-zoom/body nil
+        "Create a hydra with no body and the heads:
+
+\"r\":    `(text-scale-set 0)',
+\"0\":    `(text-scale-set 0)',
+\"1\":    `(text-scale-set 0)'
+
+The body can be accessed via `hydra-zoom/body'."
+        (interactive)
+        (hydra-default-pre)
+        (let ((hydra--ignore nil))
+          (hydra-keyboard-quit))
+        (when hydra-is-helpful
+          (if hydra-lv
+              (lv-message
+               (eval hydra-zoom/hint))
+            (message
+             (eval hydra-zoom/hint))))
+        (hydra-set-transient-map
+         hydra-zoom/keymap
+         (lambda nil
+           (hydra-keyboard-quit)
+           nil)
+         nil)
+        (setq prefix-arg
+              current-prefix-arg))))))
 
 (ert-deftest defhydradio ()
   (should (equal
@@ -741,7 +1010,7 @@ _f_ auto-fill-mode:    %`auto-fill-function
            '(concat (format "%s abbrev-mode:       %S
 %s debug-on-error:    %S
 %s auto-fill-mode:    %S
-" "{a}" abbrev-mode "{d}" debug-on-error "{f}" auto-fill-function) "[[q]]: 
quit"))))
+" "{a}" abbrev-mode "{d}" debug-on-error "{f}" auto-fill-function) "[{q}]: 
quit"))))
 
 (ert-deftest hydra-format-2 ()
   (should (equal
@@ -751,8 +1020,8 @@ _f_ auto-fill-mode:    %`auto-fill-function
               'bar
               nil
               "\n  bar %s`foo\n"
-              '(("a" (quote t) "" :cmd-name bar/lambda-a)
-                ("q" nil "" :cmd-name bar/nil))))
+              '(("a" (quote t) "" :cmd-name bar/lambda-a :exit nil)
+                ("q" nil "" :cmd-name bar/nil :exit t))))
            '(concat (format "  bar %s\n" foo) "{a}, [q]"))))
 
 (ert-deftest hydra-format-3 ()
@@ -784,7 +1053,7 @@ _f_ auto-fill-mode:    %`auto-fill-function
              (hydra--format
               'hydra-toggle nil
               "\n_n_ narrow-or-widen-dwim %(progn (message 
\"checking\")(buffer-narrowed-p))asdf\n"
-              '(("n" narrow-to-region nil) ("q" nil "cancel"))))
+              '(("n" narrow-to-region nil) ("q" nil "cancel" :exit t))))
            '(concat (format "%s narrow-or-widen-dwim %Sasdf\n"
                      "{n}"
                      (progn
@@ -799,7 +1068,7 @@ _f_ auto-fill-mode:    %`auto-fill-function
              (hydra--format
               'hydra-toggle nil
               "\n_n_ narrow-or-widen-dwim %s(progn (message 
\"checking\")(buffer-narrowed-p))asdf\n"
-              '(("n" narrow-to-region nil) ("q" nil "cancel"))))
+              '(("n" narrow-to-region nil) ("q" nil "cancel" :exit t))))
            '(concat (format "%s narrow-or-widen-dwim %sasdf\n"
                      "{n}"
                      (progn
@@ -807,36 +1076,6 @@ _f_ auto-fill-mode:    %`auto-fill-function
                        (buffer-narrowed-p)))
              "[[q]]: cancel"))))
 
-(ert-deftest hydra-compat-colors-1 ()
-  (should (equal (hydra--head-color
-                  '("e" (message "Exiting now") "blue")
-                  '(nil nil :color blue))
-                 'blue))
-  (should (equal (hydra--head-color
-                  '("c" (message "Continuing") "red" :color red)
-                  '(nil nil :color blue))
-                 'red))
-  (should (equal (hydra--head-color
-                  '("e" (message "Exiting now") "blue")
-                  '(nil nil :exit t))
-                 'blue))
-  (should (equal (hydra--head-color
-                  '("j" next-line "" :exit t)
-                  '(nil nil))
-                 'blue))
-  (should (equal (hydra--head-color
-                  '("c" (message "Continuing") "red" :exit nil)
-                  '(nil nil :exit t))
-                 'red))
-  (equal (hydra--head-color
-          '("a" abbrev-mode nil)
-          '(nil nil :color teal))
-         'teal)
-  (equal (hydra--head-color
-          '("a" abbrev-mode :exit nil)
-          '(nil nil :color teal))
-         'amaranth))
-
 (ert-deftest hydra-compat-colors-2 ()
   (should
    (equal
@@ -897,256 +1136,6 @@ _f_ auto-fill-mode:    %`auto-fill-function
        ("e" fun-e)
        ("f" fun-f))))))
 
-(ert-deftest hydra-zoom-duplicate-1 ()
-  (should
-   (equal
-    (macroexpand
-     '(defhydra hydra-zoom ()
-       "zoom"
-       ("r" (text-scale-set 0) "reset")
-       ("0" (text-scale-set 0) :bind nil :exit t)
-       ("1" (text-scale-set 0) nil :bind nil :exit t)))
-    '(progn
-      (defun hydra-zoom/lambda-r nil "Create a hydra with no body and the 
heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'.
-
-Call the head: `(text-scale-set 0)'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
(lambda nil (interactive)
-                                                                             
(text-scale-set 0)))))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-zoom/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (114 . hydra-zoom/lambda-r)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra-zoom/lambda-0-and-exit)
-                                           (48 . hydra-zoom/lambda-0-and-exit)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-zoom/lambda-0-and-exit nil "Create a hydra with no body and 
the heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'.
-
-Call the head: `(text-scale-set 0)'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function (lambda nil (interactive)
-                                                (text-scale-set 0))))))
-      (defun hydra-zoom/hint nil
-        (if hydra-lv (lv-message (format #("zoom: [r 0]: reset." 7 8 (face 
hydra-face-red)
-                                           9 10 (face hydra-face-blue))))
-          (message (format #("zoom: [r 0]: reset." 7 8 (face hydra-face-red)
-                             9 10 (face hydra-face-blue))))))
-      (defun hydra-zoom/body nil "Create a hydra with no body and the heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (when hydra-is-helpful (hydra-zoom/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (114 . hydra-zoom/lambda-r)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra-zoom/lambda-0-and-exit)
-                                           (48 . hydra-zoom/lambda-0-and-exit)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))
-               (setq prefix-arg current-prefix-arg)))))))
-
-(ert-deftest hydra-zoom-duplicate-2 ()
-  (should
-   (equal
-    (macroexpand
-     '(defhydra hydra-zoom ()
-       "zoom"
-       ("r" (text-scale-set 0) "reset")
-       ("0" (text-scale-set 0) :bind nil :exit t)
-       ("1" (text-scale-set 0) nil :bind nil)))
-    '(progn
-      (defun hydra-zoom/lambda-r nil "Create a hydra with no body and the 
heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'.
-
-Call the head: `(text-scale-set 0)'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (condition-case err (prog1 t (call-interactively (function 
(lambda nil (interactive)
-                                                                             
(text-scale-set 0)))))
-                 ((quit error)
-                  (message "%S" err)
-                  (unless hydra-lv (sit-for 0.8))
-                  nil))
-               (when hydra-is-helpful (hydra-zoom/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (114 . hydra-zoom/lambda-r)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra-zoom/lambda-r)
-                                           (48 . hydra-zoom/lambda-0-and-exit)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))))
-      (defun hydra-zoom/lambda-0-and-exit nil "Create a hydra with no body and 
the heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'.
-
-Call the head: `(text-scale-set 0)'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (hydra-cleanup)
-             (catch (quote hydra-disable)
-               (call-interactively (function (lambda nil (interactive)
-                                                (text-scale-set 0))))))
-      (defun hydra-zoom/hint nil
-        (if hydra-lv (lv-message (format #("zoom: [r 0]: reset." 7 8 (face 
hydra-face-red)
-                                           9 10 (face hydra-face-blue))))
-          (message (format #("zoom: [r 0]: reset." 7 8 (face hydra-face-red)
-                             9 10 (face hydra-face-blue))))))
-      (defun hydra-zoom/body nil "Create a hydra with no body and the heads:
-
-\"r\":    `(text-scale-set 0)',
-\"0\":    `(text-scale-set 0)',
-\"1\":    `(text-scale-set 0)'
-
-The body can be accessed via `hydra-zoom/body'."
-             (interactive)
-             (hydra-default-pre)
-             (hydra-disable)
-             (catch (quote hydra-disable)
-               (when hydra-is-helpful (hydra-zoom/hint))
-               (setq hydra-last
-                     (hydra-set-transient-map
-                      (setq hydra-curr-map
-                            (quote (keymap (7 . hydra-keyboard-quit)
-                                           (114 . hydra-zoom/lambda-r)
-                                           (switch-frame . 
hydra--handle-switch-frame)
-                                           (kp-subtract . 
hydra--negative-argument)
-                                           (kp-9 . hydra--digit-argument)
-                                           (kp-8 . hydra--digit-argument)
-                                           (kp-7 . hydra--digit-argument)
-                                           (kp-6 . hydra--digit-argument)
-                                           (kp-5 . hydra--digit-argument)
-                                           (kp-4 . hydra--digit-argument)
-                                           (kp-3 . hydra--digit-argument)
-                                           (kp-2 . hydra--digit-argument)
-                                           (kp-1 . hydra--digit-argument)
-                                           (kp-0 . hydra--digit-argument)
-                                           (57 . hydra--digit-argument)
-                                           (56 . hydra--digit-argument)
-                                           (55 . hydra--digit-argument)
-                                           (54 . hydra--digit-argument)
-                                           (53 . hydra--digit-argument)
-                                           (52 . hydra--digit-argument)
-                                           (51 . hydra--digit-argument)
-                                           (50 . hydra--digit-argument)
-                                           (49 . hydra-zoom/lambda-r)
-                                           (48 . hydra-zoom/lambda-0-and-exit)
-                                           (45 . hydra--negative-argument)
-                                           (21 . hydra--universal-argument))))
-                      t (lambda nil (hydra-cleanup))))
-               (setq prefix-arg current-prefix-arg)))))))
-
 (ert-deftest hydra--pad ()
   (should (equal (hydra--pad '(a b c) 3)
                  '(a b c)))
@@ -1200,6 +1189,77 @@ _w_ Worf:                      % -8`hydra-tng/worf^^    
_h_ Set phasers to
                    body-pre)
                  '(funcall (function foo)))))
 
+(defhydra hydra-simple-1 (global-map "C-c")
+  ("a" (insert "j"))
+  ("b" (insert "k"))
+  ("q" nil))
+
+(defhydra hydra-simple-2 (global-map "C-c" :color amaranth)
+  ("c" self-insert-command)
+  ("d" self-insert-command)
+  ("q" nil))
+
+(defhydra hydra-simple-3 (global-map "C-c")
+  ("g" goto-line)
+  ("1" find-file)
+  ("q" nil))
+
+(defmacro hydra-with (in &rest body)
+  `(let ((temp-buffer (generate-new-buffer " *temp*")))
+     (save-window-excursion
+       (unwind-protect
+            (progn
+              (switch-to-buffer temp-buffer)
+              (transient-mark-mode 1)
+              (insert ,in)
+              (goto-char (point-min))
+              (when (search-forward "~" nil t)
+                (backward-delete-char 1)
+                (set-mark (point)))
+              (goto-char (point-max))
+              (search-backward "|")
+              (delete-char 1)
+              (setq current-prefix-arg)
+              ,@body
+              (insert "|")
+              (when (region-active-p)
+                (exchange-point-and-mark)
+                (insert "~"))
+              (buffer-substring-no-properties
+               (point-min)
+               (point-max)))
+         (and (buffer-name temp-buffer)
+              (kill-buffer temp-buffer))))))
+
+(ert-deftest hydra-integration-1 ()
+  (should (string= (hydra-with "|"
+                               (execute-kbd-macro
+                                (kbd "C-c aabbaaqaabbaa")))
+                   "jjkkjjaabbaa|"))
+  (should (string= (hydra-with "|"
+                               (condition-case nil
+                                   (execute-kbd-macro
+                                    (kbd "C-c aabb C-g"))
+                                 (quit nil))
+                               (execute-kbd-macro "aaqaabbaa"))
+                   "jjkkaaqaabbaa|")))
+
+(ert-deftest hydra-integration-2 ()
+  (should (string= (hydra-with "|"
+                               (execute-kbd-macro
+                                (kbd "C-c c 1 c 2 d 4 c q")))
+                   "ccddcccc|"))
+  (should (string= (hydra-with "|"
+                               (execute-kbd-macro
+                                (kbd "C-c c 1 c C-u d C-u 10 c q")))
+                   "ccddddcccccccccc|")))
+
+(ert-deftest hydra-integration-3 ()
+  (should (string= (hydra-with "foo\nbar|"
+                               (execute-kbd-macro
+                                (kbd "C-c g 1 RET q")))
+                   "|foo\nbar")))
+
 (provide 'hydra-test)
 
 ;;; hydra-test.el ends here
diff --git a/packages/hydra/hydra.el b/packages/hydra/hydra.el
index 7195e36..27d48d5 100644
--- a/packages/hydra/hydra.el
+++ b/packages/hydra/hydra.el
@@ -5,7 +5,7 @@
 ;; Author: Oleh Krehel <address@hidden>
 ;; Maintainer: Oleh Krehel <address@hidden>
 ;; URL: https://github.com/abo-abo/hydra
-;; Version: 0.12.1
+;; Version: 0.13.0
 ;; Keywords: bindings
 ;; Package-Requires: ((cl-lib "0.5"))
 
@@ -79,24 +79,89 @@
 (require 'cl-lib)
 (require 'lv)
 
-(defalias 'hydra-set-transient-map
-    (if (fboundp 'set-transient-map)
-        'set-transient-map
-      (lambda (map _keep-pred &optional on-exit)
-        (with-no-warnings
-          (set-temporary-overlay-map map (hydra--pred on-exit))))))
-
-(defun hydra--pred (on-exit)
-  "Generate a predicate on whether to continue the Hydra state.
-Call ON-EXIT for clean-up.
-This is a compatibility code for Emacs older than 24.4."
-  `(lambda ()
-     (if (lookup-key hydra-curr-map (this-command-keys-vector))
-         t
-       (hydra-cleanup)
-       ,(when on-exit
-              `(funcall ,(hydra--make-callable on-exit)))
-       nil)))
+(defvar hydra-curr-map nil
+  "The keymap of the current Hydra called.")
+
+(defvar hydra-curr-on-exit nil
+  "The on-exit predicate for the current Hydra.")
+
+(defvar hydra-curr-foreign-keys nil
+  "The current :foreign-keys behavior.")
+
+(defun hydra-set-transient-map (keymap on-exit &optional foreign-keys)
+  "Set KEYMAP to the highest priority.
+
+Call ON-EXIT when the KEYMAP is deactivated.
+
+FOREIGN-KEYS determines the deactivation behavior, when a command
+that isn't in KEYMAP is called:
+
+nil: deactivate KEYMAP and run the command.
+run: keep KEYMAP and run the command.
+warn: keep KEYMAP and issue a warning instead of running the command."
+  (setq hydra-curr-map keymap)
+  (setq hydra-curr-on-exit on-exit)
+  (setq hydra-curr-foreign-keys foreign-keys)
+  (add-hook 'pre-command-hook 'hydra--clearfun)
+  (internal-push-keymap keymap 'overriding-terminal-local-map))
+
+(defun hydra--clearfun ()
+  "Disable the current Hydra unless `this-command' is a head."
+  (when (or
+         (memq this-command '(handle-switch-frame keyboard-quit))
+         (null overriding-terminal-local-map)
+         (not (or (eq this-command
+                      (lookup-key hydra-curr-map (this-single-command-keys)))
+                  (cl-case hydra-curr-foreign-keys
+                    (warn
+                     (setq this-command 'hydra-amaranth-warn))
+                    (run
+                     t)
+                    (t nil)))))
+    (hydra-disable)))
+
+(defvar hydra--ignore nil
+  "When non-nil, don't call `hydra-curr-on-exit'")
+
+(defun hydra-disable ()
+  "Disable the current Hydra."
+  (remove-hook 'pre-command-hook 'hydra--clearfun)
+  (dolist (frame (frame-list))
+    (with-selected-frame frame
+      (when overriding-terminal-local-map
+        (internal-pop-keymap hydra-curr-map 'overriding-terminal-local-map)
+        (unless hydra--ignore
+          (when hydra--input-method-function
+            (setq input-method-function hydra--input-method-function)
+            (setq hydra--input-method-function nil))
+          (when hydra-curr-on-exit
+            (let ((on-exit hydra-curr-on-exit))
+              (setq hydra-curr-on-exit nil)
+              (funcall on-exit))))))))
+
+(unless (fboundp 'internal-push-keymap)
+  (defun internal-push-keymap (keymap symbol)
+    (let ((map (symbol-value symbol)))
+      (unless (memq keymap map)
+        (unless (memq 'add-keymap-witness (symbol-value symbol))
+          (setq map (make-composed-keymap nil (symbol-value symbol)))
+          (push 'add-keymap-witness (cdr map))
+          (set symbol map))
+        (push keymap (cdr map))))))
+
+(unless (fboundp 'internal-pop-keymap)
+  (defun internal-pop-keymap (keymap symbol)
+    (let ((map (symbol-value symbol)))
+      (when (memq keymap map)
+        (setf (cdr map) (delq keymap (cdr map))))
+      (let ((tail (cddr map)))
+        (and (or (null tail) (keymapp tail))
+             (eq 'add-keymap-witness (nth 1 map))
+             (set symbol tail))))))
+
+(defun hydra-amaranth-warn ()
+  (interactive)
+  (message "An amaranth Hydra can only exit through a blue head"))
 
 ;;* Customize
 (defgroup hydra nil
@@ -109,11 +174,6 @@ This is a compatibility code for Emacs older than 24.4."
   :type 'boolean
   :group 'hydra)
 
-(defcustom hydra-keyboard-quit ""
-  "This binding will quit an amaranth Hydra.
-It's the only other way to quit it besides though a blue head.
-It's possible to set this to nil.")
-
 (defcustom hydra-lv t
   "When non-nil, `lv-message' (not `message') will be used to display hints."
   :type 'boolean)
@@ -128,27 +188,29 @@ When nil, you can specify your own at each location like 
this: _ 5a_.")
 
 (defface hydra-face-red
     '((t (:foreground "#FF0000" :bold t)))
-  "Red Hydra heads will persist indefinitely."
+  "Red Hydra heads don't exit the Hydra.
+Every other command exits the Hydra."
   :group 'hydra)
 
 (defface hydra-face-blue
     '((t (:foreground "#0000FF" :bold t)))
-  "Blue Hydra heads will vanquish the Hydra.")
+  "Blue Hydra heads exit the Hydra.
+Every other command exits as well.")
 
 (defface hydra-face-amaranth
     '((t (:foreground "#E52B50" :bold t)))
   "Amaranth body has red heads and warns on intercepting non-heads.
-Vanquishable only through a blue head.")
+Exitable only through a blue head.")
 
 (defface hydra-face-pink
     '((t (:foreground "#FF6EB4" :bold t)))
-  "Pink body has red heads and on intercepting non-heads calls them without 
quitting.
-Vanquishable only through a blue head.")
+  "Pink body has red heads and runs intercepted non-heads.
+Exitable only through a blue head.")
 
 (defface hydra-face-teal
     '((t (:foreground "#367588" :bold t)))
   "Teal body has blue heads an warns on intercepting non-heads.
-Vanquishable only through a blue head.")
+Exitable only through a blue head.")
 
 ;;* Fontification
 (defun hydra-add-font-lock ()
@@ -188,20 +250,9 @@ Vanquishable only through a blue head.")
     (define-key map [kp-8] 'hydra--digit-argument)
     (define-key map [kp-9] 'hydra--digit-argument)
     (define-key map [kp-subtract] 'hydra--negative-argument)
-    (define-key map [switch-frame] 'hydra--handle-switch-frame)
     map)
   "Keymap that all Hydras inherit.  See `universal-argument-map'.")
 
-(defvar hydra-curr-map
-  (make-sparse-keymap)
-  "Keymap of the current Hydra called.")
-
-(defun hydra--handle-switch-frame (evt)
-  "Quit hydra and call old switch-frame event handler for EVT."
-  (interactive "e")
-  (hydra-keyboard-quit)
-  (funcall (lookup-key (current-global-map) [switch-frame]) evt))
-
 (defun hydra--universal-argument (arg)
   "Forward to (`universal-argument' ARG)."
   (interactive "P")
@@ -209,20 +260,34 @@ Vanquishable only through a blue head.")
                        (list (* 4 (car arg)))
                      (if (eq arg '-)
                          (list -4)
-                       '(4))))
-  (hydra-set-transient-map hydra-curr-map t))
+                       '(4)))))
 
 (defun hydra--digit-argument (arg)
   "Forward to (`digit-argument' ARG)."
   (interactive "P")
-  (let ((universal-argument-map hydra-curr-map))
-    (digit-argument arg)))
+  (let* ((char (if (integerp last-command-event)
+                   last-command-event
+                 (get last-command-event 'ascii-character)))
+         (digit (- (logand char ?\177) ?0)))
+    (setq prefix-arg (cond ((integerp arg)
+                            (+ (* arg 10)
+                               (if (< arg 0)
+                                   (- digit)
+                                 digit)))
+                           ((eq arg '-)
+                            (if (zerop digit)
+                                '-
+                              (- digit)))
+                           (t
+                            digit)))))
 
 (defun hydra--negative-argument (arg)
   "Forward to (`negative-argument' ARG)."
   (interactive "P")
-  (let ((universal-argument-map hydra-curr-map))
-    (negative-argument arg)))
+  (setq prefix-arg (cond ((integerp arg) (- arg))
+                         ((eq arg '-) nil)
+                         (t '-))))
+
 ;;* Repeat
 (defvar hydra-repeat--prefix-arg nil
   "Prefix arg to use with `hydra-repeat'.")
@@ -243,9 +308,6 @@ When ARG is non-nil, use that instead."
   (funcall hydra-repeat--command))
 
 ;;* Misc internals
-(defvar hydra-last nil
-  "The result of the last `hydra-set-transient-map' call.")
-
 (defun hydra--callablep (x)
   "Test if X is callable."
   (or (functionp x)
@@ -278,72 +340,6 @@ one of the properties on the list."
 Return DEFAULT if PROP is not in H."
   (hydra-plist-get-default (cl-cdddr h) prop default))
 
-(defun hydra--aggregate-color (head-color body-color)
-  "Return the resulting head color for HEAD-COLOR and BODY-COLOR."
-  (cond ((eq head-color 'red)
-         (cl-case body-color
-           (red 'red)
-           (blue 'red)
-           (amaranth 'amaranth)
-           (pink 'pink)
-           (cyan 'amaranth)))
-        ((eq head-color 'blue)
-         (cl-case body-color
-           (red 'blue)
-           (blue 'blue)
-           (amaranth 'teal)
-           (pink 'blue)
-           (cyan 'teal)))
-        (t
-         (error "Can't aggregate head %S to body %S"
-                head-color body-color))))
-
-(defun hydra--head-color (h body)
-  "Return the color of a Hydra head H with BODY."
-  (let* ((exit (hydra--head-property h :exit 'default))
-         (color (hydra--head-property h :color))
-         (foreign-keys (hydra--body-foreign-keys body))
-         (head-color
-          (cond ((eq exit 'default)
-                 (cl-case color
-                   (blue 'blue)
-                   (red 'red)
-                   (t
-                    (unless (null color)
-                      (error "Use only :blue or :red for heads: %S" h)))))
-                ((null exit)
-                 (if color
-                     (error "Don't mix :color and :exit - they are aliases: 
%S" h)
-                   (cl-case foreign-keys
-                     (run 'pink)
-                     (warn 'amaranth)
-                     (t 'red))))
-                ((eq exit t)
-                 (if color
-                     (error "Don't mix :color and :exit - they are aliases: 
%S" h)
-                   'blue))
-                (t
-                 (error "Unknown :exit %S" exit)))))
-    (cond ((null (cadr h))
-           (when head-color
-             (hydra--complain
-              "Doubly specified blue head - nil cmd is already blue: %S" h))
-           'blue)
-          ((null head-color)
-           (hydra--body-color body))
-          ((null foreign-keys)
-           head-color)
-          ((eq foreign-keys 'run)
-           (if (eq head-color 'red)
-               'pink
-             'blue))
-          ((eq foreign-keys 'warn)
-           (if (memq head-color '(red amaranth))
-               'amaranth
-             'teal))
-          (t
-           (error "Unexpected %S %S" h body)))))
-
 (defun hydra--body-foreign-keys (body)
   "Return what BODY does with a non-head binding."
   (or
@@ -353,28 +349,14 @@ Return DEFAULT if PROP is not in H."
        ((amaranth teal) 'warn)
        (pink 'run)))))
 
-(defun hydra--body-color (body)
-  "Return the color of BODY.
-BODY is the second argument to `defhydra'"
-  (let ((color (plist-get (cddr body) :color))
-        (exit (plist-get (cddr body) :exit))
-        (foreign-keys (plist-get (cddr body) :foreign-keys)))
-    (cond ((eq foreign-keys 'warn)
-           (if exit 'teal 'amaranth))
-          ((eq foreign-keys 'run) 'pink)
-          (exit 'blue)
-          (color color)
-          (t 'red))))
-
-(defun hydra--face (h body)
-  "Return the face for a Hydra head H with BODY."
-  (cl-case (hydra--head-color h body)
-    (blue 'hydra-face-blue)
-    (red 'hydra-face-red)
-    (amaranth 'hydra-face-amaranth)
-    (pink 'hydra-face-pink)
-    (teal 'hydra-face-teal)
-    (t (error "Unknown color for %S" h))))
+(defun hydra--body-exit (body)
+  "Return the exit behavior of BODY."
+  (or
+   (plist-get (cddr body) :exit)
+   (let ((color (plist-get (cddr body) :color)))
+     (cl-case color
+       ((blue teal) t)
+       (t nil)))))
 
 (defvar hydra--input-method-function nil
   "Store overridden `input-method-function' here.")
@@ -386,58 +368,26 @@ BODY is the second argument to `defhydra'"
       (setq hydra--input-method-function input-method-function)
       (setq input-method-function nil))))
 
-(defun hydra-cleanup ()
-  "Clean up after a Hydra."
-  (when hydra--input-method-function
-    (setq input-method-function hydra--input-method-function)
-    (setq hydra--input-method-function nil))
-  (when (window-live-p lv-wnd)
-    (let ((buf (window-buffer lv-wnd)))
-      (delete-window lv-wnd)
-      (kill-buffer buf))))
-
-(defvar hydra-timer (timer-create)
+(defvar hydra-timeout-timer (timer-create)
   "Timer for `hydra-timeout'.")
 
+(defvar hydra-message-timer (timer-create)
+  "Timer for the hint.")
+
 (defun hydra-keyboard-quit ()
   "Quitting function similar to `keyboard-quit'."
   (interactive)
   (hydra-disable)
-  (hydra-cleanup)
-  (cancel-timer hydra-timer)
-  (unless hydra-lv
+  (cancel-timer hydra-timeout-timer)
+  (cancel-timer hydra-message-timer)
+  (if hydra-lv
+      (when (window-live-p lv-wnd)
+        (let ((buf (window-buffer lv-wnd)))
+          (delete-window lv-wnd)
+          (kill-buffer buf)))
     (message ""))
   nil)
 
-(defun hydra-disable ()
-  "Disable the current Hydra."
-  (cond
-    ;; Emacs 25
-    ((functionp hydra-last)
-     (funcall hydra-last))
-
-    ;; Emacs 24.3 or older
-    ((< emacs-minor-version 4)
-     (setq emulation-mode-map-alists
-           (cl-remove-if
-            (lambda (x)
-              (and (consp x)
-                   (consp (car x))
-                   (equal (cdar x) hydra-curr-map)))
-            emulation-mode-map-alists)))
-
-    ;; Emacs 24.4.1
-    (t
-     (setq overriding-terminal-local-map nil))))
-
-(defun hydra--unalias-var (str prefix)
-  "Return the symbol named STR if it's bound as a variable.
-Otherwise, add PREFIX to the symbol name."
-  (let ((sym (intern-soft str)))
-    (if (boundp sym)
-        sym
-      (intern (concat prefix "/" str)))))
-
 (defun hydra--hint (body heads)
   "Generate a hint for the echo area.
 BODY, and HEADS are parameters to `defhydra'."
@@ -469,16 +419,36 @@ BODY, and HEADS are parameters to `defhydra'."
 (defun hydra-fontify-head-default (head body)
   "Produce a pretty string from HEAD and BODY.
 HEAD's binding is returned as a string with a colored face."
-  (propertize (car head) 'face (hydra--face head body)))
+  (let* ((foreign-keys (hydra--body-foreign-keys body))
+         (head-exit (hydra--head-property head :exit))
+         (head-color
+          (if head-exit
+              (if (eq foreign-keys 'warn)
+                  'teal
+                'blue)
+            (cl-case foreign-keys
+              (warn 'amaranth)
+              (run 'pink)
+              (t 'red)))))
+    (when (and (null (cadr head))
+               (not (eq head-color 'blue)))
+      (hydra--complain "nil cmd can only be blue"))
+    (propertize (car head) 'face
+                (cl-case head-color
+                  (blue 'hydra-face-blue)
+                  (red 'hydra-face-red)
+                  (amaranth 'hydra-face-amaranth)
+                  (pink 'hydra-face-pink)
+                  (teal 'hydra-face-teal)
+                  (t (error "Unknown color for %S" head))))))
 
 (defun hydra-fontify-head-greyscale (head body)
   "Produce a pretty string from HEAD and BODY.
 HEAD's binding is returned as a string wrapped with [] or {}."
-  (let ((color (hydra--head-color head body)))
-    (format
-     (if (eq color 'blue)
-         "[%s]"
-       "{%s}") (car head))))
+  (format
+   (if (hydra--head-property head :exit)
+       "[%s]"
+     "{%s}") (car head)))
 
 (defun hydra-fontify-head (head body)
   "Produce a pretty string from HEAD and BODY."
@@ -497,7 +467,7 @@ The expressions can be auto-expanded according to NAME."
         offset)
     (while (setq start
                  (string-match
-                  "\\(?:%\\( 
?-?[0-9]*s?\\)\\(`[a-z-A-Z/0-9]+\\|(\\)\\)\\|\\(?:_\\( 
?-?[0-9]*\\)\\([a-z-A-Z~.,;:0-9/|?<>={}]+\\)_\\)"
+                  "\\(?:%\\( 
?-?[0-9]*s?\\)\\(`[a-z-A-Z/0-9]+\\|(\\)\\)\\|\\(?:_\\( 
?-?[0-9]*\\)\\([[:alnum:]-~.,;:/|?<>={}*+#]+\\)_\\)"
                   docstring start))
       (cond ((eq ?_ (aref (match-string 0 docstring) 0))
              (let* ((key (match-string 4 docstring))
@@ -537,15 +507,6 @@ The expressions can be auto-expanded according to NAME."
                  ,rest)
       `(format ,(concat docstring ": " rest ".")))))
 
-(defun hydra--message (name body docstring heads)
-  "Generate code to display the hint in the preferred echo area.
-Set `hydra-lv' to choose the echo area.
-NAME, BODY, DOCSTRING, and HEADS are parameters of `defhydra'."
-  (let ((format-expr (hydra--format name body docstring heads)))
-    `(if hydra-lv
-         (lv-message ,format-expr)
-       (message ,format-expr))))
-
 (defun hydra--complain (format-string &rest args)
   "Forward to (`message' FORMAT-STRING ARGS) unless `hydra-verbose' is nil."
   (when hydra-verbose
@@ -567,149 +528,89 @@ HEADS is a list of heads."
     heads ",\n")
    (format "The body can be accessed via `%S'." body-name)))
 
+(defun hydra--call-interactively (cmd name)
+  "Generate a `call-interactively' statement for CMD.
+Set `this-command' to NAME."
+  (if (and (symbolp name)
+           (not (memq name '(nil body))))
+      `(progn
+         (setq this-command ',name)
+         (call-interactively #',cmd))
+    `(call-interactively #',cmd)))
+
 (defun hydra--make-defun (name body doc head
-                          keymap body-pre body-post &optional other-post)
+                          keymap body-pre body-before-exit
+                          &optional body-after-exit)
   "Make a defun wrapper, using NAME, BODY, DOC, HEAD, and KEYMAP.
 NAME and BODY are the arguments to `defhydra'.
 DOC was generated with `hydra--doc'.
 HEAD is one of the HEADS passed to `defhydra'.
-BODY-PRE and BODY-POST are pre-processed in `defhydra'.
-OTHER-POST is an optional extension to the :post key of BODY."
+BODY-PRE is added to the start of the wrapper.
+BODY-BEFORE-EXIT will be called before the hydra quits.
+BODY-AFTER-EXIT is added to the end of the wrapper."
   (let ((name (hydra--head-name head name body))
         (cmd (when (car head)
                (hydra--make-callable
                 (cadr head))))
-        (color (when (car head)
-                 (hydra--head-color head body)))
         (doc (if (car head)
                  (format "%s\n\nCall the head: `%S'." doc (cadr head))
                doc))
         (hint (intern (format "%S/hint" name)))
-        (body-color (hydra--body-color body))
-        (body-timeout (plist-get body :timeout)))
+        (body-foreign-keys (hydra--body-foreign-keys body))
+        (body-timeout (plist-get body :timeout))
+        (body-idle (plist-get body :idle)))
     `(defun ,name ()
        ,doc
        (interactive)
        (hydra-default-pre)
        ,@(when body-pre (list body-pre))
-       (hydra-disable)
-       ,@(when (memq color '(blue teal)) '((hydra-cleanup)))
-       (catch 'hydra-disable
-         ,@(delq nil
-                 (if (memq color '(blue teal))
-                     `(,(when cmd `(call-interactively #',cmd))
-                        ,body-post)
-                   `(,(when cmd
-                            `(condition-case err
-                                 (prog1 t
-                                   (call-interactively #',cmd))
-                               ((quit error)
-                                (message "%S" err)
-                                (unless hydra-lv
-                                  (sit-for 0.8))
-                                nil)))
-                      (when hydra-is-helpful
-                        (,hint))
-                      (setq hydra-last
-                            (hydra-set-transient-map
-                             (setq hydra-curr-map ',keymap)
-                             t
-                             ,(if (and
-                                   (not (memq body-color
-                                              '(amaranth pink teal)))
-                                   body-post)
-                                  `(lambda () (hydra-cleanup) ,body-post)
-                                  `(lambda () (hydra-cleanup)))))
-                      ,(or other-post
-                           (when body-timeout
-                             (list 'hydra-timeout
-                                   body-timeout
-                                   (when body-post
-                                     (hydra--make-callable 
body-post))))))))))))
-
-(defun hydra-pink-fallback ()
-  "On intercepting a non-head, try to run it."
-  (let ((keys (this-command-keys))
-        kb)
-    (when (equal keys [backspace])
-      (setq keys ""))
-    (setq kb (key-binding keys))
-    (if kb
-        (if (commandp kb)
-            (condition-case err
-                (call-interactively kb)
-              ((quit error)
-               (message "%S" err)
-               (unless hydra-lv
-                 (sit-for 0.8))))
-          (message "Pink Hydra can't currently handle prefixes, continuing"))
-      (message "Pink Hydra could not resolve: %S" keys))))
-
-(defun hydra--modify-keymap (keymap def)
-  "In KEYMAP, add DEF to each sub-keymap."
-  (cl-labels
-      ((recur (map)
-         (if (atom map)
-             map
-           (if (eq (car map) 'keymap)
-               (cons 'keymap
-                     (cons
-                      def
-                      (recur (cdr map))))
-             (cons
-              (recur (car map))
-              (recur (cdr map)))))))
-    (recur keymap)))
+       ,@(if (hydra--head-property head :exit)
+             `((hydra-keyboard-quit)
+               ,@(if body-after-exit
+                     `((unwind-protect
+                            ,(when cmd
+                                   (hydra--call-interactively cmd (cadr head)))
+                         ,body-after-exit))
+                     (when cmd
+                       `(,(hydra--call-interactively cmd (cadr head))))))
+             (delq
+              nil
+              `((let ((hydra--ignore ,(not (eq (cadr head) 'body))))
+                  (hydra-keyboard-quit))
+                ,(when cmd
+                       `(condition-case err
+                            ,(hydra--call-interactively cmd (cadr head))
+                          ((quit error)
+                           (message "%S" err)
+                           (unless hydra-lv
+                             (sit-for 0.8)))))
+                ,(if (and body-idle (eq (cadr head) 'body))
+                     `(hydra-idle-message ,body-idle ,hint)
+                     `(when hydra-is-helpful
+                        (if hydra-lv
+                            (lv-message (eval ,hint))
+                          (message (eval ,hint)))))
+                (hydra-set-transient-map
+                 ,keymap
+                 (lambda () (hydra-keyboard-quit) ,body-before-exit)
+                 ,(when body-foreign-keys
+                        (list 'quote body-foreign-keys)))
+                ,body-after-exit
+                ,(when body-timeout
+                       `(hydra-timeout ,body-timeout))))))))
 
 (defmacro hydra--make-funcall (sym)
-  "Transform SYM into a `funcall' that calls it."
+  "Transform SYM into a `funcall' to call it."
   `(when (and ,sym (symbolp ,sym))
      (setq ,sym `(funcall #',,sym))))
 
-(defun hydra--handle-nonhead (keymap name body heads)
-  "Setup KEYMAP for intercepting non-head bindings.
-NAME, BODY and HEADS are parameters to `defhydra'."
-  (let ((body-color (hydra--body-color body))
-        (body-post (plist-get (cddr body) :post)))
-    (if body-post
-        (hydra--make-funcall body-post)
-      (when hydra-keyboard-quit
-        (define-key keymap hydra-keyboard-quit #'hydra-keyboard-quit)))
-    (when (memq body-color '(amaranth pink teal))
-      (if (cl-some (lambda (h)
-                     (memq (hydra--head-color h body) '(blue teal)))
-                   heads)
-          (progn
-            (setcdr
-             keymap
-             (cdr
-              (hydra--modify-keymap
-               keymap
-               (cons t
-                     `(lambda ()
-                        (interactive)
-                        ,(cond
-                          ((memq body-color '(amaranth teal))
-                           '(message "An amaranth Hydra can only exit through 
a blue head"))
-                          (t
-                           '(hydra-pink-fallback)))
-                        (hydra-set-transient-map hydra-curr-map t)
-                        (when hydra-is-helpful
-                          (unless hydra-lv
-                            (sit-for 0.8))
-                          (,(intern (format "%S/hint" name))))))))))
-        (unless (eq body-color 'teal)
-          (error
-           "An %S Hydra must have at least one blue head in order to exit"
-           body-color))))))
-
 (defun hydra--head-name (h name body)
   "Return the symbol for head H of hydra with NAME and BODY."
   (let ((str (format "%S/%s" name
                      (if (symbolp (cadr h))
                          (cadr h)
                        (concat "lambda-" (car h))))))
-    (when (and (memq (hydra--head-color h body) '(blue teal))
+    (when (and (hydra--head-property h :exit)
                (not (memq (cadr h) '(body nil))))
       (setq str (concat str "-and-exit")))
     (intern str)))
@@ -717,15 +618,15 @@ NAME, BODY and HEADS are parameters to `defhydra'."
 (defun hydra--delete-duplicates (heads)
   "Return HEADS without entries that have the same CMD part.
 In duplicate HEADS, :cmd-name is modified to whatever they duplicate."
-  (let ((ali '(((hydra-repeat . red) . hydra-repeat)))
+  (let ((ali '(((hydra-repeat . nil) . hydra-repeat)))
         res entry)
     (dolist (h heads)
       (if (setq entry (assoc (cons (cadr h)
-                                   (hydra--head-color h '(nil nil)))
+                                   (hydra--head-property h :exit))
                              ali))
           (setf (cl-cdddr h) (plist-put (cl-cdddr h) :cmd-name (cdr entry)))
         (push (cons (cons (cadr h)
-                          (hydra--head-color h '(nil nil)))
+                          (hydra--head-property h :exit))
                     (plist-get (cl-cdddr h) :cmd-name))
               ali)
         (push h res)))
@@ -824,20 +725,36 @@ NAMES should be defined by `defhydradio' or similar."
   (dolist (n names)
     (set n (aref (get n 'range) 0))))
 
+(defun hydra-idle-message (secs hint)
+  "In SECS seconds display HINT."
+  (cancel-timer hydra-message-timer)
+  (setq hydra-message-timer (timer-create))
+  (timer-set-time hydra-message-timer
+                  (timer-relative-time (current-time) secs))
+  (timer-set-function
+   hydra-message-timer
+   (lambda ()
+     (when hydra-is-helpful
+       (if hydra-lv
+           (lv-message (eval hint))
+         (message (eval hint))))
+     (cancel-timer hydra-message-timer)))
+  (timer-activate hydra-message-timer))
+
 (defun hydra-timeout (secs &optional function)
   "In SECS seconds call FUNCTION, then function `hydra-keyboard-quit'.
 Cancel the previous `hydra-timeout'."
-  (cancel-timer hydra-timer)
-  (setq hydra-timer (timer-create))
-  (timer-set-time hydra-timer
+  (cancel-timer hydra-timeout-timer)
+  (setq hydra-timeout-timer (timer-create))
+  (timer-set-time hydra-timeout-timer
                   (timer-relative-time (current-time) secs))
   (timer-set-function
-   hydra-timer
+   hydra-timeout-timer
    `(lambda ()
       ,(when function
              `(funcall ,function))
       (hydra-keyboard-quit)))
-  (timer-activate hydra-timer))
+  (timer-activate hydra-timeout-timer))
 
 ;;* Macros
 ;;;###autoload
@@ -864,7 +781,7 @@ BODY-MAP is a keymap; `global-map' is used quite often.  
Each
 function generated from HEADS will be bound in BODY-MAP to
 BODY-KEY + KEY (both are strings passed to `kbd'), and will set
 the transient map so that all following heads can be called
-though KEY only. BODY-KEY can be an empty string.
+though KEY only.  BODY-KEY can be an empty string.
 
 CMD is a callable expression: either an interactive function
 name, or an interactive lambda, or a single sexp (it will be
@@ -900,94 +817,144 @@ result of `defhydra'."
          (setq docstring "hydra")))
   (when (keywordp (car body))
     (setq body (cons nil (cons nil body))))
-  (let* ((keymap (copy-keymap hydra-base-map))
-         (body-name (intern (format "%S/body" name)))
-         (body-key (cadr body))
-         (body-plist (cddr body))
-         (body-map (or (car body)
-                       (plist-get body-plist :bind)))
-         (body-pre (plist-get body-plist :pre))
-         (body-body-pre (plist-get body-plist :body-pre))
-         (body-post (plist-get body-plist :post)))
-    (hydra--make-funcall body-post)
-    (when body-post
-      (setq heads (cons (list hydra-keyboard-quit #'hydra-keyboard-quit nil 
:exit t)
-                        heads)))
-    (dolist (h heads)
-      (let ((len (length h)))
-        (cond ((< len 2)
-               (error "Each head should have at least two items: %S" h))
-              ((= len 2)
-               (setcdr (cdr h)
-                       (list
-                        (hydra-plist-get-default body-plist :hint "")))
-               (setcdr (nthcdr 2 h)
-                       (list :cmd-name (hydra--head-name h name body))))
-              (t
-               (let ((hint (cl-caddr h)))
-                 (unless (or (null hint)
-                             (stringp hint))
-                   (setcdr (cdr h) (cons
-                                    (hydra-plist-get-default body-plist :hint 
"")
-                                    (cddr h))))
-                 (setcdr (cddr h)
-                         `(:cmd-name
-                           ,(hydra--head-name h name body)
-                           ,@(cl-cdddr h))))))))
-    (let ((doc (hydra--doc body-key body-name heads))
-          (heads-nodup (hydra--delete-duplicates heads)))
-      (mapc
-       (lambda (x)
-         (define-key keymap (kbd (car x))
-           (plist-get (cl-cdddr x) :cmd-name)))
-       heads)
-      (hydra--make-funcall body-pre)
-      (hydra--make-funcall body-body-pre)
-      (hydra--handle-nonhead keymap name body heads)
-      `(progn
-         ;; create defuns
-         ,@(mapcar
-            (lambda (head)
-              (hydra--make-defun name body doc head keymap
-                                 body-pre body-post))
-            heads-nodup)
-         ;; free up keymap prefix
-         ,@(unless (or (null body-key)
-                       (null body-map)
-                       (hydra--callablep body-map))
-                   `((unless (keymapp (lookup-key ,body-map (kbd ,body-key)))
-                       (define-key ,body-map (kbd ,body-key) nil))))
-         ;; bind keys
-         ,@(delq nil
-                 (mapcar
-                  (lambda (head)
-                    (let ((name (hydra--head-property head :cmd-name)))
-                      (when (and (cadr head)
-                                 (not (eq (cadr head) 'hydra-keyboard-quit))
-                                 (or body-key body-map))
-                        (let ((bind (hydra--head-property head :bind body-map))
-                              (final-key
-                               (if body-key
-                                   (vconcat (kbd body-key) (kbd (car head)))
-                                 (kbd (car head)))))
-                          (cond ((null bind) nil)
-                                ((hydra--callablep bind)
-                                 `(funcall ,bind ,final-key (function ,name)))
-                                ((and (symbolp bind)
-                                      (if (boundp bind)
-                                          (keymapp (symbol-value bind))
-                                        t))
-                                 `(define-key ,bind ,final-key (function 
,name)))
-                                (t
-                                 (error "Invalid :bind property `%S' for head 
%S" bind  head)))))))
-                  heads))
-         (defun ,(intern (format "%S/hint" name)) ()
-           ,(hydra--message name body docstring heads))
-         ,(hydra--make-defun
-           name body doc '(nil body)
-           keymap
-           (or body-body-pre body-pre) body-post
-           '(setq prefix-arg current-prefix-arg))))))
+  (condition-case err
+      (let* ((keymap (copy-keymap hydra-base-map))
+             (keymap-name (intern (format "%S/keymap" name)))
+             (body-name (intern (format "%S/body" name)))
+             (body-key (cadr body))
+             (body-plist (cddr body))
+             (body-map (or (car body)
+                           (plist-get body-plist :bind)))
+             (body-pre (plist-get body-plist :pre))
+             (body-body-pre (plist-get body-plist :body-pre))
+             (body-before-exit (or (plist-get body-plist :post)
+                                   (plist-get body-plist :before-exit)))
+             (body-after-exit (plist-get body-plist :after-exit))
+             (body-inherit (plist-get body-plist :inherit))
+             (body-foreign-keys (hydra--body-foreign-keys body))
+             (body-exit (hydra--body-exit body)))
+        (dolist (base body-inherit)
+          (setq heads (append heads (copy-sequence (eval base)))))
+        (dolist (h heads)
+          (let ((len (length h)))
+            (cond ((< len 2)
+                   (error "Each head should have at least two items: %S" h))
+                  ((= len 2)
+                   (setcdr (cdr h)
+                           (list
+                            (hydra-plist-get-default body-plist :hint "")))
+                   (setcdr (nthcdr 2 h) (list :exit body-exit)))
+                  (t
+                   (let ((hint (cl-caddr h)))
+                     (unless (or (null hint)
+                                 (stringp hint))
+                       (setcdr (cdr h) (cons
+                                        (hydra-plist-get-default body-plist 
:hint "")
+                                        (cddr h)))))
+                   (let ((hint-and-plist (cddr h)))
+                     (if (null (cdr hint-and-plist))
+                         (setcdr hint-and-plist (list :exit body-exit))
+                       (let* ((plist (cl-cdddr h))
+                              (h-color (plist-get plist :color)))
+                         (if h-color
+                             (progn
+                               (plist-put plist :exit
+                                          (cl-case h-color
+                                            ((blue teal) t)
+                                            (t nil)))
+                               (cl-remf (cl-cdddr h) :color))
+                           (let ((h-exit (hydra-plist-get-default plist :exit 
'default)))
+                             (plist-put plist :exit
+                                        (if (eq h-exit 'default)
+                                            body-exit
+                                          h-exit))))))))))
+          (plist-put (cl-cdddr h) :cmd-name (hydra--head-name h name body))
+          (when (null (cadr h)) (plist-put (cl-cdddr h) :exit t)))
+        (let ((doc (hydra--doc body-key body-name heads))
+              (heads-nodup (hydra--delete-duplicates heads)))
+          (mapc
+           (lambda (x)
+             (define-key keymap (kbd (car x))
+               (plist-get (cl-cdddr x) :cmd-name)))
+           heads)
+          (hydra--make-funcall body-pre)
+          (hydra--make-funcall body-body-pre)
+          (hydra--make-funcall body-before-exit)
+          (hydra--make-funcall body-after-exit)
+          (when (memq body-foreign-keys '(run warn))
+            (unless (cl-some
+                     (lambda (h)
+                       (hydra--head-property h :exit))
+                     heads)
+              (error
+               "An %S Hydra must have at least one blue head in order to exit"
+               body-foreign-keys)))
+          `(progn
+             ;; create keymap
+             (set (defvar ,keymap-name
+                    nil
+                    ,(format "Keymap for %S." name))
+                  ',keymap)
+             ;; declare heads
+             (set (defvar ,(intern (format "%S/heads" name))
+                    nil
+                    ,(format "Heads for %S." name))
+                  ',(mapcar (lambda (h)
+                              (let ((j (copy-sequence h)))
+                                (cl-remf (cl-cdddr j) :cmd-name)
+                                j))
+                            heads))
+             ;; create defuns
+             ,@(mapcar
+                (lambda (head)
+                  (hydra--make-defun name body doc head keymap-name
+                                     body-pre
+                                     body-before-exit
+                                     body-after-exit))
+                heads-nodup)
+             ;; free up keymap prefix
+             ,@(unless (or (null body-key)
+                           (null body-map)
+                           (hydra--callablep body-map))
+                       `((unless (keymapp (lookup-key ,body-map (kbd 
,body-key)))
+                           (define-key ,body-map (kbd ,body-key) nil))))
+             ;; bind keys
+             ,@(delq nil
+                     (mapcar
+                      (lambda (head)
+                        (let ((name (hydra--head-property head :cmd-name)))
+                          (when (and (cadr head)
+                                     (or body-key body-map))
+                            (let ((bind (hydra--head-property head :bind 
body-map))
+                                  (final-key
+                                   (if body-key
+                                       (vconcat (kbd body-key) (kbd (car 
head)))
+                                     (kbd (car head)))))
+                              (cond ((null bind) nil)
+                                    ((hydra--callablep bind)
+                                     `(funcall ,bind ,final-key (function 
,name)))
+                                    ((and (symbolp bind)
+                                          (if (boundp bind)
+                                              (keymapp (symbol-value bind))
+                                            t))
+                                     `(define-key ,bind ,final-key (function 
,name)))
+                                    (t
+                                     (error "Invalid :bind property `%S' for 
head %S" bind head)))))))
+                      heads))
+             (set
+              (defvar ,(intern (format "%S/hint" name)) nil
+                ,(format "Dynamic hint for %S." name))
+              ',(hydra--format name body docstring heads))
+             ,(hydra--make-defun
+               name body doc '(nil body)
+               keymap-name
+               (or body-body-pre body-pre) body-before-exit
+               '(setq prefix-arg current-prefix-arg)))))
+    (error
+     (if debug-on-error
+         (signal (car err) (cdr err))
+       (message "Error in defhydra %S: %s" name (cdr err)))
+     nil)))
 
 (defmacro defhydradio (name _body &rest heads)
   "Create radios with prefix NAME.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]