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