1
0

pyflakes.vim 10 KB

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