Overriding keybindings with Meow
Taking advantage of meow's clever design philosophy
📅 1 May 2026 | ~3 min readTags: #emacs #meow
I previously wrote about useful Emacs commands for reading. This allowed me to use j and k to scroll the window up and down instead of going forward or backward a line.
I am now using meow for my modal editing needs and have been really happy with it. Meow allows for custom states to be defined, but I have been using a different method recently that takes advantage of the built-in normal and motion states.
Meow has a very clever design choice in that when a key is pressed it can translate it to the default emacs binding and call it via a macro. Below is an example of the function meow-next, which I have bound to j. It essentially just executes the key-binding that is stored as the value of meow--kbd-forward-line which happens to be C-n.
(defun meow-next (arg)
"Move to the next line.
Will cancel all other selection, except char selection.
Use with universal argument to move to the last line of buffer.
Use with numeric argument to move multiple lines at once."
(interactive "P")
(unless (equal (meow--selection-type) '(expand . char))
(meow--cancel-selection))
(cond
((meow--with-universal-argument-p arg)
(goto-char (point-max)))
(t
(setq this-command #'next-line)
(meow--execute-kbd-macro meow--kbd-forward-line))))
(defvar meow--kbd-forward-line "C-n"
"KBD macro for command `forward-line'.")
Because meow’s keyboard macro commands are all stored as variables, it is easy for us to overwrite them locally. In the following example I will show how I made a minor mode that lets me have a more enjoyable reading experience.
Firstly, we need to bind the new functions to some obscure key combination. I chose to add these to the global keymap in this case as I want them available across a variety of major-modes.
Then I wrote functions to toggle between the original values of the meow–kbd variables and their overrides.
Finally, I defined a minor mode that can be added to a major-mode’s hook.
(keymap-global-set "M-s-j" 'scroll-up-line)
(keymap-global-set "M-s-k" 'scroll-down-line)
(defvar meow-reading-originals
'((meow--kbd-backward-line . "C-p")
(meow--kbd-forward-line . "C-n")))
(defvar meow-reading-overwrites
'((meow--kbd-backward-line . "M-s-k")
(meow--kbd-forward-line . "M-s-j")))
(defun meow-reading-restore-directions ()
"Restore Meow's direction variables locally."
(dolist (binding meow-reading-originals)
(set (make-local-variable (car binding)) (cdr binding))))
(defun meow-reading-overwrite-directions ()
"Overwrite Meow's direction variables locally."
(dolist (binding meow-reading-overwrites)
(set (make-local-variable (car binding)) (cdr binding))))
(define-minor-mode meow-reading-mode
"Adjust some bindings to make `meow-normal-mode' a better experience for reading modes."
:lighter " reading"
(if meow-reading-mode
(progn
(meow-reading-overwrite-directions)
(setq-local meow-cursor-type-motion nil)
(meow--update-cursor-motion))
(progn (meow-reading-restore-directions)
(kill-local-variable meow-cursor-type-motion)
(meow--update-cursor-motion))))
Here is an example of how you can have the minor mode be loaded for all buffers of a given major mode.
(use-package elfeed
:hook
(elfeed-show-mode . meow-reading-mode))
The reason that I prefer this to making a whole new “reading-state” is that this is still using meow’s default motion-state. I get to take advantage of all of the default key-bindings. One of the reasons that I moved from evil to meow was that I was getting tired of having to bind so many functions myself. With this method, I can essentially say “I want this mode to behave the same as all of the others, just with one or two changes”.
I am also working on a minor mode for lisp modes that takes advantage of smartparens to hopefully create a lispy like environment, but that is still a work in progress.
✉️ Respond by Email.