config/.vim/ftplugin/python/pyflakes.vim
2015-06-30 15:46:20 +02:00

322 lines
9.3 KiB
VimL

" pyflakes.vim - A script to highlight Python code on the fly with warnings
" from Pyflakes, a Python lint tool.
"
" Place this script and the accompanying pyflakes directory in
" .vim/ftplugin/python.
"
" See README for additional installation and information.
"
" Thanks to matlib.vim for ideas/code on interactive linting.
"
" Maintainer: Kevin Watters <kevin.watters@gmail.com>
" Version: 0.1
if exists("b:did_pyflakes_plugin")
finish " only load once
else
let b:did_pyflakes_plugin = 1
endif
if !exists('g:pyflakes_builtins')
let g:pyflakes_builtins = []
endif
if !exists("b:did_python_init")
let b:did_python_init = 0
if !has('python')
echoerr "Error: the pyflakes.vim plugin requires Vim to be compiled with +python"
finish
endif
if !exists('g:pyflakes_use_quickfix')
let g:pyflakes_use_quickfix = 1
endif
python << EOF
import vim
import os.path
import sys
if sys.version_info[:2] < (2, 5):
raise AssertionError('Vim must be compiled with Python 2.5 or higher; you have ' + sys.version)
# get the directory this script is in: the pyflakes python module should be installed there.
scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
sys.path.insert(0, scriptdir)
import ast
from pyflakes import checker, messages
from operator import attrgetter
import re
class loc(object):
def __init__(self, lineno, col=None):
self.lineno = lineno
self.col_offset = col
class SyntaxError(messages.Message):
message = 'could not compile: %s'
def __init__(self, filename, lineno, col, message):
messages.Message.__init__(self, filename, loc(lineno, col))
self.message_args = (message,)
class blackhole(object):
write = flush = lambda *a, **k: None
def check(buffer):
filename = buffer.name
contents = buffer[:]
# shebang usually found at the top of the file, followed by source code encoding marker.
# assume everything else that follows is encoded in the encoding.
encoding_found = False
for n, line in enumerate(contents):
if n >= 2:
break
elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line):
contents = ['']*(n+1) + contents[n+1:]
break
contents = '\n'.join(contents) + '\n'
vimenc = vim.eval('&encoding')
if vimenc:
contents = contents.decode(vimenc)
builtins = set(['__file__'])
try:
builtins.update(set(eval(vim.eval('string(g:pyflakes_builtins)'))))
except Exception:
pass
try:
# TODO: use warnings filters instead of ignoring stderr
old_stderr, sys.stderr = sys.stderr, blackhole()
try:
tree = ast.parse(contents, filename or '<unknown>')
finally:
sys.stderr = old_stderr
except:
try:
value = sys.exc_info()[1]
lineno, offset, line = value[1][1:]
except IndexError:
lineno, offset, line = 1, 0, ''
if line and line.endswith("\n"):
line = line[:-1]
return [SyntaxError(filename, lineno, offset, str(value))]
else:
# pyflakes looks to _MAGIC_GLOBALS in checker.py to see which
# UndefinedNames to ignore
old_globals = getattr(checker,' _MAGIC_GLOBALS', [])
checker._MAGIC_GLOBALS = set(old_globals) | builtins
w = checker.Checker(tree, filename)
checker._MAGIC_GLOBALS = old_globals
w.messages.sort(key = attrgetter('lineno'))
return w.messages
def vim_quote(s):
return s.replace("'", "''")
EOF
let b:did_python_init = 1
endif
if !b:did_python_init
finish
endif
au BufLeave <buffer> call s:ClearPyflakes()
au BufEnter <buffer> call s:RunPyflakes()
au InsertLeave <buffer> call s:RunPyflakes()
au InsertEnter <buffer> call s:RunPyflakes()
au BufWritePost <buffer> call s:RunPyflakes()
au CursorHold <buffer> call s:RunPyflakes()
au CursorHoldI <buffer> call s:RunPyflakes()
au CursorHold <buffer> call s:GetPyflakesMessage()
au CursorMoved <buffer> call s:GetPyflakesMessage()
if !exists("*s:PyflakesUpdate")
function s:PyflakesUpdate()
silent call s:RunPyflakes()
call s:GetPyflakesMessage()
endfunction
endif
" Call this function in your .vimrc to update PyFlakes
if !exists(":PyflakesUpdate")
command PyflakesUpdate :call s:PyflakesUpdate()
endif
" Hook common text manipulation commands to update PyFlakes
" TODO: is there a more general "text op" autocommand we could register
" for here?
noremap <buffer><silent> dd dd:PyflakesUpdate<CR>
noremap <buffer><silent> dw dw:PyflakesUpdate<CR>
noremap <buffer><silent> u u:PyflakesUpdate<CR>
noremap <buffer><silent> <C-R> <C-R>:PyflakesUpdate<CR>
" WideMsg() prints [long] message up to (&columns-1) length
" guaranteed without "Press Enter" prompt.
if !exists("*s:WideMsg")
function s:WideMsg(msg)
let x=&ruler | let y=&showcmd
set noruler noshowcmd
redraw
echo strpart(a:msg, 0, &columns-1)
let &ruler=x | let &showcmd=y
endfun
endif
if !exists("*s:GetQuickFixStackCount")
function s:GetQuickFixStackCount()
let l:stack_count = 0
try
silent colder 9
catch /E380:/
endtry
try
for i in range(9)
silent cnewer
let l:stack_count = l:stack_count + 1
endfor
catch /E381:/
return l:stack_count
endtry
endfunction
endif
if !exists("*s:ActivatePyflakesQuickFixWindow")
function s:ActivatePyflakesQuickFixWindow()
try
silent colder 9 " go to the bottom of quickfix stack
catch /E380:/
endtry
if s:pyflakes_qf > 0
try
exe "silent cnewer " . s:pyflakes_qf
catch /E381:/
echoerr "Could not activate Pyflakes Quickfix Window."
endtry
endif
endfunction
endif
if !exists("*s:RunPyflakes")
function s:RunPyflakes()
highlight link PyFlakes SpellBad
if exists("b:cleared")
if b:cleared == 0
silent call s:ClearPyflakes()
let b:cleared = 1
endif
else
let b:cleared = 1
endif
let b:matched = []
let b:matchedlines = {}
let b:qf_list = []
let b:qf_window_count = -1
python << EOF
for w in check(vim.current.buffer):
vim.command('let s:matchDict = {}')
vim.command("let s:matchDict['lineNum'] = " + str(w.lineno))
vim.command("let s:matchDict['message'] = '%s'" % vim_quote(w.message % w.message_args))
vim.command("let b:matchedlines[" + str(w.lineno) + "] = s:matchDict")
vim.command("let l:qf_item = {}")
vim.command("let l:qf_item.bufnr = bufnr('%')")
vim.command("let l:qf_item.filename = expand('%')")
vim.command("let l:qf_item.lnum = %s" % str(w.lineno))
vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args))
vim.command("let l:qf_item.type = 'E'")
if getattr(w, 'col', None) is None or isinstance(w, SyntaxError):
# without column information, just highlight the whole line
# (minus the newline)
vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')")
else:
# with a column number, highlight the first keyword there
vim.command(r"let s:mID = matchadd('PyFlakes', '^\%" + str(w.lineno) + r"l\_.\{-}\zs\k\+\k\@!\%>" + str(w.col) + r"c')")
vim.command("let l:qf_item.vcol = 1")
vim.command("let l:qf_item.col = %s" % str(w.col + 1))
vim.command("call add(b:matched, s:matchDict)")
vim.command("call add(b:qf_list, l:qf_item)")
EOF
if g:pyflakes_use_quickfix == 1
if exists("s:pyflakes_qf")
" if pyflakes quickfix window is already created, reuse it
call s:ActivatePyflakesQuickFixWindow()
call setqflist(b:qf_list, 'r')
else
" one pyflakes quickfix window for all buffer
call setqflist(b:qf_list, '')
let s:pyflakes_qf = s:GetQuickFixStackCount()
endif
endif
let b:cleared = 0
endfunction
end
" keep track of whether or not we are showing a message
let b:showing_message = 0
if !exists("*s:GetPyflakesMessage")
function s:GetPyflakesMessage()
let s:cursorPos = getpos(".")
" Bail if RunPyflakes hasn't been called yet.
if !exists('b:matchedlines')
return
endif
" if there's a message for the line the cursor is currently on, echo
" it to the console
if has_key(b:matchedlines, s:cursorPos[1])
let s:pyflakesMatch = get(b:matchedlines, s:cursorPos[1])
call s:WideMsg(s:pyflakesMatch['message'])
let b:showing_message = 1
return
endif
" otherwise, if we're showing a message, clear it
if b:showing_message == 1
echo
let b:showing_message = 0
endif
endfunction
endif
if !exists('*s:ClearPyflakes')
function s:ClearPyflakes()
let s:matches = getmatches()
for s:matchId in s:matches
if s:matchId['group'] == 'PyFlakes'
call matchdelete(s:matchId['id'])
endif
endfor
let b:matched = []
let b:matchedlines = {}
let b:cleared = 1
endfunction
endif