Switching from evil mode to meow
About Meow
Evil mode is a great vi emulator and was helpful for transitioning to Emacs from Vim, but I've been wanting a more minimal modal solution. I've found that there are some things in Emacs that I much prefer to the evil solution (such as Emacs macros). I have found meow and I think it's change from the vi verb-object paradigm to object-verb paradigm to be interesting. But coming from years of vim/evil muscle memory, I want to make the transition to meow as easy as possible. My goal in this configuration is to make many of the key bindings familiar to a vi-user.
Digit arguments
The default meow keybindings have the numeric keys expand the region in normal mode. However, if there is no region selected then the keys do nothing. So I want the number keys to act as a C-u prefix argument if there is no region selected, and if there is a region then they act as meow normally expects. Let's define a macro so we don't have to create a function for all 10 digits:
(defmacro m-meow-numeric-macro (num) `(defun ,(intern (format "m/meow-numeric-%s" num)) () (interactive) (if (region-active-p) (call-interactively #',(intern (format "meow-expand-%s" num))) (call-interactively #'meow-digit-argument))))
Now, I can bind numbers 0-9 with ease:
(meow-normal-define-key `("0" . ,(m-meow-numeric-macro 0)) `("9" . ,(m-meow-numeric-macro 9)) `("8" . ,(m-meow-numeric-macro 8)) `("7" . ,(m-meow-numeric-macro 7)) `("6" . ,(m-meow-numeric-macro 6)) `("5" . ,(m-meow-numeric-macro 5)) `("4" . ,(m-meow-numeric-macro 4)) `("3" . ,(m-meow-numeric-macro 3)) `("2" . ,(m-meow-numeric-macro 2)) `("1" . ,(m-meow-numeric-macro 1)))
Making the letter commands more like evil
I want to make the letter commands similar to evil to make it easier to learn meow. There are a few things I did to try to make this behavior more intuitive for me as an evil user:
Aappends to end of line whileIinserts at beginning of lineCchanges to end of lineFandTare the reversed version offandt
There are a few notable differences between this and evil:
C-fandC-bare forward-char and backward-char, instead of scrolling the window. This is due to how meow uses keyboard macros internally and relies onC-fandC-bbeing bound to those commands. Scrolling can still be done withC-vandM-v.- Most of the commands following the
gprefix are not implemented.
(meow-normal-define-key '("-" . negative-argument) '(";" . meow-reverse) '("," . meow-beginning-of-thing) '("." . meow-end-of-thing) '("=" . meow-indent) '("/" . meow-visit) ;;'("/" . isearch-forward) ;; alternatively use emacs built in isearch ;;'("?" . isearch-backward) `("a" . ,(defun m-meow-append () (interactive) (if (region-active-p) (call-interactively #'meow-append) (if (eolp) ;; when at eol, we don't want to go onto the next line (call-interactively #'meow-insert) (call-interactively #'meow-append))))) `("A" . ,(defun m-meow-append-eol () (interactive) (end-of-line) (meow-insert))) '("b" . meow-back-word) '("B" . meow-back-symbol) '("c" . meow-change) `("C" . ,(defun m-meow-change-eol () (interactive) (if (region-active-p) (call-interactively #'meow-change) (call-interactively #'kill-line) (call-interactively #'meow-insert)))) '("d" . meow-kill) '("e" . meow-next-word) '("E" . meow-next-symbol) '("f" . meow-find) `("F" . ,(defun m-meow-reverse-find () (interactive) (let ((current-prefix-arg (or current-prefix-arg -1))) (call-interactively #'meow-find)))) '("g g" . meow-goto-line) '("G" . meow-grab) '("h" . meow-left) '("H" . meow-left-expand) '("i" . meow-insert) `("I" . ,(defun m-meow-insert-bol () (interactive) (meow-join nil) (meow-append))) '("j" . meow-next) '("J" . meow-next-expand) '("k" . meow-prev) '("K" . meow-prev-expand) '("l" . meow-right) '("L" . meow-right-expand) '("m" . meow-join) `("n" . ,(defun m-meow-reverse-search () (interactive) (let ((current-prefix-arg (if (meow--direction-forward-p) nil 1))) (call-interactively #'meow-search)))) `("N" . ,(defun m-meow-reverse-search () (interactive) (let ((current-prefix-arg (if (meow--direction-backward-p) nil -1))) (call-interactively #'meow-search)))) '("o" . meow-open-below) '("O" . meow-open-above) `("p" . ,(defun m-meow-paste-after () (interactive) ;; if the current kill ends with \n we assume it should be pasted as a line (if (string= (substring-no-properties (current-kill 0 t) -1) "\n") (progn (next-line) (beginning-of-line) (call-interactively #'meow-yank)) (forward-char 1) (call-interactively #'meow-yank)))) `("P" . ,(defun m-meow-paste-before () (interactive) ;; if the current kill ends with \n we assume it should be pasted as a line (if (string= (substring-no-properties (current-kill 0 t) -1) "\n") (progn (beginning-of-line) (call-interactively #'meow-yank)) (call-interactively #'meow-yank)))) '("M-p" . meow-yank-pop) ; M-p instead of C-p like in evil '("q" . meow-pop-selection) '("Q" . meow-swap-grab) '("r" . meow-replace) '("R" . undo-redo) '("s" . meow-inner-of-thing) '("S" . meow-bounds-of-thing) '("t" . meow-till) `("T" . ,(defun m-meow-reverse-till () (interactive) (let ((current-prefix-arg (or current-prefix-arg -1))) (call-interactively #'meow-till)))) '("u" . meow-undo) '("U" . meow-undo-in-selection) '("v" . meow-line) '("w" . meow-mark-word) '("W" . meow-mark-symbol) '("x" . meow-delete) '("X" . meow-backward-delete) '("y" . meow-save) '("Y" . meow-sync-grab) '("<escape>" . meow-cancel-selection))
z-prefixed commands
I also frequently use the commands prefixed with z to scroll the window to center my cursor, or put my cursor on top or bottom. By looking at the implementation of recenter-top-bottom, I was able to add these bindings myself.
(meow-normal-define-key '("z z" . recenter-top-bottom) `("z t" . ,(defun m-meow-zt () (interactive) (let ((this-scroll-margin (min (max 0 scroll-margin) (truncate (/ (window-body-height) 4.0))))) (recenter this-scroll-margin t)))) `("z b" . ,(defun m-meow-zb () (interactive) (let ((this-scroll-margin (min (max 0 scroll-margin) (truncate (/ (window-body-height) 4.0))))) (recenter (- -1 this-scroll-margin) t)))))
Evil matchit
We can also use evil matchit to get the same behavior as % in evil. While the package is called evil-matchit, it doesn't need evil to function.
(use-package evil-matchit :commands 'evilmi-jump-items-native :init (meow-normal-define-key '("%" . evilmi-jump-items-native)))
Trying it out
To try it out, you can find the full configuration here.