]> 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:

Fix regression with colon-at-eol detection (#44)
[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:          CC0
7 "
8 " vim-python-pep8-indent - A nicer Python indentation style for vim.
9 " Written in 2004 by David Bustos <bustos@caltech.edu>
10 " Maintained from 2004-2005 by Eric Mc Sween <em@tomcom.de>
11 " Maintained from 2013 by Hynek Schlawack <hs@ox.cx>
12 "
13 " To the extent possible under law, the author(s) have dedicated all copyright
14 " and related and neighboring rights to this software to the public domain
15 " worldwide. This software is distributed without any warranty.
16 " You should have received a copy of the CC0 Public Domain Dedication along
17 " with this software. If not, see
18 " <http://creativecommons.org/publicdomain/zero/1.0/>.
19
20 " Only load this indent file when no other was loaded.
21 if exists("b:did_indent")
22     finish
23 endif
24 let b:did_indent = 1
25
26 setlocal expandtab
27 setlocal nolisp
28 setlocal autoindent
29 setlocal indentexpr=GetPythonPEPIndent(v:lnum)
30 setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except
31 setlocal tabstop=4
32 setlocal softtabstop=4
33 setlocal shiftwidth=4
34
35 let s:maxoff = 50
36 let s:block_rules = {
37             \ '^\s*elif\>': ['if', 'elif'],
38             \ '^\s*else\>': ['if', 'elif', 'for', 'try', 'except'],
39             \ '^\s*except\>': ['try', 'except'],
40             \ '^\s*finally\>': ['try', 'except', 'else']
41             \ }
42 let s:paren_pairs = ['()', '{}', '[]']
43 let s:control_statement = '^\s*\(class\|def\|if\|while\|with\|for\|except\)\>'
44 let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
45
46 " Skip strings and comments. Return 1 for chars to skip.
47 " jedi* refers to syntax definitions from jedi-vim for call signatures, which
48 " are inserted temporarily into the buffer.
49 let s:skip_special_chars = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
50             \ '=~? "\\vstring|comment|jedi\\S"'
51
52 let s:skip_after_opening_paren = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
53             \ '=~? "\\vcomment|jedi\\S"'
54
55 " Also ignore anything concealed.
56 " Wrapper around synconcealed for older Vim (7.3.429, used on Travis CI).
57 function! s:is_concealed(line, col)
58     let concealed = synconcealed(a:line, a:col)
59     return len(concealed) && concealed[0]
60 endfunction
61 if has('conceal')
62     let s:skip_special_chars .= '|| s:is_concealed(line("."), col("."))'
63 endif
64
65
66 let s:skip_search = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
67             \ '=~? "comment"'
68
69 " Use 'shiftwidth()' instead of '&sw'.
70 " (Since Vim patch 7.3.629, 'shiftwidth' can be set to 0 to follow 'tabstop').
71 if exists('*shiftwidth')
72     function! s:sw()
73         return shiftwidth()
74     endfunction
75 else
76     function! s:sw()
77         return &sw
78     endfunction
79 endif
80
81 function! s:pair_sort(x, y)
82     if a:x[0] == a:y[0]
83         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
84     else
85         return a:x[0] > a:y[0] ? 1 : -1
86     endif
87 endfunction
88
89 " Find backwards the closest open parenthesis/bracket/brace.
90 function! s:find_opening_paren(...)
91     " optional arguments: line and column (defaults to 1) to search around
92     if a:0 > 0
93         let view = winsaveview()
94         call cursor(a:1, a:0 > 1 ? a:2 : 1)
95         let ret = s:find_opening_paren()
96         call winrestview(view)
97         return ret
98     endif
99
100     let stopline = max([0, line('.') - s:maxoff])
101
102     " Return if cursor is in a comment.
103     exe 'if' s:skip_search '| return [0, 0] | endif'
104
105     let positions = []
106     for p in s:paren_pairs
107         call add(positions, searchpairpos(
108            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s:skip_special_chars, stopline))
109     endfor
110
111     " Remove empty matches and return the type with the closest match
112     call filter(positions, 'v:val[0]')
113     call sort(positions, 's:pair_sort')
114
115     return get(positions, -1, [0, 0])
116 endfunction
117
118 " Find the start of a multi-line statement
119 function! s:find_start_of_multiline_statement(lnum)
120     let lnum = a:lnum
121     while lnum > 0
122         if getline(lnum - 1) =~ '\\$'
123             let lnum = prevnonblank(lnum - 1)
124         else
125             let [paren_lnum, _] = s:find_opening_paren(lnum)
126             if paren_lnum < 1
127                 return lnum
128             else
129                 let lnum = paren_lnum
130             endif
131         endif
132     endwhile
133 endfunction
134
135 " Find the block starter that matches the current line
136 function! s:find_start_of_block(lnum, types)
137     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
138
139     let lnum = a:lnum
140     let last_indent = indent(lnum) + 1
141     while lnum > 0 && last_indent > 0
142         if indent(lnum) < last_indent
143             if getline(lnum) =~# re
144                 return lnum
145             endif
146             let last_indent = indent(lnum)
147         endif
148         let lnum = prevnonblank(lnum - 1)
149     endwhile
150     return 0
151 endfunction
152
153 " Is "expr" true for every position in "lnum", beginning at "start"?
154 " (optionally up to a:1 / 4th argument)
155 function! s:match_expr_on_line(expr, lnum, start, ...)
156     let text = getline(a:lnum)
157     let end = a:0 ? a:1 : len(text)
158     if a:start > end
159         return 1
160     endif
161     let save_pos = getpos('.')
162     let r = 1
163     for i in range(a:start, end)
164         call cursor(a:lnum, i)
165         if !(eval(a:expr) || text[i-1] =~ '\s')
166             let r = 0
167             break
168         endif
169     endfor
170     call setpos('.', save_pos)
171     return r
172 endfunction
173
174 " Line up with open parenthesis/bracket/brace.
175 function! s:indent_like_opening_paren(lnum)
176     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
177     if paren_lnum <= 0
178         return -2
179     endif
180     let text = getline(paren_lnum)
181     let base = indent(paren_lnum)
182
183     let nothing_after_opening_paren = s:match_expr_on_line(
184                 \ s:skip_after_opening_paren, paren_lnum, paren_col+1)
185     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
186
187     if nothing_after_opening_paren
188         if starts_with_closing_paren
189             let res = base
190         else
191             let res = base + s:sw()
192         endif
193     else
194         " Indent to match position of opening paren.
195         let res = paren_col
196     endif
197
198     " If this line is the continuation of a control statement
199     " indent further to distinguish the continuation line
200     " from the next logical line.
201     if text =~# s:control_statement && res == base + s:sw()
202         return base + s:sw() * 2
203     else
204         return res
205     endif
206 endfunction
207
208 " Match indent of first block of this type.
209 function! s:indent_like_block(lnum)
210     let text = getline(a:lnum)
211
212     for [line_re, blocks] in items(s:block_rules)
213         if text !~# line_re
214             continue
215         endif
216
217         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
218         if lnum > 0
219             return indent(lnum)
220         else
221             return -1
222         endif
223     endfor
224
225     return -2
226 endfunction
227
228 function! s:indent_like_previous_line(lnum)
229     let lnum = prevnonblank(a:lnum - 1)
230
231     " No previous line, keep current indent.
232     if lnum < 1
233       return -1
234     endif
235
236     let text = getline(lnum)
237     let start = s:find_start_of_multiline_statement(lnum)
238     let base = indent(start)
239     let current = indent(a:lnum)
240
241     " Jump to last character in previous line.
242     call cursor(lnum, len(text))
243     let ignore_last_char = eval(s:skip_special_chars)
244
245     " Search for final colon that is not inside something to be ignored.
246     while 1
247         let curpos = getpos(".")[2]
248         if curpos == 1 | break | endif
249         if eval(s:skip_special_chars) || text[curpos-1] =~ '\s'
250             normal! h
251             continue
252         elseif text[curpos-1] == ':'
253             return base + s:sw()
254         endif
255         break
256     endwhile
257
258     if text =~ '\\$' && !ignore_last_char
259         " If this line is the continuation of a control statement
260         " indent further to distinguish the continuation line
261         " from the next logical line.
262         if getline(start) =~# s:control_statement
263             return base + s:sw() * 2
264         endif
265
266         " Nest (other) explicit continuations only one level deeper.
267         return base + s:sw()
268     endif
269
270     " If the previous statement was a stop-execution statement or a pass
271     if getline(start) =~# s:stop_statement
272         " Remove one level of indentation if the user hasn't already dedented
273         if indent(a:lnum) > base - s:sw()
274             return base - s:sw()
275         endif
276         " Otherwise, trust the user
277         return -1
278     endif
279
280     " If this line is dedented and the number of indent spaces is valid
281     " (multiple of the indentation size), trust the user
282     let dedent_size = current - base
283     if dedent_size < 0 && current % s:sw() == 0
284         return -1
285     endif
286
287     " In all other cases, line up with the start of the previous statement.
288     return base
289 endfunction
290
291 " Is the syntax at lnum (and optionally cnum) a python string?
292 function! s:is_python_string(lnum, ...)
293     let line = getline(a:lnum)
294     let linelen = len(line)
295     if linelen < 1
296       let linelen = 1
297     endif
298     let cols = a:0 ? type(a:1) != type([]) ? [a:1] : a:1 : range(1, linelen)
299     for cnum in cols
300         if match(map(synstack(a:lnum, cnum),
301                     \ 'synIDattr(v:val,"name")'), 'python\S*String') == -1
302             return 0
303         end
304     endfor
305     return 1
306 endfunction
307
308 function! GetPythonPEPIndent(lnum)
309
310     " First line has indent 0
311     if a:lnum == 1
312         return 0
313     endif
314
315     " Multilinestrings: continous, docstring or starting.
316     if s:is_python_string(a:lnum)
317         if s:is_python_string(a:lnum-1)
318             " Previous line is (completely) a string.
319             return s:indent_like_previous_line(a:lnum)
320         endif
321
322         if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1
323             " docstring.
324             return s:indent_like_previous_line(a:lnum)
325         endif
326
327         if s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
328             " String started in previous line.
329             return 0
330         endif
331     endif
332
333     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
334     let indent = s:indent_like_opening_paren(a:lnum)
335     if indent >= -1
336         return indent
337     endif
338
339     " Blocks: Match indent of first block of this type.
340     let indent = s:indent_like_block(a:lnum)
341     if indent >= -1
342         return indent
343     endif
344
345     return s:indent_like_previous_line(a:lnum)
346 endfunction