]> git.madduck.net Git - etc/vim.git/blob - indent/python.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

unindent 'else' after 'for' and 'try/except'
[etc/vim.git] / indent / python.vim
1 " PEP8 compatible Python indent file
2 " Language:         Python
3 " Maintainer:       Hynek Schlawack <hs@ox.cx>
4 " Prev Maintainer:  Eric Mc Sween <em@tomcom.de> (address invalid)
5 " Original Author:  David Bustos <bustos@caltech.edu> (address invalid)
6 " License:          Public Domain
7
8 " Only load this indent file when no other was loaded.
9 if exists("b:did_indent")
10     finish
11 endif
12 let b:did_indent = 1
13
14 setlocal expandtab
15 setlocal nolisp
16 setlocal autoindent
17 setlocal indentexpr=GetPythonPEPIndent(v:lnum)
18 setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except
19
20 let s:maxoff = 50
21 let s:block_rules = {
22   \ '^\s*elif\>': ['if', 'elif'],
23   \ '^\s*else\>': ['if', 'elif', 'for', 'try', 'except'],
24   \ '^\s*except\>': ['try', 'except'],
25   \ '^\s*finally\>': ['try', 'except', 'else']
26   \ }
27 let s:paren_pairs = ['()', '{}', '[]']
28 let s:control_statement = '^\s*\(if\|while\|with\|for\|except\)\>'
29 let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
30 if v:version >= 704 || (v:version == 703 && has('patch1037'))
31   let s:string_literal = '".\{-}\\\@1<!"\|''.\{-}\\\@1<!'''
32 else
33   let s:string_literal = '".\{-}\\\@<!"\|''.\{-}\\\@<!'''
34 endif
35
36 " compatibility with vim patch 7.3.629: 'sw' can be set to -1 to follow 'ts'
37 if exists('*shiftwidth')
38     function! s:sw()
39         return shiftwidth()
40     endfunction
41 else
42     function! s:sw()
43         return &sw
44     endfunction
45 endif
46
47 function! s:pair_sort(x, y)
48     if a:x[0] == a:y[0]
49         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
50     else
51         return a:x[0] > a:y[0] ? 1 : -1
52     endif
53 endfunction
54
55 " Find backwards the closest open parenthesis/bracket/brace.
56 function! s:find_opening_paren(...)
57     " optional arguments: line and column (defaults to 1) to search around
58     if a:0 > 0
59         let view = winsaveview()
60         call cursor(a:1, a:0 > 1 ? a:2 : 1)
61         let ret = s:find_opening_paren()
62         call winrestview(view)
63         return ret
64     endif
65
66     let stopline = max([0, line('.') - s:maxoff])
67
68     " Skip strings and comments
69     let skip = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
70              \ '=~? "string\\|comment"'
71
72     " Return if cursor is in a comment or string
73     exe 'if' skip '| return [0, 0] | endif'
74
75     let positions = []
76     for p in s:paren_pairs
77         call add(positions, searchpairpos('\V'.p[0], '', '\V'.p[1], 'bnW', skip, stopline))
78     endfor
79
80     " Remove empty matches and return the type with the closest match
81     call filter(positions, 'v:val[0]')
82     call sort(positions, 's:pair_sort')
83
84     return get(positions, -1, [0, 0])
85 endfunction
86
87 " Find the start of a multi-line statement
88 function! s:find_start_of_multiline_statement(lnum)
89     let lnum = a:lnum
90     while lnum > 0
91         if getline(lnum - 1) =~ '\\$'
92             let lnum = prevnonblank(lnum - 1)
93         else
94             let [paren_lnum, _] = s:find_opening_paren(lnum)
95             if paren_lnum < 1
96                 return lnum
97             else
98                 let lnum = paren_lnum
99             endif
100         endif
101     endwhile
102 endfunction
103
104 " Find the block starter that matches the current line
105 function! s:find_start_of_block(lnum, types)
106     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
107
108     let lnum = a:lnum
109     let last_indent = indent(lnum) + 1
110     while lnum > 0 && last_indent > 0
111         if indent(lnum) < last_indent
112             if getline(lnum) =~# re
113                 return lnum
114             endif
115             let last_indent = indent(lnum)
116         endif
117         let lnum = prevnonblank(lnum - 1)
118     endwhile
119     return 0
120 endfunction
121
122 " Line up with open parenthesis/bracket/brace.
123 function! s:indent_like_opening_paren(lnum)
124     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
125     if paren_lnum <= 0
126         return -2
127     endif
128     let text = getline(paren_lnum)
129     let base = indent(paren_lnum)
130
131     let nothing_after_opening_paren = text =~ '\%'.(paren_col + 1).'c\s*$'
132     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
133
134     if nothing_after_opening_paren
135         if starts_with_closing_paren
136             let res = base
137         else
138             let res = base + s:sw()
139         endif
140     else
141         " Indent to match position of opening paren.
142         let res = paren_col
143     endif
144
145     " If this line is the continuation of a control statement
146     " indent further to distinguish the continuation line
147     " from the next logical line.
148     if text =~# s:control_statement && res == base + s:sw()
149         return base + s:sw() * 2
150     else
151         return res
152     endif
153 endfunction
154
155 " Match indent of first block of this type.
156 function! s:indent_like_block(lnum)
157     let text = getline(a:lnum)
158
159     for [line_re, blocks] in items(s:block_rules)
160         if text !~# line_re
161             continue
162         endif
163
164         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
165         if lnum > 0
166             return indent(lnum)
167         else
168             return -1
169         endif
170     endfor
171
172     return -2
173 endfunction
174
175 function! s:indent_like_previous_line(lnum)
176     let lnum = prevnonblank(a:lnum - 1)
177     let text = getline(lnum)
178     let start = s:find_start_of_multiline_statement(lnum)
179     let base = indent(start)
180
181     " Remove string literals.
182     let text = substitute(text, s:string_literal, '', 'g')
183
184     " If the previous line ended with a colon and is not a comment, indent
185     " relative to statement start.
186     if text =~ '^[^#]*:\s*\(#.*\)\?$'
187         return base + s:sw()
188     endif
189
190
191     if text =~ '\\$'
192         " If this line is the continuation of a control statement
193         " indent further to distinguish the continuation line
194         " from the next logical line.
195         if getline(start) =~# s:control_statement
196             return base + s:sw() * 2
197         endif
198
199         " Nest (other) explicit continuations only one level deeper.
200         return base + s:sw()
201     endif
202
203     " If the previous statement was a stop-execution statement or a pass
204     if getline(start) =~# s:stop_statement
205         " Remove one level of indentation if the user hasn't already dedented
206         if indent(a:lnum) > base - s:sw()
207             return base - s:sw()
208         endif
209         " Otherwise, trust the user
210         return -1
211     endif
212
213     " In all other cases, line up with the start of the previous statement.
214     return base
215 endfunction
216
217 function! GetPythonPEPIndent(lnum)
218
219     " First line has indent 0
220     if a:lnum == 1
221         return 0
222     endif
223
224     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
225     let indent = s:indent_like_opening_paren(a:lnum)
226     if indent >= -1
227         return indent
228     endif
229
230     " Blocks: Match indent of first block of this type.
231     let indent = s:indent_like_block(a:lnum)
232     if indent >= -1
233         return indent
234     endif
235
236     return s:indent_like_previous_line(a:lnum)
237 endfunction