]> code.delx.au - gnu-emacs-elpa/blob - hydra.el
d44893a8e96517580ce5284f31f609ff1ae44e18
[gnu-emacs-elpa] / hydra.el
1 ;;; hydra.el --- Make bindings that stick around
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; Author: Oleh Krehel <ohwoeowho@gmail.com>
6 ;; Maintainer: Oleh Krehel <ohwoeowho@gmail.com>
7 ;; URL: https://github.com/abo-abo/hydra
8 ;; Version: 0.9.0
9 ;; Keywords: bindings
10 ;; Package-Requires: ((cl-lib "0.5"))
11
12 ;; This file is part of GNU Emacs.
13
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
18
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
26
27 ;;; Commentary:
28 ;;
29 ;; This package can be used to tie related commands into a family of
30 ;; short bindings with a common prefix - a Hydra.
31 ;;
32 ;; Once you summon the Hydra (through the prefixed binding), all the
33 ;; heads can be called in succession with only a short extension.
34 ;; The Hydra is vanquished once Hercules, any binding that isn't the
35 ;; Hydra's head, arrives. Note that Hercules, besides vanquishing the
36 ;; Hydra, will still serve his orignal purpose, calling his proper
37 ;; command. This makes the Hydra very seamless, it's like a minor
38 ;; mode that disables itself automagically.
39 ;;
40 ;; Here's an example Hydra, bound in the global map (you can use any
41 ;; keymap in place of `global-map'):
42 ;;
43 ;; (defhydra hydra-zoom (global-map "<f2>")
44 ;; "zoom"
45 ;; ("g" text-scale-increase "in")
46 ;; ("l" text-scale-decrease "out"))
47 ;;
48 ;; It allows to start a command chain either like this:
49 ;; "<f2> gg4ll5g", or "<f2> lgllg".
50 ;;
51 ;; Here's another approach, when you just want a "callable keymap":
52 ;;
53 ;; (defhydra hydra-toggle (:color blue)
54 ;; "toggle"
55 ;; ("a" abbrev-mode "abbrev")
56 ;; ("d" toggle-debug-on-error "debug")
57 ;; ("f" auto-fill-mode "fill")
58 ;; ("t" toggle-truncate-lines "truncate")
59 ;; ("w" whitespace-mode "whitespace")
60 ;; ("q" nil "cancel"))
61 ;;
62 ;; This binds nothing so far, but if you follow up with:
63 ;;
64 ;; (global-set-key (kbd "C-c C-v") 'hydra-toggle/body)
65 ;;
66 ;; you will have bound "C-c C-v a", "C-c C-v d" etc.
67 ;;
68 ;; Knowing that `defhydra' defines e.g. `hydra-toggle/body' command,
69 ;; you can nest Hydras if you wish, with `hydra-toggle/body' possibly
70 ;; becoming a blue head of another Hydra.
71 ;;
72 ;; Initially, Hydra shipped with a simplified `hydra-create' macro, to
73 ;; which you could hook up the examples from hydra-examples.el. It's
74 ;; better to take the examples simply as templates and use `defhydra'
75 ;; instead of `hydra-create', since it's more flexible.
76
77 ;;; Code:
78 ;;* Requires
79 (require 'cl-lib)
80
81 (defalias 'hydra-set-transient-map
82 (if (fboundp 'set-transient-map)
83 'set-transient-map
84 'set-temporary-overlay-map))
85
86 ;;* Customize
87 (defgroup hydra nil
88 "Make bindings that stick around."
89 :group 'bindings
90 :prefix "hydra-")
91
92 (defcustom hydra-is-helpful t
93 "When t, display a hint with possible bindings in the echo area."
94 :type 'boolean
95 :group 'hydra)
96
97 (defcustom hydra-keyboard-quit "\a"
98 "This binding will quit an amaranth Hydra.
99 It's the only other way to quit it besides though a blue head.
100 It's possible to set this to nil.")
101
102 (defface hydra-face-red
103 '((t (:foreground "#7F0055" :bold t)))
104 "Red Hydra heads will persist indefinitely."
105 :group 'hydra)
106
107 (defface hydra-face-blue
108 '((t (:foreground "#758BC6" :bold t)))
109 "Blue Hydra heads will vanquish the Hydra.")
110
111 (defface hydra-face-amaranth
112 '((t (:foreground "#E52B50" :bold t)))
113 "Amaranth Hydra can exit only through a blue head.")
114 ;;* Fontification
115 (defun hydra-add-font-lock ()
116 "Fontify `defhydra' statements."
117 (font-lock-add-keywords
118 'emacs-lisp-mode
119 '(("(\\(defhydra\\)\\_> +\\(.*?\\)\\_>"
120 (1 font-lock-keyword-face)
121 (2 font-lock-type-face)))))
122
123 ;;* Universal Argument
124 (defvar hydra-base-map
125 (let ((map (make-sparse-keymap)))
126 (define-key map [?\C-u] 'hydra--universal-argument)
127 (define-key map [?-] 'hydra--negative-argument)
128 (define-key map [?0] 'hydra--digit-argument)
129 (define-key map [?1] 'hydra--digit-argument)
130 (define-key map [?2] 'hydra--digit-argument)
131 (define-key map [?3] 'hydra--digit-argument)
132 (define-key map [?4] 'hydra--digit-argument)
133 (define-key map [?5] 'hydra--digit-argument)
134 (define-key map [?6] 'hydra--digit-argument)
135 (define-key map [?7] 'hydra--digit-argument)
136 (define-key map [?8] 'hydra--digit-argument)
137 (define-key map [?9] 'hydra--digit-argument)
138 (define-key map [kp-0] 'hydra--digit-argument)
139 (define-key map [kp-1] 'hydra--digit-argument)
140 (define-key map [kp-2] 'hydra--digit-argument)
141 (define-key map [kp-3] 'hydra--digit-argument)
142 (define-key map [kp-4] 'hydra--digit-argument)
143 (define-key map [kp-5] 'hydra--digit-argument)
144 (define-key map [kp-6] 'hydra--digit-argument)
145 (define-key map [kp-7] 'hydra--digit-argument)
146 (define-key map [kp-8] 'hydra--digit-argument)
147 (define-key map [kp-9] 'hydra--digit-argument)
148 (define-key map [kp-subtract] 'hydra--negative-argument)
149 map)
150 "Keymap that all Hydras inherit. See `universal-argument-map'.")
151
152 (defvar hydra-curr-map
153 (make-sparse-keymap)
154 "Keymap of the current Hydra called.")
155
156 (defun hydra--universal-argument (arg)
157 "Forward to (`universal-argument' ARG)."
158 (interactive "P")
159 (setq prefix-arg (if (consp arg)
160 (list (* 4 (car arg)))
161 (if (eq arg '-)
162 (list -4)
163 '(4))))
164 (hydra-set-transient-map hydra-curr-map t))
165
166 (defun hydra--digit-argument (arg)
167 "Forward to (`digit-argument' ARG)."
168 (interactive "P")
169 (let ((universal-argument-map hydra-curr-map))
170 (digit-argument arg)))
171
172 (defun hydra--negative-argument (arg)
173 "Forward to (`negative-argument' ARG)."
174 (interactive "P")
175 (let ((universal-argument-map hydra-curr-map))
176 (negative-argument arg)))
177
178 ;;* Misc internals
179 (defvar hydra-last nil
180 "The result of the last `hydra-set-transient-map' call.")
181
182 (defun hydra--callablep (x)
183 "Test if X is callable."
184 (or (functionp x)
185 (and (consp x)
186 (memq (car x) '(function quote)))))
187
188 (defun hydra--make-callable (x)
189 "Generate a callable symbol from X.
190 If X is a function symbol or a lambda, return it. Otherwise, it
191 should be a single statement. Wrap it in an interactive lambda."
192 (if (or (symbolp x) (functionp x))
193 x
194 `(lambda ()
195 (interactive)
196 ,x)))
197
198 (defun hydra--head-property (h prop &optional default)
199 "Return for Hydra head H the value of property PROP.
200 Return DEFAULT if PROP is not in H."
201 (let ((plist (if (or (stringp (cl-caddr h))
202 (null (cl-caddr h)))
203 (cl-cdddr h)
204 (cddr h))))
205 (if (memq prop h)
206 (plist-get plist prop)
207 default)))
208
209 (defun hydra--color (h body-color)
210 "Return the color of a Hydra head H with BODY-COLOR."
211 (if (null (cadr h))
212 'blue
213 (or (hydra--head-property h :color) body-color)))
214
215 (defun hydra--face (h body-color)
216 "Return the face for a Hydra head H with BODY-COLOR."
217 (cl-case (hydra--color h body-color)
218 (blue 'hydra-face-blue)
219 (red 'hydra-face-red)
220 (amaranth 'hydra-face-amaranth)
221 (t (error "Unknown color for %S" h))))
222
223 (defun hydra--hint (docstring heads body-color)
224 "Generate a hint from DOCSTRING and HEADS and BODY-COLOR.
225 It's intended for the echo area, when a Hydra is active."
226 (let (alist)
227 (dolist (h heads)
228 (let ((val (assoc (cadr h) alist))
229 (pstr (propertize (car h) 'face
230 (hydra--face h body-color))))
231 (unless (and (> (length h) 2)
232 (null (cl-caddr h)))
233 (if val
234 (setf (cadr val)
235 (concat (cadr val) " " pstr))
236 (push
237 (cons (cadr h)
238 (cons pstr
239 (and (stringp (cl-caddr h)) (cl-caddr h))))
240 alist)))))
241
242 (format "%s: %s."
243 docstring
244 (mapconcat
245 (lambda (x)
246 (format
247 (if (cdr x)
248 (concat "[%s]: " (cdr x))
249 "%s")
250 (car x)))
251 (nreverse (mapcar #'cdr alist))
252 ", "))))
253
254 (defun hydra-disable ()
255 "Disable the current Hydra."
256 (cond
257 ;; Emacs 25
258 ((functionp hydra-last)
259 (funcall hydra-last))
260
261 ;; Emacs 24.4.1
262 ((boundp 'overriding-terminal-local-map)
263 (setq overriding-terminal-local-map nil))
264
265 ;; older
266 (t
267 (while (and (consp (car emulation-mode-map-alists))
268 (consp (caar emulation-mode-map-alists))
269 (equal (cl-cdaar emulation-mode-map-alists) ',keymap))
270 (setq emulation-mode-map-alists
271 (cdr emulation-mode-map-alists))))))
272
273 (defun hydra--doc (body-key body-name heads)
274 "Generate a part of Hydra docstring.
275 BODY-KEY is the body key binding.
276 BODY-NAME is the symbol that identifies the Hydra.
277 HEADS is a list of heads."
278 (format
279 "Create a hydra with %s body and the heads:\n\n%s\n\n%s"
280 (if body-key
281 (format "a \"%s\"" body-key)
282 "no")
283 (mapconcat
284 (lambda (x)
285 (format "\"%s\": `%S'" (car x) (cadr x)))
286 heads ",\n")
287 (format "The body can be accessed via `%S'." body-name)))
288
289 (defun hydra--make-defun (name cmd color
290 doc hint keymap
291 body-color body-pre body-post &optional other-post)
292 "Make a defun wrapper, using NAME, CMD, COLOR, DOC, HINT, and KEYMAP.
293 BODY-COLOR, BODY-PRE, BODY-POST, and OTHER-POST are used as well."
294 `(defun ,name ()
295 ,doc
296 (interactive)
297 ,@(when body-pre (list body-pre))
298 (hydra-disable)
299 (catch 'hydra-disable
300 ,@(delq nil
301 (if (eq color 'blue)
302 `(,(when cmd `(call-interactively #',cmd))
303 ,body-post)
304 `(,(when cmd
305 `(condition-case err
306 (prog1 t
307 (call-interactively #',cmd))
308 (error
309 (message "%S" err)
310 (sit-for 0.8)
311 nil)))
312 (when hydra-is-helpful
313 (message ,hint))
314 (setq hydra-last
315 (hydra-set-transient-map
316 (setq hydra-curr-map ',keymap)
317 t
318 ,@(if (and (not (eq body-color 'amaranth)) body-post)
319 `((lambda () ,body-post)))))
320 ,other-post))))))
321
322 ;;* Macros
323 ;;** hydra-create
324 ;;;###autoload
325 (defmacro hydra-create (body heads &optional method)
326 "Create a hydra with a BODY prefix and HEADS with METHOD.
327 This will result in `global-set-key' statements with the keys
328 being the concatenation of BODY and each head in HEADS. HEADS is
329 an list of (KEY FUNCTION &optional HINT).
330
331 After one of the HEADS is called via BODY+KEY, it and the other
332 HEADS can be called with only KEY (no need for BODY). This state
333 is broken once any key binding that is not in HEADS is called.
334
335 METHOD is a lambda takes two arguments: a KEY and a COMMAND.
336 It defaults to `global-set-key'.
337 When `(keymapp METHOD)`, it becomes:
338
339 (lambda (key command) (define-key METHOD key command))"
340 (declare (indent 1)
341 (obsolete defhydra "0.8.0"))
342 `(defhydra ,(intern
343 (concat
344 "hydra-" (replace-regexp-in-string " " "_" body)))
345 ,(cond ((hydra--callablep method)
346 method)
347 ((null method)
348 `(global-map ,body))
349 (t
350 (list method body)))
351 "hydra"
352 ,@(eval heads)))
353
354 ;;** defhydra
355 ;;;###autoload
356 (defmacro defhydra (name body &optional docstring &rest heads)
357 "Create a Hydra - a family of functions with prefix NAME.
358
359 NAME should be a symbol, it will be the prefix of all functions
360 defined here.
361
362 BODY has the format:
363
364 (BODY-MAP BODY-KEY &rest PLIST)
365
366 DOCSTRING will be displayed in the echo area to identify the
367 Hydra.
368
369 Functions are created on basis of HEADS, each of which has the
370 format:
371
372 (KEY CMD &optional HINT &rest PLIST)
373
374 BODY-MAP is a keymap; `global-map' is used quite often. Each
375 function generated from HEADS will be bound in BODY-MAP to
376 BODY-KEY + KEY, and will set the transient map so that all
377 following heads can be called though KEY only.
378
379 The heads inherit their PLIST from the body and are allowed to
380 override each key. The keys recognized are :color and :bind.
381 :color can be:
382
383 - red (default): this head will continue the Hydra state.
384 - blue: this head will stop the Hydra state.
385 - amaranth (applies to body only): similar to red, but no binding
386 except a blue head can stop the Hydra state.
387
388 :bind can be:
389 - nil: this head will not be bound in BODY-MAP.
390 - a lambda taking KEY and CMD used to bind a head"
391 (declare (indent defun))
392 (unless (stringp docstring)
393 (setq heads (cons docstring heads))
394 (setq docstring "hydra"))
395 (when (keywordp (car body))
396 (setq body (cons nil (cons nil body))))
397 (let* ((keymap (copy-keymap hydra-base-map))
398 (names (mapcar
399 (lambda (x)
400 (define-key keymap (kbd (car x))
401 (intern (format "%S/%s" name
402 (if (symbolp (cadr x))
403 (cadr x)
404 (concat "lambda-" (car x)))))))
405 heads))
406 (body-name (intern (format "%S/body" name)))
407 (body-key (unless (hydra--callablep body)
408 (cadr body)))
409 (body-color (if (hydra--callablep body)
410 'red
411 (or (plist-get (cddr body) :color)
412 'red)))
413 (body-pre (plist-get (cddr body) :pre))
414 (body-post (plist-get (cddr body) :post))
415 (method (or (plist-get body :bind)
416 (car body)))
417 (hint (hydra--hint docstring heads body-color))
418 (doc (hydra--doc body-key body-name heads)))
419 (when (and (or body-pre body-post)
420 (version< emacs-version "24.4"))
421 (error "At least Emacs 24.4 is needed for :pre and :post"))
422 (when (and body-pre (symbolp body-pre))
423 (setq body-pre `(funcall #',body-pre)))
424 (when (and body-post (symbolp body-post))
425 (setq body-post `(funcall #',body-post)))
426 (when (eq body-color 'amaranth)
427 (if (cl-some `(lambda (h)
428 (eq (hydra--color h ',body-color) 'blue))
429 heads)
430 (progn
431 (when (cl-some `(lambda (h)
432 (eq (hydra--color h ',body-color) 'red))
433 heads)
434 (warn "Amaranth body color: upgrading all red heads to amaranth"))
435 (define-key keymap [t]
436 `(lambda ()
437 (interactive)
438 (message "An amaranth Hydra can only exit through a blue head")
439 (hydra-set-transient-map hydra-curr-map t)
440 (when hydra-is-helpful
441 (sit-for 0.8)
442 (message ,hint)))))
443 (error "An amaranth Hydra must have at least one blue head in order to exit"))
444 (when hydra-keyboard-quit
445 (define-key keymap hydra-keyboard-quit
446 `(lambda ()
447 (interactive)
448 (hydra-disable)
449 ,body-post))))
450 `(progn
451 ,@(cl-mapcar
452 (lambda (head name)
453 (hydra--make-defun
454 name (hydra--make-callable (cadr head)) (hydra--color head body-color)
455 (format "%s\n\nCall the head: `%S'." doc (cadr head))
456 hint keymap
457 body-color body-pre body-post))
458 heads names)
459 ,@(unless (or (null body-key)
460 (null method)
461 (hydra--callablep method))
462 `((unless (keymapp (lookup-key ,method (kbd ,body-key)))
463 (define-key ,method (kbd ,body-key) nil))))
464 ,@(delq nil
465 (cl-mapcar
466 (lambda (head name)
467 (when (or body-key method)
468 (let ((bind (hydra--head-property head :bind 'default))
469 (final-key (if body-key
470 (vconcat (kbd body-key) (kbd (car head)))
471 (kbd (car head)))))
472 (cond ((null bind) nil)
473
474 ((eq bind 'default)
475 (list
476 (if (hydra--callablep method)
477 'funcall
478 'define-key)
479 method
480 final-key
481 (list 'function name)))
482
483 ((hydra--callablep bind)
484 `(funcall (function ,bind)
485 ,final-key
486 (function ,name)))
487
488 (t
489 (error "Invalid :bind property %S" head))))))
490 heads names))
491 ,(hydra--make-defun body-name nil nil doc hint keymap
492 body-color body-pre body-post
493 '(setq prefix-arg current-prefix-arg)))))
494
495 (provide 'hydra)
496
497 ;;; Local Variables:
498 ;;; outline-regexp: ";;\\*+"
499 ;;; End:
500
501 ;;; hydra.el ends here