When I first started working in Vim, reading Bram Moolenar’s Seven Habits of Effective Text Editing was very helpful. In it he outlines three fundamental tips for gradually improving one’s efficacy in the editor.

  1. While you are editing, keep an eye out for actions you repeat and/or spend quite a bit of time on

  2. Find out if there is an editor command that will do this action quicker. Read the documentation, ask a friend, look at how others do this.

  3. Train using the command. Do this until your fingers type it without thinking.

These are so basic, yet incredibly profound. In Vim there is always a quicker way to do things and that doesn’t just apply to editing text in a conventional sense, any adjacent processes can also be optimized.

Consider the process of TDD. There are plenty of ways we can use Vim to make the Red, Green, Refactor loop as tight and fast as possible. Let’s take a look at each stage of the cycle and delve into some of those potential optimizations.

RED - Writing a Failing Test

If we’re writing tests, there’s going to be some basic boilerplate to write. In the case of BDD style frameworks like Rspec and Speclj, we know we are going to be frequently writing code that looks like this:


(describe "The App"
  (context "Starting it up"
    (it "loads the thing we expect it to load"
      (let [something {:this "and that"}]
        (should= something something_else)))))

While it’s not a ton of boilerplate, we’ll probably be repeating it a fair amount. Let’s make things easier by writing some basic snippets that will significantly cut down on the amount of time we need to create this boilerplate. One really easy way to do this by creating some abbreviations in our .vimrc file.

:iabbrev dci (describe " " <CR>  (context "  " <CR>  (it "  " <CR>  #body here#)))

Now simply by typing dci in insert mode we can create much of the boilerplate for our tests.

(describe " "
  (context " "
    (it " "
      #body here#)))

Considering that we will probably want multiple it blocks per context and multiple context blocks per describe, we might also add the following abbreviations as well:

:iabbrev ci (context "  " <CR>  (it "  " <CR>  #body here#)))
:iabbrev ib (it "  " <CR>  #body here#)))

We can also specify the what types of files these abbreviations work in using an autocommand.

"" au is just an abbreviation of autocmnd
:au FileType clojure :iabbrev dci (describe " " <CR>  (context "  " <CR>  (it "  " <CR>  #body here#)))
:au FileType ruby :iabbrev dci describe " " do <CR> context " " do <CR> it " " do #body here# <CR> end <CR> end <CR> end

By adding autocommands to to our abbreviations we can ensure that our abbreviations are only expanded in the file type we want. Also, we can now have the same abbreviation for multiple file types, but with different behavior.

Abbreviations work well for creating quick on the fly expansions for text you write frequently, but for boilerplate code, they are, at best, a stop-gap solution. Much better options exist in the form of Plugins like SnipMate, and UtiliSnips that were explicitly created to help Vim users quickly and easily create small reusable “snippets” of code. I’d recommend starting off with the abbreviations first though, they might turn out to be more than enough.

GREEN - Passing The Failing Test

Alright! we’ve now written our first failing test. How can we get to ‘Green’ as quickly as possible? First, we need to actually determine that our current test failed, which means two things.

  1. We want the time between writing our tests and running them to be as short as possible.
  2. We want to verify that our test failed with minimal effort.

This is a common workflow I’ve seen and have used before:

  • write a failing test
  • hit ctrl z to ‘pause’ Vim and go back to the terminal
  • type in test command, something like lein test or hit the up arrow to get the most recent command
  • hit enter
  • wait for the test to run
  • look at the results
  • hit fg to return to Vim
  • probably switch back and forht a couple more times to reference the error message.

Not bad, but there’s a lot of room for error, and all things considered this approach is pretty slow. Ideally, our tests should already have been run before we even have had time to think. Also, wouldn’t it be nice to be able to see our fialing test output without needing to switch contexts all the time? With the aid of a terminal multiplexer like tmux (though any will do) we can do both with out much fuss.

Setting Up The Multiplexer

install tmux and run the following commands:

#create a tmux session
tmux
#open up split
#the tmux prefix key is <Ctrl b> by default

#vertical
<prefix> %

#horizontal
<prefix> "
#resize the split with option and arrow keys
<prefix> ⌥  <left> <right> <up> <down>

Once you have things configured the way you want open up Vim in one of the windows. Personally I like to have Vim on the left with a narrow column on the right displaying test output.

Sweet now we can see our code and test output on the same screen! That’s a huge step forward from the workflow I described earlier, but we can still make it better. It can be difficult and to switch between, and keep straight Vim and tmux commands. I like to think of tmux as a sort of extension to Vim, so I added the following lines to my .tmux.conf to make things a bit easier and more Vim like.

setw -g mode-keys vi
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

Now I can use Vim naviagtion keys in tmux. I just have to prepend each one with the tmux prefix.

Even though we’ve made moving around tmux a breeze, we don’t want to have to switch over to our other tmux pane just to run our tests. Ideally, we should never have to leave Vim. We ought to just be able to glance over, see the error message, and then go right back to working on making that test pass.

Creating A Test Autorunner

I like for my tests to run as soon as I save the file I’m currently working on. In Vim-speak that’s called writing to the buffer and it’s known as an autocommand event. Vim includes a multitiude of these types of triggers, including the FileType event that we saw when talking about abbrevaitons and snippets. the BufWrite command is what we’ll use as our autocommand event, but feel free to checkout out any of the others that are available by typing :help autocommand-events when in Vim.

Let’s say that we are working in an Elixir project. Here’s an autocommand we can use to run our tests.

autocmd BufWrite *.ex :silent exec "!tmux send-keys -t 1 C-l \"mix test\" Enter" | redraw!

We already know what BufWrite is about, but there’s a lot going on here, so let’s walk through the rest of it.

*.ex

This says only run this autocommand when the file has a .ex extension

:silent exec

:silent will allow us to run the command without needing making us leave Vim and hit enter afterwards. exec will allow Vim to evaluate the proceeding string as a command.

"!tmux send-keys

This is the beginning of our command. First, ! basically puts us in the terminal, meaning whatever we write after it, is executed by whatever shell we are running. Essentially, this is the equivalent of switching over to the terminal and typing in the tmux send-keys command

-t 1

This is an option passed to the tmux send-keys command it specifies to which pane the proceeding keys must be sent.

C-l \"mix test"\ Enter"

These are the keys that we are actually sending to pane we specified with the -t option. C-l will reload the pane prior to \"mix text"\ Enter being sent, so our tests are always run on a fresh screen. \"mix test\" Enter is the command we would normally input into the terminal if we wanted our tests to run.

| redraw!

Finally | redraw! will clear the current Vim screen and redraw it. This is helpful because some times Vim can get in a funky state after doing an external command. This just ensures we are never bothered by this occasional nuisance.

Awesome! Now whenever we write to a buffer our tests will automatically run and we need only to have them skate across our periphery to ensure they fail or pass.

Quick Aside On Vim’s “!” Operator

Vim is a Unix style program. It does one thing, text editing, exceedingly well. However, because it can interact with the shell via ! it also has access to anything you can do in the shell. As such, just as we are delegating to a test runner and tmux to run our tests we can do something similar with any other command line application. For example, we could easily envision some sort of git workflow in which we never have to leave Vim!

NB: If you don’t like you’re screen real-estate eaten up by a Tmux column, then you might want to try out vim-dispatch. There’s also plenty of Plugins that take care of helping Vim interface with tmux (vimux and tslime) . If you are finding your efficiency hampered by the limitations of the techniques I described above, I’d recommend looking into any of those.

Refactor - Stay Green, but Make Things a Little Nicer

We are green! Now how can we make our code better?

Project-Wide Search and Replace

One of the most common refactorings I find myself making is changing the name of a function. That’s a pretty simple task in Vim if we are only talking about one file, but what If we need to make a change across multiple files? Well, actually it’s pretty easy too, most people just don’t know about it.

:grep foo
:cdo %s/foo/bar/g | update

:grep foo will find all instances of foo in your project, and populate them in what is called the quickfix window :cdo will then take whatever command follows it and apply it to every single one. In our case, the command we want to apply is a global substitution of foo with bar, but it could be anything. After making the changes, we want to save the changes, which is exactly what the | update portion does.

NB: The quickfix window isn’t normally visible. To see it just use the :copen command.

Aside from a few specific use cases here and there, like project-wide serach and replace, much of refactoring comes down to plain old text editing. To that end, I think the single most helpful thing one can do to optimize their refactoring. Is to learn th fundamentals of Vim’s powerful language.

Plain ‘Ol Text Editing

In normal mode, every vim command follows the same structure

<command><number><text-object or motion>

Every text object is created using either a literally meaning “a” or i meaning “inner” as well as one of the following:

w, W, s, p, [, ], (, ), {,  }, >, <, t, ", ', `

For example, aw is “a word”, iw is “inner word”, a} is “a block” (it contains both { and }) i}, is “inner block” (it doesn’t contain { or }) and so on… (you can look up the rest with :help text-objects)

We can then combine text-objects with some basic commands like d, c and y for some powerful functionality. For instance lets say we want to change an entire block of code, cap “change a paragraph” should get the job done. Maybe we actually just want to copy the body of a javascript function between curly-braces yiB or yi} will both work. By familiarizing ourselves with Vim’s unique lexicon we can start to think using this language and come up with even better ways to do things. HIN: :help movement.txt is an excellent place to start.

Wrapping Things Up

Vim is very well suited to help sofware professionals optimize their craft. For things that we do everyday like TDD it’s helpful to take some time upfront to consider potential changes to our current approach that we might make better. Hopefully, some of the things we discussed above inspire you to take a critical look at your current workflow and try out some new new things.

Even if you aren’t someone who uses Vim regularly, I think Bram’s suggestions still apply. Get in the habit of not only identifying repetition, but also try to make a habit of doing something about it. That mgiht be just writing it down after it dawns on you and then scheduling some time later to find a better way, or just stopping whatever you’re doing and finding a better way right then and there. The point is, as professionals we should never be complacent, and always strive to find better ways of doing things.

Now if you excuse me, I really want to try this out.