Kungsgeten/ryo-modal

Roll your own modal mode

Emacs Lisp

237

63 commits

updated Aug 20, 2024

See the code

README

#+TITLE:RYO modal mode!

=ryo-modal= is an Emacs minor-mode, providing useful features for creating your own modal editing environment. Unlike [[https://bitbucket.org/lyro/evil/wiki/Home][evil]], [[https://github.com/jyp/boon][boon]], [[http://ergoemacs.org/misc/ergoemacs_vi_mode.html][xah-fly-keys]], [[https://github.com/chrisdone/god-mode][god-mode]], [[https://github.com/fgeller/fingers.el][fingers]], and [[http://retroj.net/modal-mode][modal-mode]], =ryo-modal= does not provide any default keybindings: roll your own! =ryo-modal= is similar to (and inspired by) [[https://github.com/mrkkrp/modalka][modalka]], but provides more features.

The package [[https://github.com/jmorag/kakoune.el][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 [[https://github.com/jwiegley/use-package][use-package]]:

#+BEGIN_SRC emacs-lisp
  (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")))
#+END_SRC

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 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=.
- =:mc-all= :: If you're using =multiple-cursors= it can be annoying that it asks you if you want to use the commands generated by =ryo= for all cursors. If =:mc-all= is =t= then the command will be run by all cursors. If it instead is =0= it will only be run once. Note that setting =:mc-all= to =nil= 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 like =repeat-mode= where =repeat-map= property of the symbol specifies whether the command will be supported by this mode. Example:
  #+begin_src emacs-lisp
    (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))))))
  #+end_src

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=:

#+BEGIN_SRC emacs-lisp
  (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))
#+END_SRC

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:

#+BEGIN_SRC emacs-lisp
  (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))
#+END_SRC

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:

#+BEGIN_SRC emacs-lisp
  (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))))
#+END_SRC

** Creating and binding hydras to keys

[[https://github.com/abo-abo/hydra][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:

#+BEGIN_SRC emacs-lisp
  (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)))
#+END_SRC

*** 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:

#+BEGIN_SRC emacs-lisp
  (ryo-modal-key
   "SPC g" :hydra+
   '(hydra-git ()
               "A hydra for git!"
               ("g" magit-status "magit" :color blue)))
#+END_SRC

** Defining "normal mode" keys which enter =ryo-modal=

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:

#+begin_src emacs-lisp
(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))))
#+end_src

Notice that the target should not be quoted if using =:ryo= (although the third argument when using =:hydra= should be.

** =which-key= integration

[[https://github.com/justbur/emacs-which-key][which-key]] respects the =:name= keyword of both prefixes and commands.

* Keybindings when region is active

If you want (some) special keybindings when the region is active, you can use [[https://github.com/Kungsgeten/selected.el][selected.el]]. In order to turn it on/off at the same time as =ryo-modal=, you could do something like this:

#+BEGIN_SRC emacs-lisp
  (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)))
#+END_SRC

* Credits

A lot of inspiration and code peeking from [[https://github.com/mrkkrp/modalka][modalka]], but also from [[https://github.com/jwiegley/use-package][use-package/bind-key]].

* Changelog

- August 2024 :: Some tweaks to the =:name= keyword. There's no longer a need to use =which-key-replacement-alist= when using =which-key=.
- November 2020 :: =:mc-all= keyword added, to be used by =muliple-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 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).

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Kungsgeten

45 commits

BlankSpruce

5 commits

hgersen

4 commits

Kungsgeten/ryo-modal

Roll your own modal mode

Emacs Lisp

237

63 commits

updated Aug 20, 2024

See the code

README

#+TITLE:RYO modal mode!

=ryo-modal= is an Emacs minor-mode, providing useful features for creating your own modal editing environment. Unlike [[https://bitbucket.org/lyro/evil/wiki/Home][evil]], [[https://github.com/jyp/boon][boon]], [[http://ergoemacs.org/misc/ergoemacs_vi_mode.html][xah-fly-keys]], [[https://github.com/chrisdone/god-mode][god-mode]], [[https://github.com/fgeller/fingers.el][fingers]], and [[http://retroj.net/modal-mode][modal-mode]], =ryo-modal= does not provide any default keybindings: roll your own! =ryo-modal= is similar to (and inspired by) [[https://github.com/mrkkrp/modalka][modalka]], but provides more features.

The package [[https://github.com/jmorag/kakoune.el][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 [[https://github.com/jwiegley/use-package][use-package]]:

#+BEGIN_SRC emacs-lisp
  (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")))
#+END_SRC

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 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=.
- =:mc-all= :: If you're using =multiple-cursors= it can be annoying that it asks you if you want to use the commands generated by =ryo= for all cursors. If =:mc-all= is =t= then the command will be run by all cursors. If it instead is =0= it will only be run once. Note that setting =:mc-all= to =nil= 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 like =repeat-mode= where =repeat-map= property of the symbol specifies whether the command will be supported by this mode. Example:
  #+begin_src emacs-lisp
    (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))))))
  #+end_src

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=:

#+BEGIN_SRC emacs-lisp
  (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))
#+END_SRC

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:

#+BEGIN_SRC emacs-lisp
  (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))
#+END_SRC

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:

#+BEGIN_SRC emacs-lisp
  (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))))
#+END_SRC

** Creating and binding hydras to keys

[[https://github.com/abo-abo/hydra][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:

#+BEGIN_SRC emacs-lisp
  (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)))
#+END_SRC

*** 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:

#+BEGIN_SRC emacs-lisp
  (ryo-modal-key
   "SPC g" :hydra+
   '(hydra-git ()
               "A hydra for git!"
               ("g" magit-status "magit" :color blue)))
#+END_SRC

** Defining "normal mode" keys which enter =ryo-modal=

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:

#+begin_src emacs-lisp
(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))))
#+end_src

Notice that the target should not be quoted if using =:ryo= (although the third argument when using =:hydra= should be.

** =which-key= integration

[[https://github.com/justbur/emacs-which-key][which-key]] respects the =:name= keyword of both prefixes and commands.

* Keybindings when region is active

If you want (some) special keybindings when the region is active, you can use [[https://github.com/Kungsgeten/selected.el][selected.el]]. In order to turn it on/off at the same time as =ryo-modal=, you could do something like this:

#+BEGIN_SRC emacs-lisp
  (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)))
#+END_SRC

* Credits

A lot of inspiration and code peeking from [[https://github.com/mrkkrp/modalka][modalka]], but also from [[https://github.com/jwiegley/use-package][use-package/bind-key]].

* Changelog

- August 2024 :: Some tweaks to the =:name= keyword. There's no longer a need to use =which-key-replacement-alist= when using =which-key=.
- November 2020 :: =:mc-all= keyword added, to be used by =muliple-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 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).

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Kungsgeten

45 commits

BlankSpruce

5 commits

hgersen

4 commits

Languages

Emacs Lisp

99.7%