VIM Scripting with Python: Lookup IP Country

I’ve been playing around with VIM scripting today, experimenting with a very small and limited portion of VIM’s full capacity. VIM scripting is powerful, however one is able to leverage the power of built-in and external Python libraries instead. Imagine coding an XML library in VIM script, it’s time consuming and error-prone. To make the article a little more fun, I’ll code up a simple VIM script that looks up a country for an IP.

VIM IP Lookup in Python

This mini-tutorial assumes you know the basics of VIM – movement, modes, buffers, windows, etc. I’m also going to assume that you’re using a terminal version of VIM.

VIM has to be compiled with +python to work. If you’re not sure, open up VIM and type :py print "Python is supported!". In most cases it will be supported, unless it was compiled from source manually.

I find it convenient to work in two terminals to keep a developing environment and a testing environment separated. Browse over to any comfortable working directory for your script to be stored, and create a new script. vim iplookup.vim works well.

Open up the VIM scripting manual. VIML/VIMscript syntax is not too difficult to understand if you already comfortable with Python.

" Lookup the country for an IP address under the current cursor

" Make sure Python is ready
if !has("python")
    echo "vim has to be compiled with +python to run this"
    finish
endif

This is a standard guard, since we’ll be writing most of the code in Python. The script will exit if Python is not enabled.

python << endpython

# your python code goes here

endpython

Python is encapsulated in a heredoc and then evaluated by Python. Python interfaces with VIM via the vim object, which exposes eval and command methods. The command method does not return any value and any command that you would type in VIM, like :echo 'hello' works. The eval method evaluates VIM script statements and returns their values.

Remember, Python code goes into heredocs.

import vim, urllib

def getCountryFromIP( ip ):
  # use the minimal http://www.hostip.info/use.html API
  return urllib.urlopen('http://api.hostip.info/country.php?ip='+ip).read()

I’m using the very minimal hostip.info API for lookups. Save the file buffer and open up the testing environment and type :source iplookup.vim to load the script. Nothing will happen, however you can now use the defined function like so :py print getCountryFromIP( '8.8.8.8' ). You should see “US” in the command window.

Moving on, let’s get the IP from under the cursor.

def getWordUnderCursor():
  return vim.eval("expand('<cWORD>')")

Simple enough. The expand() vimscript command expands special keywords, cWORD being one of them. cWORD expands to the value of the current word under the cursor in VIM. Give it a go, type in some IP addresses or words and see it in action. :python print getWordUnderCursor()

Now that the functions are ready, let’s map some VIM key combination to get the country for an IP. Outside of the heredoc add the following VIM expression:

nmap <silent> ,IP :python print getCountryFromIP(getWordUnderCursor())<CR>

The statement binds the character sequence of ,IP in normal mode VIM to execute the Python statement silently. The <CR> is expanded to a return key. Read about map-which-keys and Mapping keys in VIM for some basics.

Type in some IPs, go back to normal mode with the cursor over an IP and press ,IP in sequence. You should get a simple 2 letter response in a couple of seconds.

Let’s implement some visual feedback of a lookup in progress though. Back in the Python heredoc, let’s make a new command to encapsulate the two that we already have.

def lookupIPUnderCursor():
  ip = getWordUnderCursor()
  print "Looking up " + ip + "..."
  country = getCountryFromIP( ip )
  vim.command( "redraw" ) # discard previous messages
  print "Country: " + country

Remap the key to call this function now

nmap <silent> ,IP :python print lookupIPUnderCursor()<CR>

Here’s the full script:

" Lookup the country for an IP address under the current cursor

" Make sure Python is ready
if !has("python")
  echo "vim has to be compiled with +python to run this"
  finish
endif

python << en

import vim, urllib

def getCountryFromIP( ip ):
  # use the minimal http://www.hostip.info/use.html API
  return urllib.urlopen('http://api.hostip.info/country.php?ip='+ip).read()

def getWordUnderCursor():
  return vim.eval("expand('<cWORD>')")
  
def lookupIPUnderCursor():
  ip = getWordUnderCursor()
  print "Looking up " + ip + "..."
  country = getCountryFromIP( ip )
  vim.command( "redraw" ) # discard previous messages
  print "Country: " + country
en

nmap <silent> ,IP :python lookupIPUnderCursor()<CR>

iplookup.vim (code comes with no guarantees; use, derive, enjoy, comment)

You can place this script into the ~/.vim/autoload/ directory to load it up automatically with every launch of VIM. So next time you’re looking through your server logs in VIM press ,IP to check out the country.

Things to try:

  • get more information on the IP calling via this API interface: http://api.hostip.info/get_html.php?ip=...
  • providing feedback for invalid IPs in lookupIPUnderCursor with some regular expressions
  • test out IPv6

There should be more elegant and compact ways to achieve this. VIM is one powerful beast. Put Python’s simplicity and flexibility on top and you have all the power in the world.