I am a big fan of the syntastic package. In python, it is useful for viewing both Pyflakes and Bandit errors. However, this only works if syntastic knows what python interpreter to use. Not everyone is fortunate enough to have upgraded all of their software to python3. For reasons out of my control, I end up spending most of my time writing python2 code. This means that the times when I finally get to write python3, all of my syntax checking is broken. How cool would it be to have VIM determine what version of python to use automatically? So, begins my grand experiment...
For this to work, I will be using a virtualenv for python2 and one for python3. It's not absolutely necessary to use virtualenvs but I definitely recommend it. Setting up virtualenvs is out of the scope of this gist but make sure that each virtualenv has pyflakes and bandit installed.
" Python configs
let g:python2_dir = $HOME . '/path/to/python2_venv/bin/'
let g:python3_dir = $HOME . '/path/to/python3_venv/bin/'
function! g:DetectPyVersion()
silent %yank p
new
silent 0put p
I decided to name my function, crazy enough, DetectPyVersion. The first thing I need to do is make a copy of the current buffer and drop it into a temporary space.
silent! exe '%!' . g:python2_dir . 'python -c "import ast; import sys; ast.parse(sys.stdin.read())"'
bd!
if v:shell_error == 0
let b:py_version = 'py2'
return
endif
This is where the magic happens. I am using the python AST module to determine if the syntax in the current buffer is python2 compliant or not. The AST module can build a syntax tree for a given python grammar. If the inline python generates a traceback, then we know the code is not python2. After running an external command, VIM sets the variable v:shell_error to the return code of the command. If the command was successful, it will return 0 or some other int to signal failure. The bd! command simply removes the temporary buffer.
silent %yank p
new
silent 0put p
silent! exe '%!' . g:python3_dir . 'python -c "import ast; import sys; ast.parse(sys.stdin.read())"'
bd!
if v:shell_error == 0
let b:py_version = 'py3'
return
endif
Similar to above, repeat the steps for python3.
Next, if neither python2 or python3 were successful just set an error string. Altogether,
function! g:DetectPyVersion()
silent %yank p
new
silent 0put p
silent! exe '%!' . g:python2_dir . 'python -c "import ast; import sys; ast.parse(sys.stdin.read())"'
bd!
if v:shell_error == 0
let b:py_version = 'py2'
return
endif
silent %yank p
new
silent 0put p
silent! exe '%!' . g:python3_dir . 'python -c "import ast; import sys; ast.parse(sys.stdin.read())"'
bd!
if v:shell_error == 0
let b:py_version = 'py3'
return
endif
let b:py_version = 'Err'
endfunction
Now that the function to detect the version of python has been written, next is to assign the updated python version in syntastic.
function! s:SetSyntasticPyVersion()
if &filetype == 'python'
if exists("b:py_version") && b:py_version == 'py3'
let g:syntastic_python_pyflakes_exec = g:python3_dir . 'pyflakes'
let g:syntastic_python_bandit_exec = g:python3_dir . 'bandit'
else
let g:syntastic_python_pyflakes_exec = g:python2_dir . 'pyflakes'
let g:syntastic_python_bandit_exec = g:python2_dir . 'bandit'
endif
endif
endfunction
After checking that the current file is a python module, check that the py_version buffer variable has been defined and is set to py3. If not, use python2 versions.
Finally, to tie everything together, autocommands are used to call the above functions at the right times. Since DetectPyVersion makes external calls to the python interpreter, it can be relatively slow. Therefore, it is best to only run it when necessary. I don't expect python syntax for a file to change very often so it should be good enough to just check it when opening the file.
autocmd bufreadpost * call g:DetectPyVersion()
Also, notice that I have chosen to make DetectPyVersion a global function. In case I decide to switch syntaxes while working on a file, this gives the ability to re-run the detection function manually. Since the variables that control syntastic are defined globally, it is necessary to update them every time we switch buffers. I also run the update just before writing a buffer since syntastic can also be configured to run on writes.
autocmd bufenter,bufwritepre * call s:SetSyntasticPyVersion()
Good call. Fixed it.