hydra icon indicating copy to clipboard operation
hydra copied to clipboard

Feature request: activate heads conditionally

Open zhaojiangbin opened this issue 9 years ago • 7 comments

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:

  1. 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.
  2. 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)))

zhaojiangbin avatar Mar 23 '15 03:03 zhaojiangbin

Nice idea. I'll try to work on this soon.

abo-abo avatar Mar 23 '15 07:03 abo-abo

+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?

joostkremers avatar Apr 02 '15 22:04 joostkremers

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.

zhaojiangbin avatar Apr 03 '15 01:04 zhaojiangbin

This would be great

justmytwospence avatar Oct 01 '15 00:10 justmytwospence

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 avatar Oct 29 '15 23:10 justmytwospence

@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 avatar Oct 30 '15 08:10 abo-abo

@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:

  1. Recreates the hydra every time which would prevent any sort of additional modification to it.
  2. 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.
  3. 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.

chasecaleb avatar Sep 17 '21 20:09 chasecaleb