├── README └── plugin └── sort_python_imports.vim /README: -------------------------------------------------------------------------------- 1 | This is a mirror of http://www.vim.org/scripts/script.php?script_id=2225 2 | 3 | Keep your imports tidy! 4 | This script allows you to sort python imports easlily. 5 | 6 | Use :PyFixImports, command to fix imports in the beginning of currently edited file. 7 | You can also use visual mode to select range of lines and then use to sort imports in those lines. 8 | 9 | Needs vim compiled with python support! 10 | -------------------------------------------------------------------------------- /plugin/sort_python_imports.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | " sort_python_imports.vim - sorts python imports alphabetically 3 | " Author: Krzysiek Goj 4 | " Version: 1.1 5 | " Last Change: 2008-05-02 6 | " URL: http://tbw13.blogspot.com 7 | " Requires: Python and Vim compiled with +python option 8 | " Licence: This script is released under the Vim License. 9 | " Installation: Put into plugin directory 10 | " Usage: 11 | " Use :PyFixImports, command to fix imports in the beginning of 12 | " currently edited file. 13 | " 14 | " You can also use visual mode to select range of lines and then 15 | " use to sort imports in those lines. 16 | " 17 | " Changelog: 18 | " 1.2 - bugfix: from foo import (bar, baz) 19 | " Now requires only python 2.3 (patch from Konrad Delong) 20 | " 1.1 - bugfix: from foo.bar import baz 21 | " 1.0 - initial upload 22 | " 23 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 24 | if !has('python') 25 | s:ErrMsg( "Error: Required vim compiled with +python" ) 26 | finish 27 | endif 28 | 29 | python << EOF 30 | import vim 31 | import re 32 | try: 33 | set 34 | except NameError: 35 | from sets import Set as set 36 | 37 | __global_import_re = re.compile('(?P\s*)import\s(?P[^#]*)(?P(#.*)?)') 38 | __from_import_re = re.compile('(?P\s*)from\s+(?P\S*)\s+import\s(?P[^#]*)(?P(#.*)?)') 39 | __boring_re = re.compile('\s*(#.*)?$') 40 | __endl_re = re.compile('\n?$') 41 | 42 | def sorted(l, key=lambda x: x): 43 | l = map(lambda x: (key(x), x), list(l)) 44 | l.sort() 45 | l = map(lambda pair: pair[1], l) 46 | return l 47 | 48 | 49 | def is_global_import(line): 50 | """checks if line is a 'import ...'""" 51 | return __global_import_re.match(line) is not None 52 | 53 | 54 | def is_from_import(line): 55 | """checks if line is a 'from ... import ...'""" 56 | return __from_import_re.match(line) is not None 57 | 58 | 59 | def is_boring(line): 60 | """checks if line is boring (empty or comment)""" 61 | return __boring_re.match(line) is not None 62 | 63 | 64 | def has_leading_ws(line): 65 | if not line: return False 66 | return line[0].isspace() 67 | 68 | 69 | def is_unindented_import(line): 70 | """checks if line is an unindented import""" 71 | return not has_leading_ws(line) and (is_global_import(line) or is_from_import(line)) 72 | 73 | 74 | def make_template(indent, comment): 75 | """makes template out of indentation and comment""" 76 | if comment: 77 | comment = ' ' + comment 78 | return indent + '%s' + comment 79 | 80 | 81 | def _split_import(regex, line): 82 | """splits import line (using regex) intro triple: module (may be None), set_of_items, line_template""" 83 | imports = regex.match(line) 84 | if not imports: 85 | raise ValueError, 'this line isn\'t an import' 86 | indent, items, comment = map(lambda name: imports.groupdict()[name], 'indent items comment'.split()) 87 | module = imports.groupdict().get('module') 88 | if items.startswith('(') and items.endswith(')'): 89 | items = items[1:-1] 90 | return module, set(map(lambda item: item.strip(), items.split(','))), make_template(indent, comment) 91 | 92 | 93 | def split_globals(line): 94 | """splits 'import ...' line intro pair: set_of_items, line_template""" 95 | return _split_import(__global_import_re, line)[1:] # ignore module 96 | 97 | 98 | def split_from(line): 99 | """splits 'from ... import ...' line intro triple: module_name, set_of_items, line_template""" 100 | return _split_import(__from_import_re, line) 101 | 102 | 103 | def get_lines(lines): 104 | """returns numbers -- [from, to) -- of first lines with unindented imports""" 105 | start, end = 0, 0 106 | start_found = False 107 | for num, line in enumerate(lines): 108 | if is_unindented_import(line): 109 | if not start_found: 110 | start = num 111 | start_found = True 112 | end = num + 1 113 | elif end and not is_boring(line): 114 | break 115 | return start, end 116 | 117 | 118 | def sort_and_join(items): 119 | """returns alphabetically (case insensitive) sorted and comma-joined collection""" 120 | return ', '.join(sorted(items, key=lambda x: x.upper())) 121 | 122 | 123 | def make_global_import(items, template='%s'): 124 | return template % 'import %s' % sort_and_join(items) 125 | 126 | 127 | def make_from_import(module, items, template='%s'): 128 | return template % 'from %s import %s' % (module, sort_and_join(items)) 129 | 130 | 131 | def repair_any(line): 132 | """repairs any import line (doesn't affect boring lines)""" 133 | suffix = __endl_re.search(line).group() 134 | if is_global_import(line): 135 | return make_global_import(*split_globals(line)) + suffix 136 | elif is_from_import(line): 137 | return make_from_import(*split_from(line)) + suffix 138 | elif is_boring(line): 139 | return line 140 | else: 141 | raise ValueError, '"%s" isn\'t an import line' % line.rstrip() 142 | 143 | 144 | def fixed(lines): 145 | """returns fixed lines""" 146 | def rank(line): 147 | if is_global_import(line): return 2 148 | if is_from_import(line): return 1 149 | if is_boring(line): return 0 150 | lines = filter(lambda line: line.strip(), lines) 151 | lines = map(lambda line: repair_any(line), lines) 152 | return sorted(lines, key=lambda x: (-rank(x), x.upper())) 153 | 154 | 155 | def fix_safely(lines): 156 | """fixes all unindented imports in the beginning of list of lines""" 157 | start, end = get_lines(lines) 158 | lines[start:end] = fixed(lines[start:end]) 159 | EOF 160 | 161 | command! PyFixImports python fix_safely(vim.current.buffer) 162 | autocmd FileType python,scons vnoremap :python vim.current.range[:]=fixed(vim.current.range) 163 | --------------------------------------------------------------------------------