ryo-modal
is an Emacs minor-mode, providing useful features for creating your own modal editing environment. Unlike evil, boon, xah-fly-keys, god-mode, fingers, and modal-mode, ryo-modal
does not provide any default keybindings: roll your own! ryo-modal
is similar to (and inspired by) modalka, but provides more features.
The package kakoune.el uses ryo-modal-mode
to implement its bindings.
You can use M-x ryo-modal-mode
to activate ryo-modal
, but without configuration nothing will happen. You need to add keybindings to it first; this can be done by ryo-modal-key
(bind one key), ryo-modal-keys
(bind many keys at once) or ryo-modal-major-mode-keys
(bind several keys at once, but only if in a specific major mode, or a major mode derived from another).
Here’s a simple configuration, using use-package:
(use-package ryo-modal
:commands ryo-modal-mode
:bind ("C-c SPC" . ryo-modal-mode)
:config
(ryo-modal-keys
("," ryo-modal-repeat)
("q" ryo-modal-mode)
("h" backward-char)
("j" next-line)
("k" previous-line)
("l" forward-char))
(ryo-modal-keys
;; First argument to ryo-modal-keys may be a list of keywords.
;; These keywords will be applied to all keybindings.
(:norepeat t)
("0" "M-0")
("1" "M-1")
("2" "M-2")
("3" "M-3")
("4" "M-4")
("5" "M-5")
("6" "M-6")
("7" "M-7")
("8" "M-8")
("9" "M-9")))
Now I can start ryo-modal-mode
by pressing C-c SPC
, and get vim-like hjkl
-navigation and use digit arguments by pressing the number keys. Notice that other keys are unmodified, so pressing r
would insert r
into the buffer. ryo
also defines the command ryo-modal-repeat
, which will repeat the last command executed by ryo
(but see :norepeat
below).
When defining keys the first argument of each binding is the key (will be wrapped inside kbd
) and the second argument is the target; usually a command or a string representing a keypress that should be simulated. The rest of the arguments are keyword pairs, providing extra features. The following keywords exist:
:name
- The name of the binding. By default this name will depend on the target of the binding, but by using
:name
and a string you can give it your own name. It is perfectly fine to have whitespace, or any other symbol, in the name. :mode
- If
:mode
is set to a quoted major or minor mode symbol (for instance:mode 'org-mode
) the command will only be active in that mode (or in a major mode that derives from it). If you have a lot of major mode specific bindings, you may want to useryo-modal-major-mode-keys
instead to reduce clutter. :exit
- By providing
:exit t
you will exitryo-modal-mode
before running the command. This is useful if you have a command and always want to input text after running it. :read
- If
:read t
you will be prompted to insert a string in the minibuffer after running the command, and this string will be inserted into the buffer. This can be useful if you want to have a command which for instance replaces a word with another word, without exitingryo-modal-mode
. :then
- By providing a quoted list of command symbols, and/or functions to be run with zero arguments (lambdas works too), to
:then
you can specify additional commands that should be run after the “real” command. This way you can easily define command chains, without usingdefun
or similar. :first
- Similar to
:then
, but will be run before the “real” command. Keep in mind that commands run here will consumeuniversal-argument
etc, before the real command is run. :norepeat
- If you specify
:norepeat t
then using the binding will not make it overwrite the current command being triggered byryo-modal-repeat
. :mc-all
- If you’re using
multiple-cursors
it can be annoying that it asks you if you want to use the commands generated byryo
for all cursors. If:mc-all
ist
then the command will be run by all cursors. If it instead is0
it will only be run once. Note that setting:mc-all
tonil
will do nothing. :properties
- Since
ryo-modal
might create new symbol for bound command which can be determined after the binding is defined putting symbol properties would have to be done afterwards. If you specify:properties
with list of pairs(PROPNAME . VALUE)
these properties will be stored for that new symbol. It might be useful minor for modes likerepeat-mode
whererepeat-map
property of the symbol specifies whether the command will be supported by this mode. Example:(defvar my-switch-buffer-repeat-map (let ((map (make-sparse-keymap))) (define-key map (kbd "[") 'switch-to-prev-buffer) (define-key map (kbd "]") 'switch-to-next-buffer) map)) (put 'switch-to-prev-buffer 'repeat-map 'my-switch-buffer-repeat-map) (put 'switch-to-next-buffer 'repeat-map 'my-switch-buffer-repeat-map) (ryo-modal-keys ("A" (("[" switch-to-prev-buffer :name "Switch to previous buffer" ;; When Repeat mode is enabled due to `repeat-map' property ;; and `my-switch-buffer-repeat-map' keymap you can do ;; "A [ [ [" instead of "A [ A [ A [" to switch to third ;; previous buffer :properties ((repeat-map . my-switch-buffer-repeat-map))) ("]" switch-to-next-buffer ;; or alternate with "A [ ] [ ] [ ] [" to switch between ;; previous and next buffer :name "Switch to next buffer" :properties ((repeat-map . my-switch-buffer-repeat-map))))))
Here’s an example using the keyword arguments (can be used in ryo-modal-keys
too), and an example of ryo-modal-major-mode-keys
:
(ryo-modal-key "SPC k" 'org-previous-visible-heading :then '(forward-to-word
org-kill-line)
:mode 'org-mode :name "org-replace-previous-heading" :read t)
(ryo-modal-major-mode-keys
'python-mode
("J" python-nav-forward-defun)
("K" python-nav-backward-defun))
Notice that the target command argument needs to be quoted when using ryo-modal-key
, but not when using ryo-modal-keys
!
In order to get an overview of all the bindings you’ve defined, use M-x ryo-modal-bindings
. If you want to change the cursor color or cursor type, edit ryo-modal-cursor-color
and/or ryo-modal-cursor-type
.
Sometimes you want many keys bound under the same prefix key. A convenient way of doing this is to let the target be a list of the keys in the prefix map. Each element of the list will be sent to ryo-modal-key
, using the key as a prefix. If the key has any arguments, these will be sent too. Prefix examples:
(ryo-modal-key
"SPC" '(("s" save-buffer)
("g" magit-status)
("b" ibuffer-list-buffers)))
(ryo-modal-keys
("v"
(("w" er/mark-word :name "Mark word")
("d" er/mark-defun :name "Mark defun")
("s" er/mark-sentence :name "Mark sentence")))
("k"
(("w" er/mark-word :name "Kill word")
("d" er/mark-defun :name "Kill defun")
("s" er/mark-sentence :name "Kill sentence"))
:then '(kill-region))
("c"
(("w" er/mark-word :name "Change word")
("d" er/mark-defun :name "Change defun")
("s" er/mark-sentence :name "Change sentence"))
:then '(kill-region) :exit t))
Notice that the target should not be quoted if using ryo-modal-keys
, but it should if using ryo-modal-key
.
As can be seen above, prefix keys could be used in a similar way as verbs and text objects in Vim. An easy way of doing this is to let the text objects be commands which marks a region, and then the verbs kan be simulated by :then
, operating upon the selected region. In order to not repeat yourself (specifying the text objects over and over again, as the example above), you could do something like the following:
(let ((text-objects
'(("w" er/mark-word :name "Word")
("d" er/mark-defun :name "Defun")
("s" er/mark-sentence :name "Sentence"))))
(eval `(ryo-modal-keys
("v" ,text-objects)
("k" ,text-objects :then '(kill-region))
("c" ,text-objects :then '(kill-region) :exit t))))
Hydra is a package that allows creation of bindings which are sort of modal. ryo-modal
does not require hydra
, but if you have it installed you can easily define and bind hydras to keys. This way you can easily create a new “modal state”.
In order to create a hydra, bind it to a key using ryo-modal-key
or ryo-modal-keys
. The target of the key should be :hydra
and the third argument should be a (quoted) list; this list will be used as the arguments sent to defhydra
. An example:
(ryo-modal-key
"SPC g" :hydra
'(hydra-git ()
"A hydra for git!"
("j" git-gutter:next-hunk "next")
("k" git-gutter:previous-hunk "previous")
("d" git-gutter:popup-hunk "diff")
("s" git-gutter:stage-hunk "stage")
("r" git-gutter:revert-hunk "revert")
("m" git-gutter:mark-hunk "mark")
("q" nil "cancel" :color blue)))
If, for example, you wanted to add the magit-status
function to the previously created hydra-git
example, you would do the following:
(ryo-modal-key
"SPC g" :hydra+
'(hydra-git ()
"A hydra for git!"
("g" magit-status "magit" :color blue)))
If you’re not in ryo-modal-mode
you may want a key sequence which first triggers
a command, and then enters ryo-modal-mode
. You can then use
ryo-modal-command-then-ryo
. It takes a keybinding and usually a command to bind
it to. You may also specify a keymap in which the command is bound, but
global-map is used by default.
Ryo-modal also provides a use-package
keyword: :ryo
, which is similar to :bind
in that it implies :defer t
and create autoloads for the bound commands. The keyword is followed by one or more key-binding commands, using the same syntax as used by ryo-modal-keys
as is illustrated by the following example:
(use-package simple
:ensure nil
:ryo
("SPC" (("n" next-line :name "my next line")
("p" previous-line)))
;; A list of keywords will be applied to all following keybindings up to the next list of keywords.
(:mode 'org-mode :norepeat t)
("0" "M-0")
("G" end-of-buffer :name "insert at buffer end" :read t)
;; This new list of keywords will reset the applied defaults; it applies to all keybindings following.
(:norepeat t)
("SPC g" :hydra
'(hydra-nav ()
"A hydra for navigation"
("n" next-line "next line")
("p" previous-line "previous line")
("q" nil "cancel" :color blue))))
Notice that the target should not be quoted if using :ryo
(although the third argument when using :hydra
should be.
which-key respects the :name
keyword of both prefixes and commands.
If you want (some) special keybindings when the region is active, you can use selected.el. In order to turn it on/off at the same time as ryo-modal
, you could do something like this:
(use-package ryo-modal
:commands ryo-modal-mode
:bind ("C-c SPC" . ryo-modal-mode)
:init
(add-hook 'ryo-modal-mode-hook
(lambda ()
(if ryo-modal-mode
(selected-minor-mode 1)
(selected-minor-mode -1))))
:config
(ryo-modal-keys
("q" ryo-modal-mode)
("0" "M-0")
("1" "M-1")
("2" "M-2")
("3" "M-3")
("4" "M-4")
("5" "M-5")
("6" "M-6")
("7" "M-7")
("8" "M-8")
("9" "M-9")
("h" backward-char)
("j" next-line)
("k" previous-line)
("l" forward-char)))
A lot of inspiration and code peeking from modalka, but also from use-package/bind-key.
- August 2024
- Some tweaks to the
:name
keyword. There’s no longer a need to usewhich-key-replacement-alist
when usingwhich-key
. - November 2020
:mc-all
keyword added, to be used bymuliple-cursors
.- October 2019
- The
:mode
keyword now works on modes which derive from the specified mode. - March 2018
- Support for naming prefix keys with
which-key
. - February 2018
ryo-modal-key
now defines commands, in order to make it work withmultiple-cursors
and similar. Also added:first
keyword, and:then
(and:first
) can have functions (taking zero arguments) instead of commands (0.4).- January 2018
- Added
use-package
keyword:ryo
. Also addedryo-modal-set-key
andryo-modal-unset-key
(0.3). - February 2017
- Added
ryo-modal-major-mode-keys
. Also possible to specify keywords on all keys with a prefix, or all keys inryo-modal-keys
. Addedryo-modal-repeat
(0.2). - October 2016
- Initial version (0.1).