Hydra 0.13.0 is out

A lot of the changes for this release aren't very user-visible, although they are important in improving the usability. The main change is the move from the standard set-transient-map to my own hydra-set-transient-map .

This change has allowed to remove the part where the amaranth and pink hydras intercept a binding which doesn't belong to them and then try to forward it back to Emacs. It was done in this way, which might be interesting for people who write Elisp:

( define-key hydra-keymap t 'hydra-intercept )

Binding t means that when the keymap is active, any binding which doesn't belong to the keymap will be interpreted as t . Then, I would use lookup-key and this-command-keys and try to call the result. That method was quite fragile:

It didn't work for prefix keys while a pink hydra was active.

It didn't work for some keys in the terminal because of input-decode-map .

The new method solves the mentioned issues by not using t and instead running this function in the pre-command-hook :

( defun hydra--clearfun () "Disable the current Hydra unless `this-command' is a head." ( if ( memq this-command ' ( handle-switch-frame keyboard-quit )) ( hydra-disable ) ( unless ( eq this-command ( lookup-key hydra-curr-map ( this-single-command-keys ))) ( unless ( cl-case hydra-curr-foreign-keys ( warn ( setq this-command 'hydra-amaranth-warn )) ( run t ) ( t nil )) ( hydra-disable )))))

This approach is actually very similar to what the built-in set-transient-map does from Emacs 24.4 onward. Of course, changing the a large cog in the Hydra mechanism can lead to some new bugs, or even old bugs to re-surface. So I really appreciate the help from @jhonnyseven in testing the new code.

As always, if you find something very broken, you can roll back to the GNU ELPA version and raise an issue.

Fixes

single command red/blue issue

Fix the uniqueness issue, when a single command is assigned to both a red and a blue head.

Here's an example:

( defhydra hydra-zoom ( global-map "<f2>" ) "zoom" ( "g" text-scale-increase "in" ) ( "l" text-scale-decrease "out" ) ( "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 ))

Here, three heads are assigned (text-scale-set 0) , however their behavior is different:

r doesn't exit and has a string hint.

doesn't exit and has a string hint. 0 exits and has an empty hint (so only the key is in the docstring).

exits and has an empty hint (so only the key is in the docstring). 1 exits and has a nil hint (will not be displayed in the docstring).

The latter two call hydra-zoom/lambda-0-and-exit , while r calls hydra-zoom/lambda-r .

Don't default hydra-repeast--prefix-arg to 1

See #61 for more info.

Allow hydra-repeat to take a numeric arg

For hydra-vi , it's now possible to do this 4j.2.. . The line will be forwarded:

4 times by 4j

4 times by .

2 times by 2.

2 times by .

See #92 for more info.

Key chord will be disabled for the duration of a hydra

This means that hydras have become much more easy to use with key chords. For instance, if dj key chord calls a hydra or is part of the hydra, you won't call the jj key chord by accident with djj .

See #97 for more info.

New Features

Variable as a string docstring spec

You can now use this form in your hydras:

( defvar foo "a b c" ) ( defhydra bar () " bar %s`foo " ( "a" 't ) ( "q" nil ))

Previously, it would only work for %s(foo) forms.

:bind property can also be a keymap

If you remember, you can set :bind in the body to define in which way the heads should be bound outside the Hydra. You also assign/override :bind for each head. This is especially useful to set :bind to nil for a few heads that you don't want to bind outside.

Previously, :bind could be either a lambda or nil. Now a keymap is also accepted.

Integration tests

In addition to the abundant macro-expansion tests, integration tests are now also running, both for emacs24 and for emacs-snapshot . This means that hydra should be a lot more stable now.

Here's an example test:

( defhydra hydra-simple-1 ( global-map "C-c" ) ( "a" ( insert "j" )) ( "b" ( insert "k" )) ( "q" nil )) ( 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|" )))

In the tests above, hydra-simple is a defined and bound hydra. "|" represents the buffer text (empty), where | is the point position. And (kbd "C-c aabbaaqaabbaa") represents the key sequence that you can normally press by hand. Finally, "jjkkjjaabbaa|" is what the buffer and the point position should look like afterwards. If you find a hydra bug, it would be really cool to submit a new integration test to make sure that this bug doesn't happen in the future.

Basic error handling

I really like the use-package feature where it catches load-time errors and issues a message instead of bringing up the debugger. This is really useful, since it's hard to fix the bug with a mostly broken Emacs, in the case when the error happened early in the load process. So the same behavior now happens with defhydra . In case of an error, defhydra will be equivalent to a no-op, and the error message will be written to the *Messages* buffer.

Use a variable instead of a function for the hint

This leads up to the yet unresolved #86, which asks for heads to be activated conditionally.

For now, you can modify the docstring on your own if you wish. Here's some code from the expansion of hydra-zoom to explain what I mean:

( set ( defvar hydra-zoom/hint nil "Dynamic hint for hydra-zoom." ) ' ( format #( "zoom: [g]: in, [l]: out." 7 8 ( face hydra-face-red ) 16 17 ( face hydra-face-red )))) ;; in head body: ( when hydra-is-helpful ( if hydra-lv ( lv-message ( eval hydra-zoom/hint )) ( message ( eval hydra-zoom/hint ))))

Eventually, I'll add some automatic stuff to fix #86. But for now, you can experiment with modifying e.g. hydra-zoom/hint inside heads, if you want.

Multiple inheritance for Hydra heads

Each hydra, e.g. hydra-foo will now declare its own heads as a variable hydra-foo/heads . It's possible to inherit them like this:

( defhydra hydra-zoom ( global-map "<f2>" ) "zoom" ( "g" text-scale-increase "in" ) ( "s" text-scale-decrease "out" )) ( defhydra hydra-arrows () ( "h" backward-char "left" ) ( "j" next-line "down" ) ( "k" previous-line "up" ) ( "l" forward-char "right" )) ( defhydra hydra-zoom-child ( :inherit ( hydra-zoom/heads hydra-arrows/heads ) :color amaranth ) "zoom" ( "q" nil ))

Here, hydra-zoom-child inherits the heads of hydra-zoom and hydra-arrows . It adds one more head q to quit. Also, it changes the color to amaranth, which means that it's only possible to exit it with q or C-g . This hydra's parents remain at their original (red) color.

See #57 for more info.

Outro

A big thanks to all who contributed towards this release and to the wiki. If you have an idea that would be cool in Hydra, do raise an issue. And if you written some cool code that uses the already available Hydra features, please share it on the wiki. It's also a good way to protect your code against regressions (although the best one would be an integration test).

Finally, check out the new version of pandoc-mode which is one of the coolest and most elaborate uses of Hydra yet.