(or emacs irrelevant)

A new Hydra demo on Youtube

This will be a short post sharing two pieces of information.

A Hydra demo is on Youtube

Here is the link, and here is the current window-switching code:

(global-set-key
 (kbd "C-M-o")
 (defhydra hydra-window (:color amaranth)
   "window"
   ("h" windmove-left)
   ("j" windmove-down)
   ("k" windmove-up)
   ("l" windmove-right)
   ("v" (lambda ()
          (interactive)
          (split-window-right)
          (windmove-right))
        "vert")
   ("x" (lambda ()
          (interactive)
          (split-window-below)
          (windmove-down))
        "horz")
   ("t" transpose-frame "'")
   ("o" delete-other-windows "one" :color blue)
   ("a" ace-window "ace")
   ("s" ace-swap-window "swap")
   ("d" ace-delete-window "del")
   ("i" ace-maximize-window "ace-one" :color blue)
   ("b" ido-switch-buffer "buf")
   ("m" headlong-bookmark-jump "bmk")
   ("q" nil "cancel")))

ace-window 0.7.0 is out

You can see the release notes here. There's not a whole lot of user-visible changes, but a large portion of the code was re-written to facilitate the use of the API. I hope that no new bugs were introduced with this change, you can still fall back to 0.6.1 and send me an issue if something broke. There's a benefit in the long run, just see how simple the code has become:

(defun ace-maximize-window ()
  "Ace maximize window."
  (interactive)
  (select-window
   (aw-select " Ace - Maximize Window"))
  (delete-other-windows))

Here, aw-select just returns a selected window, and nothing else. Very easy to use.

The Elisp Synergy

After having written a few Emacs packages, it happens sometimes when I'm reading questions on Emacs Stack Exchange, I think to myself that either:

I've done this before in package X

or:

This feature would fit nicely in package Y

On very rare occasions it happens that a question matches two packages at once. Here is such a question:

Is there a yasnippet producing a prepopulated doxygen comment?

For the following C++ function:

bool importantStuff(double a, double b);

It should output the following snippet, perhaps without the tags:

/**
* <Insert description of importantStuff>
*
* @param a <Insert description of a>
* @param b <Insert description of b>
* @return <Insert description of the return value>
*/

Well, I've got a package called function-args for C++ that uses CEDET to:

  • display tool-tips with function arguments
  • jump to a semantic tag in current file
  • do a bit of completion
  • do a bit of generation (like inherited function signatures)

And the expansion part can be handled by auto-yasnippet, which I've covered in an earlier post.

The code

I think it might be useful to look at the code, since it's small and shows how to use CEDET's and auto-yasnippet's API:

(defun moo-doxygen ()
  "Generate a doxygen yasnippet and expand it with `aya-expand'.
The point should be on the top-level function name."
  (interactive)
  (move-beginning-of-line nil)
  (let ((tag (semantic-current-tag)))
    (unless (semantic-tag-of-class-p tag 'function)
      (error "Expected function, got %S" tag))
    (let* ((name (semantic-tag-name tag))
           (attrs (semantic-tag-attributes tag))
           (args (plist-get attrs :arguments))
           (ord 1))
      (setq aya-current
            (format
             "/**
* $1
*
%s
* @return $%d
*/
"
             (mapconcat
              (lambda (x)
                (format "* @param %s $%d"
                        (car x) (incf ord)))
              args
              "\n")
             (incf ord)))
      (aya-expand))))

The bonus

If you're interested in Doxygen, here's a bit of code that I found laying around. It will prettify e.g. <tt>Numerical Recipies</tt> to Numerical Recipies in the comments. This code uses a similar approach to the one used in my posts about prettifying Elisp regex, and ElTeX.

(defface font-lock-doxygen-face
    '((nil (:foreground "SaddleBrown" :background "#f7f7f7") ))
    "Special face to highlight doxygen tags such as <tt>...</tt>
and <code>...</code>."
    :group 'font-lock-highlighting-faces)

(font-lock-add-keywords
 'c++-mode
 '(("\\(<\\(?:code\\|tt\\)>\"?\\)\\([^<]*?\\)\\(\"?</\\(?:code\\|tt\\)>\\)"
    (0 (prog1 ()
         (let* ((expr (match-string-no-properties 2))
                (expr-len (length expr)))
           (if (eq 1 expr-len)
               (compose-region (match-beginning 0)
                               (match-end 0)
                               (aref expr 0))
             (compose-region (match-beginning 1)
                             (1+ (match-end 1))
                             (aref expr 0))
             (compose-region (1- (match-beginning 3))
                             (match-end 3)
                             (aref expr (1- expr-len)))))))
    (0 'font-lock-doxygen-face t))))

Outro

I hope that the code listed here will be useful to someone other than me. It's a nice highlight of how a language can be made much cooler by just having a package manager that allows to quickly glue various things together. I mean, what would happen to JavaScript without npm (besides stopping people requiring Angular just to use arrays)?

C++, on the other hand, is too cool to care about such things as package managers and modules and stuff. But we're stuck with it for performance reasons, so might as well try to make the experience more bearable by adding some niceties to c++-mode.

Introducing amaranth Hydras

An amaranth planted in a garden near a Rose-Tree, thus addressed it: "What a lovely flower is the Rose, a favorite alike with Gods and with men. I envy you your beauty and your perfume." The Rose replied, "I indeed, dear Amaranth, flourish but for a brief season! If no cruel hand pluck me from my stem, yet I must perish by an early doom. But thou art immortal and dost never fade, but bloomest for ever in renewed youth."

Prompted by the "Avoid exiting the hydra on hitting a wrong key" issue, I decided to add a new body color for Hydra. Turns out that there exists a color that represents immortality, and it's called amaranth. It's also very pretty.

So what amaranth Hydras do basically, is make it impossible to quit them. Of course, it would be totally lame if there wasn't at least one way to quit them. And what better to utilize for this purpose, than the good-old quitters - the blue Hydra heads.

An Example code

(global-set-key
 (kbd "C-z")
 (defhydra hydra-vi
     (:pre
      (set-cursor-color "#e52b50")
      :post
      (set-cursor-color "#ffffff")
      :color amaranth)
   "vi"
   ("l" forward-char)
   ("h" backward-char)
   ("j" next-line)
   ("k" previous-line)
   ("m" set-mark-command "mark")
   ("a" move-beginning-of-line "beg")
   ("e" move-end-of-line "end")
   ("d" delete-region "del" :color blue)
   ("y" kill-ring-save "yank" :color blue)
   ("q" nil "quit")))

This Hydra has only three exit points:

  • q, which does nothing
  • y, which copies the active region
  • d, which deletes the active region

In addition to the usual arrows on hjkl, a and e move to beginning and end of line respectively.

Outro

I hope that you like the new idea, also thanks to @vkazanov for the push in this direction. Things should be backwards-compatible, so this feature costs you nothing if you're not using it. See the release notes for 0.8.0.

One more thanks goes to Sridhar Ratnakumar for this suggestion that lead to an improvement in auto-yasnippet. Thanks to this improvement, I was able to quickly wrap words like this:

<font color="#FF007F">Rose</font>

New in Hydra - :pre and :post clauses

Only three commits happened since 0.6.1, but already 0.7.0 has to be released, since the behavior has changed slightly.

The ultimate window switching setup, revised

The code from the last post had to be changed. Note that only ace-window-related heads are affected, since ace-window uses set-transient-map as well. This was necessary to fix issue #15 (unable to use goto-line in a Hydra, since it requires input).

(global-set-key
 (kbd "C-M-o")
 (defhydra hydra-window ()
   "window"
   ("h" windmove-left)
   ("j" windmove-down)
   ("k" windmove-up)
   ("l" windmove-right)
   ("a" (lambda ()
          (interactive)
          (ace-window 1)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body)
          (throw 'hydra-disable t))
        "ace")
   ("v" (lambda ()
          (interactive)
          (split-window-right)
          (windmove-right))
        "vert")
   ("x" (lambda ()
          (interactive)
          (split-window-below)
          (windmove-down))
        "horz")
   ("s" (lambda ()
          (interactive)
          (ace-window 4)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body)
          (throw 'hydra-disable t))
        "swap")
   ("t" transpose-frame "'")
   ("d" (lambda ()
          (interactive)
          (ace-window 16)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body)
          (throw 'hydra-disable t))
        "del")
   ("o" delete-other-windows "one" :color blue)
   ("i" ace-maximize-window "ace-one" :color blue)
   ("q" nil "cancel")))

You can also see an awesome addition sneak in, with transpose-frame on t.

Technical note

If you look closely at the code, you'll see the ace-window-related lambdas throwing the hydra-disable symbol. If the Hydra code catches that symbol, it will not call set-transient-map again, effectively making the head that threw the symbol blue. In this example, though, ace-window-related functions are still red, since the Hydra is resumed by calling hydra-window/body in ace-window-end-once-hook.

This is a trick that I have to play because of how ace-jump-mode works. Hopefully, it won't be needed for other commands. But still, it's a simple trick that you can use if you want to have a head quit conditionally, or something.

goto-line ad infinum

(defhydra hydra-test (global-map "M-g")
  ("g" goto-line "goto-line"))

With the Hydra above, it's possible to:

  • M-g g 10 RET to go to line 10
  • g 20 RET to go to line 20
  • g 50 RET to go to line 50 etc.

Change the cursor color when a Hydra is active

Here's how the new :pre and :post statements work:

(global-set-key
 (kbd "C-z")
 (defhydra hydra-vi
     (:pre
      (set-cursor-color "#40e0d0")
      :post
      (set-cursor-color "#ffffff"))
   "vi"
   ("l" forward-char)
   ("h" backward-char)
   ("j" next-line)
   ("k" previous-line)
   ("q" nil "quit")))

In this example, the cursor color will change for the duration of the Hydra. Both :pre and :post should match to a single Elisp statement; you can use progn to tie a bunch of statements together if you want.

A more evil helm

Lit Wakefield has a nice article on using hydra and helm together. Do check it out, I'll certainly steal some of his code.

Outro

I hope that you don't have an objection to Hydra being developed at a fast pace with many features popping up. I try my best to keep things backwards compatible, but I really want to quickly fix the areas where the code is lacking. Thanks to all the people contributing issues, especially @atykhonov and @nandryshak for the last two, which are the core of this version bump.

One Hydra Two Hydra Red Hydra Blue Hydra

Hydra evolution picks up the pace with hydra 0.6.1. This version adds prefix arguments to all Hydras.

Some Examples

Look ma, no modifiers!

Now it's possible to write this:

(global-set-key
 (kbd "C-z")
 (defhydra hydra-vi ()
   "vi"
   ("l" forward-char)
   ("h" backward-char)
   ("j" next-line)
   ("k" previous-line)))

And now C-z 5j7l will move 5 lines down and 7 characters left, still with the option to press h, j, k, l some more.

Additionally C-z C-u C-u C-u k will move 64 lines up, since C-u multiplies its argument by 4 each time.

The good-old zoom

If you remember, this was the original Hydra:

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

Now, <f2> g 4g 2l will zoom in 5 times, and zoom out 2 times for a total of +3 zoom.

The good-old move-splitter

(defhydra hydra-splitter (global-map "C-M-s")
  "splitter"
  ("h" hydra-move-splitter-left)
  ("j" hydra-move-splitter-down)
  ("k" hydra-move-splitter-up)
  ("l" hydra-move-splitter-right))

This Hydra can benefit from numeric arguments as well: C-M-s l 40l will quickly make the right window a lot smaller.

If I wanted to type C-M-s 40 l, I would have to use this definition instead:

(global-set-key
 (kbd "C-M-s")
 (defhydra hydra-splitter ()
   "splitter"
   ("h" hydra-move-splitter-left)
   ("j" hydra-move-splitter-down)
   ("k" hydra-move-splitter-up)
   ("l" hydra-move-splitter-right)))

For that case, I would get the hint immediately after C-M-s and would be able to give the numeric argument immediately, but I wouldn't be able to bind anything else on C-M-s as a prefix, e.g.:

(global-set-key (kbd "C-M-s z") 'recenter-top-bottom)

The code above would give the error "Key sequence C-M-s z starts with non-prefix key C-M-s". So you can pick the method that you prefer, the choice is there.

The ultimate window switching setup

(global-set-key
 (kbd "C-M-o")
 (defhydra hydra-window ()
   "window"
   ("h" windmove-left)
   ("j" windmove-down)
   ("k" windmove-up)
   ("l" windmove-right)
   ("a" (lambda ()
          (interactive)
          (ace-window 1)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body))
        "ace")
   ("v" (lambda ()
          (interactive)
          (split-window-right)
          (windmove-right))
        "vert")
   ("x" (lambda ()
          (interactive)
          (split-window-below)
          (windmove-down))
        "horz")
   ("s" (lambda ()
          (interactive)
          (ace-window 4)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body))
        "swap")
   ("d" (lambda ()
          (interactive)
          (ace-window 16)
          (add-hook 'ace-window-end-once-hook
                    'hydra-window/body))
        "del")
   ("o" delete-other-windows "1" :color blue)
   ("i" ace-maximize-window "a1" :color blue)
   ("q" nil "cancel")))

The credit for this monster goes to bcarell, I just refined his approach with making ace-window not quit the hydra-window Hydra. This setup needs the latest ace-window.

Here's the result of C-M-o xvxv starting from a single window:

hydra-window

From here, I can quickly maximize the current window with o while simultaneously quitting the Hydra; i will maximize a window as well, but it will select it with ace-window, instead of maximizing the current one.

Note that, since numerical arguments are working now, 4a is the same as s (swap) and 16a or C-u C-u a is the same as d (delete).