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

Improve handling of multiline strings (#50)
[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 if !exists('g:python_pep8_indent_multiline_string')
36     let g:python_pep8_indent_multiline_string = 0
37 endif
38
39 let s:maxoff = 50
40 let s:block_rules = {
41             \ '^\s*elif\>': ['if', 'elif'],
42             \ '^\s*except\>': ['try', 'except'],
43             \ '^\s*finally\>': ['try', 'except', 'else']
44             \ }
45 let s:block_rules_multiple = {
46             \ '^\s*else\>': ['if', 'elif', 'for', 'try', 'except'],
47             \ }
48 let s:paren_pairs = ['()', '{}', '[]']
49 if &ft == 'pyrex' || &ft == 'cython'
50     let b:control_statement = '\v^\s*(class|def|if|while|with|for|except|cdef|cpdef)>'
51 else
52     let b:control_statement = '\v^\s*(class|def|if|while|with|for|except)>'
53 endif
54 let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
55
56 " Skip strings and comments. Return 1 for chars to skip.
57 " jedi* refers to syntax definitions from jedi-vim for call signatures, which
58 " are inserted temporarily into the buffer.
59 let s:skip_special_chars = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
60             \ '=~? "\\vstring|comment|jedi\\S"'
61
62 let s:skip_after_opening_paren = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
63             \ '=~? "\\vcomment|jedi\\S"'
64
65 " Also ignore anything concealed.
66 " Wrapper around synconcealed for older Vim (7.3.429, used on Travis CI).
67 function! s:is_concealed(line, col)
68     let concealed = synconcealed(a:line, a:col)
69     return len(concealed) && concealed[0]
70 endfunction
71 if has('conceal')
72     let s:skip_special_chars .= '|| s:is_concealed(line("."), col("."))'
73 endif
74
75
76 let s:skip_search = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
77             \ '=~? "comment"'
78
79 " Use 'shiftwidth()' instead of '&sw'.
80 " (Since Vim patch 7.3.629, 'shiftwidth' can be set to 0 to follow 'tabstop').
81 if exists('*shiftwidth')
82     function! s:sw()
83         return shiftwidth()
84     endfunction
85 else
86     function! s:sw()
87         return &sw
88     endfunction
89 endif
90
91 function! s:pair_sort(x, y)
92     if a:x[0] == a:y[0]
93         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
94     else
95         return a:x[0] > a:y[0] ? 1 : -1
96     endif
97 endfunction
98
99 " Find backwards the closest open parenthesis/bracket/brace.
100 function! s:find_opening_paren(...)
101     " optional arguments: line and column (defaults to 1) to search around
102     if a:0 > 0
103         let view = winsaveview()
104         call cursor(a:1, a:0 > 1 ? a:2 : 1)
105         let ret = s:find_opening_paren()
106         call winrestview(view)
107         return ret
108     endif
109
110     let stopline = max([0, line('.') - s:maxoff])
111
112     " Return if cursor is in a comment.
113     exe 'if' s:skip_search '| return [0, 0] | endif'
114
115     let positions = []
116     for p in s:paren_pairs
117         call add(positions, searchpairpos(
118            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s:skip_special_chars, stopline))
119     endfor
120
121     " Remove empty matches and return the type with the closest match
122     call filter(positions, 'v:val[0]')
123     call sort(positions, 's:pair_sort')
124
125     return get(positions, -1, [0, 0])
126 endfunction
127
128 " Find the start of a multi-line statement
129 function! s:find_start_of_multiline_statement(lnum)
130     let lnum = a:lnum
131     while lnum > 0
132         if getline(lnum - 1) =~ '\\$'
133             let lnum = prevnonblank(lnum - 1)
134         else
135             let [paren_lnum, _] = s:find_opening_paren(lnum)
136             if paren_lnum < 1
137                 return lnum
138             else
139                 let lnum = paren_lnum
140             endif
141         endif
142     endwhile
143 endfunction
144
145 " Find possible indent(s) of the block starter that matches the current line.
146 function! s:find_start_of_block(lnum, types, multiple)
147     let r = []
148     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
149     let lnum = a:lnum
150     let last_indent = indent(lnum) + 1
151     while lnum > 0 && last_indent > 0
152         let indent = indent(lnum)
153         if indent < last_indent
154             if getline(lnum) =~# re
155                 if !a:multiple
156                     return [indent]
157                 endif
158                 if !len(r) || index(r, indent) == -1
159                     let r += [indent]
160                 endif
161             endif
162             let last_indent = indent(lnum)
163         endif
164         let lnum = prevnonblank(lnum - 1)
165     endwhile
166     return r
167 endfunction
168
169 " Is "expr" true for every position in "lnum", beginning at "start"?
170 " (optionally up to a:1 / 4th argument)
171 function! s:match_expr_on_line(expr, lnum, start, ...)
172     let text = getline(a:lnum)
173     let end = a:0 ? a:1 : len(text)
174     if a:start > end
175         return 1
176     endif
177     let save_pos = getpos('.')
178     let r = 1
179     for i in range(a:start, end)
180         call cursor(a:lnum, i)
181         if !(eval(a:expr) || text[i-1] =~ '\s')
182             let r = 0
183             break
184         endif
185     endfor
186     call setpos('.', save_pos)
187     return r
188 endfunction
189
190 " Line up with open parenthesis/bracket/brace.
191 function! s:indent_like_opening_paren(lnum)
192     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
193     if paren_lnum <= 0
194         return -2
195     endif
196     let text = getline(paren_lnum)
197     let base = indent(paren_lnum)
198
199     let nothing_after_opening_paren = s:match_expr_on_line(
200                 \ s:skip_after_opening_paren, paren_lnum, paren_col+1)
201     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
202
203     if nothing_after_opening_paren
204         if starts_with_closing_paren
205             let res = base
206         else
207             let res = base + s:sw()
208         endif
209     else
210         " Indent to match position of opening paren.
211         let res = paren_col
212     endif
213
214     " If this line is the continuation of a control statement
215     " indent further to distinguish the continuation line
216     " from the next logical line.
217     if text =~# b:control_statement && res == base + s:sw()
218         return base + s:sw() * 2
219     else
220         return res
221     endif
222 endfunction
223
224 " Match indent of first block of this type.
225 function! s:indent_like_block(lnum)
226     let text = getline(a:lnum)
227     for [multiple, block_rules] in [
228                 \ [0, s:block_rules],
229                 \ [1, s:block_rules_multiple]]
230         for [line_re, blocks] in items(block_rules)
231             if text !~# line_re
232                 continue
233             endif
234
235             let indents = s:find_start_of_block(a:lnum - 1, blocks, multiple)
236             if !len(indents)
237                 return -1
238             endif
239             if len(indents) == 1
240                 return indents[0]
241             endif
242             " Multiple valid indents, e.g. for 'else' with both try and if.
243             let indent = indent(a:lnum)
244             for possible_indent in indents
245                 if indent == possible_indent
246                     return indent
247                 endif
248             endfor
249             return -2
250         endfor
251     endfor
252     return -2
253 endfunction
254
255 function! s:indent_like_previous_line(lnum)
256     let lnum = prevnonblank(a:lnum - 1)
257
258     " No previous line, keep current indent.
259     if lnum < 1
260       return -1
261     endif
262
263     let text = getline(lnum)
264     let start = s:find_start_of_multiline_statement(lnum)
265     let base = indent(start)
266     let current = indent(a:lnum)
267
268     " Jump to last character in previous line.
269     call cursor(lnum, len(text))
270     let ignore_last_char = eval(s:skip_special_chars)
271
272     " Search for final colon that is not inside something to be ignored.
273     while 1
274         let curpos = getpos(".")[2]
275         if curpos == 1 | break | endif
276         if eval(s:skip_special_chars) || text[curpos-1] =~ '\s'
277             normal! h
278             continue
279         elseif text[curpos-1] == ':'
280             return base + s:sw()
281         endif
282         break
283     endwhile
284
285     if text =~ '\\$' && !ignore_last_char
286         " If this line is the continuation of a control statement
287         " indent further to distinguish the continuation line
288         " from the next logical line.
289         if getline(start) =~# b:control_statement
290             return base + s:sw() * 2
291         endif
292
293         " Nest (other) explicit continuations only one level deeper.
294         return base + s:sw()
295     endif
296
297     " If the previous statement was a stop-execution statement or a pass
298     if getline(start) =~# s:stop_statement
299         " Remove one level of indentation if the user hasn't already dedented
300         if indent(a:lnum) > base - s:sw()
301             return base - s:sw()
302         endif
303         " Otherwise, trust the user
304         return -1
305     endif
306
307     if s:is_dedented_already(current, base)
308         return -1
309     endif
310
311     " In all other cases, line up with the start of the previous statement.
312     return base
313 endfunction
314
315 " If this line is dedented and the number of indent spaces is valid
316 " (multiple of the indentation size), trust the user.
317 function! s:is_dedented_already(current, base)
318     let dedent_size = a:current - a:base
319     return (dedent_size < 0 && a:current % s:sw() == 0) ? 1 : 0
320 endfunction
321
322 " Is the syntax at lnum (and optionally cnum) a python string?
323 function! s:is_python_string(lnum, ...)
324     let line = getline(a:lnum)
325     let linelen = len(line)
326     if linelen < 1
327       let linelen = 1
328     endif
329     let cols = a:0 ? type(a:1) != type([]) ? [a:1] : a:1 : range(1, linelen)
330     for cnum in cols
331         if match(map(synstack(a:lnum, cnum),
332                     \ 'synIDattr(v:val,"name")'), 'python\S*String') == -1
333             return 0
334         end
335     endfor
336     return 1
337 endfunction
338
339 function! GetPythonPEPIndent(lnum)
340     " First line has indent 0
341     if a:lnum == 1
342         return 0
343     endif
344
345     " Multilinestrings: continous, docstring or starting.
346     if s:is_python_string(a:lnum, 1)
347                 \ && s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
348         " Keep existing indent.
349         if match(getline(a:lnum), '\v^\s*\S') != -1
350             return -1
351         endif
352
353         if s:is_python_string(a:lnum-1)
354             " Previous line is (completely) a string.
355             return indent(a:lnum-1)
356         endif
357
358         if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1
359             " docstring.
360             return indent(a:lnum-1)
361         endif
362
363         let indent_multi = get(b:, 'python_pep8_indent_multiline_string',
364                     \ get(g:, 'python_pep8_indent_multiline_string', 0))
365         if indent_multi != -2
366             return indent_multi
367         endif
368
369         if match(getline(a:lnum-1), '\v%("""|'''''')$') != -1
370             " Opening multiline string, started in previous line.
371             return indent(a:lnum-1) + s:sw()
372         endif
373         return s:indent_like_opening_paren(a:lnum)
374     endif
375
376     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
377     let indent = s:indent_like_opening_paren(a:lnum)
378     if indent >= -1
379         return indent
380     endif
381
382     " Blocks: Match indent of first block of this type.
383     let indent = s:indent_like_block(a:lnum)
384     if indent >= -1
385         return indent
386     endif
387
388     return s:indent_like_previous_line(a:lnum)
389 endfunction