hydra
hydra copied to clipboard
Feature request: activate heads conditionally
I am imagining something like:
(defhydra foo ()
"foo"
("a" 't :when (pred1))
("b" 't :when (pred2))
("c" 't)
("q" nil))
In such a hydra, until their condition, (pred1)
or (pred2)
, returns non-nil, the head "a" and "b" remain inactive - hint invisible, key unmapped, as if they didn't exist.
A couple of use cases I can think of:
- Some heads do not make sense in certain modes. For example, the vi-style hydra could have a head mapped to
(beginning-of-defun)
in elisp mode. Having such heads in, say text mode, would be useless and could be confusing. - In this wiki page, the second hydra variant could have its heads defined as:
("o" (my/visit-buffer list 1))
("1" (my/visit-buffer list 1))
("2" (my/visit-buffer list 2) :when (> 1 (length list)))
("3" (my/visit-buffer list 3) :when (> 2 (length list)))
("4" (my/visit-buffer list 4) :when (> 3 (length list)))
Nice idea. I'll try to work on this soon.
+1
Menu-bar menus also have such an option (keyword :active
) so this would be a nice thing to have in hydras as well.
Will there be a way to hide the corresponding entry in the doc string as well?
Simply giving a unique color to such heads, like dimming out, might be much easier than trying to hide them in docstrings.
It would still be nicer to hide them in hints.
This would be great
Here's a related use case I've been trying to figure out, with no success. I'd like to have a hydra that executes other-window
when I press C-b C-o
. If (> 2 (count-windows))
, then pressing o
more times will continue to execute other-window
. If (<= 2 (count-windows))
, the hydra will exit immediately. Am I missing a simpler way to implement that?
@justmytwospence Yes, there's a simple way:
(defhydra hydra-other-window (global-map "C-x")
"other window"
("o"
(progn
(other-window 1)
(when (<= (length (window-list)) 2)
(setq hydra-deactivate t)))))
See the doc of hydra-deactivate
:
If a Hydra head sets this to t, exit the Hydra. This will be done even if the head wasn't designated for exiting.
@abo-abo So how's that "try to work on this soon" going? :rofl: (Don't worry, I get how that goes)
Are there any better ways of doing this in 2021? There's an example in the wiki (last updated in June 2015) that shows one way to do it, but it's awkward due to the amount of duplication and dealing with hint strings.
Here's one way that I hacked together to illustrate my use case:
(defmacro cc/dynamic-hydra--create (name body docstring &rest conditional-heads)
"Hydra with heads that are conditionally evaluated/shown."
(declare (indent defun))
`(progn
(eval
(append
'(defhydra ,name ,body ,docstring)
(cl-loop for ch in ',conditional-heads
if (eval (car ch))
collect (cdr ch))))))
(defmacro cc/dynamic-hydra (name body docstring &rest conditional-heads)
(declare (indent defun))
`(defun ,name ()
(interactive)
(cc/dynamic-hydra--create ,name ,body ,docstring ,@conditional-heads)
(funcall-interactively ',(intern (concat (symbol-name name) "/body")))))
(cc/dynamic-hydra my-hydra-foo (:color blue) ""
(t . ("a" (message "always available") "cmd a"))
((derived-mode-p 'org-mode 'org-agenda-mode) . ("b" (message "org/org-agenda only") "cmd b")))
;; The hydra is recreated each time, so call this directly, not `my-hydra-foo/body`
(my-hydra-foo)
It's kinda gross, but it works for my own use at least. Notable quirks:
- Recreates the hydra every time which would prevent any sort of additional modification to it.
- The conditions are only evaluated when the hydra is entered, whereas it would be ideal to also evaluate after every head execution (in
:after-exit
I think), but I haven't had a reason to bother with that so far. - There isn't any special handling of doc strings. The main issue here is having to add identical
:column "foo"
for multiple heads in a row since conditional ones may not be included.
P.S. constructive criticism is more than welcome, and anyone is free to copy the macros above if they're helpful.