RYO

Roll your own modal mode!

Modal text editing is a concept where the text editor can be in different states. Vi wrote the book on this, and Vim took it further. In Vim there’s a normal mode where key presses manipulate text, or navigate the text document. To insert new text you’d have to enter the insert mode. Then there’s a visual mode when you’re marking text etc. The benefits of modal editing (compared to modeless editing) is supposed to be faster and more ergonomic text editing.

Emacs is by default a modeless editor, but there are several packages1 which implement modal ways of editing. Most of these come with predefined keybindings and a certain philosophy of how things should be done. An exception is Modalka; a package which uses a modal minor mode, and lets the user bind keys to this mode. You’d use (modalka-define-kbd "e" "C-e") to bind e to do what C-e normally does. You could also use (define-key modalka-mode-map (kbd "e") #'end-of-line) if you’d want to bind a key to a command instead of the key simulating another keypress. Emacs is very configurable at its core, and I believe it is a good thing having a framework for defining your own modal editing environment – if that’s a thing you want, or like to try out.

I started working on RYO because there was a few things I wanted to do, which were a bit cumbersome in Modalka. Like Modalka, RYO doesn’t define any default keybindings, but compared to Modalka’s modalka-define-kbd, there are more options in RYO. For instance in Vim there are several commands in normal mode which navigates/modifies text and then puts you into insert mode. An example is =c w=2 which deletes the word you’re at, and puts you into insert mode. Since Emacs by default is modeless there’s no function to do this, so you’d have to write it yourself:

(defun kill-word-then-insert-mode (arg)
  "Kill ARG words at point, then exit `modalka-mode'."
  (interactive "p")
  (kill-word arg)
  (modalka-mode -1))

(define-key modalka-mode-map (kbd "c w") #'kill-word-then-insert-mode)

If you have a lot of bindings like this, there’s many functions to write. What if we could just bind the key to a function, and at the same time specify that we should enter insert mode afterwards?3 RYO provides this functionality, and then some!

(ryo-modal-key "c w" #'kill-word :exit t)

I love use-package and its bind-keys functionality: You can bind several keybindings at once with one command. To me this makes it easier to organize and navigate your configuration code. RYO provides this too, via ryo-modal-keys:

(ryo-modal-keys
 ("h" backward-char)
 ("j" next-line)
 ("k" previous-line)
 ("l" forward-char)

 ("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"))

First we specify the key we want to use, and then the target of the key. Above I’ve bound hjkl to vim-like navigation, and the digit keys are used to simulate the digit-argument bindings. There’s also more features, which is useful in a modal environment:

(ryo-modal-keys
 ("c"
  (("c" kill-whole-line :then '(open-line) :exit t)
   ("w" kill-word :exit t)
   ("h" org-previous-heading :then '(forward-to-word
                                     org-kill-line)
    :read t :mode 'org-mode :name "org-change-previous-heading"))))

Here I have a list of keybindings as the target for the c key. This means that the list will use c as a prefix, so c w would run kill-word and then exit ryo-modal-mode. I also use the :then keyword, which makes it possible to bind the key to a chain of commands; pressing c c will kill the line and then make a new line where the line was, and then exit ryo-modal-mode.

The c h binding is complex. It will run org-previous-heading, then forward-to-word and then org-kill-line. It also uses the :read keyword: the user will insert text into the minibuffer and that text will then be inserted. This keybinding only makes sense if we’re in org-mode, so we use the :mode keyword to specify this. If we’d run C-h k to describe a binding in RYO, RYO will make up a name for the command4. By using the :name keyword we can give our command a custom name. The name and keys of all RYO keybindings can be viewed with =M-x ryo-modal-bindings=5.

The Hydra package let’s you define “Emacs bindings that stick around”6. Its a bit hard to describe, but basically it creates mini modal modes, called hydras, where entering a hydra provides keybindings for that mode. The minibuffer also provides you with help about what the hydra does. To me, having this functionality in an already modal environment extends the modal editing experience, so I added functionality to easily create and bind hydras to keys in RYO.

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

The code above uses commands from Magit and git-gutter to define a hydra for git, and binds it to SPC g. This is done by having :hydra as the key’s target, and then a quoted list containing the definition of the hydra. Hydra isn’t a requirement for RYO; you do not need to have it installed in order to use RYO, but I think its a good additition to any Emacs configuration.

Selected.el is a way to have special keybindings when the region is active7, so its a natural fit for RYO. If you only want it active when using RYO, you can use ryo-modal-mode-hook to turn it on and off:

(add-hook 'ryo-modal-mode-hook
            (lambda ()
              (if ryo-modal-mode
                  (selected-minor-mode 1)
                (selected-minor-mode -1))))

Ryo is a minor mode, and it doesn’t bind any default keys for you. This have both upsides and downsides: if you haven’t bound a key in RYO, it will have its usual functionality8, including inserting text9. Perhaps there will be an update in the future to prevent the user from inserting text while using RYO, but for now the power is in the hands of the users.

Please have a look at RYO on its Github page, and please share any configurations you make with it!

Footnotes:

1

The most famous are Vi(m) emulations like Evil and Viper. There’s also others which provide modal editing, but doesn’t try to mimic Vim: Boon, Xah Fly Keys, God Mode, Fingers

2

Mnemonic change word.

3

I actually made a pull request to Modalka, providing this functionality. The maintainer wants to keep the codebase as lightweight and minimal as possible though, which I respect.

4

Based on the target of the key.

5

Like describe-personal-keybindings in use-package.

7

Like Vim’s visual mode when you’ve selected some text.

8

Like C-x s to save etc.

9

So typing P would insert P into the buffer, unless you have bound P to something.