Wednesday, July 22, 2009

Python File Type Plugin for Vim - Part 1

* Made a correction below. Using the ~/.vim/ftplugin directory for a file type plugin causes the plugin to be reloaded repeatedly causing any autocommands to be appended for BufWrite actions. This causes multiple executions of every autocommand every time you save the file. A better location is the ~/.vim/plugin directory. Where the plugin is loaded once at startup. I have changed the location below. *

I wanted to add some IDE style conveniences to my preferred editor, console Vim. Rather than reinventing the wheel I decided to search the Internet and start with what was already out there. I found a bunch of very useful scripts and snippets. In part 1 I am documenting some of what I found here by publishing a snippet that I have only made minor changes to to make it useful for my needs.

This script needs to be put in a file named python.vim and saved in ~/.vim/plugin. If the directory does not exist it needs to be created. The scripts create the pyflakes, pylinks, and pychecker commands that can be used at the vim command prompt, ':'. The pylint, pyflakes, pychecker packages will have to be installed as prerequisites for using these commands.

All three commands act on the current buffer's file name. Running one of the commands at the vim command prompt will open a Vim quickfix window which will seem familiar to anyone who has used a graphical IDE for development.

You can press c to close the quickfix window when it has the focus. You can double click with a mouse under X or press enter to go to the error or warning associated with the current quickfix line. This will take you to the line referred to by that message. You will have to restart vim if you have already loaded a python file during the currently loaded Vim session.

Here is the code:

function! PythonGrep(tool)
  set lazyredraw
  " Close any existing cwindows.
  let l:grepformat_save = &grepformat
  let l:grepprogram_save = &grepprg
  set grepformat&vim
  set grepformat&vim
  let &grepformat = '%f:%l:%m'
  if a:tool == "pylint"
    let &grepprg = 'pylint --output-format=parseable --reports=n --indent-string="        "'
  elseif a:tool == "pychecker"
    let &grepprg = 'pychecker --quiet -q'
  elseif a:tool == "pyflakes"
     let &grepprg = 'pyflakes'
    echohl WarningMsg
    echo "PythonGrep Error: Unknown Tool"
    echohl none
  if &readonly == 0 | update | endif
  silent! grep! %
  let &grepformat = l:grepformat_save
  let &grepprg = l:grepprogram_save
  let l:mod_total = 0
  let l:win_count = 1
  " Determine correct window height
  windo let l:win_count = l:win_count + 1
  if l:win_count <= 2 | let l:win_count = 4 | endif
  windo let l:mod_total = l:mod_total + winheight(0)/l:win_count |
  \ execute 'resize +'.l:mod_total
  " Open cwindow
  execute 'belowright cw '.l:mod_total
  nnoremap   c :cclose
  set nolazyredraw

command! Pyflakes call PythonGrep('pyflakes')
command! PyFlakes call PythonGrep('pyflakes')
command! Pychecker call PythonGrep('pychecker')
command! PyChecker call PythonGrep('pychecker')
command! Pylint call PythonGrep('pylint')
command! PyLint call PythonGrep('pylint')

" These three are successively more informative and aggressive in their
" warnings with pyflakes as the least noisy. Only uncomment one.
"autocmd BufWrite *.{py} :Pyflakes
autocmd BufWrite *.{py} :Pychecker
"autocmd BufWrite *.{py} :Pylint

In Vim a single double quote, ", is considered the beginning of a comment for the rest of the line.

The benefit of having the autocmd run one of the Python lint programs every time you save the file is that you can never get too far from sane working code as any errors and warnings pop up. This, in your face, style of development can keep you from pulling your hair out over something as simple as a semicolon or comma typo and can also warn you about more complex problems and proper coding style.

More to follow. I will try to combine the output of the three commands as their output doesn't seem to overlap for the most part. And the noisier programs (pychecker and pylint) don't appear to give the same advice when it comes to warnings. pylinker is by far the noisiest of them all but all it's warnings are configurable.

Warning: If you are using tabs for indenting you will have to modify the pylint parameter '--indent-string='. Set it to the empty string for tabs, or the actual number of spaces used for indenting. In my code above I have it set for 8 spaces which is project specific.