├── .gitignore ├── .gitmodules ├── README.rst ├── ftplugin └── python │ ├── flaker.py │ ├── pyflakes.vim │ └── test_flaker.py ├── makerelease.py └── test-requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | pyflakes-vim.zip 2 | *.pyc 3 | .cache/ 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ftplugin/python/pyflakes"] 2 | path = ftplugin/python/pyflakes 3 | url = https://github.com/PyCQA/pyflakes.git 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | WARNING: DEPRECATED! pyflakes-vim is officially deprecated! 2 | -------------------------------------- 3 | 4 | The features of pyflakes-vim have been merged into other more widely-used and updated tools. 5 | 6 | If you're using vim 8.0 or neovim, try ALE_ and flakes8_ instead. 7 | 8 | .. _ALE: https://github.com/w0rp/ale 9 | .. _flakes8: http://flake8.pycqa.org/en/latest/ 10 | 11 | On older vims you can try Syntastic_. 12 | 13 | .. _Syntastic: https://github.com/scrooloose/syntastic 14 | 15 | pyflakes-vim 16 | ============ 17 | 18 | A Vim plugin for checking Python code on the fly. 19 | 20 | PyFlakes catches common Python errors like mistyping a variable name or 21 | accessing a local before it is bound, and also gives warnings for things like 22 | unused imports. 23 | 24 | pyflakes-vim uses the output from PyFlakes to highlight errors in your code. 25 | To locate errors quickly, use quickfix_ commands like :cc. 26 | 27 | Make sure to check vim.org_ for the latest updates. 28 | 29 | .. _pyflakes.vim: http://www.vim.org/scripts/script.php?script_id=2441 30 | .. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441 31 | .. _quickfix: http://vimdoc.sourceforge.net/htmldoc/quickfix.html#quickfix 32 | 33 | Quick Installation 34 | ------------------ 35 | 36 | 1. Make sure your ``.vimrc`` has:: 37 | 38 | filetype on " enables filetype detection 39 | filetype plugin on " enables filetype specific plugins 40 | 41 | 2. Download the latest release_. 42 | 43 | 3. If you're using pathogen_, unzip the contents of ``pyflakes-vim.zip`` into 44 | its own bundle directory, i.e. into ``~/.vim/bundle/pyflakes-vim/``. 45 | 46 | Otherwise unzip ``pyflakes.vim`` and the ``pyflakes`` directory into 47 | ``~/.vim/ftplugin/python`` (or somewhere similar on your 48 | `runtime path`_ that will be sourced for Python files). 49 | 50 | Test 51 | ---- 52 | 53 | Some tiny tests with:: 54 | 55 | pip install -r test-requirements.txt 56 | py.test ftplugin/python/test_flaker.py 57 | 58 | .. _release: http://www.vim.org/scripts/script.php?script_id=2441 59 | .. _pathogen: http://www.vim.org/scripts/script.php?script_id=2332 60 | .. _runtime path: http://vimdoc.sourceforge.net/htmldoc/options.html#'runtimepath' 61 | 62 | Running from source 63 | ------------------- 64 | 65 | If you're running pyflakes-vim "from source," you'll need the PyFlakes library 66 | on your PYTHONPATH somewhere. (It is included in the vim.org zipfile.) I recommend 67 | getting the github.com/pyflakes PyFlakes_ fork, which retains column number 68 | information, giving more specific error locations. 69 | 70 | .. _vim.org: http://www.vim.org/scripts/script.php?script_id=2441 71 | .. _PyFlakes: http://github.com/pyflakes/pyflakes 72 | 73 | Hacking 74 | ------- 75 | 76 | :: 77 | 78 | git clone --recursive git://github.com/kevinw/pyflakes-vim.git 79 | 80 | or use the PyFlakes_ submodule:: 81 | 82 | git clone git://github.com/kevinw/pyflakes-vim.git 83 | cd pyflakes-vim 84 | git submodule init 85 | git submodule update 86 | 87 | 88 | Options 89 | ------- 90 | 91 | Set this option in your vimrc file to disable quickfix support:: 92 | 93 | let g:pyflakes_use_quickfix = 0 94 | 95 | The value is set to 1 by default. 96 | 97 | Pyflakes can use Python 2 or Python 3 compiled into Vim. If you have both, 98 | you can ask Pyflakes to prefer one or the other, with this in your vimrc:: 99 | 100 | let g:pyflakes_prefer_python_version = 3 101 | 102 | or:: 103 | 104 | let g:pyflakes_prefer_python_version = 2 105 | 106 | Pyflakes will chose Python 2 by default, if you have both. If you prefer a 107 | version that you don't have, Pyflakes will quietly fall back to the version 108 | that you do have. 109 | 110 | TODO 111 | ---- 112 | * signs_ support (show warning and error icons to left of the buffer area) 113 | * configuration variables 114 | * parse or intercept useful output from the warnings module 115 | 116 | .. _signs: http://vimdoc.sourceforge.net/htmldoc/sign.html 117 | 118 | Changelog 119 | --------- 120 | 121 | Please see http://www.vim.org/scripts/script.php?script_id=2441 for a history of 122 | all changes. 123 | 124 | -------------------------------------------------------------------------------- /ftplugin/python/flaker.py: -------------------------------------------------------------------------------- 1 | """ Code for running pyflakes checks in Vim buffer 2 | 3 | The main function is ``check``, which runs the pyflakes check on a buffer. 4 | """ 5 | 6 | import sys 7 | import ast 8 | from operator import attrgetter 9 | import re 10 | 11 | from pyflakes import checker, messages 12 | 13 | try: 14 | # Vim module available within vim 15 | import vim 16 | except ImportError: 17 | # Otherwise, mock it up for tests 18 | from mock import Mock 19 | vim = Mock() 20 | 21 | 22 | class loc(object): 23 | 24 | def __init__(self, lineno, col=None): 25 | self.lineno = lineno 26 | self.col_offset = col 27 | 28 | 29 | class SyntaxError(messages.Message): 30 | 31 | message = 'could not compile: %s' 32 | 33 | def __init__(self, filename, lineno, col, message): 34 | messages.Message.__init__(self, filename, loc(lineno, col)) 35 | self.message_args = (message,) 36 | self.lineno = lineno 37 | 38 | 39 | class blackhole(object): 40 | write = flush = lambda *a, **k: None 41 | 42 | 43 | def check(buffer): 44 | filename = buffer.name 45 | contents = buffer[:] 46 | 47 | # shebang usually found at the top of the file, followed by source code encoding marker. 48 | # assume everything else that follows is encoded in the encoding. 49 | for n, line in enumerate(contents): 50 | if n >= 2: 51 | break 52 | elif re.match(r'#.*coding[:=]\s*([-\w.]+)', line): 53 | contents = ['']*(n+1) + contents[n+1:] 54 | break 55 | 56 | contents = '\n'.join(contents) + '\n' 57 | 58 | vimenc = vim.eval('&encoding') 59 | if vimenc and hasattr(contents, 'decode'): 60 | contents = contents.decode(vimenc) 61 | 62 | builtins = set(['__file__']) 63 | try: 64 | builtins.update(set(eval(vim.eval('string(g:pyflakes_builtins)')))) 65 | except Exception: 66 | pass 67 | 68 | try: 69 | # TODO: use warnings filters instead of ignoring stderr 70 | old_stderr, sys.stderr = sys.stderr, blackhole() 71 | try: 72 | tree = ast.parse(contents, filename or '') 73 | finally: 74 | sys.stderr = old_stderr 75 | except: 76 | exc_value = sys.exc_info()[1] 77 | try: 78 | lineno = exc_value.lineno 79 | offset = exc_value.offset 80 | line = exc_value.text 81 | except IndexError: 82 | lineno, offset, line = 1, 0, '' 83 | if line and line.endswith("\n"): 84 | line = line[:-1] 85 | 86 | return [SyntaxError(filename, lineno, offset, str(exc_value))] 87 | else: 88 | # pyflakes looks to _MAGIC_GLOBALS in checker.py to see which 89 | # UndefinedNames to ignore 90 | old_globals = getattr(checker,' _MAGIC_GLOBALS', []) 91 | checker._MAGIC_GLOBALS = set(old_globals) | builtins 92 | 93 | filename = '(none)' if filename is None else filename 94 | w = checker.Checker(tree, filename) 95 | 96 | checker._MAGIC_GLOBALS = old_globals 97 | 98 | w.messages.sort(key = attrgetter('lineno')) 99 | return w.messages 100 | 101 | 102 | def vim_quote(s): 103 | return s.replace("'", "''") 104 | -------------------------------------------------------------------------------- /ftplugin/python/pyflakes.vim: -------------------------------------------------------------------------------- 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 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') && !has('python3') 28 | echoerr "Error: Requires Vim compiled with +python or +python3" 29 | finish 30 | endif 31 | " Default to Python 2 32 | if has('python') 33 | let py_cmd_ver = 'python' 34 | else 35 | let py_cmd_ver = 'python3' 36 | endif 37 | if exists('g:pyflakes_prefer_python_version') 38 | if g:pyflakes_prefer_python_version == 3 && has('python3') 39 | let py_cmd_ver = 'python3' 40 | elseif g:pyflakes_prefer_python_version == 2 && has('python') 41 | let py_cmd_ver = 'python' 42 | endif 43 | endif 44 | if py_cmd_ver == 'python' 45 | command! -nargs=1 Python python 46 | else 47 | command! -nargs=1 Python python3 48 | endif 49 | 50 | if !exists('g:pyflakes_use_quickfix') 51 | let g:pyflakes_use_quickfix = 1 52 | endif 53 | 54 | Python << EOF 55 | import vim 56 | from os.path import dirname, join as pjoin 57 | import sys 58 | 59 | if sys.version_info[:2] < (2, 5): 60 | raise AssertionError('Vim must be compiled with Python 2.5 or higher; you have ' + sys.version) 61 | 62 | # get the directory this script is in: the pyflakes python module should be installed there. 63 | script_dir = dirname(vim.eval('expand("")')) 64 | flakes_dir = pjoin(script_dir, 'pyflakes') 65 | for path in (script_dir, flakes_dir): 66 | if path not in sys.path: 67 | sys.path.insert(0, path) 68 | 69 | from flaker import check, vim_quote, SyntaxError 70 | EOF 71 | let b:did_python_init = 1 72 | endif 73 | 74 | if !b:did_python_init 75 | finish 76 | endif 77 | 78 | au BufLeave call s:ClearPyflakes() 79 | 80 | au BufEnter call s:RunPyflakes() 81 | au InsertLeave call s:RunPyflakes() 82 | au InsertEnter call s:RunPyflakes() 83 | au BufWritePost call s:RunPyflakes() 84 | 85 | au CursorHold call s:RunPyflakes() 86 | au CursorHoldI call s:RunPyflakes() 87 | 88 | au CursorHold call s:GetPyflakesMessage() 89 | au CursorMoved call s:GetPyflakesMessage() 90 | 91 | if !exists("*s:PyflakesUpdate") 92 | function s:PyflakesUpdate() 93 | silent call s:RunPyflakes() 94 | call s:GetPyflakesMessage() 95 | endfunction 96 | endif 97 | 98 | " Call this function in your .vimrc to update PyFlakes 99 | if !exists(":PyflakesUpdate") 100 | command PyflakesUpdate :call s:PyflakesUpdate() 101 | endif 102 | 103 | " Hook common text manipulation commands to update PyFlakes 104 | " TODO: is there a more general "text op" autocommand we could register 105 | " for here? 106 | noremap dd dd:PyflakesUpdate 107 | noremap dw dw:PyflakesUpdate 108 | noremap u u:PyflakesUpdate 109 | noremap :PyflakesUpdate 110 | 111 | " WideMsg() prints [long] message up to (&columns-1) length 112 | " guaranteed without "Press Enter" prompt. 113 | if !exists("*s:WideMsg") 114 | function s:WideMsg(msg) 115 | let x=&ruler | let y=&showcmd 116 | set noruler noshowcmd 117 | redraw 118 | echo strpart(a:msg, 0, &columns-1) 119 | let &ruler=x | let &showcmd=y 120 | endfun 121 | endif 122 | 123 | if !exists("*s:GetQuickFixStackCount") 124 | function s:GetQuickFixStackCount() 125 | let l:stack_count = 0 126 | try 127 | silent colder 9 128 | catch /E380:/ 129 | endtry 130 | 131 | try 132 | for i in range(9) 133 | silent cnewer 134 | let l:stack_count = l:stack_count + 1 135 | endfor 136 | catch /E381:/ 137 | return l:stack_count 138 | endtry 139 | endfunction 140 | endif 141 | 142 | if !exists("*s:ActivatePyflakesQuickFixWindow") 143 | function s:ActivatePyflakesQuickFixWindow() 144 | try 145 | silent colder 9 " go to the bottom of quickfix stack 146 | catch /E380:/ 147 | catch /E788:/ 148 | endtry 149 | 150 | if s:pyflakes_qf > 0 151 | try 152 | exe "silent cnewer " . s:pyflakes_qf 153 | catch /E381:/ 154 | echoerr "Could not activate Pyflakes Quickfix Window." 155 | endtry 156 | endif 157 | endfunction 158 | endif 159 | 160 | if !exists("*s:RunPyflakes") 161 | function s:RunPyflakes() 162 | highlight link PyFlakes SpellBad 163 | 164 | if exists("b:cleared") 165 | if b:cleared == 0 166 | silent call s:ClearPyflakes() 167 | let b:cleared = 1 168 | endif 169 | else 170 | let b:cleared = 1 171 | endif 172 | 173 | let b:matched = [] 174 | let b:matchedlines = {} 175 | 176 | let b:qf_list = [] 177 | let b:qf_window_count = -1 178 | 179 | Python << EOF 180 | for w in check(vim.current.buffer): 181 | if not isinstance(w.lineno, int): 182 | lineno = str(w.lineno.lineno) 183 | else: 184 | lineno = str(w.lineno) 185 | 186 | vim.command('let s:matchDict = {}') 187 | vim.command("let s:matchDict['lineNum'] = " + lineno) 188 | vim.command("let s:matchDict['message'] = '%s'" % vim_quote(w.message % w.message_args)) 189 | vim.command("let b:matchedlines[" + lineno + "] = s:matchDict") 190 | 191 | vim.command("let l:qf_item = {}") 192 | vim.command("let l:qf_item.bufnr = bufnr('%')") 193 | vim.command("let l:qf_item.filename = expand('%')") 194 | vim.command("let l:qf_item.lnum = %s" % lineno) 195 | vim.command("let l:qf_item.text = '%s'" % vim_quote(w.message % w.message_args)) 196 | vim.command("let l:qf_item.type = 'E'") 197 | 198 | if getattr(w, 'col', None) is None or isinstance(w, SyntaxError): 199 | # without column information, just highlight the whole line 200 | # (minus the newline) 201 | vim.command(r"let s:mID = matchadd('PyFlakes', '\%" + lineno + r"l\n\@!')") 202 | else: 203 | # with a column number, highlight the first keyword there 204 | vim.command(r"let s:mID = matchadd('PyFlakes', '^\%" + lineno + r"l\_.\{-}\zs\k\+\k\@!\%>" + str(w.col) + r"c')") 205 | 206 | vim.command("let l:qf_item.vcol = 1") 207 | vim.command("let l:qf_item.col = %s" % str(w.col + 1)) 208 | 209 | vim.command("call add(b:matched, s:matchDict)") 210 | vim.command("call add(b:qf_list, l:qf_item)") 211 | EOF 212 | if g:pyflakes_use_quickfix == 1 213 | if exists("s:pyflakes_qf") 214 | " if pyflakes quickfix window is already created, reuse it 215 | call s:ActivatePyflakesQuickFixWindow() 216 | call setqflist(b:qf_list, 'r') 217 | else 218 | " one pyflakes quickfix window for all buffer 219 | call setqflist(b:qf_list, ' ') 220 | let s:pyflakes_qf = s:GetQuickFixStackCount() 221 | endif 222 | endif 223 | 224 | let b:cleared = 0 225 | endfunction 226 | end 227 | 228 | " keep track of whether or not we are showing a message 229 | let b:showing_message = 0 230 | 231 | if !exists("*s:GetPyflakesMessage") 232 | function s:GetPyflakesMessage() 233 | let s:cursorPos = getpos(".") 234 | 235 | " Bail if RunPyflakes hasn't been called yet. 236 | if !exists('b:matchedlines') 237 | return 238 | endif 239 | 240 | " if there's a message for the line the cursor is currently on, echo 241 | " it to the console 242 | if has_key(b:matchedlines, s:cursorPos[1]) 243 | let s:pyflakesMatch = get(b:matchedlines, s:cursorPos[1]) 244 | call s:WideMsg(s:pyflakesMatch['message']) 245 | let b:showing_message = 1 246 | return 247 | endif 248 | 249 | " otherwise, if we're showing a message, clear it 250 | if b:showing_message == 1 251 | echo 252 | let b:showing_message = 0 253 | endif 254 | endfunction 255 | endif 256 | 257 | if !exists('*s:ClearPyflakes') 258 | function s:ClearPyflakes() 259 | let s:matches = getmatches() 260 | for s:matchId in s:matches 261 | if s:matchId['group'] == 'PyFlakes' 262 | call matchdelete(s:matchId['id']) 263 | endif 264 | endfor 265 | let b:matched = [] 266 | let b:matchedlines = {} 267 | let b:cleared = 1 268 | endfunction 269 | endif 270 | 271 | -------------------------------------------------------------------------------- /ftplugin/python/test_flaker.py: -------------------------------------------------------------------------------- 1 | """ Test flaker module 2 | 3 | The flaker module contains Python functions used by the Vim code. 4 | 5 | Run with:: 6 | 7 | pip install nose pytest 8 | py.test test_flaker.py 9 | """ 10 | 11 | from os.path import dirname, join as pjoin 12 | import sys 13 | 14 | # Put our copy of pyflakes on the PATH 15 | sys.path.insert(0, pjoin(dirname(__file__), 'pyflakes')) 16 | 17 | import flaker 18 | 19 | 20 | class Buffer(object): 21 | 22 | def __init__(self, **kwargs): 23 | self.__dict__.update(kwargs) 24 | 25 | def __getitem__(self, slicer): 26 | return self.contents[slicer] 27 | 28 | 29 | def test_check(): 30 | # Try a syntax error 31 | buffer = Buffer(name='foo', contents = ["First line\n"]) 32 | ret = flaker.check(buffer) 33 | assert len(ret) == 1 34 | assert isinstance(ret[0], flaker.SyntaxError) 35 | # Code OK, empty list returned 36 | buffer = Buffer(name='foo', contents = ["a = 1\n"]) 37 | assert flaker.check(buffer) == [] 38 | -------------------------------------------------------------------------------- /makerelease.py: -------------------------------------------------------------------------------- 1 | ''' 2 | creates the pyflakes-vim.zip bundle 3 | ''' 4 | 5 | import os.path 6 | import zipfile 7 | 8 | BUNDLE_FILENAME = 'pyflakes-vim.zip' 9 | 10 | def get_directory(): 11 | return os.path.join( 12 | os.path.abspath(os.path.dirname(__file__))) 13 | 14 | def include_dir(d): 15 | return not any((d.startswith('.git'), 16 | d.startswith('_trial_temp'), 17 | d.startswith('.svn'))) 18 | 19 | def include_file(f): 20 | return not any((f.endswith('.pyc'), 21 | f.endswith('.zip'), 22 | f.startswith('.git'), 23 | f == __file__, 24 | f == '.DS_Store', 25 | f.endswith('.diff'))) 26 | 27 | def make_dist(): 28 | z = zipfile.ZipFile(BUNDLE_FILENAME, 'w', zipfile.ZIP_DEFLATED) 29 | base = get_directory() 30 | count = 0 31 | for root, dirs, files in os.walk(base): 32 | dirs[:] = filter(include_dir, dirs) 33 | 34 | for f in files: 35 | name = os.path.join(root, f) 36 | if include_file(f): 37 | zipname = os.path.relpath(name, base) 38 | print zipname 39 | count += 1 40 | z.writestr(zipname, open(name, 'rb').read()) 41 | z.close() 42 | 43 | print 44 | print '%s is %d files, %d bytes' % (BUNDLE_FILENAME, count, os.path.getsize(BUNDLE_FILENAME)) 45 | 46 | if __name__ == '__main__': 47 | make_dist() 48 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | mock 3 | --------------------------------------------------------------------------------