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.
Usage
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
ryo-modal
creates a new symbol for the command you bind. 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
.
Prefix keys
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))))
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-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)))
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)))
ryo-modal
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.
Use-package keyword
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
integration
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 +prefix
:
(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)))
Credits
A lot of inspiration and code peeking from modalka, but also from use-package/bind-key.
Changelog
- 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).