]> 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 lookup in previous lines
[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 search(':', 'bcW', lnum)
247         let curpos = getpos(".")[2]
248         if curpos == 1 | break | endif
249         if eval(s:skip_special_chars)
250             normal! h
251             continue
252         endif
253         if !s:match_expr_on_line(s:skip_special_chars, lnum, curpos)
254             return base + s:sw()
255         endif
256         normal! h
257     endwhile
258
259     if text =~ '\\$' && !ignore_last_char
260         " If this line is the continuation of a control statement
261         " indent further to distinguish the continuation line
262         " from the next logical line.
263         if getline(start) =~# s:control_statement
264             return base + s:sw() * 2
265         endif
266
267         " Nest (other) explicit continuations only one level deeper.
268         return base + s:sw()
269     endif
270
271     " If the previous statement was a stop-execution statement or a pass
272     if getline(start) =~# s:stop_statement
273         " Remove one level of indentation if the user hasn't already dedented
274         if indent(a:lnum) > base - s:sw()
275             return base - s:sw()
276         endif
277         " Otherwise, trust the user
278         return -1
279     endif
280
281     " If this line is dedented and the number of indent spaces is valid
282     " (multiple of the indentation size), trust the user
283     let dedent_size = current - base
284     if dedent_size < 0 && current % s:sw() == 0
285         return -1
286     endif
287
288     " In all other cases, line up with the start of the previous statement.
289     return base
290 endfunction
291
292 " Is the syntax at lnum (and optionally cnum) a python string?
293 function! s:is_python_string(lnum, ...)
294     let line = getline(a:lnum)
295     let linelen = len(line)
296     if linelen < 1
297       let linelen = 1
298     endif
299     let cols = a:0 ? type(a:1) != type([]) ? [a:1] : a:1 : range(1, linelen)
300     for cnum in cols
301         if match(map(synstack(a:lnum, cnum),
302                     \ 'synIDattr(v:val,"name")'), 'python\S*String') == -1
303             return 0
304         end
305     endfor
306     return 1
307 endfunction
308
309 function! GetPythonPEPIndent(lnum)
310
311     " First line has indent 0
312     if a:lnum == 1
313         return 0
314     endif
315
316     " Multilinestrings: continous, docstring or starting.
317     if s:is_python_string(a:lnum)
318         if s:is_python_string(a:lnum-1)
319             " Previous line is (completely) a string.
320             return s:indent_like_previous_line(a:lnum)
321         endif
322
323         if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1
324             " docstring.
325             return s:indent_like_previous_line(a:lnum)
326         endif
327
328         if s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
329             " String started in previous line.
330             return 0
331         endif
332     endif
333
334     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
335     let indent = s:indent_like_opening_paren(a:lnum)
336     if indent >= -1
337         return indent
338     endif
339
340     " Blocks: Match indent of first block of this type.
341     let indent = s:indent_like_block(a:lnum)
342     if indent >= -1
343         return indent
344     endif
345
346     return s:indent_like_previous_line(a:lnum)
347 endfunction