RYO modal mode!
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
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:
ryo-modalcreates a new symbol for the command you bind. By default this name will depend on the target of the binding, but by using
:nameand a string you can give it your own name. It is perfectly fine to have whitespace, or any other symbol, in the name.
:modeis 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 use
ryo-modal-major-mode-keysinstead to reduce clutter.
- By providing
:exit tyou will exit
ryo-modal-modebefore running the command. This is useful if you have a command and always want to input text after running it.
:read tyou 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 exiting
- By providing a quoted list of command symbols, and/or functions to be run with zero arguments (lambdas works too), to
:thenyou can specify additional commands that should be run after the “real” command. This way you can easily define command chains, without using
- Similar to
:then, but will be run before the “real” command. Keep in mind that commands run here will consume
universal-argumentetc, before the real command is run.
- If you specify
:norepeat tthen using the binding will not make it overwrite the current command being triggered by
- If you’re using
multiple-cursorsit can be annoying that it asks you if you want to use the commands generated by
ryofor all cursors. If
tthen the command will be run by all cursors. If it instead is
0it will only be run once. Note that setting
nilwill do nothing.
ryo-modalmight 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
:propertieswith list of pairs
(PROPNAME . VALUE)these properties will be stored for that new symbol. It might be useful minor for modes like
repeat-mapproperty 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-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
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
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
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))))
Creating and binding hydras to keys
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-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)))
Adding to preexisting hydras
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)))
Defining “normal mode” keys which enter
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
: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.
If you’re using which-key you might be annoyed that
ryo prefixes some commands with
ryo:<hash>:. In order to remove that from the
which-key menus, add this to your init-file:
(push '((nil . "ryo:.*:") . (nil . "")) which-key-replacement-alist)
If you use prefix keys you can name these, making
which-key show something useful instead of
(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")) :name "mark") ("k" (("w" er/mark-word :name "Kill word") ("d" er/mark-defun :name "Kill defun") ("s" er/mark-sentence :name "Kill sentence")) :name "kill" :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")) :name "change" :then '(kill-region) :exit t))
If you have an old version of
which-key you may need to update it, since
which-key-replacement-alist and wasn’t there from the beginning.
Keybindings when region is active
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)))
- November 2020
:mc-allkeyword added, to be used by
- October 2019
:modekeyword now works on modes which derive from the specified mode.
- March 2018
- Support for naming prefix keys with
- February 2018
ryo-modal-keynow defines commands, in order to make it work with
multiple-cursorsand similar. Also added
:first) can have functions (taking zero arguments) instead of commands (0.4).
- January 2018
:ryo. Also added
- February 2017
ryo-modal-major-mode-keys. Also possible to specify keywords on all keys with a prefix, or all keys in
- October 2016
- Initial version (0.1).