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