The seamless integration of Vim and the Shell

2017-05-23

I've been a Vim user for around 3 years now, and one of the best things about it is the constant discovery of new tools and functionalities available in the editor. I'm not the only one. Almost every stackoverflow question about vim has an answer with a comment in the form of "I've been using Vim for X years and I never knew I could do this".

After a few months of using Vim as my primary text editor, I discovered that I could hit Ctrl-[ to exit the insert mode. After about 2 years I discovered that I could just hit Ctrl-C to do the same. In any case, I'd learnt about key mappings early on and mapped kj to <Escape>. (Though I might have never bothered with the mappings if I'd known about Ctrl-C)

3 years in, I feel like I'm just scratching the surface of what this decades-old editor offers.

I'm going to highlight some examples of how to extend Vim's functionality with the shell.

Case A

Say you're working on a Python web-scraping project. You type out the script, save it, go to a separate terminal session and run the script from there.

Let's see an alternate way of doing the same job without ever leaving our Vim environment.

import urllib2
page = urllib2.urlopen("http://www.website.com/page")
print page.read()

After typing out the script, we exit the insert mode and visually select the entire buffer with ggVG. Next, enter the command mode by pressing : (the vim command prompt will show :'<,'>), type python press Enter.

Vim will run all the visually selected lines in a python interpreter and output the result directly inside the current buffer.

Just undo the entire thing to get back to the code.

Want an even more convenient way of pulling a page source from the web? Use curl.

:r! curl www.website.com/page 2>/dev/null

Here we're telling Vim to read in the output of the given shell command (curl) to the current buffer. We redirect curl's stderr to /dev/null so it doesn't output it's download stats alongwith the page source.

Use :r! ls to output the directory contents to the buffer or :r! netstat -na to list out all active network connections on your computer. Any shell command can be executed inside Vim and the command's output spilled to the current buffer.

Case B

In big projects, I prefer to sort the import declarations based on the length of the lines. Let's take the following import declarations in Scala:

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.Path
import org.apache.hadoop.fs.FSDataInputStream
import org.apache.hadoop.fs.FileSystem

import com.typesafe.config.ConfigFactory
import com.typesafe.config.Config

To sort these lines based on the length, visually select the individual sections (vip will select a paragraph, i.e. all lines between two blank lines.) and use the following pipe composition to get the job done:

:'<,'>!awk '{print length, $0}' | sort -n | cut -d' ' -f2-

Explanation :

All we have to do is visually select the sections we want to sort based on the length, scroll up the command history and press Enter.

Case C

I've tried Regex engines in a few different environments and I found Perl's regex engine to be the nicest to work with. Vim has a Regex engine that is slightly different and it's syntax feels a bit clunky, not to mention I'd rather avoid keeping different implementations in mind.

This is where Vim's shell integration comes to the rescue. I can easily write a Perl one-liner instead of using Vim's built-in regex engine for the task.

Let's say I want to substitute the word "Sublime" with "Vim" in the current buffer.

:%! perl -pe 's/Sublime/Vim/g'

% stands for the current buffer and using it tells Vim to run the following command on all the lines in the current buffer. We could have specified a line range like :1,10! perl -pe ... in it's place or we could've run the command on a visual selection like in the previous examples.

Funny thing - Just a few days back I came across the :perldo and :rubydo commands in vim. So instead of the previous one-liner, I could have just done :perldo s/Sublime/Vim/g to get the same result.