├── .gitignore ├── .gitmodules ├── README.mkd └── ftplugin └── python ├── builtins.py ├── flake8.py └── flake8.vim /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.sw? 3 | *.py[cod] 4 | *.zip 5 | .coverage 6 | .tox 7 | .mr.developer.cfg 8 | .project 9 | .pydevproject 10 | .ropeproject 11 | .idea 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ftplugin/python/mccabe"] 2 | path = ftplugin/python/mccabe 3 | url = https://github.com/flintwork/mccabe.git 4 | [submodule "ftplugin/python/autopep8"] 5 | path = ftplugin/python/autopep8 6 | url = https://github.com/hhatto/autopep8.git 7 | [submodule "ftplugin/python/pycodestyle"] 8 | path = ftplugin/python/pycodestyle 9 | url = https://github.com/PyCQA/pycodestyle.git 10 | [submodule "ftplugin/python/frosted"] 11 | path = ftplugin/python/frosted 12 | url = https://github.com/timothycrosley/frosted.git 13 | [submodule "ftplugin/python/pies"] 14 | path = ftplugin/python/pies 15 | url = https://github.com/timothycrosley/pies.git 16 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # Flake8-vim: frosted, pep8 and mcabe checker for Vim 2 | 3 | Flake8-vim is a Vim plugin that checks python sources for errors. It's based on 4 | _pylint-mode_ plugin by Kirill Klenov. 5 | 6 | ## Installation 7 | 8 | ### Manual installation 9 | 10 | Clone plugin repository using Git: 11 | 12 | git clone --recursive https://github.com/andviro/flake8-vim.git 13 | 14 | Copy `ftplugin` folder into your Vim home directory. 15 | 16 | ### Using plugin manager (recommended) 17 | 18 | Install [Vundle](http://github.com/gmarik/vundle), then run bundle install 19 | command: 20 | 21 | BundleInstall andviro/flake8-vim 22 | 23 | 24 | Alternatively install [Neobundle](https://github.com/Shougo/neobundle.vim) and 25 | load plugin using command: 26 | 27 | NeoBundleInstall andviro/flake8-vim 28 | 29 | ## Configuration 30 | 31 | By default python source code is checked with [frosted](https://github.com/timothycrosley/frosted), 32 | [pep8](https://github.com/jcrocholl/pep8) and 33 | [mccabe code complexity utility](https://github.com/flintwork/mccabe). 34 | The following options can be configured through global variables in .vimrc: 35 | 36 | Auto-check file for errors on write: 37 | 38 | let g:PyFlakeOnWrite = 1 39 | 40 | List of checkers used: 41 | 42 | let g:PyFlakeCheckers = 'pep8,mccabe,frosted' 43 | 44 | Default maximum complexity for mccabe: 45 | 46 | let g:PyFlakeDefaultComplexity=10 47 | 48 | List of disabled pep8 warnings and errors: 49 | 50 | let g:PyFlakeDisabledMessages = 'E501' 51 | 52 | Default aggressiveness for autopep8: 53 | 54 | let g:PyFlakeAggressive = 0 55 | 56 | Default height of quickfix window: 57 | 58 | let g:PyFlakeCWindow = 6 59 | 60 | Whether to place signs or not: 61 | 62 | let g:PyFlakeSigns = 1 63 | 64 | When usign signs, this is the first id that will be used to identify the 65 | signs. Tweak this if you are using other plugins that also use the sign 66 | gutter 67 | 68 | let g:PyFlakeSignStart = 1 69 | 70 | Maximum line length for PyFlakeAuto command 71 | 72 | let g:PyFlakeMaxLineLength = 100 73 | 74 | Visual-mode key command for PyFlakeAuto 75 | 76 | let g:PyFlakeRangeCommand = 'Q' 77 | 78 | Force python 3 interface: 79 | 80 | let g:PyFlakeForcePyVersion = 3 81 | 82 | ## Commands 83 | 84 | Disable/enable automatic checking of current file 85 | 86 | :PyFlakeToggle 87 | 88 | Run checks for current file 89 | 90 | :PyFlake 91 | 92 | Auto-fix pep8 errors for current file 93 | 94 | :PyFlakeAuto 95 | 96 | `PyFlakeAuto` command by default works with the whole file, but also accepts 97 | line ranges. Select some lines in Python code, then use `:'<,'>PyFlakeAuto` to 98 | re-format and fix pep8 errors for them. For every Python buffer Visual-mode 99 | shortcut is added to quickly fix selection ('Q' by default). Set global 100 | variable `g:PyFlakeRangeCommand` in .vimrc to change the key sequence. To 101 | disable visual mode mapping, set this variable to empty string. 102 | 103 | ## Author 104 | 105 | Andrew Rodionoff (@andviro) 106 | 107 | ## Contributors 108 | 109 | - OGURA_Daiki (@hachibeeDI) 110 | - Pål (@maedox) 111 | - Charles (@char101) 112 | - Ronaldo Maia (@romaia) 113 | - @sgur 114 | -------------------------------------------------------------------------------- /ftplugin/python/builtins.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from __builtin__ import * 4 | -------------------------------------------------------------------------------- /ftplugin/python/flake8.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | SUBMODULES = ['mccabe', 'pycodestyle', 'autopep8', 'frosted', 'pies'] 4 | 5 | import sys 6 | import os 7 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | for module in SUBMODULES: 9 | module_dir = os.path.join(BASE_DIR, module) 10 | if module_dir not in sys.path: 11 | sys.path.insert(0, module_dir) 12 | 13 | 14 | from mccabe import McCabeChecker 15 | from frosted.api import checker, _noqa_lines 16 | from frosted import messages 17 | import _ast 18 | import pycodestyle as p8 19 | from autopep8 import fix_file as pep8_fix, fix_lines as pep8_fix_lines, DEFAULT_INDENT_SIZE, continued_indentation as autopep8_c_i 20 | from contextlib import contextmanager 21 | from operator import attrgetter 22 | 23 | 24 | @contextmanager 25 | def patch_pep8(): 26 | if autopep8_c_i in p8._checks['logical_line']: 27 | del p8._checks['logical_line'][autopep8_c_i] 28 | p8.register_check(p8.continued_indentation) 29 | try: 30 | yield 31 | finally: 32 | del p8._checks['logical_line'][p8.continued_indentation] 33 | p8.register_check(autopep8_c_i) 34 | 35 | 36 | class Pep8Options(): 37 | verbose = 0 38 | diff = False 39 | in_place = True 40 | recursive = False 41 | pep8_passes = 100 42 | max_line_length = 79 43 | line_range = None 44 | indent_size = DEFAULT_INDENT_SIZE 45 | ignore = '' 46 | select = '' 47 | aggressive = 0 48 | experimental = False 49 | hang_closing = False 50 | 51 | 52 | class MccabeOptions(): 53 | complexity = 10 54 | 55 | 56 | flake_code_mapping = { 57 | 'W402': (messages.UnusedImport,), 58 | 'W403': (messages.ImportShadowedByLoopVar,), 59 | 'W404': (messages.ImportStarUsed,), 60 | 'W405': (messages.LateFutureImport,), 61 | 'W801': (messages.RedefinedWhileUnused, 62 | messages.RedefinedInListComp,), 63 | 'W802': (messages.UndefinedName,), 64 | 'W803': (messages.UndefinedExport,), 65 | 'W804': (messages.UndefinedLocal, 66 | messages.UnusedVariable,), 67 | 'W805': (messages.DuplicateArgument,), 68 | 'W806': (messages.Redefined,), 69 | } 70 | 71 | flake_class_mapping = dict( 72 | (k, c) for (c, v) in flake_code_mapping.items() for k in v) 73 | 74 | 75 | def fix_file(filename): 76 | pep8_fix(filename, Pep8Options) 77 | 78 | 79 | def fix_lines(lines): 80 | return pep8_fix_lines(lines, Pep8Options) 81 | 82 | 83 | def run_checkers(filename, checkers, ignore): 84 | 85 | result = [] 86 | 87 | for c in checkers: 88 | 89 | checker_fun = globals().get(c) 90 | if not checker_fun: 91 | continue 92 | try: 93 | for e in checker_fun(filename): 94 | e.update( 95 | col=e.get('col') or 0, 96 | text="{0} [{1}]".format( 97 | e.get('text', '').strip( 98 | ).replace("'", "\"").splitlines()[0], 99 | c), 100 | filename=os.path.normpath(filename), 101 | type=e.get('type') or 'W', 102 | bufnr=0, 103 | ) 104 | result.append(e) 105 | except Exception: 106 | pass 107 | 108 | result = filter(lambda e: _ignore_error(e, ignore), result) 109 | return sorted(result, key=lambda x: x['lnum']) 110 | 111 | 112 | def mccabe(filename): 113 | with open(filename, "rU") as mod: 114 | code = mod.read() 115 | try: 116 | tree = compile(code, filename, "exec", _ast.PyCF_ONLY_AST) 117 | except Exception: 118 | return [] 119 | 120 | complx = [] 121 | McCabeChecker.max_complexity = MccabeOptions.complexity 122 | for lineno, offset, text, check in McCabeChecker(tree, filename).run(): 123 | complx.append(dict(col=offset, lnum=lineno, text=text)) 124 | 125 | return complx 126 | 127 | 128 | def pep8(filename): 129 | with patch_pep8(): 130 | style = PEP8 or _init_pep8() 131 | return style.input_file(filename) 132 | 133 | 134 | def frosted(filename): 135 | codeString = open(filename, 'U').read() + '\n' 136 | errors = [] 137 | try: 138 | tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) 139 | except SyntaxError as e: 140 | errors.append(dict( 141 | lnum=e.lineno or 0, 142 | col=e.offset or 0, 143 | text=getattr(e, 'msg', None) or str(e), 144 | type='E' 145 | )) 146 | else: 147 | w = checker.Checker(tree, filename, ignore_lines=_noqa_lines(codeString)) 148 | for w in sorted(w.messages, key=attrgetter('lineno')): 149 | errors.append(dict( 150 | lnum=w.lineno, 151 | col=0, 152 | text=u'{0} {1}'.format( 153 | flake_class_mapping.get(w.__class__, ''), 154 | w.message.split(':', 2)[-1].strip()), 155 | type='E' 156 | )) 157 | return errors 158 | 159 | 160 | PEP8 = None 161 | 162 | 163 | def _init_pep8(): 164 | global PEP8 165 | 166 | class _PEP8Report(p8.BaseReport): 167 | 168 | def init_file(self, filename, lines, expected, line_offset): 169 | super(_PEP8Report, self).init_file( 170 | filename, lines, expected, line_offset) 171 | self.errors = [] 172 | 173 | def error(self, line_number, offset, text, check): 174 | code = super(_PEP8Report, self).error( 175 | line_number, offset, text, check) 176 | 177 | self.errors.append(dict( 178 | text=text, 179 | type=code, 180 | col=offset + 1, 181 | lnum=line_number, 182 | )) 183 | 184 | def get_file_results(self): 185 | return self.errors 186 | 187 | PEP8 = p8.StyleGuide(reporter=_PEP8Report, 188 | ignore=Pep8Options.ignore, 189 | select=Pep8Options.select, 190 | max_line_length=Pep8Options.max_line_length, 191 | hang_closing=Pep8Options.hang_closing) 192 | return PEP8 193 | 194 | 195 | def _ignore_error(e, ignore): 196 | for i in ignore: 197 | if e['text'].startswith(i): 198 | return False 199 | return True 200 | 201 | 202 | if __name__ == '__main__': 203 | for r in run_checkers(__file__, checkers=['mccabe', 'frosted', 'pep8'], ignore=[]): 204 | print(r) 205 | -------------------------------------------------------------------------------- /ftplugin/python/flake8.vim: -------------------------------------------------------------------------------- 1 | " Check python support 2 | if !has('python') && !has('python3') 3 | echo "Error: PyFlake.vim required vim compiled with +python or +python3." 4 | finish 5 | endif 6 | 7 | let s:pycmd = has('python') && (!exists('g:PyFlakeForcePyVersion') || g:PyFlakeForcePyVersion != 3) ? ':py' : ':py3' 8 | " This is the last sign used, so that we can remove them individually. 9 | let s:last_sign = 1 10 | 11 | if !exists('g:PyFlakeRangeCommand') 12 | let g:PyFlakeRangeCommand = 'Q' 13 | endif 14 | 15 | if !exists('b:PyFlake_initialized') 16 | let b:PyFlake_initialized = 1 17 | 18 | au BufWritePost call flake8#on_write() 19 | au CursorHold call flake8#get_message() 20 | au CursorMoved call flake8#get_message() 21 | 22 | " Commands 23 | command! -buffer PyFlakeToggle :let b:PyFlake_disabled = exists('b:PyFlake_disabled') ? b:PyFlake_disabled ? 0 : 1 : 1 24 | command! -buffer PyFlake :call flake8#run() 25 | command! -buffer -range=% PyFlakeAuto :call flake8#auto(,) 26 | 27 | " Keymaps 28 | if g:PyFlakeRangeCommand != '' 29 | exec 'vnoremap ' . g:PyFlakeRangeCommand . ' :PyFlakeAuto' 30 | endif 31 | 32 | let b:showing_message = 0 33 | 34 | " Signs definition 35 | sign define W text=WW texthl=Todo 36 | sign define C text=CC texthl=Comment 37 | sign define R text=RR texthl=Visual 38 | sign define E text=EE texthl=Error 39 | endif 40 | 41 | "Check for flake8 plugin is loaded 42 | if exists("g:PyFlakeDirectory") 43 | finish 44 | endif 45 | 46 | if !exists('g:PyFlakeOnWrite') 47 | let g:PyFlakeOnWrite = 1 48 | endif 49 | 50 | " Init variables 51 | let g:PyFlakeDirectory = expand(':p:h') 52 | 53 | if !exists('g:PyFlakeCheckers') 54 | let g:PyFlakeCheckers = 'pep8,mccabe,frosted' 55 | endif 56 | if !exists('g:PyFlakeDefaultComplexity') 57 | let g:PyFlakeDefaultComplexity=10 58 | endif 59 | if !exists('g:PyFlakeDisabledMessages') 60 | let g:PyFlakeDisabledMessages = 'E501' 61 | endif 62 | if !exists('g:PyFlakeCWindow') 63 | let g:PyFlakeCWindow = 6 64 | endif 65 | if !exists('g:PyFlakeSigns') 66 | let g:PyFlakeSigns = 1 67 | endif 68 | if !exists('g:PyFlakeSignStart') 69 | " What is the first sign id that we should use. This is usefull when 70 | " there are multiple plugins intalled that use the sign gutter 71 | let g:PyFlakeSignStart = 1 72 | endif 73 | if !exists('g:PyFlakeAggressive') 74 | let g:PyFlakeAggressive = 0 75 | endif 76 | if !exists('g:PyFlakeMaxLineLength') 77 | let g:PyFlakeMaxLineLength = 100 78 | endif 79 | if !exists('g:PyFlakeHangClosing') 80 | let g:PyFlakeHangClosing = 0 81 | endif 82 | if !exists('g:PyFlakeLineIndentGlitch') 83 | let g:PyFlakeLineIndentGlitch = 1 84 | endif 85 | 86 | function! flake8#on_write() 87 | if !g:PyFlakeOnWrite || exists("b:PyFlake_disabled") && b:PyFlake_disabled 88 | return 89 | endif 90 | call flake8#check() 91 | endfunction 92 | 93 | function! flake8#run() 94 | if &modifiable && &modified 95 | write 96 | endif 97 | call flake8#check() 98 | endfun 99 | 100 | function! flake8#check() 101 | exec s:pycmd ' flake8_check()' 102 | let s:matchDict = {} 103 | for err in g:qf_list 104 | let s:matchDict[err.lnum] = err.text 105 | endfor 106 | call setqflist(g:qf_list, 'r') 107 | 108 | " Place signs 109 | if g:PyFlakeSigns 110 | call flake8#place_signs() 111 | endif 112 | 113 | " Open cwindow 114 | if g:PyFlakeCWindow 115 | cclose 116 | if len(g:qf_list) 117 | let l:winsize = len(g:qf_list) > g:PyFlakeCWindow ? g:PyFlakeCWindow : len(g:qf_list) 118 | exec 'botright ' . l:winsize . 'cwindow' 119 | endif 120 | endif 121 | endfunction 122 | 123 | function! flake8#auto(l1, l2) "{{{ 124 | cclose 125 | while s:last_sign >= g:PyFlakeSignStart 126 | execute "sign unplace" s:last_sign 127 | let s:last_sign -= 1 128 | endwhile 129 | let s:matchDict = {} 130 | call setqflist([]) 131 | 132 | exec s:pycmd . ' << EOF' 133 | start, end = int(vim.eval('a:l1'))-1, int(vim.eval('a:l2')) 134 | enc = vim.eval('&enc') 135 | lines = fix_lines(list(unicode(x, enc, 'replace') for x in vim.current.buffer[start:end])).splitlines() 136 | res = [ln.encode(enc, 'replace') for ln in lines] 137 | vim.current.buffer[start:end] = res 138 | EOF 139 | endfunction "}}} 140 | 141 | function! flake8#place_signs() 142 | " first remove previous inserted signs. Removing them by id instead of 143 | " unplace *, so that this can live in peace with other plugins. 144 | while s:last_sign >= g:PyFlakeSignStart 145 | execute "sign unplace" s:last_sign 146 | let s:last_sign -= 1 147 | endwhile 148 | 149 | "now we place one sign for every quickfix line 150 | let s:last_sign = g:PyFlakeSignStart - 1 151 | for item in getqflist() 152 | let s:last_sign += 1 153 | execute(':sign place '.s:last_sign.' name='.l:item.type.' line='.l:item.lnum.' buffer='.l:item.bufnr) 154 | endfor 155 | endfunction 156 | 157 | " keep track of whether or not we are showing a message 158 | " WideMsg() prints [long] message up to (&columns-1) length 159 | " guaranteed without "Press Enter" prompt. 160 | function! flake8#wide_msg(msg) 161 | let x=&ruler | let y=&showcmd 162 | set noruler noshowcmd 163 | redraw 164 | echo strpart(a:msg, 0, &columns-1) 165 | let &ruler=x | let &showcmd=y 166 | endfun 167 | 168 | 169 | function! flake8#get_message() 170 | let s:cursorPos = getpos(".") 171 | 172 | " Bail if RunPyflakes hasn't been called yet. 173 | if !exists('s:matchDict') 174 | return 175 | endif 176 | 177 | " if there's a message for the line the cursor is currently on, echo 178 | " it to the console 179 | if has_key(s:matchDict, s:cursorPos[1]) 180 | let s:pyflakesMatch = get(s:matchDict, s:cursorPos[1]) 181 | call flake8#wide_msg(s:pyflakesMatch) 182 | let b:showing_message = 1 183 | return 184 | endif 185 | 186 | " otherwise, if we're showing a message, clear it 187 | if b:showing_message == 1 188 | echo 189 | let b:showing_message = 0 190 | endif 191 | endfunction 192 | 193 | function! flake8#force_py_version(version) 194 | let ver = a:version == 3 ? '3' : '' 195 | let s:pycmd = ':py' . ver 196 | if !py{ver}eval('"flake8_check" in dir()') 197 | call s:init_py_modules() 198 | endif 199 | endfunction "}}} 200 | 201 | function! s:init_py_modules() 202 | exec s:pycmd . ' << EOF' 203 | 204 | import sys 205 | import json 206 | import vim 207 | 208 | if sys.version_info >= (3,): 209 | def unicode(str, *args): 210 | return str 211 | 212 | sys.path.insert(0, vim.eval("g:PyFlakeDirectory")) 213 | from flake8 import run_checkers, fix_lines, Pep8Options, MccabeOptions 214 | 215 | def flake8_check(): 216 | checkers=vim.eval('g:PyFlakeCheckers').split(',') 217 | ignore=vim.eval('g:PyFlakeDisabledMessages').split(',') 218 | MccabeOptions.complexity=int(vim.eval('g:PyFlakeDefaultComplexity')) 219 | Pep8Options.max_line_length=int(vim.eval('g:PyFlakeMaxLineLength')) 220 | Pep8Options.aggressive=int(vim.eval('g:PyFlakeAggressive')) 221 | Pep8Options.hang_closing=int(vim.eval('g:PyFlakeHangClosing')) 222 | filename=vim.current.buffer.name 223 | parse_result(run_checkers(filename, checkers, ignore)) 224 | 225 | def parse_result(result): 226 | vim.command('let g:qf_list = {0}'.format(json.dumps(result, ensure_ascii=False))) 227 | 228 | EOF 229 | endfunction 230 | 231 | call s:init_py_modules() 232 | --------------------------------------------------------------------------------