The Year of Living evil
ly
I was introduced to the venerable vi text editor back in the
early ’90s, apparently not long after the release of
Vim. It might have been my first editor (pico and
nano being the other possibilities). Neither was I ever an expert, confining myself to writing
simple Perl scripts, nor did I use it for long, since I soon started favouring Windows. Still, it’s
hard for a software developer not to encounter Linux with Vim (or at least vi) installed on a
regular basis, so I kept my rudimentary skills sharp over the years. Meanwhile, after trying a
succession of editors that failed to satisfy me—the ones I remember being Notepad++, UltraEdit, and
the Komodo IDE—I adopted Emacs as my religion primary editor in 2006.
15 long years later, in November 2021, I replaced jsonconfig-mode with JSON Par mode (in tandem with json-mode). I learnt it slowly, with one Firefox tab permanently dedicated to the documentation, but I was very impressed. It was my first taste of modal editing in Emacs, albeit in a smarter, language-aware form.
With that success under my belt, a few months later, I migrated from smartparens to a combination of the intelligently modal Lispy and the non-modal Puni. While this was much harder to learn and there are still large gaps in my understanding, I can use it effectively enough to see the value in it. Maybe I’ll even give Symex a go someday in the (far) future.
Now, I only took these first tentative steps towards modal editing because I’d been contemplating
the concept for a while already, prompted by several conversations about its efficiency and the
comparatively lower physical strain. evil-mode (the extensible vi layer for
Emacs
), specifically, had been on my mind for years. In fact,
I’d even put this in my configuration after watching a System Crafters
video in October 2021:
Emacs Lisp(use-package evil
; (…elided…)
;; not yet not yet
:disabled t)
I wasn’t ready back then, but I finally took the plunge at the end of January last year by enabling evil-mode, evil-collection, and lispyville. As you might expect, I had no idea what anything did when I started. All the guides said to learn Vim, presumably because evil-mode, in the abstract, only emulates it, so I looked up several Vim guides, cheatsheets, and the like to gain some modicum of efficiency. Here are my scattered thoughts, 14 months into my delicious fall from grace.
Embracing evil after years of innocence
What I initially found most confusing and frustrating is being in insert mode and wanting to do something that I knew had an Emacs shortcut but not an insert mode equivalent (such as toggling case or killing a sexp). I had to learn to fluidly switch modes as well as keep an eye on which commands I use most often with a view towards moving them to simpler insert mode shortcuts. Perhaps it’s also acceptable to, for example, use M-c to capitalize things in insert mode.[1] No one’s going to yell at me. I hope.
By the end of the first month, I had concluded that there were two missing pieces: an easy way to
yank text (C-y in insert mode first runs evil-copy-from-above
, then pops the right text after
it with M-y; S-insert is awkward; p requires being in normal mode) and multiple cursors.
I couldn’t figure out which library to use among
multiple-cursors,
evil-mc, and
evil-multiedit after seeing a possibly outdated
Reddit
post.
evil-mc seemed fine, except I didn’t know how to mimic the multiple-cursors behaviour of marking
only occurrences either before or after the cursor.
I deeply enjoyed deleting swathes of code from my Emacs configuration as they were made redundant. I did have to disable the lispy section of evil-collection, though. Not only did it conflict with lispyville, it also set TAB to self-insert when not activating a hydra, so I unwittingly put a bunch of tab characters in my files.
Following the common wisdom, I set SPC to be my leader key. I would like to give C-h its normal Vim binding, but I constantly rely on Emacs’s help system. I’m still debating whether to bind help to C-H instead, even though it would confuse me a bit.
It took a little time to get used to C-z switching in and out of Emacs mode instead of minimizing the window (for which I now need to use C-x C-z). I also had to work to remember that C-o in insert mode runs the next command in normal mode.
A taste of super-vi-llainy
Two months into the switch, I felt like I had superpowers, notwithstanding that I couldn’t determine the best way to work with structured or balanced expressions via Puni, or that I desperately needed to improve my use of evil-textobj-tree-sitter. One continual mystery has been how to perform regular motions using such text objects, without a verb. For instance, I can delete or select any custom text objects I define without extra work, but to simply move to their beginning or end, I need additional key bindings. (A similar question on the Emacs Stack Exchange has no clear answer.)
Another persistent annoyance is not having an easy way to move back to the previous point after a motion: I would normally use C-x C-x, but the concept itself is more complicated and nebulous with evil-mode. g ; is only a partial replacement. C-o may be what I need, although I don’t find its behaviour entirely intuitive. pwnedary on Reddit suggested ` `; I’ve tried it before but I’m giving it another shot now.
Coming to terms with more complex devilry
At the three-month mark, I began exploring ex commands and setting up more useful leader key shortcuts for file and buffer management. I also forced myself to use the Vim style of managing windows as much as possible. Having to think about the keys certainly slowed me down, but it was worth it.
The feeling of having superpowers remained. By this time, I had settled on primarily using evil-multiedit for multiple cursors, and even done a bit of navigating between matches for the first time. I added key bindings for Magit and more complicated file and buffer management under the very convenient leader key too.
Somewhere in there, I had the brilliant idea of adding a key in normal mode to yank text. After a
few days of not using it, I remembered with embarrassment that p already exists and I was
reaching for it automatically anyway. (Now that I think about it, I can probably use M-y to call
consult-yank-pop
instead.) In time, it became second nature to press C-o p in insert mode too.
Pragmatic evil
I came across the useful-looking
evil-textobj-anyblock thanks to a chance Reddit
comment. However, the description said it had been replaced by
targets.el. The description for that library says it’s
Dead (will eventually be replaced by things.el)
… but things.el hasn’t been updated since
2020 and isn’t being actively
developed, whereas
targets.el has been updated more recently than that. I’m completely at sea. According to the
author, noctuid, the eventual successor might be youkai.
evil-goggles is exceedingly useful but I had to disable it because of performance issues. Editing Org Mode tables was painfully slow (pressing TAB would make Emacs lock up for many minutes as it realigned the table). Even then, unfortunately, the delay didn’t go away completely—something in evil-mode continues to cause the errant behaviour. I thought it was an issue with undo-fu, because everything briefly worked fine without it, but the problem soon returned regardless of whether that library was enabled.
Something I find a little annoying if understandable is that r and R don’t take the input method into account. For that, I have to enter insert mode.
persp-mode
and input methods
Along the way, I also started using persp-mode
, which
I’ve considered a few times before. I like it, but it made my custom-built deferred LSP
initialization stop working… and then start working again. In addition, at some point, my input
method stopped being activated by default (something I had explicitly configured before descending
into evil). I got it working again by adding advice around evil-insert-state
:
Emacs Lisp(defun aankh/activate-rfc1345 ()
(setq-local default-input-method 'rfc1345)
(activate-input-method 'rfc1345))
(defun aankh/activate-input-method-in-insert-state (&rest args)
(activate-input-method default-input-method))
(advice-add 'evil-insert-state :after 'aankh/activate-input-method-in-insert-state)
Baby’s first demonic manifestations
Fairly early on, I wanted a way to combine
avy-goto-char-2
with text objects. My first attempt almost worked, but I had to make a few mostly-unrelated changes:
- Bind
evil-avy-goto-char-timer
instead ofavy-goto-char-timer
. - Remove the existing binding for SPC in
evil-motion-state-map
, which was clobbering its use as a leader key. - Run any evil-specific general-configuration only after loading evil.
I prefer to have the motion behave like t (up to the selected character) instead of f (including the selected character), so I redefined it to always do that:
Emacs Lisp(evil-define-avy-motion avy-goto-char-timer exclusive)
After all this, though, I have to admit I hardly use it.
On the other hand, I also built my first custom operator about six months in, to convert between JSON and YAML using yq, which saved me a lot of time while working on my Kubernetes cluster. I’ll write about that in another article.
Being bad ain’t all that simple
Here are just some of the subjects I have yet to fully comprehend as of this writing:
- Most ex commands. For one thing, I never use iedit, or multiedit with regexes.
- Macros, because defining them is a bit fragile and I see no way to mass-undo them. (Probably
because I have
evil-want-fine-undo
enabled.) - Marks, which I don’t use at all right now.
- Lispy in the context of evil.
- As mentioned earlier, Puni and balanced expressions. In contrast, I use
evil-matchit all the time. Puni is much more
feature-filled and deals with expressions in different ways, however, so it’s presumably worth
keeping both around. I do miss being able to incrementally expand the selection like I used to
with smartparens:
puni-mark-sexp-at-point
abandons the current selection to move to the next expression. - The text object navigation I spoke of earlier, particularly as it relates to indentation. I have block-nav.el installed, but I never remember how to use it. The same goes for yaml-pro. On the other hand, I use evil-indent-plus daily.
- Although I’m quite comfortable using single-letter commands like f and T to manipulate text (selecting it, deleting it, etc.), only in the past month have I made an effort to use them to navigate as well. I’m too used to moving around character-by-character or through isearch.
- I ‘reclaimed’ three terminal equivalence keys (C-i as T-i, for instance) but haven’t yet bound them to anything.
I tried to get Emacs to treat Unicode quotation marks (e.g. ’
) the same way as their ASCII
equivalents by modifying the syntax tables. For example, the w
motion before it’s ought to
move past the entire thing, like with it's, not stop before the apostrophe. I was able to change
the character classes, but the behaviour is still different with or without evil. I can’t tell what
I’m missing even after looking at the source for evil-forward-word
, evil-forward-word-begin
,
bounds-of-thing-at-point
, thing-at-point
, forward-word
(in C), and word_boundary_p
(ditto).
evil feels good
It was a bit difficult to get started, but selling my soul has been extremely rewarding. Although the novelty has worn off, I still feel like I’ve developed superpowers. Even the imperfect composability of motions and operators allows me to do so much with so little. I look forward to all the many discoveries I’m sure lie ahead of me. I wish I’d done this much earlier, but then again, I imagine I’ve benefited greatly from the years of effort the community has put into the ecosystem, so perhaps the change came at just the right time.
I’ll admit I occasionally glance with jealousy at the Kakoune modal editor. The core concept is simplicity itself: switch the order of actions from verb-object to object-verb, allowing the user to see the area that will be affected by the subsequent command. The advantages of its approach are immediately obvious but, setting aside the trivial matter of having to relearn everything, how likely is it that the world of editors will embrace a competing, er, model of modal editing?
noctuid kindly linked their comprehensive rebuttal of Kakoune’s claimed advantages, curing me of my lamentable envy in one fell swoop. Their argument is clear and persuasive even to my limited understanding.
Philosophical questions aside, my father—who introduced me to Linux, vi, and Emacs all those years ago—would have found the whole affair hilarious. First I come crawling back to Emacs, now Vim… in Emacs?
- I eventually wrote an operator to ‘twiddle’ case based on a Reddit comment, and promptly forgot it existed.↩