branch: master commit 5aa78963734dc6975cad8df1c8853b65a4f1b826 Merge: af29d76 7783f89 Author: Oleh Krehel <ohwoeo...@gmail.com> Commit: Oleh Krehel <ohwoeo...@gmail.com>
Merge commit '7783f89cdbb3e3ba57f232552729715148e0b9a1' from hydra --- packages/hydra/Makefile | 8 +- packages/hydra/README.md | 537 +++++++++++++++++++++++--------------- packages/hydra/hydra-examples.el | 140 +++++++--- packages/hydra/hydra-init.el | 29 ++ packages/hydra/hydra-ox.el | 10 +- packages/hydra/hydra-test.el | 348 +++++++++++++----------- packages/hydra/hydra.el | 249 +++++++++++------- packages/hydra/lv.el | 13 +- 8 files changed, 813 insertions(+), 521 deletions(-) diff --git a/packages/hydra/Makefile b/packages/hydra/Makefile index 4b6451f..35709e1 100644 --- a/packages/hydra/Makefile +++ b/packages/hydra/Makefile @@ -1,5 +1,5 @@ -EMACS = emacs -# EMACS = emacs-24.3 +emacs ?= emacs +# emacs = emacs-24.3 LOAD = -l lv.el -l hydra.el -l hydra-test.el @@ -8,10 +8,10 @@ LOAD = -l lv.el -l hydra.el -l hydra-test.el all: test test: - $(EMACS) -batch $(LOAD) -f ert-run-tests-batch-and-exit + $(emacs) -batch $(LOAD) -f ert-run-tests-batch-and-exit compile: - $(EMACS) -q $(LOAD) -l init.el --eval "(progn (mapc #'byte-compile-file '(\"hydra.el\" \"init.el\")) (switch-to-buffer \"*Compile-Log*\") (ert t))" + $(emacs) -q $(LOAD) -l hydra-init.el make clean clean: diff --git a/packages/hydra/README.md b/packages/hydra/README.md index 70b31bf..172524e 100644 --- a/packages/hydra/README.md +++ b/packages/hydra/README.md @@ -1,23 +1,48 @@ [](https://travis-ci.org/abo-abo/hydra) -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. - - - -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 orignal purpose, calling his proper -command. This makes the Hydra very seamless, it's like a minor mode -that disables itself auto-magically. - -## Sample global Hydras -### Zoom +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. + + + +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. + +<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc/generate-toc again --> +**Table of Contents** + +- [Sample Hydras](#sample-hydras) + - [The one with the least amount of code](#the-one-with-the-least-amount-of-code) + - [The impressive-looking one](#the-impressive-looking-one) +- [Community wiki](#community-wiki) +- [The Rules of Hydra-tics](#the-rules-of-hydra-tics) + - [`hydra-awesome`](#hydra-awesome) + - [`awesome-map` and `awesome-binding`](#awesome-map-and-awesome-binding) + - [`awesome-plist`](#awesome-plist) + - [`:pre` and `:post`](#pre-and-post) + - [`:exit`](#exit) + - [`:foreign-keys`](#foreign-keys) + - [`:color`](#color) + - [`:timeout`](#timeout) + - [`:hint`](#hint) + - [`:bind`](#bind) + - [`awesome-docstring`](#awesome-docstring) + - [`awesome-head-1`](#awesome-head-1) + - [`head-binding`](#head-binding) + - [`head-command`](#head-command) + - [`head-hint`](#head-hint) + - [`head-plist`](#head-plist) + +<!-- markdown-toc end --> + +# Sample Hydras + +## The one with the least amount of code ```cl (defhydra hydra-zoom (global-map "<f2>") @@ -26,238 +51,341 @@ that disables itself auto-magically. ("l" text-scale-decrease "out")) ``` -### Goto-error +With this simple code, you can: -```cl -(defhydra hydra-error (global-map "M-g") - "goto-error" - ("h" first-error "first") - ("j" next-error "next") - ("k" previous-error "prev") - ("v" recenter-top-bottom "recenter") - ("q" nil "quit")) -``` +- Start zooming in with <kbd><f2> g</kbd>. +- Continue to zoom in with <kbd>g</kbd>. +- Or zoom out with <kbd>l</kbd>. +- Zoom in five times at once with <kbd>5g</kbd>. +- Stop zooming with *any* key that isn't <kbd>g</kbd> or <kbd>l</kbd>. -### Splitter +For any Hydra: -```cl -(require 'hydra-examples) -(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)) -``` +- `digit-argument` can be called with <kbd>0</kbd>-<kbd>9</kbd>. +- `negative-argument` can be called with <kbd>-</kbd>. +- `universal-argument` can be called with <kbd>C-u</kbd>. -### Community wiki -A few useful hydras are aggregated in projects [community wiki](https://github.com/abo-abo/hydra/wiki/Hydras%20by%20Topic). Feel free to add your own or edit existing ones. +## The impressive-looking one -## Using the functions generated by `defhydra` +Here's the result of pressing <kbd>.</kbd> in the good-old Buffer menu: -With the example above, you can e.g.: + + +The code is large but very simple: ```cl -(key-chord-define-global "tt" 'hydra-zoom/body) +(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) ``` -In fact, since `defhydra` returns the body symbol, you can even write -it like this: +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. -```cl -(key-chord-define-global - "tt" - (defhydra hydra-zoom (global-map "<f2>") - "zoom" - ("g" text-scale-increase "in") - ("l" text-scale-decrease "out"))) -``` +To write your own hydras, you can: + +- Either modify an existing hydra to do what you want to do. +- Or read [the rules](#the-rules-of-hydra-tics), + [the examples](https://github.com/abo-abo/hydra/blob/master/hydra-examples.el), + 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](https://github.com/abo-abo/hydra/wiki/). Feel free to add your +own or edit the existing ones. -If you like key chords so much that you don't want to touch the global -map at all, you can e.g.: +# The Rules of Hydra-tics + +Each hydra (take `awesome` as a prefix to make it more specific) looks like this: ``` -(key-chord-define-global - "hh" - (defhydra hydra-error () - "goto-error" - ("h" first-error "first") - ("j" next-error "next") - ("k" previous-error "prev"))) +(defhydra hydra-awesome (awesome-map awesome-binding awesome-plist) + awesome-docstring + awesome-head-1 + awesome-head-2 + awesome-head-3 + ...) ``` -You can also substitute `global-map` with any other keymap, like -`c++-mode-map` or `yas-minor-mode-map`. +## `hydra-awesome` -See the [introductory blog post](http://oremacs.com/2015/01/20/introducing-hydra/) for more information. +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. -## Using Hydra for major-mode or minor-mode bindings +If you name your hydra `hydra-awesome`, the return result of `defhydra` will be `hydra-awesome/body`. -Here's an example: +Here's what `hydra-zoom/body` looks like, if you're interested: ```cl -(defhydra lispy-vi (lispy-mode-map "C-z") - "vi" - ("l" forward-char) - ("h" backward-char) - ("j" next-line) - ("k" previous-line)) +(defun hydra-zoom/body nil + "Create a hydra with a \"<f2>\" body and the heads: + +\"g\": `text-scale-increase', +\"l\": `text-scale-decrease' + +The body can be accessed via `hydra-zoom/body'." + (interactive) + (hydra-disable) + (catch (quote hydra-disable) + (when hydra-is-helpful (hydra-zoom/hint)) + (setq hydra-last + (hydra-set-transient-map + (setq hydra-curr-map + (quote + (keymap (7 . hydra-keyboard-quit) + (108 . hydra-zoom/text-scale-decrease) + (103 . hydra-zoom/text-scale-increase) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) + t (lambda nil (hydra-cleanup)))) + (setq prefix-arg current-prefix-arg))) ``` -## Can Hydras can be helpful? +## `awesome-map` and `awesome-binding` -They can, if +This can be any keymap, for instance, `global-map` or `isearch-mode-map`. -```cl -(setq hydra-is-helpful t) -``` - -This is the default setting. In this case, you'll get a hint in the -echo area consisting of current Hydra's base comment and heads. You -can even add comments to the heads like this: +For this example: -``` +```cl (defhydra hydra-zoom (global-map "<f2>") "zoom" ("g" text-scale-increase "in") ("l" text-scale-decrease "out")) ``` -With this, you'll see `zoom: [g]: in, [l]: out.` in your echo area, -once the zoom Hydra becomes active. +- `awesome-map` is `global-map` +- `awesome-binding` is `"<f2>"` -## Colorful Hydras +And here's the relevant generated code: -Since version `0.5.0`, Hydra's heads all have a color associated with them: +```cl +(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)) +``` -- *red* (default) means the calling this head will not vanquish the Hydra -- *blue* means that the Hydra will be vanquished after calling this head +As you see, `"<f2>"` is used as a prefix for <kbd>g</kbd> (char value 103) and <kbd>l</kbd> +(char value 108). -In all the older examples, all heads are red by default. You can specify blue heads like this: +If you don't want to use a map right now, you can skip it like this: ```cl -(global-set-key - (kbd "C-c C-v") - (defhydra toggle () - "toggle" - ("a" abbrev-mode "abbrev" :color blue) - ("d" toggle-debug-on-error "debug" :color blue) - ("f" auto-fill-mode "fill" :color blue) - ("t" toggle-truncate-lines "truncate" :color blue) - ("w" whitespace-mode "whitespace" :color blue) - ("q" nil "cancel"))) +(defhydra hydra-zoom (nil nil) + "zoom" + ("g" text-scale-increase "in") + ("l" text-scale-decrease "out")) ``` -Or, since the heads can inherit the color from the body, the following is equivalent: +Or even simpler: ```cl -(global-set-key - (kbd "C-c C-v") - (defhydra toggle (:color blue) - "toggle" - ("a" abbrev-mode "abbrev") - ("d" toggle-debug-on-error "debug") - ("f" auto-fill-mode "fill") - ("t" toggle-truncate-lines "truncate") - ("w" whitespace-mode "whitespace") - ("q" nil "cancel"))) +(defhydra hydra-zoom () + "zoom" + ("g" text-scale-increase "in") + ("l" text-scale-decrease "out")) ``` -The above Hydra is very similar to this code: +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](https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html). + +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: ```cl -(global-set-key (kbd "C-c C-v t") 'toggle-truncate-lines) -(global-set-key (kbd "C-c C-v f") 'auto-fill-mode) -(global-set-key (kbd "C-c C-v a") 'abbrev-mode) +(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")) ``` -However, there are two important differences: +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`. -- you get a hint like this right after <kbd>C-c C-v</kbd>: +### `:exit` - toggle: [t]: truncate, [f]: fill, [a]: abbrev, [q]: cancel. +The `:exit` key is inherited by every head (they can override it) and influences what will happen +after executing head's command: -- you can cancel <kbd>C-c C-v</kbd> with a command while executing that command, instead of e.g. -getting an error `C-c C-v C-n is undefined` for <kbd>C-c C-v C-n</kbd>. +- `: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. -## Hydras and numeric arguments +### `:foreign-keys` -Since version `0.6.0`, for any Hydra: +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: -- `digit-argment` can be called with <kbd>0</kbd>-<kbd>9</kbd>. -- `negative-argument` can be called with <kbd>-</kbd> -- `universal-argument` can be called with <kbd>C-u</kbd> +- `: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. -## Hydras can have `:pre` and `:post` statements +### `:color` -Since version `0.7.0`, you can specify code that will be called before each head, and -after the body. For example: +The `:color` key is a shortcut. It aggregates `:exit` and `:foreign-keys` key in the following way: -```cl -(global-set-key - (kbd "C-z") - (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"))) -``` + | 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. + +## `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. -## New Hydra color: amaranth +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. -Since version `0.8.0`, a new color - amaranth, in addition to the previous red and blue, is -available for the Hydra body. +To insert an empty character, use `^`. The only use of this is to have your code aligned as +nicely as the result. -According to [Wikipedia](http://en.wikipedia.org/wiki/Amaranth): +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. -> The word amaranth comes from the Greek word amaranton, meaning "unwilting" (from the -> verb marainesthai, meaning "wilt"). The word was applied to amaranth because it did not -> soon fade and so symbolized immortality. +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. -Hydras with amaranth body are impossible to quit with any binding *except* a blue head. -A check for at least one blue head exists in `defhydra`, so that you don't get stuck by accident. +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")`. -Here's an example of an amaranth Hydra: +## `awesome-head-1` + +Each head looks like this: ```cl -(global-set-key - (kbd "C-z") - (defhydra hydra-vi - (:pre - (set-cursor-color "#40e0d0") - :post - (set-cursor-color "#ffffff") - :color amaranth) - "vi" - ("l" forward-char) - ("h" backward-char) - ("j" next-line) - ("k" previous-line) - ("q" nil "quit"))) +(head-binding head-command head-hint head-plist) ``` -The only way to exit it, is to press <kbd>q</kbd>. No other methods will work. You can -use an amaranth Hydra instead of a red one, if for you the cost of being able to exit only -though certain bindings is less than the cost of accidentally exiting a red Hydra by -pressing the wrong prefix. +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: -Note that it does not make sense to define a single amaranth head, so this color can only -be assigned to the body. An amaranth body will always have some amaranth heads and some -blue heads (otherwise, it's impossible to exit), no reds. +- command name, like `text-scale-increase`. +- a lambda, like -## Generate simple lambdas in-place: + ("g" (lambda () + (interactive) + (let ((current-prefix-arg 4)) + (call-interactively #'magit-status))) + "git") -Since version `0.9.0` it's possible to pass a single sexp instead of a function name or a lambda -to a head. This sexp will be wrapped in an interactive lambda. Here's an example: +- nil, which exits the hydra. +- a single sexp, which will be wrapped in an interactive lambda. + +Here's an example of the last option: ```cl (defhydra hydra-launcher (:color blue) @@ -270,40 +398,27 @@ to a head. This sexp will be wrapped in an interactive lambda. Here's an example (global-set-key (kbd "C-c r") 'hydra-launcher/body) ``` -## Define Hydra heads that don't show up in the hint at all - -This can be done by setting the head's hint explicitly to `nil`, instead of the usual string. - -## Use a dedicated window for Hydra hints +### `head-hint` -Since version `0.10.0`, setting `hydra-lv` to `t` (the default setting) will make it use a dedicated -window right above the Echo Area for hints. This has the advantage that you can immediately see -any `message` output from the functions that you call, since Hydra no longer uses `message` to display -the hint. You can still have the old behavior by setting `hydra-lv` to `nil`. +In case of a large body docstring, you usually don't want the head hint to show up, since +you've already documented it the the body docstring. +You can set the head hint to `nil` to do this. -## Color table +Example: +```cl +(defhydra hydra-zoom (global-map "<f2>") + " +Press _g_ to zoom in. +" + ("g" text-scale-increase nil) + ("l" text-scale-decrease "out")) +``` - | Body Color | Head Inherited | Executing NON-HEADS | Executing HEADS | - |------------+----------------+-----------------------+-----------------| - | amaranth | red | Disallow and Continue | Continue | - | teal | blue | Disallow and Continue | Quit | - | pink | red | Allow and Continue | Continue | - | red | red | Allow and Quit | Continue | - | blue | blue | Allow and Quit | Quit | - -## Color to toggle correspondence +### `head-plist` -By popular demand, an alternative syntax has been implemented that translates to colors without -using them in the syntax. `:exit` can be used both in body (heads will inherit) and in heads -(possible to override body). `:exit` is nil by default, corresponding to `red` head; you don't need -to set it explicitly to nil. `:foreign-keys` can be used only in body and can be either nil (default), -`warn` or `run`. +Here's a list of body keys that can be overridden in each head: - | color | toggle | - |----------+----------------------------| - | red | | - | blue | :exit t | - | amaranth | :foreign-keys warn | - | teal | :foreign-keys warn :exit t | - | pink | :foreign-keys run | +- `:exit` +- `:color` +- `:bind` diff --git a/packages/hydra/hydra-examples.el b/packages/hydra/hydra-examples.el index 50773b0..872814b 100644 --- a/packages/hydra/hydra-examples.el +++ b/packages/hydra/hydra-examples.el @@ -85,9 +85,10 @@ ;;** Example 4: toggle rarely used modes (when (bound-and-true-p hydra-examples-verbatim) + (defvar whitespace-mode nil) (global-set-key (kbd "C-c C-v") - (defhydra hydra-toggle (:color blue) + (defhydra hydra-toggle-simple (:color blue) "toggle" ("a" abbrev-mode "abbrev") ("d" toggle-debug-on-error "debug") @@ -96,7 +97,7 @@ ("w" whitespace-mode "whitespace") ("q" nil "cancel")))) -;; Note that in this case, `defhydra' returns the `hydra-toggle/body' +;; Note that in this case, `defhydra' returns the `hydra-toggle-simple/body' ;; symbol, which is then passed to `global-set-key'. ;; ;; Another new thing is that both the keymap and the body prefix are @@ -160,26 +161,26 @@ ;; This example will bind "C-x `" in `global-map', but it will not ;; bind "C-x j" and "C-x k". ;; You can still "C-x `jjk" though. + ;;** Example 7: toggle with Ruby-style docstring -(when (bound-and-true-p hydra-examples-verbatim) - (defhydra hydra-toggle (:color pink) - " +(defvar whitespace-mode nil) +(defhydra hydra-toggle (:color pink) + " _a_ abbrev-mode: %`abbrev-mode _d_ debug-on-error: %`debug-on-error _f_ auto-fill-mode: %`auto-fill-function -_g_ golden-ratio-mode: %`golden-ratio-mode _t_ truncate-lines: %`truncate-lines _w_ whitespace-mode: %`whitespace-mode " - ("a" abbrev-mode nil) - ("d" toggle-debug-on-error nil) - ("f" auto-fill-mode nil) - ("g" golden-ratio-mode nil) - ("t" toggle-truncate-lines nil) - ("w" whitespace-mode nil) - ("q" nil "quit")) - (global-set-key (kbd "C-c C-v") 'hydra-toggle/body)) + ("a" abbrev-mode nil) + ("d" toggle-debug-on-error nil) + ("f" auto-fill-mode nil) + ("t" toggle-truncate-lines nil) + ("w" whitespace-mode nil) + ("q" nil "quit")) +;; Recommended binding: +;; (global-set-key (kbd "C-c C-v") 'hydra-toggle/body) ;; Here, using e.g. "_a_" translates to "a" with proper face. ;; More interestingly: @@ -187,46 +188,49 @@ _w_ whitespace-mode: %`whitespace-mode ;; "foobar %`abbrev-mode" means roughly (format "foobar %S" abbrev-mode) ;; ;; This means that you actually see the state of the mode that you're changing. + ;;** Example 8: the whole menu for `Buffer-menu-mode' -(defhydra hydra-buffer-menu (:color pink) +(defhydra hydra-buffer-menu (:color pink + :hint nil) " - Mark Unmark Actions Search -------------------------------------------------------------------------- (__) +^Mark^ ^Unmark^ ^Actions^ ^Search +^^^^^^^^----------------------------------------------------------------- (__) _m_: mark _u_: unmark _x_: execute _R_: re-isearch (oo) _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 ~~ ~~ +_d_: delete ^ ^ _g_: refresh _O_: multi-occur / | || +_D_: delete up ^ ^ _T_: files only: % -28`Buffer-menu-files-only^^ * /\\---/\\ +_~_: modified ^ ^ ^ ^ ^^ ~~ ~~ " - ("m" Buffer-menu-mark nil) - ("u" Buffer-menu-unmark nil) - ("U" Buffer-menu-backup-unmark nil) - ("d" Buffer-menu-delete nil) - ("D" Buffer-menu-delete-backwards nil) - ("s" Buffer-menu-save nil) - ("~" Buffer-menu-not-modified nil) - ("x" Buffer-menu-execute nil) - ("b" Buffer-menu-bury nil) - ("g" revert-buffer nil) - ("T" Buffer-menu-toggle-files-only nil) - ("O" Buffer-menu-multi-occur nil :color blue) - ("I" Buffer-menu-isearch-buffers nil :color blue) - ("R" Buffer-menu-isearch-buffers-regexp nil :color blue) + ("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)) ;; Recommended binding: ;; (define-key Buffer-menu-mode-map "." 'hydra-buffer-menu/body) + ;;** Example 9: s-expressions in the docstring ;; You can inline s-expresssions into the docstring like this: (when (bound-and-true-p hydra-examples-verbatim) - (eval-after-load 'dired - (defhydra hydra-marked-items (dired-mode-map "") - " + (require 'dired) + (defhydra hydra-marked-items (dired-mode-map "") + " Number of marked items: %(length (dired-get-marked-files)) " - ("m" dired-mark "mark")))) + ("m" dired-mark "mark"))) ;; This results in the following dynamic docstring: ;; @@ -235,7 +239,55 @@ Number of marked items: %(length (dired-get-marked-files)) ;; ;; You can use `format'-style width specs, e.g. % 10(length nil). -;;* Windmove helpers +;;** Example 10: apropos family +(defhydra hydra-apropos (:color blue + :hint nil) + " +_a_propos _c_ommand +_d_ocumentation _l_ibrary +_v_ariable _u_ser-option +^ ^ valu_e_" + ("a" apropos) + ("d" apropos-documentation) + ("v" apropos-variable) + ("c" apropos-command) + ("l" apropos-library) + ("u" apropos-user-option) + ("e" apropos-value)) +;; Recommended binding: +;; (global-set-key (kbd "C-c h") 'hydra-apropos/body) + +;;** Example 11: rectangle-mark-mode +(defhydra hydra-rectangle (:body-pre (rectangle-mark-mode 1) + :color pink + :post (deactivate-mark)) + " + ^_k_^ _d_elete _s_tring |\\ _,,,--,,_ +_h_ _l_ _o_k _y_ank /,`.-'`' ._ \-;;,_ + ^_j_^ _n_ew-copy _r_eset |,4- ) )_ .;.( `'-' +^^^^ _e_xchange _u_ndo '---''(_/._)-'(_\_) +^^^^ ^ ^ _p_aste +" + ("h" backward-char nil) + ("l" forward-char nil) + ("k" previous-line nil) + ("j" next-line nil) + ("e" hydra-ex-point-mark nil) + ("n" copy-rectangle-as-kill nil) + ("d" delete-rectangle nil) + ("r" (if (region-active-p) + (deactivate-mark) + (rectangle-mark-mode 1)) nil) + ("y" yank-rectangle nil) + ("u" undo nil) + ("s" string-rectangle nil) + ("p" kill-rectangle nil) + ("o" nil nil)) + +;; Recommended binding: +;; (global-set-key (kbd "C-x SPC") 'hydra-rectangle/body) + +;;* Helpers (require 'windmove) (defun hydra-move-splitter-left (arg) @@ -270,5 +322,15 @@ Number of marked items: %(length (dired-get-marked-files)) (shrink-window arg) (enlarge-window arg))) +(defvar rectangle-mark-mode) +(defun hydra-ex-point-mark () + "Exchange point and mark." + (interactive) + (if rectangle-mark-mode + (exchange-point-and-mark) + (let ((mk (mark))) + (rectangle-mark-mode 1) + (goto-char mk)))) + (provide 'hydra-examples) ;;; hydra-examples.el ends here diff --git a/packages/hydra/hydra-init.el b/packages/hydra/hydra-init.el new file mode 100644 index 0000000..80b4159 --- /dev/null +++ b/packages/hydra/hydra-init.el @@ -0,0 +1,29 @@ +;;; hydra-test.el --- bare hydra init + +;; Copyright (C) 2015 Free Software Foundation, Inc. + +;; Author: Oleh Krehel + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +(add-to-list 'load-path default-directory) +(require 'hydra) +(setq hydra-examples-verbatim t) +(require 'hydra-examples) +(require 'hydra-test) +(mapc #'byte-compile-file '("hydra.el")) +(switch-to-buffer "*Compile-Log*") +(ert t) diff --git a/packages/hydra/hydra-ox.el b/packages/hydra/hydra-ox.el index 4053081..5f1a5c9 100644 --- a/packages/hydra/hydra-ox.el +++ b/packages/hydra/hydra-ox.el @@ -27,11 +27,11 @@ (require 'org) (defhydradio hydra-ox () - (body-only) - (export-scope [buffer subtree]) - (async-export) - (visible-only) - (force-publishing)) + (body-only "Export only the body.") + (export-scope "Export scope." [buffer subtree]) + (async-export "When non-nil, export async.") + (visible-only "When non-nil, export visible only") + (force-publishing "Toggle force publishing")) (defhydra hydra-ox-html (:color blue) "ox-html" diff --git a/packages/hydra/hydra-test.el b/packages/hydra/hydra-test.el index 754984d..b908ac0 100644 --- a/packages/hydra/hydra-test.el +++ b/packages/hydra/hydra-test.el @@ -64,6 +64,7 @@ Call the head: `first-error'." (107 . hydra-error/previous-error) (106 . hydra-error/next-error) (104 . hydra-error/first-error) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -115,6 +116,7 @@ Call the head: `next-error'." (107 . hydra-error/previous-error) (106 . hydra-error/next-error) (104 . hydra-error/first-error) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -166,6 +168,7 @@ Call the head: `previous-error'." (107 . hydra-error/previous-error) (106 . hydra-error/next-error) (104 . hydra-error/first-error) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -223,35 +226,35 @@ The body can be accessed via `hydra-error/body'." (setq hydra-last (hydra-set-transient-map (setq hydra-curr-map - (quote - (keymap (7 . hydra-keyboard-quit) - (32 . hydra-repeat) - (107 . hydra-error/previous-error) - (106 . hydra-error/next-error) - (104 . hydra-error/first-error) - (kp-subtract . hydra--negative-argument) - (kp-9 . hydra--digit-argument) - (kp-8 . hydra--digit-argument) - (kp-7 . hydra--digit-argument) - (kp-6 . hydra--digit-argument) - (kp-5 . hydra--digit-argument) - (kp-4 . hydra--digit-argument) - (kp-3 . hydra--digit-argument) - (kp-2 . hydra--digit-argument) - (kp-1 . hydra--digit-argument) - (kp-0 . hydra--digit-argument) - (57 . hydra--digit-argument) - (56 . hydra--digit-argument) - (55 . hydra--digit-argument) - (54 . hydra--digit-argument) - (53 . hydra--digit-argument) - (52 . hydra--digit-argument) - (51 . hydra--digit-argument) - (50 . hydra--digit-argument) - (49 . hydra--digit-argument) - (48 . hydra--digit-argument) - (45 . hydra--negative-argument) - (21 . hydra--universal-argument)))) + (quote (keymap (7 . hydra-keyboard-quit) + (32 . hydra-repeat) + (107 . hydra-error/previous-error) + (106 . hydra-error/next-error) + (104 . hydra-error/first-error) + (switch-frame . hydra--handle-switch-frame) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) t (lambda nil (hydra-cleanup)))) (setq prefix-arg current-prefix-arg))))))) @@ -349,35 +352,35 @@ The body can be accessed via `hydra-toggle/body'." (setq hydra-last (hydra-set-transient-map (setq hydra-curr-map - (quote - (keymap (7 . hydra-keyboard-quit) - (113 . hydra-toggle/nil) - (97 . hydra-toggle/abbrev-mode) - (102 . hydra-toggle/auto-fill-mode) - (116 . hydra-toggle/toggle-truncate-lines) - (kp-subtract . hydra--negative-argument) - (kp-9 . hydra--digit-argument) - (kp-8 . hydra--digit-argument) - (kp-7 . hydra--digit-argument) - (kp-6 . hydra--digit-argument) - (kp-5 . hydra--digit-argument) - (kp-4 . hydra--digit-argument) - (kp-3 . hydra--digit-argument) - (kp-2 . hydra--digit-argument) - (kp-1 . hydra--digit-argument) - (kp-0 . hydra--digit-argument) - (57 . hydra--digit-argument) - (56 . hydra--digit-argument) - (55 . hydra--digit-argument) - (54 . hydra--digit-argument) - (53 . hydra--digit-argument) - (52 . hydra--digit-argument) - (51 . hydra--digit-argument) - (50 . hydra--digit-argument) - (49 . hydra--digit-argument) - (48 . hydra--digit-argument) - (45 . hydra--negative-argument) - (21 . hydra--universal-argument)))) + (quote (keymap (7 . hydra-keyboard-quit) + (113 . hydra-toggle/nil) + (97 . hydra-toggle/abbrev-mode) + (102 . hydra-toggle/auto-fill-mode) + (116 . hydra-toggle/toggle-truncate-lines) + (switch-frame . hydra--handle-switch-frame) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) t (lambda nil (hydra-cleanup)))) (setq prefix-arg current-prefix-arg))))))) @@ -396,8 +399,26 @@ The body can be accessed via `hydra-toggle/body'." ("k" previous-line) ("q" nil "quit"))) '(progn + (defun hydra-vi/hydra-keyboard-quit nil "Create a hydra with no body and the heads: + +\"\": `hydra-keyboard-quit', +\"j\": `next-line', +\"k\": `previous-line', +\"q\": `nil' + +The body can be accessed via `hydra-vi/body'. + +Call the head: `hydra-keyboard-quit'." + (interactive) + (set-cursor-color "#e52b50") + (hydra-disable) + (hydra-cleanup) + (catch (quote hydra-disable) + (call-interactively (function hydra-keyboard-quit)) + (set-cursor-color "#ffffff"))) (defun hydra-vi/next-line nil "Create a hydra with no body and the heads: +\"\": `hydra-keyboard-quit', \"j\": `next-line', \"k\": `previous-line', \"q\": `nil' @@ -410,49 +431,51 @@ Call the head: `next-line'." (hydra-disable) (catch (quote hydra-disable) (condition-case err (prog1 t (call-interactively (function next-line))) - ((quit error) (message "%S" err) + ((quit error) + (message "%S" err) (unless hydra-lv (sit-for 0.8)) nil)) (when hydra-is-helpful (hydra-vi/hint)) (setq hydra-last (hydra-set-transient-map (setq hydra-curr-map - (quote - (keymap (t lambda nil (interactive) - (message "An amaranth Hydra can only exit through a blue head") - (hydra-set-transient-map hydra-curr-map t) - (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) - (hydra-vi/hint))) - (7 . hydra-keyboard-quit) - (113 . hydra-vi/nil) - (107 . hydra-vi/previous-line) - (106 . hydra-vi/next-line) - (kp-subtract . hydra--negative-argument) - (kp-9 . hydra--digit-argument) - (kp-8 . hydra--digit-argument) - (kp-7 . hydra--digit-argument) - (kp-6 . hydra--digit-argument) - (kp-5 . hydra--digit-argument) - (kp-4 . hydra--digit-argument) - (kp-3 . hydra--digit-argument) - (kp-2 . hydra--digit-argument) - (kp-1 . hydra--digit-argument) - (kp-0 . hydra--digit-argument) - (57 . hydra--digit-argument) - (56 . hydra--digit-argument) - (55 . hydra--digit-argument) - (54 . hydra--digit-argument) - (53 . hydra--digit-argument) - (52 . hydra--digit-argument) - (51 . hydra--digit-argument) - (50 . hydra--digit-argument) - (49 . hydra--digit-argument) - (48 . hydra--digit-argument) - (45 . hydra--negative-argument) - (21 . hydra--universal-argument)))) + (quote (keymap (t lambda nil (interactive) + (message "An amaranth Hydra can only exit through a blue head") + (hydra-set-transient-map hydra-curr-map t) + (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) + (hydra-vi/hint))) + (113 . hydra-vi/nil) + (107 . hydra-vi/previous-line) + (106 . hydra-vi/next-line) + (7 . hydra-vi/hydra-keyboard-quit) + (switch-frame . hydra--handle-switch-frame) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) t (lambda nil (hydra-cleanup)))))) (defun hydra-vi/previous-line nil "Create a hydra with no body and the heads: +\"\": `hydra-keyboard-quit', \"j\": `next-line', \"k\": `previous-line', \"q\": `nil' @@ -465,49 +488,51 @@ Call the head: `previous-line'." (hydra-disable) (catch (quote hydra-disable) (condition-case err (prog1 t (call-interactively (function previous-line))) - ((quit error) (message "%S" err) + ((quit error) + (message "%S" err) (unless hydra-lv (sit-for 0.8)) nil)) (when hydra-is-helpful (hydra-vi/hint)) (setq hydra-last (hydra-set-transient-map (setq hydra-curr-map - (quote - (keymap (t lambda nil (interactive) - (message "An amaranth Hydra can only exit through a blue head") - (hydra-set-transient-map hydra-curr-map t) - (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) - (hydra-vi/hint))) - (7 . hydra-keyboard-quit) - (113 . hydra-vi/nil) - (107 . hydra-vi/previous-line) - (106 . hydra-vi/next-line) - (kp-subtract . hydra--negative-argument) - (kp-9 . hydra--digit-argument) - (kp-8 . hydra--digit-argument) - (kp-7 . hydra--digit-argument) - (kp-6 . hydra--digit-argument) - (kp-5 . hydra--digit-argument) - (kp-4 . hydra--digit-argument) - (kp-3 . hydra--digit-argument) - (kp-2 . hydra--digit-argument) - (kp-1 . hydra--digit-argument) - (kp-0 . hydra--digit-argument) - (57 . hydra--digit-argument) - (56 . hydra--digit-argument) - (55 . hydra--digit-argument) - (54 . hydra--digit-argument) - (53 . hydra--digit-argument) - (52 . hydra--digit-argument) - (51 . hydra--digit-argument) - (50 . hydra--digit-argument) - (49 . hydra--digit-argument) - (48 . hydra--digit-argument) - (45 . hydra--negative-argument) - (21 . hydra--universal-argument)))) + (quote (keymap (t lambda nil (interactive) + (message "An amaranth Hydra can only exit through a blue head") + (hydra-set-transient-map hydra-curr-map t) + (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) + (hydra-vi/hint))) + (113 . hydra-vi/nil) + (107 . hydra-vi/previous-line) + (106 . hydra-vi/next-line) + (7 . hydra-vi/hydra-keyboard-quit) + (switch-frame . hydra--handle-switch-frame) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) t (lambda nil (hydra-cleanup)))))) (defun hydra-vi/nil nil "Create a hydra with no body and the heads: +\"\": `hydra-keyboard-quit', \"j\": `next-line', \"k\": `previous-line', \"q\": `nil' @@ -530,6 +555,7 @@ Call the head: `nil'." 11 12 (face hydra-face-blue)))))) (defun hydra-vi/body nil "Create a hydra with no body and the heads: +\"\": `hydra-keyboard-quit', \"j\": `next-line', \"k\": `previous-line', \"q\": `nil' @@ -543,39 +569,39 @@ The body can be accessed via `hydra-vi/body'." (setq hydra-last (hydra-set-transient-map (setq hydra-curr-map - (quote - (keymap (t lambda nil (interactive) - (message "An amaranth Hydra can only exit through a blue head") - (hydra-set-transient-map hydra-curr-map t) - (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) - (hydra-vi/hint))) - (7 . hydra-keyboard-quit) - (113 . hydra-vi/nil) - (107 . hydra-vi/previous-line) - (106 . hydra-vi/next-line) - (kp-subtract . hydra--negative-argument) - (kp-9 . hydra--digit-argument) - (kp-8 . hydra--digit-argument) - (kp-7 . hydra--digit-argument) - (kp-6 . hydra--digit-argument) - (kp-5 . hydra--digit-argument) - (kp-4 . hydra--digit-argument) - (kp-3 . hydra--digit-argument) - (kp-2 . hydra--digit-argument) - (kp-1 . hydra--digit-argument) - (kp-0 . hydra--digit-argument) - (57 . hydra--digit-argument) - (56 . hydra--digit-argument) - (55 . hydra--digit-argument) - (54 . hydra--digit-argument) - (53 . hydra--digit-argument) - (52 . hydra--digit-argument) - (51 . hydra--digit-argument) - (50 . hydra--digit-argument) - (49 . hydra--digit-argument) - (48 . hydra--digit-argument) - (45 . hydra--negative-argument) - (21 . hydra--universal-argument)))) + (quote (keymap (t lambda nil (interactive) + (message "An amaranth Hydra can only exit through a blue head") + (hydra-set-transient-map hydra-curr-map t) + (when hydra-is-helpful (unless hydra-lv (sit-for 0.8)) + (hydra-vi/hint))) + (113 . hydra-vi/nil) + (107 . hydra-vi/previous-line) + (106 . hydra-vi/next-line) + (7 . hydra-vi/hydra-keyboard-quit) + (switch-frame . hydra--handle-switch-frame) + (kp-subtract . hydra--negative-argument) + (kp-9 . hydra--digit-argument) + (kp-8 . hydra--digit-argument) + (kp-7 . hydra--digit-argument) + (kp-6 . hydra--digit-argument) + (kp-5 . hydra--digit-argument) + (kp-4 . hydra--digit-argument) + (kp-3 . hydra--digit-argument) + (kp-2 . hydra--digit-argument) + (kp-1 . hydra--digit-argument) + (kp-0 . hydra--digit-argument) + (57 . hydra--digit-argument) + (56 . hydra--digit-argument) + (55 . hydra--digit-argument) + (54 . hydra--digit-argument) + (53 . hydra--digit-argument) + (52 . hydra--digit-argument) + (51 . hydra--digit-argument) + (50 . hydra--digit-argument) + (49 . hydra--digit-argument) + (48 . hydra--digit-argument) + (45 . hydra--negative-argument) + (21 . hydra--universal-argument)))) t (lambda nil (hydra-cleanup)))) (setq prefix-arg current-prefix-arg))))))) @@ -837,6 +863,7 @@ Call the head: `(text-scale-set 0)'." (setq hydra-curr-map (quote (keymap (7 . hydra-keyboard-quit) (114 . hydra-zoom/lambda-r) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -897,6 +924,7 @@ The body can be accessed via `hydra-zoom/body'." (setq hydra-curr-map (quote (keymap (7 . hydra-keyboard-quit) (114 . hydra-zoom/lambda-r) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -957,6 +985,7 @@ Call the head: `(text-scale-set 0)'." (setq hydra-curr-map (quote (keymap (7 . hydra-keyboard-quit) (114 . hydra-zoom/lambda-r) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) @@ -1017,6 +1046,7 @@ The body can be accessed via `hydra-zoom/body'." (setq hydra-curr-map (quote (keymap (7 . hydra-keyboard-quit) (114 . hydra-zoom/lambda-r) + (switch-frame . hydra--handle-switch-frame) (kp-subtract . hydra--negative-argument) (kp-9 . hydra--digit-argument) (kp-8 . hydra--digit-argument) diff --git a/packages/hydra/hydra.el b/packages/hydra/hydra.el index dcdf03b..a3e8b9b 100644 --- a/packages/hydra/hydra.el +++ b/packages/hydra/hydra.el @@ -188,6 +188,7 @@ Vanquishable only through a blue head.") (define-key map [kp-8] 'hydra--digit-argument) (define-key map [kp-9] 'hydra--digit-argument) (define-key map [kp-subtract] 'hydra--negative-argument) + (define-key map [switch-frame] 'hydra--handle-switch-frame) map) "Keymap that all Hydras inherit. See `universal-argument-map'.") @@ -195,6 +196,12 @@ Vanquishable only through a blue head.") (make-sparse-keymap) "Keymap of the current Hydra called.") +(defun hydra--handle-switch-frame (evt) + "Quit hydra and call old switch-frame event handler." + (interactive "e") + (hydra-keyboard-quit) + (funcall (lookup-key (current-global-map) [switch-frame]) evt)) + (defun hydra--universal-argument (arg) "Forward to (`universal-argument' ARG)." (interactive "P") @@ -252,13 +259,21 @@ should be a single statement. Wrap it in an interactive lambda." (interactive) ,x))) +(defun hydra-plist-get-default (plist prop default) + "Extract a value from a property list. +PLIST is a property list, which is a list of the form +\(PROP1 VALUE1 PROP2 VALUE2...). + +Return the value corresponding to PROP, or DEFAULT if PROP is not +one of the properties on the list." + (if (memq prop plist) + (plist-get plist prop) + default)) + (defun hydra--head-property (h prop &optional default) "Return for Hydra head H the value of property PROP. Return DEFAULT if PROP is not in H." - (let ((plist (cl-cdddr h))) - (if (memq prop h) - (plist-get plist prop) - default))) + (hydra-plist-get-default (cl-cdddr h) prop default)) (defun hydra--aggregate-color (head-color body-color) "Return the resulting head color for HEAD-COLOR and BODY-COLOR." @@ -372,6 +387,8 @@ BODY is the second argument to `defhydra'" (hydra-disable) (hydra-cleanup) (cancel-timer hydra-timer) + (unless hydra-lv + (message "")) nil) (defun hydra-disable () @@ -464,7 +481,7 @@ The expressions can be auto-expanded according to NAME." offset) (while (setq start (string-match - "\\(?:%\\( ?-?[0-9]*s?\\)\\(`[a-z-A-Z/0-9]+\\|(\\)\\)\\|\\(?:_\\( ?-?[0-9]*\\)\\([a-z-~A-Z0-9/|?<>={}]+\\)_\\)" + "\\(?:%\\( ?-?[0-9]*s?\\)\\(`[a-z-A-Z/0-9]+\\|(\\)\\)\\|\\(?:_\\( ?-?[0-9]*\\)\\([a-z-~A-Z;:0-9/|?<>={}]+\\)_\\)" docstring start)) (cond ((eq ?_ (aref (match-string 0 docstring) 0)) (let* ((key (match-string 4 docstring)) @@ -482,7 +499,8 @@ The expressions can be auto-expanded according to NAME." ((eq ?` (aref (match-string 2 docstring) 0)) (push (hydra--unalias-var - (substring (match-string 2 docstring) 1) prefix) varlist) + (substring (match-string 2 docstring) 1) prefix) + varlist) (setq docstring (replace-match (concat "%" (match-string 1 docstring) "S") @@ -490,13 +508,14 @@ The expressions can be auto-expanded according to NAME." (t (let* ((spec (match-string 1 docstring)) - (lspec (length spec))) + (lspec (length spec)) + (me2 (match-end 2))) (setq offset (with-temp-buffer (insert (substring docstring (+ 1 start (length spec)))) (goto-char (point-min)) (push (read (current-buffer)) varlist) - (point))) + (- (point) (point-min)))) (when (or (zerop lspec) (/= (aref spec (1- (length spec))) ?s)) (setq spec (concat spec "S"))) @@ -504,8 +523,7 @@ The expressions can be auto-expanded according to NAME." (concat (substring docstring 0 start) "%" spec - (substring docstring - (+ (match-end 2) offset -2)))))))) + (substring docstring (+ me2 offset -1)))))))) (if (eq ?\n (aref docstring 0)) `(concat (format ,(substring docstring 1) ,@(nreverse varlist)) ,rest) @@ -595,7 +613,10 @@ OTHER-POST is an optional extension to the :post key of BODY." `(lambda () (hydra-cleanup))))) ,(or other-post (when body-timeout - `(hydra-timeout ,body-timeout)))))))))) + (list 'hydra-timeout + body-timeout + (when body-post + (hydra--make-callable body-post)))))))))))) (defun hydra-pink-fallback () "On intercepting a non-head, try to run it." @@ -615,33 +636,55 @@ OTHER-POST is an optional extension to the :post key of BODY." (message "Pink Hydra can't currently handle prefixes, continuing")) (message "Pink Hydra could not resolve: %S" keys)))) +(defun hydra--modify-keymap (keymap def) + "In KEYMAP, add DEF to each sub-keymap." + (cl-labels + ((recur (map) + (if (atom map) + map + (if (eq (car map) 'keymap) + (cons 'keymap + (cons + def + (recur (cdr map)))) + (cons + (recur (car map)) + (recur (cdr map))))))) + (recur keymap))) + (defun hydra--handle-nonhead (keymap name body heads) "Setup KEYMAP for intercepting non-head bindings. NAME, BODY and HEADS are parameters to `defhydra'." (let ((body-color (hydra--body-color body)) (body-post (plist-get (cddr body) :post))) - (when (and body-post (symbolp body-post)) - (setq body-post `(funcall #',body-post))) - (when hydra-keyboard-quit - (define-key keymap hydra-keyboard-quit #'hydra-keyboard-quit)) + (if body-post + (when (symbolp body-post) + (setq body-post `(funcall #',body-post))) + (when hydra-keyboard-quit + (define-key keymap hydra-keyboard-quit #'hydra-keyboard-quit))) (when (memq body-color '(amaranth pink teal)) - (if (cl-some `(lambda (h) - (memq (hydra--head-color h body) '(blue teal))) + (if (cl-some (lambda (h) + (memq (hydra--head-color h body) '(blue teal))) heads) (progn - (define-key keymap [t] - `(lambda () - (interactive) - ,(cond - ((memq body-color '(amaranth teal)) - '(message "An amaranth Hydra can only exit through a blue head")) - (t - '(hydra-pink-fallback))) - (hydra-set-transient-map hydra-curr-map t) - (when hydra-is-helpful - (unless hydra-lv - (sit-for 0.8)) - (,(intern (format "%S/hint" name))))))) + (setcdr + keymap + (cdr + (hydra--modify-keymap + keymap + (cons t + `(lambda () + (interactive) + ,(cond + ((memq body-color '(amaranth teal)) + '(message "An amaranth Hydra can only exit through a blue head")) + (t + '(hydra-pink-fallback))) + (hydra-set-transient-map hydra-curr-map t) + (when hydra-is-helpful + (unless hydra-lv + (sit-for 0.8)) + (,(intern (format "%S/hint" name)))))))))) (unless (eq body-color 'teal) (error "An %S Hydra must have at least one blue head in order to exit" @@ -758,16 +801,18 @@ NAMES should be defined by `defhydradio' or similar." "Timer for `hydra-timeout'.") (defun hydra-timeout (secs &optional function) - "In SECS seconds call FUNCTION. -FUNCTION defaults to `hydra-disable'. + "In SECS seconds call FUNCTION, then `hydra-keyboard-quit'. Cancel the previous `hydra-timeout'." (cancel-timer hydra-timer) (setq hydra-timer (timer-create)) (timer-set-time hydra-timer - (timer-relative-time nil secs)) + (timer-relative-time (current-time) secs)) (timer-set-function hydra-timer - (or function #'hydra-keyboard-quit)) + `(lambda () + ,(when function + `(funcall ,function)) + (hydra-keyboard-quit))) (timer-activate hydra-timer)) ;;* Macros @@ -833,60 +878,68 @@ result of `defhydra'." (setq docstring "hydra"))) (when (keywordp (car body)) (setq body (cons nil (cons nil body)))) - (dolist (h heads) - (let ((len (length h)) - (cmd-name (hydra--head-name h name))) - (cond ((< len 2) - (error "Each head should have at least two items: %S" h)) - ((= len 2) - (setcdr (cdr h) `("" :cmd-name ,cmd-name))) - (t - (let ((hint (cl-caddr h))) - (unless (or (null hint) - (stringp hint)) - (setcdr (cdr h) (cons "" (cddr h)))) - (setcdr (cddr h) `(:cmd-name ,cmd-name ,@(cl-cdddr h)))))))) - (let* ((keymap (copy-keymap hydra-base-map)) - (body-name (intern (format "%S/body" name))) - (body-key (unless (hydra--callablep body) - (cadr body))) - (body-color (hydra--body-color body)) - (body-pre (plist-get (cddr body) :pre)) - (body-body-pre (plist-get (cddr body) :body-pre)) - (body-post (plist-get (cddr body) :post)) - (method (or (plist-get body :bind) - (car body))) - (doc (hydra--doc body-key body-name heads)) - (heads-nodup (hydra--delete-duplicates heads))) - (mapc - (lambda (x) - (define-key keymap (kbd (car x)) - (plist-get (cl-cdddr x) :cmd-name))) - heads) - (when (and body-pre (symbolp body-pre)) - (setq body-pre `(funcall #',body-pre))) - (when (and body-body-pre (symbolp body-body-pre)) - (setq body-body-pre `(funcall #',body-body-pre))) - (when (and body-post (symbolp body-post)) - (setq body-post `(funcall #',body-post))) - (hydra--handle-nonhead keymap name body heads) - `(progn - ,@(mapcar - (lambda (head) - (hydra--make-defun name body doc head keymap - body-pre body-post)) - heads-nodup) - ,@(unless (or (null body-key) - (null method) - (hydra--callablep method)) - `((unless (keymapp (lookup-key ,method (kbd ,body-key))) - (define-key ,method (kbd ,body-key) nil)))) - ,@(delq nil - (cl-mapcar - (lambda (head) - (let ((name (hydra--head-property head :cmd-name))) - (when (cadr head) - (when (or body-key method) + (let ((keymap (copy-keymap hydra-base-map)) + (body-name (intern (format "%S/body" name))) + (body-key (cadr body)) + (body-color (hydra--body-color body)) + (body-pre (plist-get (cddr body) :pre)) + (body-body-pre (plist-get (cddr body) :body-pre)) + (body-post (plist-get (cddr body) :post)) + (method (or (plist-get body :bind) + (car body)))) + (when body-post + (when (symbolp body-post) + (setq body-post `(funcall #',body-post))) + (setq heads (cons (list hydra-keyboard-quit #'hydra-keyboard-quit nil :exit t) + heads))) + (dolist (h heads) + (let ((len (length h)) + (cmd-name (hydra--head-name h name))) + (cond ((< len 2) + (error "Each head should have at least two items: %S" h)) + ((= len 2) + (setcdr (cdr h) + (list + (hydra-plist-get-default (cddr body) :hint "") + :cmd-name cmd-name))) + (t + (let ((hint (cl-caddr h))) + (unless (or (null hint) + (stringp hint)) + (setcdr (cdr h) (cons + (hydra-plist-get-default (cddr body) :hint "") + (cddr h)))) + (setcdr (cddr h) `(:cmd-name ,cmd-name ,@(cl-cdddr h)))))))) + (let ((doc (hydra--doc body-key body-name heads)) + (heads-nodup (hydra--delete-duplicates heads))) + (mapc + (lambda (x) + (define-key keymap (kbd (car x)) + (plist-get (cl-cdddr x) :cmd-name))) + heads) + (when (and body-pre (symbolp body-pre)) + (setq body-pre `(funcall #',body-pre))) + (when (and body-body-pre (symbolp body-body-pre)) + (setq body-body-pre `(funcall #',body-body-pre))) + (hydra--handle-nonhead keymap name body heads) + `(progn + ,@(mapcar + (lambda (head) + (hydra--make-defun name body doc head keymap + body-pre body-post)) + heads-nodup) + ,@(unless (or (null body-key) + (null method) + (hydra--callablep method)) + `((unless (keymapp (lookup-key ,method (kbd ,body-key))) + (define-key ,method (kbd ,body-key) nil)))) + ,@(delq nil + (cl-mapcar + (lambda (head) + (let ((name (hydra--head-property head :cmd-name))) + (when (and (cadr head) + (not (eq (cadr head) 'hydra-keyboard-quit)) + (or body-key method)) (let ((bind (hydra--head-property head :bind 'default)) (final-key (if body-key @@ -909,15 +962,15 @@ result of `defhydra'." (function ,name))) (t - (error "Invalid :bind property %S" head)))))))) - heads)) - (defun ,(intern (format "%S/hint" name)) () - ,(hydra--message name body docstring heads)) - ,(hydra--make-defun - name body doc '(nil body) - keymap - (or body-body-pre body-pre) body-post - '(setq prefix-arg current-prefix-arg))))) + (error "Invalid :bind property %S" head))))))) + heads)) + (defun ,(intern (format "%S/hint" name)) () + ,(hydra--message name body docstring heads)) + ,(hydra--make-defun + name body doc '(nil body) + keymap + (or body-body-pre body-pre) body-post + '(setq prefix-arg current-prefix-arg)))))) (defmacro defhydradio (name body &rest heads) "Create radios with prefix NAME. @@ -982,7 +1035,7 @@ DOC defaults to TOGGLE-NAME split and capitalized." (while (< i l) (if (equal (aref range i) val) (throw 'done (1+ i)) - (incf i))) + (cl-incf i))) (error "Val not in range for %S" sym))) (set sym (aref range diff --git a/packages/hydra/lv.el b/packages/hydra/lv.el index 7b19074..ee5a739 100644 --- a/packages/hydra/lv.el +++ b/packages/hydra/lv.el @@ -41,7 +41,6 @@ (if (window-live-p lv-wnd) lv-wnd (let ((ori (selected-window)) - (golden-ratio-mode nil) buf) (prog1 (setq lv-wnd (select-window @@ -50,7 +49,7 @@ (if (setq buf (get-buffer "*LV*")) (switch-to-buffer buf) (switch-to-buffer "*LV*") - (setq truncate-lines nil) + (set-window-hscroll lv-wnd 0) (setq mode-line-format nil) (setq cursor-type nil) (set-window-dedicated-p lv-wnd t) @@ -59,13 +58,17 @@ (defun lv-message (format-string &rest args) "Set LV window contents to (`format' FORMAT-STRING ARGS)." - (let ((ori (selected-window)) - (str (apply #'format format-string args)) - deactivate-mark) + (let* ((ori (selected-window)) + (str (apply #'format format-string args)) + (n-lines (cl-count ?\n str)) + deactivate-mark + golden-ratio-mode) (select-window (lv-window)) (unless (string= (buffer-string) str) (delete-region (point-min) (point-max)) (insert str) + (setq-local window-min-height n-lines) + (setq truncate-lines (> n-lines 1)) (fit-window-to-buffer nil nil 1)) (goto-char (point-min)) (select-window ori)))