• Stars
    star
    1,818
  • Rank 25,509 (Top 0.6 %)
  • Language
    Emacs Lisp
  • Created almost 10 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

make Emacs bindings that stick around

Hydra

Build Status GNU ELPA MELPA MELPA Stable

This is a package for GNU Emacs that can be used to tie related commands into a family of short bindings with a common prefix - a Hydra.

hydra

Description for Poets

Once you summon the Hydra through the prefixed binding (the body + any one head), all heads can be called in succession with only a short extension.

The Hydra is vanquished once Hercules, any binding that isn't the Hydra's head, arrives. Note that Hercules, besides vanquishing the Hydra, will still serve his original purpose, calling his proper command. This makes the Hydra very seamless, it's like a minor mode that disables itself auto-magically.

Description for Pragmatics

Imagine that you have bound C-c j and C-c k in your config. You want to call C-c j and C-c k in some (arbitrary) sequence. Hydra allows you to:

  • Bind your functions in a way that pressing C-c jjkk3j5k is equivalent to pressing C-c j C-c j C-c k C-c k M-3 C-c j M-5 C-c k. Any key other than j or k exits this state.

  • Assign a custom hint to this group of functions, so that you know immediately after pressing C-c that you can follow up with j or k.

If you want to quickly understand the concept, see the video demo.

Table of Contents

Sample Hydras

The one with the least amount of code

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

With this simple code, you can:

  • Start zooming in with <f2> g.
  • Continue to zoom in with g.
  • Or zoom out with l.
  • Zoom in five times at once with 5g.
  • Stop zooming with any key that isn't g or l.

For any Hydra:

  • digit-argument can be called with 0-9.
  • negative-argument can be called with -.
  • universal-argument can be called with C-u.

The impressive-looking one

Here's the result of pressing . in the good-old Buffer menu:

hydra-buffer-menu

The code is large but very simple:

(defhydra hydra-buffer-menu (:color pink
                             :hint nil)
  "
^Mark^             ^Unmark^           ^Actions^          ^Search
^^^^^^^^-----------------------------------------------------------------
_m_: mark          _u_: unmark        _x_: execute       _R_: re-isearch
_s_: save          _U_: unmark up     _b_: bury          _I_: isearch
_d_: delete        ^ ^                _g_: refresh       _O_: multi-occur
_D_: delete up     ^ ^                _T_: files only: % -28`Buffer-menu-files-only
_~_: modified
"
  ("m" Buffer-menu-mark)
  ("u" Buffer-menu-unmark)
  ("U" Buffer-menu-backup-unmark)
  ("d" Buffer-menu-delete)
  ("D" Buffer-menu-delete-backwards)
  ("s" Buffer-menu-save)
  ("~" Buffer-menu-not-modified)
  ("x" Buffer-menu-execute)
  ("b" Buffer-menu-bury)
  ("g" revert-buffer)
  ("T" Buffer-menu-toggle-files-only)
  ("O" Buffer-menu-multi-occur :color blue)
  ("I" Buffer-menu-isearch-buffers :color blue)
  ("R" Buffer-menu-isearch-buffers-regexp :color blue)
  ("c" nil "cancel")
  ("v" Buffer-menu-select "select" :color blue)
  ("o" Buffer-menu-other-window "other-window" :color blue)
  ("q" quit-window "quit" :color blue))

(define-key Buffer-menu-mode-map "." 'hydra-buffer-menu/body)

Looking at the code, you can see hydra-buffer-menu as sort of a namespace construct that wraps each function that it's given in code that shows that hint and makes it easy to call the related functions. One additional function is created and returned as the result of defhydra - hydra-buffer-menu/body. This function does nothing except setting up the hint and the keymap, and is usually the entry point to complex hydras.

To write your own hydras, you can:

  • Either modify an existing hydra to do what you want to do.
  • Or read the rules, the examples, the docstrings and comments in the source.

Community wiki

You can find some user created hydras and more documentation in the project's community wiki. Feel free to add your own or edit the existing ones.

The Rules of Hydra-tics

Each hydra (take awesome as a prefix to make it more specific) looks like this:

(defhydra hydra-awesome (awesome-map awesome-binding awesome-plist)
  awesome-docstring
  awesome-head-1
  awesome-head-2
  awesome-head-3
  ...)

hydra-awesome

Each hydra needs a name, and this one is named hydra-awesome. You can name your hydras as you wish, but I prefer to start each one with hydra-, because it acts as an additional namespace layer, for example: hydra-zoom, hydra-helm, hydra-apropos etc.

If you name your hydra hydra-awesome, the return result of defhydra will be hydra-awesome/body.

Here's what hydra-zoom/body looks like, if you're interested:

(defun hydra-zoom/body ()
  "Call the body in the \"hydra-zoom\" hydra.

The heads for the associated hydra are:

\"g\":    `text-scale-increase',
\"l\":    `text-scale-decrease'

The body can be accessed via `hydra-zoom/body', which is bound to \"<f2>\"."
  (interactive)
  (require 'hydra)
  (hydra-default-pre)
  (let ((hydra--ignore nil))
    (hydra-keyboard-quit)
    (setq hydra-curr-body-fn
          'hydra-zoom/body))
  (hydra-show-hint
   hydra-zoom/hint
   'hydra-zoom)
  (hydra-set-transient-map
   hydra-zoom/keymap
   (lambda nil
     (hydra-keyboard-quit)
     nil)
   nil)
  (setq prefix-arg
        current-prefix-arg))

awesome-map and awesome-binding

This can be any keymap, for instance, global-map or isearch-mode-map.

For this example:

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

And here's the relevant generated code:

(unless (keymapp (lookup-key global-map (kbd "<f2>")))
  (define-key global-map (kbd "<f2>") nil))
(define-key global-map [f2 103]
  (function hydra-zoom/text-scale-increase))
(define-key global-map [f2 108]
  (function hydra-zoom/text-scale-decrease))

As you see, "<f2>" is used as a prefix for g (char value 103) and l (char value 108).

If you don't want to use a map right now, you can skip it like this:

(defhydra hydra-zoom (nil nil)
  "zoom"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

Or even simpler:

(defhydra hydra-zoom ()
  "zoom"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

But then you would have to bind hydra-zoom/text-scale-increase and hydra-zoom/text-scale-decrease yourself.

awesome-plist

You can read up on what a plist is in the Elisp manual.

You can use awesome-plist to modify the behavior of each head in some way. Below is a list of each key.

:pre and :post

You can specify code that will be called before each head, and after the body. For example:

(defhydra hydra-vi (:pre (set-cursor-color "#40e0d0")
                    :post (progn
                            (set-cursor-color "#ffffff")
                            (message
                             "Thank you, come again.")))
  "vi"
  ("l" forward-char)
  ("h" backward-char)
  ("j" next-line)
  ("k" previous-line)
  ("q" nil "quit"))

Thanks to :pre, each time any head is called, the cursor color is changed. And when the hydra quits, the cursor color will be made black again with :post.

:exit

The :exit key is inherited by every head (they can override it) and influences what will happen after executing head's command:

  • :exit nil (the default) means that the hydra state will continue - you'll still see the hint and be able to use short bindings.
  • :exit t means that the hydra state will stop.

:foreign-keys

The :foreign-keys key belongs to the body and decides what to do when a key is pressed that doesn't belong to any head:

  • :foreign-keys nil (the default) means that the hydra state will stop and the foreign key will do whatever it was supposed to do if there was no hydra state.
  • :foreign-keys warn will not stop the hydra state, but instead will issue a warning without running the foreign key.
  • :foreign-keys run will not stop the hydra state, and try to run the foreign key.

:color

The :color key is a shortcut. It aggregates :exit and :foreign-keys key in the following way:

| color    | toggle                     |
|----------+----------------------------|
| red      |                            |
| blue     | :exit t                    |
| amaranth | :foreign-keys warn         |
| teal     | :foreign-keys warn :exit t |
| pink     | :foreign-keys run          |

It's also a trick to make you instantly aware of the current hydra keys that you're about to press: the keys will be highlighted with the appropriate color.

:timeout

The :timeout key starts a timer for the corresponding amount of seconds that disables the hydra. Calling any head will refresh the timer.

:hint

The :hint key will be inherited by each head. Each head is allowed to override it, of course. One value that makes sense is :hint nil. See below for an explanation of head hint.

:bind

The :bind key provides a lambda to be used to bind each head. This is quite advanced and rarely used, you're not likely to need it. But if you would like to bind your heads with e.g. bind-key instead of define-key you can use this option.

The :bind key can be overridden by each head. This is useful if you want to have a few heads that are not bound outside the hydra.

:base-map

Use this option if you want to override hydra-base-map for the current hydra.

awesome-docstring

This can be a simple string used to build the final hydra hint. However, if you start it with a newline, the key-highlighting and Ruby-style string interpolation becomes enabled, as you can see in hydra-buffer-menu above.

To highlight a key, just wrap it in underscores. Note that the key must belong to one of the heads. The key will be highlighted with the color that is appropriate to the behavior of the key, i.e. if the key will make the hydra exit, the color will be blue.

To insert an empty character, use ^. The only use of this is to have your code aligned as nicely as the result.

To insert a dynamic Elisp variable, use %` followed by the variable. Each time the variable changes due to a head, the docstring will be updated. format-style width specifiers can be used.

To insert a dynamic Elisp expression, use e.g. %(length (dired-get-marked-files)). If a head will change the amount of marked files, for example, it will be appropriately updated.

If the result of the Elisp expression is a string and you don't want to quote it, use this form: %s(shell-command-to-string "du -hs").

awesome-head-1

Each head looks like this:

(head-binding head-command head-hint head-plist)

For the head ("g" text-scale-increase "in"):

  • head-binding is "g".
  • head-command is text-scale-increase.
  • head-hint is "in".
  • head-plist is nil.

head-binding

The head-binding is a string that can be passed to kbd.

head-command

The head-command can be:

  • command name, like text-scale-increase.

  • a lambda, like

      ("g" (lambda ()
             (interactive)
             (let ((current-prefix-arg 4))
               (call-interactively #'magit-status)))
           "git")
    
  • nil, which exits the hydra.

  • a single sexp, which will be wrapped in an interactive lambda.

Here's an example of the last option:

(defhydra hydra-launcher (:color blue)
   "Launch"
   ("h" man "man")
   ("r" (browse-url "http://www.reddit.com/r/emacs/") "reddit")
   ("w" (browse-url "http://www.emacswiki.org/") "emacswiki")
   ("s" shell "shell")
   ("q" nil "cancel"))
(global-set-key (kbd "C-c r") 'hydra-launcher/body)

head-hint

In case of a large body docstring, you usually don't want the head hint to show up, since you've already documented it in the body docstring. You can set the head hint to nil to do this.

Example:

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

head-plist

Here's a list of body keys that can be overridden in each head:

  • :exit
  • :color
  • :bind
  • :column

Use :column feature to have an aligned rectangular docstring without defining it manually. See hydra-examples.el for an example code.

More Repositories

1

swiper

Ivy - a generic completion frontend for Emacs, Swiper - isearch with an overview, and more. Oh, man!
Emacs Lisp
2,284
star
2

avy

Jump to things in Emacs tree-style
Emacs Lisp
1,704
star
3

lispy

Short and sweet LISP editing
Emacs Lisp
1,195
star
4

org-download

Drag and drop images to Emacs org-mode
Emacs Lisp
1,055
star
5

ace-window

Quickly switch windows in Emacs
Emacs Lisp
968
star
6

oremacs

My Emacs config
Emacs Lisp
296
star
7

auto-yasnippet

quickly create disposable yasnippets
Emacs Lisp
243
star
8

pamparam

Simple and fast flashcards for Emacs
Emacs Lisp
228
star
9

ace-link

Quickly follow links in Emacs
Emacs Lisp
210
star
10

lpy

Minimal Python IDE for GNU Emacs
Emacs Lisp
185
star
11

define-word

Display the definition of word at point in Emacs
Emacs Lisp
154
star
12

tiny

Quickly generate linear ranges in Emacs
Emacs Lisp
143
star
13

worf

vi-like bindings for org-mode
Emacs Lisp
132
star
14

function-args

C++ completion for GNU Emacs
Emacs Lisp
120
star
15

orca

ORg CApture
Emacs Lisp
104
star
16

swiper-helm

Helm version of swiper
Emacs Lisp
79
star
17

helm-make

Select a Makefile target with helm.
Emacs Lisp
62
star
18

plain-org-wiki

Simple jump-to-org-files in a directory package
Emacs Lisp
53
star
19

make-it-so

Transform files with Makefile recipes
Emacs Lisp
47
star
20

org-fu

My org-capture and org-protocol setup
Emacs Lisp
37
star
21

cook

Makefile -> Cookbook.py
Python
36
star
22

whicher

Audit and install the programs that your Emacs config depends on
Emacs Lisp
23
star
23

eclipse-theme

Eclipse theme for Emacs
Emacs Lisp
21
star
24

orly

Additional Org-mode link types and completion for them
Emacs Lisp
18
star
25

helm-j-cheatsheet

Quick J reference for Emacs
Emacs Lisp
17
star
26

zoutline

Emacs library for outlines
Emacs Lisp
16
star
27

gtk-pomodoro-indicator

A simple pomodoro indicator for Unity/GTK tray
Python
16
star
28

elf-mode

Show symbol list when opening a binary file in Emacs
Emacs Lisp
13
star
29

matlab-mode

My fork of matlab-mode
Emacs Lisp
13
star
30

short-lambda

Clojure-style anonymous function literal for Elisp
Emacs Lisp
12
star
31

centimacro

Assign multiple macros as global key bindings in Emacs
Emacs Lisp
12
star
32

xlx

XML <-> LISP converter
Common Lisp
11
star
33

eltex

Write LaTeX in Emacs Lisp
Emacs Lisp
10
star
34

headlong

reckless completion for Emacs
Emacs Lisp
10
star
35

semimap

.Xmodmap that uses semicolon as a modifier
8
star
36

dired-guess

Associate file extensions with programs that can open them for dired
Emacs Lisp
8
star
37

cc-chainsaw

A few tricks to make c++-mode go
Emacs Lisp
7
star
38

ukrainian-holidays

Ukrainian holidays for Emacs calendar
Emacs Lisp
7
star
39

org-mode

http://orgmode.org/org-mode.git
Emacs Lisp
6
star
40

profile-dotemacs

My mirror of http://www.randomsample.de/profile-dotemacs.el
Emacs Lisp
6
star
41

abel

abbrevs for Elisp
Emacs Lisp
5
star
42

touchpad

Toggle touchpad on/off
Python
5
star
43

cedet

My mirror of CEDET (http://cedet.sourceforge.net/).
Emacs Lisp
5
star
44

abo-abo.github.io

HTML
4
star
45

ido-occasional

Use ido where you choose
Emacs Lisp
4
star
46

netherlands-holidays

Netherlands holidays for Emacs calendar
Emacs Lisp
4
star
47

latex-wrap

Wrap selection with a LaTeX environment
Emacs Lisp
4
star
48

simple-benchmark

My notes comparing several PL on a simple benchmark
C++
4
star
49

ciao

Emacs Lisp
4
star
50

xkb-indicator

GTK indicator that allows to run setxkbmap via a keybinding
Python
2
star
51

malk

a simple multi-collection completion demo
Emacs Lisp
1
star
52

soap

Smart Operator a posteriori
Emacs Lisp
1
star