Roll your own modal mode
Switch branches/tags
Nothing to show
Clone or download

README.org

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.

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).

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 argyment 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 mode symbol (for instance :mode 'org-mode) the command will only be active in that major mode. If you have a lot of major mode specific bindings, you may want to use ryo-modal-major-mode-keys instead to reduce clutter.
:exit
By providing :exit t you will exit ryo-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 exiting ryo-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 using defun or similar.
:first
Similar to :then, but will be run before the “real” command. Keep in mind that commands run here will consume universal-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 by ryo-modal-repeat.

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!"
             ("g" magit-status "magit" :color blue)
             ("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)))

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. In order to do this you must set which-key-enable-extended-define-key to t before loading which-key (please see the which-key readme on what this does). Then you could use the normal :name argument on your ryo prefix keys:

(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 which-key-enable-extended-define-key weren’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

March 2018
Support for naming prefix keys with which-key.
February 2018
ryo-modal-key now defines commands, in order to make it work with multiple-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 added ryo-modal-set-key and ryo-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 in ryo-modal-keys. Added ryo-modal-repeat (0.2).
October 2016
Initial version (0.1).