annotate vim/vimfiles/bundle/pyflakes-vim/ftplugin/python/pyflakes.vim @ 8:097c95760fd0

Added pyflakes-vim plugin support.
author Brian Neal <bgneal@gmail.com>
date Sun, 29 Apr 2012 16:30:44 -0500
parents
children
rev   line source
bgneal@8 1 " pyflakes.vim - A script to highlight Python code on the fly with warnings
bgneal@8 2 " from Pyflakes, a Python lint tool.
bgneal@8 3 "
bgneal@8 4 " Place this script and the accompanying pyflakes directory in
bgneal@8 5 " .vim/ftplugin/python.
bgneal@8 6 "
bgneal@8 7 " See README for additional installation and information.
bgneal@8 8 "
bgneal@8 9 " Thanks to matlib.vim for ideas/code on interactive linting.
bgneal@8 10 "
bgneal@8 11 " Maintainer: Kevin Watters <kevin.watters@gmail.com>
bgneal@8 12 " Version: 0.1
bgneal@8 13
bgneal@8 14 if exists("b:did_pyflakes_plugin")
bgneal@8 15 finish " only load once
bgneal@8 16 else
bgneal@8 17 let b:did_pyflakes_plugin = 1
bgneal@8 18 endif
bgneal@8 19
bgneal@8 20 if !exists('g:pyflakes_builtins')
bgneal@8 21 let g:pyflakes_builtins = []
bgneal@8 22 endif
bgneal@8 23
bgneal@8 24 if !exists("b:did_python_init")
bgneal@8 25 let b:did_python_init = 0
bgneal@8 26
bgneal@8 27 if !has('python')
bgneal@8 28 echoerr "Error: the pyflakes.vim plugin requires Vim to be compiled with +python"
bgneal@8 29 finish
bgneal@8 30 endif
bgneal@8 31
bgneal@8 32 if !exists('g:pyflakes_use_quickfix')
bgneal@8 33 let g:pyflakes_use_quickfix = 1
bgneal@8 34 endif
bgneal@8 35
bgneal@8 36
bgneal@8 37 python << EOF
bgneal@8 38 import vim
bgneal@8 39 import os.path
bgneal@8 40 import sys
bgneal@8 41
bgneal@8 42 if sys.version_info[:2] < (2, 5):
bgneal@8 43 raise AssertionError('Vim must be compiled with Python 2.5 or higher; you have ' + sys.version)
bgneal@8 44
bgneal@8 45 # get the directory this script is in: the pyflakes python module should be installed there.
bgneal@8 46 scriptdir = os.path.join(os.path.dirname(vim.eval('expand("<sfile>")')), 'pyflakes')
bgneal@8 47 sys.path.insert(0, scriptdir)
bgneal@8 48
bgneal@8 49 import ast
bgneal@8 50 from pyflakes import checker, messages
bgneal@8 51 from operator import attrgetter
bgneal@8 52 import re
bgneal@8 53
bgneal@8 54 class loc(object):
bgneal@8 55 def __init__(self, lineno, col=None):
bgneal@8 56 self.lineno = lineno
bgneal@8 57 self.col_offset = col
bgneal@8 58
bgneal@8 59 class SyntaxError(messages.Message):
bgneal@8 60 message = 'could not compile: %s'
bgneal@8 61 def __init__(self, filename, lineno, col, message):
bgneal@8 62 messages.Message.__init__(self, filename, loc(lineno, col))
bgneal@8 63 self.message_args = (message,)
bgneal@8 64
bgneal@8 65 class blackhole(object):
bgneal@8 66 write = flush = lambda *a, **k: None
bgneal@8 67
bgneal@8 68 def check(buffer):
bgneal@8 69 filename = buffer.name
bgneal@8 70 contents = buffer[:]
bgneal@8 71
bgneal@8 72 # shebang usually found at the top of the file, followed by source code encoding marker.
bgneal@8 73 # assume everything else that follows is encoded in the encoding.
bgneal@8 74 encoding_found = False
bgneal@8 75 for n, line in enumerate(contents):
bgneal@8 76 if n >= 2:
bgneal@8 77 break
bgneal@8 78 elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line):
bgneal@8 79 contents = ['']*(n+1) + contents[n+1:]
bgneal@8 80 break
bgneal@8 81
bgneal@8 82 contents = '\n'.join(contents) + '\n'
bgneal@8 83
bgneal@8 84 vimenc = vim.eval('&encoding')
bgneal@8 85 if vimenc:
bgneal@8 86 contents = contents.decode(vimenc)
bgneal@8 87
bgneal@8 88 builtins = set(['__file__'])
bgneal@8 89 try:
bgneal@8 90 builtins.update(set(eval(vim.eval('string(g:pyflakes_builtins)'))))
bgneal@8 91 except Exception:
bgneal@8 92 pass
bgneal@8 93
bgneal@8 94 try:
bgneal@8 95 # TODO: use warnings filters instead of ignoring stderr
bgneal@8 96 old_stderr, sys.stderr = sys.stderr, blackhole()
bgneal@8 97 try:
bgneal@8 98 tree = ast.parse(contents, filename or '<unknown>')
bgneal@8 99 finally:
bgneal@8 100 sys.stderr = old_stderr
bgneal@8 101 except:
bgneal@8 102 try:
bgneal@8 103 value = sys.exc_info()[1]
bgneal@8 104 lineno, offset, line = value[1][1:]
bgneal@8 105 except IndexError:
bgneal@8 106 lineno, offset, line = 1, 0, ''
bgneal@8 107 if line and line.endswith("\n"):
bgneal@8 108 line = line[:-1]
bgneal@8 109
bgneal@8 110 return [SyntaxError(filename, lineno, offset, str(value))]
bgneal@8 111 else:
bgneal@8 112 # pyflakes looks to _MAGIC_GLOBALS in checker.py to see which
bgneal@8 113 # UndefinedNames to ignore
bgneal@8 114 old_globals = getattr(checker,' _MAGIC_GLOBALS', [])
bgneal@8 115 checker._MAGIC_GLOBALS = set(old_globals) | builtins
bgneal@8 116
bgneal@8 117 w = checker.Checker(tree, filename)
bgneal@8 118
bgneal@8 119 checker._MAGIC_GLOBALS = old_globals
bgneal@8 120
bgneal@8 121 w.messages.sort(key = attrgetter('lineno'))
bgneal@8 122 return w.messages
bgneal@8 123
bgneal@8 124
bgneal@8 125 def vim_quote(s):
bgneal@8 126 return s.replace("'", "''")
bgneal@8 127 EOF
bgneal@8 128 let b:did_python_init = 1
bgneal@8 129 endif
bgneal@8 130
bgneal@8 131 if !b:did_python_init
bgneal@8 132 finish
bgneal@8 133 endif
bgneal@8 134
bgneal@8 135 au BufLeave <buffer> call s:ClearPyflakes()
bgneal@8 136
bgneal@8 137 au BufEnter <buffer> call s:RunPyflakes()
bgneal@8 138 au InsertLeave <buffer> call s:RunPyflakes()
bgneal@8 139 au InsertEnter <buffer> call s:RunPyflakes()
bgneal@8 140 au BufWritePost <buffer> call s:RunPyflakes()
bgneal@8 141
bgneal@8 142 au CursorHold <buffer> call s:RunPyflakes()
bgneal@8 143 au CursorHoldI <buffer> call s:RunPyflakes()
bgneal@8 144
bgneal@8 145 au CursorHold <buffer> call s:GetPyflakesMessage()
bgneal@8 146 au CursorMoved <buffer> call s:GetPyflakesMessage()
bgneal@8 147
bgneal@8 148 if !exists("*s:PyflakesUpdate")
bgneal@8 149 function s:PyflakesUpdate()
bgneal@8 150 silent call s:RunPyflakes()
bgneal@8 151 call s:GetPyflakesMessage()
bgneal@8 152 endfunction
bgneal@8 153 endif
bgneal@8 154
bgneal@8 155 " Call this function in your .vimrc to update PyFlakes
bgneal@8 156 if !exists(":PyflakesUpdate")
bgneal@8 157 command PyflakesUpdate :call s:PyflakesUpdate()
bgneal@8 158 endif
bgneal@8 159
bgneal@8 160 " Hook common text manipulation commands to update PyFlakes
bgneal@8 161 " TODO: is there a more general "text op" autocommand we could register
bgneal@8 162 " for here?
bgneal@8 163 noremap <buffer><silent> dd dd:PyflakesUpdate<CR>
bgneal@8 164 noremap <buffer><silent> dw dw:PyflakesUpdate<CR>
bgneal@8 165 noremap <buffer><silent> u u:PyflakesUpdate<CR>
bgneal@8 166 noremap <buffer><silent> <C-R> <C-R>:PyflakesUpdate<CR>
bgneal@8 167
bgneal@8 168 " WideMsg() prints [long] message up to (&columns-1) length
bgneal@8 169 " guaranteed without "Press Enter" prompt.
bgneal@8 170 if !exists("*s:WideMsg")
bgneal@8 171 function s:WideMsg(msg)
bgneal@8 172 let x=&ruler | let y=&showcmd
bgneal@8 173 set noruler noshowcmd
bgneal@8 174 redraw
bgneal@8 175 echo strpart(a:msg, 0, &columns-1)
bgneal@8 176 let &ruler=x | let &showcmd=y
bgneal@8 177 endfun
bgneal@8 178 endif
bgneal@8 179
bgneal@8 180 if !exists("*s:GetQuickFixStackCount")
bgneal@8 181 function s:GetQuickFixStackCount()
bgneal@8 182 let l:stack_count = 0
bgneal@8 183 try
bgneal@8 184 silent colder 9
bgneal@8 185 catch /E380:/
bgneal@8 186 endtry
bgneal@8 187
bgneal@8 188 try
bgneal@8 189 for i in range(9)
bgneal@8 190 silent cnewer
bgneal@8 191 let l:stack_count = l:stack_count + 1
bgneal@8 192 endfor
bgneal@8 193 catch /E381:/
bgneal@8 194 return l:stack_count
bgneal@8 195 endtry
bgneal@8 196 endfunction
bgneal@8 197 endif
bgneal@8 198
bgneal@8 199 if !exists("*s:ActivatePyflakesQuickFixWindow")
bgneal@8 200 function s:ActivatePyflakesQuickFixWindow()
bgneal@8 201 try
bgneal@8 202 silent colder 9 " go to the bottom of quickfix stack
bgneal@8 203 catch /E380:/
bgneal@8 204 endtry
bgneal@8 205
bgneal@8 206 if s:pyflakes_qf > 0
bgneal@8 207 try
bgneal@8 208 exe "silent cnewer " . s:pyflakes_qf
bgneal@8 209 catch /E381:/
bgneal@8 210 echoerr "Could not activate Pyflakes Quickfix Window."
bgneal@8 211 endtry
bgneal@8 212 endif
bgneal@8 213 endfunction
bgneal@8 214 endif
bgneal@8 215
bgneal@8 216 if !exists("*s:RunPyflakes")
bgneal@8 217 function s:RunPyflakes()
bgneal@8 218 highlight link PyFlakes SpellBad
bgneal@8 219
bgneal@8 220 if exists("b:cleared")
bgneal@8 221 if b:cleared == 0
bgneal@8 222 silent call s:ClearPyflakes()
bgneal@8 223 let b:cleared = 1
bgneal@8 224 endif
bgneal@8 225 else
bgneal@8 226 let b:cleared = 1
bgneal@8 227 endif
bgneal@8 228
bgneal@8 229 let b:matched = []
bgneal@8 230 let b:matchedlines = {}
bgneal@8 231
bgneal@8 232 let b:qf_list = []
bgneal@8 233 let b:qf_window_count = -1
bgneal@8 234
bgneal@8 235 python << EOF
bgneal@8 236 for w in check(vim.current.buffer):
bgneal@8 237 vim.command('let s:matchDict = {}')
bgneal@8 238 vim.command("let s:matchDict['lineNum'] = " + str(w.lineno))
bgneal@8 239 vim.command("let s:matchDict['message'] = '%s'" % vim_quote(w.message % w.message_args))
bgneal@8 240 vim.command("let b:matchedlines[" + str(w.lineno) + "] = s:matchDict")
bgneal@8 241
bgneal@8 242 vim.command("let l:qf_item = {}")
bgneal@8 243 vim.command("let l:qf_item.bufnr = bufnr('%')")
bgneal@8 244 vim.command("let l:qf_item.filename = expand('%')")
bgneal@8 245 vim.command("let l:qf_item.lnum = %s" % str(w.lineno))
bgneal@8 246 vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args))
bgneal@8 247 vim.command("let l:qf_item.type = 'E'")
bgneal@8 248
bgneal@8 249 if getattr(w, 'col', None) is None or isinstance(w, SyntaxError):
bgneal@8 250 # without column information, just highlight the whole line
bgneal@8 251 # (minus the newline)
bgneal@8 252 vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + str(w.lineno) + r"l\n\@!')")
bgneal@8 253 else:
bgneal@8 254 # with a column number, highlight the first keyword there
bgneal@8 255 vim.command(r"let s:mID = matchadd('PyFlakes', '^\%" + str(w.lineno) + r"l\_.\{-}\zs\k\+\k\@!\%>" + str(w.col) + r"c')")
bgneal@8 256
bgneal@8 257 vim.command("let l:qf_item.vcol = 1")
bgneal@8 258 vim.command("let l:qf_item.col = %s" % str(w.col + 1))
bgneal@8 259
bgneal@8 260 vim.command("call add(b:matched, s:matchDict)")
bgneal@8 261 vim.command("call add(b:qf_list, l:qf_item)")
bgneal@8 262 EOF
bgneal@8 263 if g:pyflakes_use_quickfix == 1
bgneal@8 264 if exists("s:pyflakes_qf")
bgneal@8 265 " if pyflakes quickfix window is already created, reuse it
bgneal@8 266 call s:ActivatePyflakesQuickFixWindow()
bgneal@8 267 call setqflist(b:qf_list, 'r')
bgneal@8 268 else
bgneal@8 269 " one pyflakes quickfix window for all buffer
bgneal@8 270 call setqflist(b:qf_list, '')
bgneal@8 271 let s:pyflakes_qf = s:GetQuickFixStackCount()
bgneal@8 272 endif
bgneal@8 273 endif
bgneal@8 274
bgneal@8 275 let b:cleared = 0
bgneal@8 276 endfunction
bgneal@8 277 end
bgneal@8 278
bgneal@8 279 " keep track of whether or not we are showing a message
bgneal@8 280 let b:showing_message = 0
bgneal@8 281
bgneal@8 282 if !exists("*s:GetPyflakesMessage")
bgneal@8 283 function s:GetPyflakesMessage()
bgneal@8 284 let s:cursorPos = getpos(".")
bgneal@8 285
bgneal@8 286 " Bail if RunPyflakes hasn't been called yet.
bgneal@8 287 if !exists('b:matchedlines')
bgneal@8 288 return
bgneal@8 289 endif
bgneal@8 290
bgneal@8 291 " if there's a message for the line the cursor is currently on, echo
bgneal@8 292 " it to the console
bgneal@8 293 if has_key(b:matchedlines, s:cursorPos[1])
bgneal@8 294 let s:pyflakesMatch = get(b:matchedlines, s:cursorPos[1])
bgneal@8 295 call s:WideMsg(s:pyflakesMatch['message'])
bgneal@8 296 let b:showing_message = 1
bgneal@8 297 return
bgneal@8 298 endif
bgneal@8 299
bgneal@8 300 " otherwise, if we're showing a message, clear it
bgneal@8 301 if b:showing_message == 1
bgneal@8 302 echo
bgneal@8 303 let b:showing_message = 0
bgneal@8 304 endif
bgneal@8 305 endfunction
bgneal@8 306 endif
bgneal@8 307
bgneal@8 308 if !exists('*s:ClearPyflakes')
bgneal@8 309 function s:ClearPyflakes()
bgneal@8 310 let s:matches = getmatches()
bgneal@8 311 for s:matchId in s:matches
bgneal@8 312 if s:matchId['group'] == 'PyFlakes'
bgneal@8 313 call matchdelete(s:matchId['id'])
bgneal@8 314 endif
bgneal@8 315 endfor
bgneal@8 316 let b:matched = []
bgneal@8 317 let b:matchedlines = {}
bgneal@8 318 let b:cleared = 1
bgneal@8 319 endfunction
bgneal@8 320 endif
bgneal@8 321