(or emacs irrelevant)

Context aware hydra

In this post, I'll demonstrate a snippet that came up this week in a conversation over John Kitchin's post. While the following snippet isn't inherently useful, it might help a creative user to understand Hydra a bit more and accomplish more advanced things.

This hydra demonstrates that the hint and even the key bindings aren't set in stone: you can modify them depending on the current state of your Emacs, in this case the current line number.

(defhydra hydra-vi (:hint nil)
  "vi"
  ("j" next-line)
  ("k" previous-line)
  ("n" next-line)
  ("p" previous-line))

(setq hydra-vi/hint
  '(if (evenp (line-number-at-pos))
    (prog1 (eval
            (hydra--format nil '(nil nil :hint nil)
                           "\neven: _j_ _k_\n" hydra-vi/heads))
      (define-key hydra-vi/keymap "n" nil)
      (define-key hydra-vi/keymap "p" nil)
      (define-key hydra-vi/keymap "j" 'hydra-vi/next-line)
      (define-key hydra-vi/keymap "k" 'hydra-vi/previous-line))
    (prog1 (eval
            (hydra--format nil '(nil nil :hint nil)
                           "\nodd: _n_ _p_\n" hydra-vi/heads))
      (define-key hydra-vi/keymap "j" nil)
      (define-key hydra-vi/keymap "k" nil)
      (define-key hydra-vi/keymap "n" 'hydra-vi/next-line)
      (define-key hydra-vi/keymap "p" 'hydra-vi/previous-line))))

The first statement is one of the most elementary defhydra calls. The only extra thing is that it sets the :hint to nil.

The defhydra statement generates a bunch of function and variable definitions. You can examine them closely by evaluating:

(macroexpand
 '(defhydra hydra-vi (:hint nil)
   "vi"
   ("j" next-line)
   ("k" previous-line)
   ("n" next-line)
   ("p" previous-line)))

Just paste that code into *scratch*, and press C-j. If you want pretty output (153 lines of code instead of 26), turn on lispy-mode and press E instead of C-j.

Anyway, among these defined variables is hydra-vi/hint which is evaluated each time to display the hint. So now we can just redefine hydra-vi/hint to make it so that on even lines n calls next-line, while on odd lines it's j, with the appropriate changes in the doc. The change in bindings, modifying hydra-vi/keymap - also one of the defined variables, needs to be a side-effect, since hydra-vi/hint is expected to evaluate to a string.

Just to give you some idea of how it could be used: you can have a context-aware "open" command that, for instance, delegates to open-in-pdf-tools or open-in-firefox or open-in-emacs when it detects that the point is on a link. And of course all these commands would have their own key binding that works only if the command makes sense.

This approach is described on the wiki, in case you read this post much later and want to see an up-to-date code, or even update it yourself. In case something cool comes out of this snippet, I can try to implement a more palatable API for defhydra, most likely an option to supply a function name in the docstring argument position.