Grokking the Zen of the Vi Wu-Wei

28 Mar 2010
Posted by Jeet Sukumaran

I love text editors. Which is a good thing, because I spend the overwhelming majority of my computing time (and, hence, sadly, most of my conscious life) in one text editor or another. For years I have been an Emacs user, only relatively recently moving to BBEdit with my adoption/inheritance of a Mac as a personal machine. Using and often administrating Linux-based systems has necessitated that I use Vi now and then, but I have long held the opinion that the only Vi command one needs to know is: ":q!", perhaps to be followed by "emacs".

This attitude was born out of some unpleasant experiences really early on in my computing history. I distinctly remember a few occassions when I was trapped in an apparently psychotic terminal session that would not accept my typing despite dozens of increasingly-frenzied keystrokes, and then suddenly and inexplicably it started accepting my typing but refused to let me stop typing and exit. This was my introduction to the Vi editor. After once or twice resorting to disconnecting and relogging-in as the only way break out of the grip of this insane editor, I learned how to properly quit it: ":q!". For many years after that, those three keystrokes probably summed up 90-99% of my Vi usage: whenever I inadvertently triggered an editing session with it, I would quit it with alacrity and get on with life. It was a long while before I stopped getting a flash of a "Arrgh! Not again!" semi-panicky feeling whenever I saw a screen with all those tildes running down the left hand side. As far as I was concerned, a Vi session was synonymous with an operating system glitch or failure.

All that has recently radically changed ...

For a couple of years now, since getting a Mac, I have been using BBEdit as my primary local editor, and my Emacs usage has been limited to systems administration tasks as well as patching, viewing, editing etc. program and data files on remote clusters/hosts. Many times in a day, I find that I need to quickly open up a file, check or tweak a couple of lines, and retry running something. I was getting a little irritated with having to wait one to several seconds for Emacs to fire up each time I need to do this. Also, I was secretly a little ashamed and tired of lugging around my bloated monstrosity (>1.5K SLOC) of a "~/.emacs" configuration file, without which I was practically helpless.

So I decided to give Vim -- the golden child of the Vi family -- a try. It took me a couple of hours of forced usage to get used to the modal paradigm and basic navigation. But I was impressed enough with its speed and responsiveness (loading in micro-seconds as opposed to the several of Emacs) to stick with it. As I did, I began to appreciate the efficiency, elegance, and, for lack of a better word, sheer poetry of its movement and editing commands. It seemed that, without my hands leaving the keyboard, just a few strokes here and a few taps there, I was capable of dancing all over the document, and perform everything from extremely precise targetted micro-surgery to massive document-wide renovations. I felt like I was working with a well-designed artificial intelligence that smartly and smoothly executed my commands on the document, while other (modeless) text editors began more and more to seem like dumb keystroke-sucking-and-storing buckets in contrast.

I was hooked.

But what I want to share in this post is not (just) a confession or declaration of leaving the "Church of Emacs" for the "Editor-of-the-Beast". Rather I would like to link to another post that I believe is the most succint yet intuitive description of how to think when using Vim that I have yet read. While I had grasped in a semi-epiphany (if an epiphany can occur over a couple of decades as opposed to an instant) the compelling sublimity that is the Vim way of doing things, I did not truly begin to grok the Vim way till I read this post:

The beginning of understanding the Zen of Vi comes when you realize that you are not memorizing key-bindings, but rather, you are learning a language.

What a beautiful concept! You are not memorizing that "d$" means "delete from here to the end of the line". You are instead learning how to tell Vi that you want to delete ("d") from here to the end of the line ("$"). The operation command, "d", can be seen as a verb, while the target of the operation, the end of the line, "$", can be seen as the object. Carrying out the same operation with different parts of the document is just learning how to refer to different "object" (in the grammatical sense) in Vi-speak: to delete to the beginning of the line means learning how to say "beginning of the line" in Vi (" ") with the same verb ("d0"), while to delete to the end of the sentence means learning how to say "to the end of the sentence" in Vi (")") with the same verb ("d)"). On the other hand, learning how to carry out different operations is just a matter of learning new "verbs" (e.g., "y" for "yank/copy", "g" for "go to"), and applying them to the same "objects" ("end of line", "beginning of line", etc.). Seen like this, coding with Vi becomes like programming a program, or, alternatively, a better analogy that more accurately captures the sense of using Vi would the one I used previously: coding with VI is like communicating with an AI. Either is bound to appeal to any programmer!

I am still digesting all this, learning the language, along with the rest of the Vim knowledge base. Or, rather: absorbing the Zen of Vim while I (try to) act without doing with it. I realize that I am at a bottom of a steep learning curve ...

... but I am amazed at not only how much I can get done with what little I already do know, but how elegantly I can get it done as well.

So, bottom of the learning curve or not, I am now a Vim-er. This has geekoriously been made official by my "~/.bashrc" file exporting "EDITOR='vi'" instead of "EDITOR='emacs'", followed by a (relatively sparse) "~/.vimrc" file being committed to my personal environmental repository.

I have also set up my "~/.inputrc" so that readline uses Vi-mode by default:

# Be 8 bit clean.
set meta-flag on
set input-meta on
set output-meta on
set convert-meta off
 
# mode in everything that uses readline
set editing-mode vi
set keymap vi

As well as modifying my "~/.pythonstartup" file so that my Python shell does the same:

#! /usr/bin/env python
 
try:
    import readline, rlcompleter, os, atexit
    def savehist():
        try:
            import os, readline
            histfile_path = os.path.join(os.environ['HOME'], '.pythonhistory')
            readline.write_history_file(histfile_path)
            del os, readline, histfile_path
        except:
            pass
    readline.parse_and_bind('tab: tab-insert')
    readline.parse_and_bind('"\eOP": complete') # <F1>
    readline.parse_and_bind("set editing-mode vi")
    readline.parse_and_bind("set show-all-if-ambiguous on")
    readline.parse_and_bind("set meta-flag on")
    readline.parse_and_bind("set input-meta on")
    readline.parse_and_bind("set output-meta on")
    readline.parse_and_bind("set convert-meta off")
    readline.parse_and_bind('"\C-a": beginning-of-line')
    readline.parse_and_bind('"\C-e": end-of-line')
    histfile_path = os.path.join(os.environ['HOME'], '.pythonhistory')
    atexit.register(savehist)
    readline.read_history_file(histfile_path)
    del os, histfile_path, readline, rlcompleter, atexit
except IOError:
    pass
except ImportError:
    pass

Interesting side note: dozens if not hundreds of Emacs key chords that I thought were buried in muscle memory vanished without a trace within a week of using BBEdit. For future cognitive science/interface design researchers out there: the only ones that remained were "Ctrl-A" and "Ctrl-E". Which is why I have mapped in those Emacs keys into the Vi-mode Python shell above, as well as in my "~/.bashrc".

So what does Vi-mode readline get you in the Python and Bash shells, apart from the obvious? Here is what clinched the deal for me: in normal mode (Esc), hit "v", and a Vim session opens up with the current line for editing. Saving and exiting results in the buffer being executed, while ":cq" results in the command being canceled. How awesome is that?

Vi is Everywhere When You Need It

Nice post. I learned to love vi and vim when I spent all day, everyday in remote console sessions on servers that I didn't control. vi was always available and always worked the same way. Of course, since I had a login I could customize it as needed but it didn't turn out to be necessary very often. I'm stuck in a Java/Groovy gig now and Eclipse and Intellij are certainly tools that help productivity in some respects but nothing about them is easier when it comes to keyboard navigation or fast operations like line copies or deletes. Your insight that vi is a language is spot on. I had to make an effort to get comfortable in vi but now that I'm considerably along the learning curve I view knowing vi as indispensable.

vi mode in readline?

I'm a somewhat proficient Vim user, using it since a few year, and loving it, however, each time I try to "set -o vi" in bash, I go back quickly, I use "alt-." a LOT (insert last parameter of last command in place), and did not find any quick equivalent, less important but still, more tan a few ctrl-.. (u,y,w,a,e,x) are in my muscle memory now, I will try again with your tips, but habits are strong ^^. (Oh, you should try Vimperator and the nice ctrl-i in textareas) Cheers, happy new Vim-er ;)

You Can "import" Emacs Key-Bindings into Vi-mode Bash

Yes, some of those Emacs keys do come in handy on the prompt, and habits die hard.

I have the following lines in my "~/.bashrc", lifted almost verbatim from a Usenet posting somewhere:

################################################################################
# READLINE
################################################################################
 
# For those who want to use Vi bindings in bash, this corrects a
# few annoyances:
#
# 1) up and down arrows retrieve history lines even in insert mode
# 2) left and right arrows work in insert mode
# 3) Ctrl-A and Ctrl-E work how you expect if you have had to
# live in Emacs mode in the past.
# 4) So does Ctrl-D.
 
## Command-mode bindings
# Ctrl-A or Home: insert at line beginning like in emacs mode
bind -m vi-command 'Control-a: vi-insert-beg'
# Ctrl-E or End: append at line end like in emacs mode
bind -m vi-command 'Control-e: vi-append-eol'
# to switch to emacs editing mode
bind -m vi-command '"ZZ": emacs-editing-mode'
 
## Insert-mode bindings
# up arrow or PgUp: append to previous history line
bind -m vi-insert '"\M-[A": ""' # <---- CTRL-P CTRL-E
bind -m vi-insert '"\M-[5~": ""' #<---- CTRL-P CTRL-E
bind -m vi-insert 'Control-p: previous-history'
# dn arrow or PgDn: append to next history line
bind -m vi-insert '"\M-[B": ""' #<---- CTRL-P CTRL-E
bind -m vi-insert '"\M-[6~": ""' #<---- CTRL-P CTRL-E
bind -m vi-insert 'Control-n: next-history'
# Ctrl-A: insert at line start like in emacs mode
bind -m vi-insert 'Control-a: beginning-of-line'
# Ctrl-E: append at line end like in emacs mode
bind -m vi-insert 'Control-e: end-of-line'
# Ctrl-D: delete character
bind -m vi-insert 'Control-d: delete-char'
# Ctrl-L: clear screen
bind -m vi-insert 'Control-l: clear-screen'
 
## Emacs bindings
# Meta-V: go back to vi editing
bind -m emacs '"\ev": vi-editing-mode'


funny, I went the opposite route

I was an exclusive Vim user for 5 - 6 years.

I think I started making the switch to Emacs when I began getting into Lisp (the two tend to go hand in hand).

The thing is, it stuck. Emacs was far easier to program for and made a lot more sense than Vim. The thing that took the most time getting over was the use of pre-fix commands. There seems to be a barrier between the modal and non-modal editors that takes some getting used to. One thing I don't miss is constantly reaching for the ESC key (though I do find myself re-configuring my keyboards more often now which pisses off anyone else who uses my computer to no end).

Vim keys are super-sweet but confusing in their own right. Once you know it though, it's freaking awesome.

Both are great editors. I just get more bang for the buck out of Emacs.


If you know and like Lisp ...

... I cannot imagine why you would use any editor other than Emacs!

I do not know much Lisp, but all that I do know I learned from customizing Emacs over the years. And, like most long-term Emacs-users, I customized the bejeezus out of it. But actually, it was the Lisp-based approach to scripting Emacs (if that is the right term) that actually made it less appealing to me. Conversely, the fact that it is possible to script Vim using Python is a major plus for Vim (for me).

Nonetheless, Emacs definitely has its pluses, and as far as modeless editor goes, it is excellent, easily at least the second-best on most platforms that I have worked with.

No, the "second-best" was not a typo. On Macs, my favorite modeless editor has to be BBEdit (its only downside is that it uses the hideous monstrosity called "AppleScript" for its scripting), while on Windows it is UltraEdit (though I don't know how or whether this can be scripted). On Linuxes, there are couple of decent editors (Kate, BlueFish), but none that I was ever truly happy with: only here do I think that Emacs shines as clearly above the rest in the lead. Factor in scripting needs, however, then sure, Emacs takes the lead on all three platforms.


Emacs on Macs..

I use Aquamacs myself and it's definitely a first-class citizen on my Macbook. I get all of the nice OSX font goodness and it integrates into the desktop environment, respecting the Apple interface guidelines. So you can both Apple-Q and C-x C-c. If you're a little more hardcore, I used to build a nightly carbon version of emacs as well. Either way, there are lots of options.

I'm not trying to win you back to the emacs camp but it should be pointed out that emacs can be scripted with Python as well (or Perl or whatever). Pymacs provides the bridge and along with rope and flymake on top of the python-mode; emacs becomes one mean python hacking machine. An in-editor interpreter, automatically scrubbed source with pyflakes/pylint, integrated test runner... it's freaking sweet (I hack python on my day job, can you tell? :)

There are a lot of reasons to like Vim and I think you hit most of them in your post. Good stuff. :)


Aquamacs ...

"first-class citizen" ... "Apple interface guidelines"?

Have you opened the Aquamacs "Preferences" dialog recently? Like I said, I have been using (mostly console-based) Emacs for years. When I switched to a Mac as my primary machine, the first thing that I did was check out the various flavors of native GUI Emacs. Maybe it was just me, but the entire "Preferences" subsystem is worthy of its own chapter in a UI design textbook: "Approximating a Gordian Knot in Interface Design, Or How to Ensure that 90% of Your Users Never Use this Feature".

But yes, I agree, on the whole, Emacs is a great little modeless text editor .. and (potentially) quite a bit more. I suppose it comes down to how important that "bit more" part is to you over dedicated/specialized text processing and manipulation. I tend to call all the programming support tools and operations from the shell, even when it is supported by the IDE (as it was in Emacs): my typical configuration is a text editor window open one on half of the screen, and terminal open on the other side, both at full height, side-by-side. Tests, VCS, lint, etc. all run directly from Bash. So their integration/availability in Emacs was just so much bloated deadweight. This did not, and does not, bother me either way --- i.e., I never seen the availability of an unused feature a problem. But this also means that the integration of the tools into the text editor environment ranks pretty close to 0 as a selling a point for me.

On the other hand, the advantages of the significantly extra power, flexibility and elegance of (native) text processing operations available in a text editor does make a sense to me, and thus this easily trumps all the other probably-useful-but-just-not-used-by-me-so-often bells and whistles. But that might just be the song I'm singing now. Let us see what I'll be singing once the Kool Aid wears off.


Using `emacs --daemon` and

Using `emacs --daemon` and `emacsclient` would give you an emacs even faster starting than vim (if you have some addons). I switched a few weeks ago from vim to emacs, because of the better environment (and honestly flymake and flyspell rock) but stayed with viper-mode and vimpulse. That's IMO the best of both worlds.

Yes, but ...

Is that feasible in contexts where you might be logging on and off frequently?

In any case, now that I have discovered the sublime beauty of "communicating" to my text editor via a grammar, rather then bashing out memorized keys, I'm not sure I can go back ...


Well I don't do that, but

Well I don't do that, but using screen/tmux/... in that context would help keeping the emacs daemon alive, as long as you don't shutdown. But you need to kill emacs gracefully before shutting down, it doesn't like being killed by SIGKILL (not that any other program likes that ..). viper-mode and vimpulse give you that grammar back, at least a whole deal of it - I didn't miss a command in that few weeks. Granted that combo is weak in ex-style commands because only a few are supported, but emacs comes with a nicer command-line anyway.

I was trying to use emacs for

I was trying to use emacs for a while with viper, and vimpulse, but still it doesn't feel the same, because the shortcuts feels clunky and bolted on, and a lot of the things that I know and love from vim doesn't really work too well even with vimpulse. And the configuration for emacs is a big mess, so for me, vim is the way ;)

Another happy customer ..

Happy vimming! Regarding `:cq`, I suppose you could also do `:q` or `:q!` to 'cancel' the current command. For me `:wq` saves and `:q!` exits _dangerously_

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
This question is for testing whether you are a biological visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.