Skip to content

Commit 6f142e3

Browse files
committed
Distinguish red and blue hydra heads
* hydra.el (hydra-face-red): New face. (hydra-face-blue): New face. (hydra--color): Each head now has a color: red is persistent, blue is single-use. Head color inherits body color if it's not explicitly overridden. Body color is red unless explicitly stated. (hydra--face): Return face that corresponds to color. (hydra--hint): New function, moved out of `defhydra'. (hydra-disable): New function, moved out of `defhydra'. (hydra--doc): New function, moved out of `defhydra'. (defhydra): Commands that will vanquish the Hydra should be colored with `hydra-face-blue'. The ones that will make the Hydra persist should be colored with `hydra-face-red'. Add autoload, move some code outside, Test HEAD's second element with `null' instead of `functionp'. * hydra-test.el (defhydra-red-error): Rename from `defhydra'. (hydra-blue-toggle): Add test. * README.md: Update. Example: (global-set-key (kbd "C-c C-v") (defhydra toggle () "toggle" ("t" toggle-truncate-lines "truncate" :color blue) ("f" auto-fill-mode "fill" :color blue) ("a" abbrev-mode "abbrev" :color blue) ("q" nil "cancel"))) Alternatively, since heads inherit color from the body: (global-set-key (kbd "C-c C-v") (defhydra toggle (:color blue) "toggle" ("a" abbrev-mode "abbrev") ("d" toggle-debug-on-error "debug") ("f" auto-fill-mode "fill") ("t" toggle-truncate-lines "truncate") ("w" whitespace-mode "whitespace") ("q" nil "cancel")))
1 parent b359db6 commit 6f142e3

File tree

3 files changed

+241
-37
lines changed

3 files changed

+241
-37
lines changed

README.md

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This is a package for GNU Emacs that can be used to tie related
44
commands into a family of short bindings with a common prefix - a
55
Hydra.
66

7+
![hydra](http://oremacs.com/download/Hydra.png)
8+
79
Once you summon the Hydra through the prefixed binding (the body + any
810
one head), all heads can be called in succession with only a short
911
extension.
@@ -14,6 +16,8 @@ Hydra, will still serve his orignal purpose, calling his proper
1416
command. This makes the Hydra very seamless, it's like a minor mode
1517
that disables itself auto-magically.
1618

19+
## Simplified usage
20+
1721
Here's how to quickly bind the examples bundled with Hydra:
1822

1923
```cl
@@ -23,6 +27,8 @@ Here's how to quickly bind the examples bundled with Hydra:
2327
(hydra-create "<f2>" hydra-example-text-scale)
2428
```
2529

30+
## Using Hydra for global bindings
31+
2632
But it's much better to just take the examples as a template and write
2733
down everything explicitly:
2834

@@ -51,7 +57,8 @@ it like this:
5157
("l" text-scale-decrease "out")))
5258
```
5359

54-
If you like key chords so much that you don't want to touch the global map at all, you can e.g.:
60+
If you like key chords so much that you don't want to touch the global
61+
map at all, you can e.g.:
5562

5663
```
5764
(key-chord-define-global
@@ -68,9 +75,7 @@ You can also substitute `global-map` with any other keymap, like
6875

6976
See the [introductory blog post](http://oremacs.com/2015/01/20/introducing-hydra/) for more information.
7077

71-
![hydra](http://oremacs.com/download/Hydra.png)
72-
73-
## Using Hydra to define bindings other than global ones
78+
## Using Hydra for major-mode or minor-mode bindings
7479

7580
Here's an example:
7681

@@ -104,3 +109,57 @@ can even add comments to the heads like this:
104109

105110
With this, you'll see `zoom: [g]: in, [l]: out.` in your echo area,
106111
once the zoom Hydra becomes active.
112+
113+
## Colorful Hydras
114+
115+
Since version `0.5.0`, Hydra's heads all have a color associated with them:
116+
117+
- *red* (default) means the calling this head will not vanquish the Hydra
118+
- *blue* means that the Hydra will be vanquished after calling this head
119+
120+
In all the older examples, all heads are red by default. You can specify blue heads like this:
121+
122+
```cl
123+
(global-set-key
124+
(kbd "C-c C-v")
125+
(defhydra toggle ()
126+
"toggle"
127+
("a" abbrev-mode "abbrev" :color blue)
128+
("d" toggle-debug-on-error "debug" :color blue)
129+
("f" auto-fill-mode "fill" :color blue)
130+
("t" toggle-truncate-lines "truncate" :color blue)
131+
("w" whitespace-mode "whitespace" :color blue)
132+
("q" nil "cancel")))
133+
```
134+
135+
Or, since the heads can inherit the color from the body, the following is equivalent:
136+
137+
```cl
138+
(global-set-key
139+
(kbd "C-c C-v")
140+
(defhydra toggle (:color blue)
141+
"toggle"
142+
("a" abbrev-mode "abbrev")
143+
("d" toggle-debug-on-error "debug")
144+
("f" auto-fill-mode "fill")
145+
("t" toggle-truncate-lines "truncate")
146+
("w" whitespace-mode "whitespace")
147+
("q" nil "cancel")))
148+
```
149+
150+
The above Hydra is very similar to this code:
151+
152+
```cl
153+
(global-set-key (kbd "C-c C-v t") 'toggle-truncate-lines)
154+
(global-set-key (kbd "C-c C-v f") 'auto-fill-mode)
155+
(global-set-key (kbd "C-c C-v a") 'abbrev-mode)
156+
```
157+
158+
However, there are two important differences:
159+
160+
- you get a hint like this right after <kbd>C-c C-v</kbd>:
161+
162+
toggle: [t]: truncate, [f]: fill, [a]: abbrev, [q]: cancel.
163+
164+
- you can cancel <kbd>C-c C-v</kbd> with a command while executing that command, instead of e.g.
165+
getting an error `C-c C-v C-n is undefined` for <kbd>C-c C-v C-n</kbd>.

hydra-test.el

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
(require 'ert)
22

3-
(ert-deftest defhydra ()
3+
(ert-deftest defhydra-red-error ()
44
(should
55
(equal
66
(macroexpand
@@ -109,4 +109,94 @@ The body can be accessed via `hydra-error/body'."
109109
(106 . hydra-error/next-error)
110110
(104 . hydra-error/first-error)) t)))))))
111111

112+
(ert-deftest hydra-blue-toggle ()
113+
(should
114+
(equal
115+
(macroexpand
116+
'(defhydra toggle (:color blue)
117+
"toggle"
118+
("t" toggle-truncate-lines "truncate")
119+
("f" auto-fill-mode "fill")
120+
("a" abbrev-mode "abbrev")
121+
("q" nil "cancel")))
122+
'(progn
123+
(defun toggle/toggle-truncate-lines ()
124+
"Create a hydra with no body and the heads:
125+
126+
\"t\": `toggle-truncate-lines',
127+
\"f\": `auto-fill-mode',
128+
\"a\": `abbrev-mode',
129+
\"q\": `nil'
130+
131+
The body can be accessed via `toggle/body'.
132+
133+
Call the head: `toggle-truncate-lines'."
134+
(interactive)
135+
(hydra-disable)
136+
(call-interactively #'toggle-truncate-lines))
137+
(defun toggle/auto-fill-mode ()
138+
"Create a hydra with no body and the heads:
139+
140+
\"t\": `toggle-truncate-lines',
141+
\"f\": `auto-fill-mode',
142+
\"a\": `abbrev-mode',
143+
\"q\": `nil'
144+
145+
The body can be accessed via `toggle/body'.
146+
147+
Call the head: `auto-fill-mode'."
148+
(interactive)
149+
(hydra-disable)
150+
(call-interactively #'auto-fill-mode))
151+
(defun toggle/abbrev-mode ()
152+
"Create a hydra with no body and the heads:
153+
154+
\"t\": `toggle-truncate-lines',
155+
\"f\": `auto-fill-mode',
156+
\"a\": `abbrev-mode',
157+
\"q\": `nil'
158+
159+
The body can be accessed via `toggle/body'.
160+
161+
Call the head: `abbrev-mode'."
162+
(interactive)
163+
(hydra-disable)
164+
(call-interactively #'abbrev-mode))
165+
(defun toggle/nil ()
166+
"Create a hydra with no body and the heads:
167+
168+
\"t\": `toggle-truncate-lines',
169+
\"f\": `auto-fill-mode',
170+
\"a\": `abbrev-mode',
171+
\"q\": `nil'
172+
173+
The body can be accessed via `toggle/body'.
174+
175+
Call the head: `nil'."
176+
(interactive)
177+
(hydra-disable))
178+
(defun toggle/body ()
179+
"Create a hydra with no body and the heads:
180+
181+
\"t\": `toggle-truncate-lines',
182+
\"f\": `auto-fill-mode',
183+
\"a\": `abbrev-mode',
184+
\"q\": `nil'
185+
186+
The body can be accessed via `toggle/body'."
187+
(interactive)
188+
(when hydra-is-helpful
189+
(message #("toggle: [t]: truncate, [f]: fill, [a]: abbrev, [q]: cancel."
190+
9 10 (face hydra-face-blue)
191+
24 25 (face hydra-face-blue)
192+
35 36 (face hydra-face-blue)
193+
48 49 (face hydra-face-blue))))
194+
(setq hydra-last
195+
(hydra-set-transient-map
196+
'(keymap (113 . toggle/nil)
197+
(97 . toggle/abbrev-mode)
198+
(102 . toggle/auto-fill-mode)
199+
(116 . toggle/toggle-truncate-lines))
200+
t)))))))
201+
112202
(provide 'hydra-test)

hydra.el

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
;; Author: Oleh Krehel <[email protected]>
66
;; Maintainer: Oleh Krehel <[email protected]>
77
;; URL: https://github.com/abo-abo/hydra
8-
;; Version: 0.4.1
8+
;; Version: 0.5.0
99
;; Keywords: bindings
1010
;; Package-Requires: ((cl-lib "0.5"))
1111

@@ -73,6 +73,15 @@
7373
:type 'boolean
7474
:group 'hydra)
7575

76+
(defface hydra-face-red
77+
'((t (:foreground "#7F0055" :bold t)))
78+
"Red Hydra heads will persist indefinitely."
79+
:group 'hydra)
80+
81+
(defface hydra-face-blue
82+
'((t (:foreground "#758BC6" :bold t)))
83+
"Blue Hydra heads will vanquish the Hydra.")
84+
7685
(defalias 'hydra-set-transient-map
7786
(if (fboundp 'set-transient-map)
7887
'set-transient-map
@@ -111,11 +120,70 @@ When `(keymapp METHOD)`, it becomes:
111120
,@(eval heads)))
112121

113122
(defun hydra--callablep (x)
114-
"Test if X looks like it's callable."
123+
"Test if X is callable."
115124
(or (functionp x)
116125
(and (consp x)
117126
(memq (car x) '(function quote)))))
118127

128+
(defun hydra--color (h body-color)
129+
"Return the color of a Hydra head H with BODY-COLOR."
130+
(if (null (cadr h))
131+
'blue
132+
(let ((plist (if (stringp (cl-caddr h))
133+
(cl-cdddr h)
134+
(cddr h))))
135+
(or (plist-get plist :color) body-color))))
136+
137+
(defun hydra--face (h body-color)
138+
"Return the face for a Hydra head H with BODY-COLOR."
139+
(cl-case (hydra--color h body-color)
140+
(blue 'hydra-face-blue)
141+
(red 'hydra-face-red)
142+
(t (error "Unknown color for %S" h))))
143+
144+
(defun hydra--hint (docstring heads)
145+
"Generate a hint from DOCSTRING and HEADS.
146+
It's intended for the echo area, when a Hydra is active."
147+
(format "%s: %s."
148+
docstring
149+
(mapconcat
150+
(lambda (h)
151+
(format
152+
(if (stringp (cl-caddr h))
153+
(concat "[%s]: " (cl-caddr h))
154+
"%s")
155+
(propertize
156+
(car h) 'face
157+
(hydra--face h body-color))))
158+
heads ", ")))
159+
160+
(defun hydra-disable ()
161+
"Disable the current Hydra."
162+
(if (functionp hydra-last)
163+
(funcall hydra-last)
164+
(while (and (consp (car emulation-mode-map-alists))
165+
(consp (caar emulation-mode-map-alists))
166+
(equal (cl-cdaar emulation-mode-map-alists) ',keymap))
167+
(setq emulation-mode-map-alists
168+
(cdr emulation-mode-map-alists)))))
169+
170+
(defun hydra--doc (body-key body-name heads)
171+
"Generate a part of Hydra docstring.
172+
BODY-KEY is the body key binding.
173+
BODY-NAME is the symbol that identifies the Hydra.
174+
HEADS is a list of heads."
175+
(format
176+
"Create a hydra with %s body and the heads:\n\n%s\n\n%s"
177+
(if body-key
178+
(format "a \"%s\"" body-key)
179+
"no")
180+
(mapconcat
181+
(lambda (x)
182+
(format "\"%s\": `%S'" (car x) (cadr x)))
183+
heads ",\n")
184+
(format "The body can be accessed via `%S'." body-name)))
185+
186+
;;;###autoload
119187
(defmacro defhydra (name body &optional docstring &rest heads)
120188
"Create a hydra named NAME with a prefix BODY.
121189
@@ -124,7 +192,7 @@ defined here.
124192
125193
BODY should be either:
126194
127-
(BODY-MAP &optional BODY-KEY)
195+
(BODY-MAP &optional BODY-KEY &rest PLIST)
128196
or:
129197
130198
(lambda (KEY CMD) ...)
@@ -135,10 +203,15 @@ BODY-KEY should be a string processable by `kbd'.
135203
DOCSTRING will be displayed in the echo area to identify the
136204
hydra.
137205
138-
HEADS is a list of (KEY CMD &optional HINT)."
206+
HEADS is a list of (KEY CMD &optional HINT &rest PLIST).
207+
208+
PLIST in both cases recognizes only the :color key so far, which
209+
in turn can be either red or blue."
139210
(unless (stringp docstring)
140211
(setq heads (cons docstring heads))
141212
(setq docstring "hydra"))
213+
(when (keywordp (car body))
214+
(setq body (cons nil (cons nil body))))
142215
(let* ((keymap (make-sparse-keymap))
143216
(names (mapcar
144217
(lambda (x)
@@ -148,43 +221,25 @@ HEADS is a list of (KEY CMD &optional HINT)."
148221
(body-name (intern (format "%S/body" name)))
149222
(body-key (unless (hydra--callablep body)
150223
(cadr body)))
224+
(body-color (if (hydra--callablep body)
225+
'red
226+
(or (plist-get (cddr body) :color)
227+
'red)))
151228
(method (if (hydra--callablep body)
152229
body
153230
(car body)))
154-
(hint (format "%s: %s."
155-
docstring
156-
(mapconcat
157-
(lambda (h)
158-
(format
159-
(if (cl-caddr h)
160-
(concat "[%s]: " (cl-caddr h))
161-
"%s")
162-
(propertize (car h) 'face 'font-lock-keyword-face)))
163-
heads ", ")))
164-
(doc (format
165-
"Create a hydra with %s body and the heads:\n\n%s\n\n%s"
166-
(if body-key
167-
(format "a \"%s\"" body-key)
168-
"no")
169-
(mapconcat
170-
(lambda (x)
171-
(format "\"%s\": `%S'" (car x) (cadr x)))
172-
heads ",\n")
173-
(format "The body can be accessed via `%S'." body-name))))
231+
(hint (hydra--hint docstring heads))
232+
(doc (hydra--doc body-key body-name heads)))
174233
`(progn
175234
,@(cl-mapcar
176235
(lambda (head name)
177236
`(defun ,name ()
178237
,(format "%s\n\nCall the head: `%S'." doc (cadr head))
179238
(interactive)
180-
,@(if (null (cadr head))
181-
`((if (functionp hydra-last)
182-
(funcall hydra-last)
183-
(while (and (consp (car emulation-mode-map-alists))
184-
(consp (caar emulation-mode-map-alists))
185-
(equal (cl-cdaar emulation-mode-map-alists) ',keymap))
186-
(setq emulation-mode-map-alists
187-
(cdr emulation-mode-map-alists)))))
239+
,@(if (eq (hydra--color head body-color) 'blue)
240+
`((hydra-disable)
241+
,@(unless (null (cadr head))
242+
`((call-interactively #',(cadr head)))))
188243
`((call-interactively #',(cadr head))
189244
(when hydra-is-helpful
190245
(message ,hint))

0 commit comments

Comments
 (0)