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

007c7e160ea0810803a8086de4f289fe497c0b00
[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
47 let s:skip_special_chars = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
48             \ '=~? "string\\|comment"'
49
50 let s:skip_search = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
51             \ '=~? "comment"'
52
53 " Use 'shiftwidth()' instead of '&sw'.
54 " (Since Vim patch 7.3.629, 'shiftwidth' can be set to 0 to follow 'tabstop').
55 if exists('*shiftwidth')
56     function! s:sw()
57         return shiftwidth()
58     endfunction
59 else
60     function! s:sw()
61         return &sw
62     endfunction
63 endif
64
65 function! s:pair_sort(x, y)
66     if a:x[0] == a:y[0]
67         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
68     else
69         return a:x[0] > a:y[0] ? 1 : -1
70     endif
71 endfunction
72
73 " Find backwards the closest open parenthesis/bracket/brace.
74 function! s:find_opening_paren(...)
75     " optional arguments: line and column (defaults to 1) to search around
76     if a:0 > 0
77         let view = winsaveview()
78         call cursor(a:1, a:0 > 1 ? a:2 : 1)
79         let ret = s:find_opening_paren()
80         call winrestview(view)
81         return ret
82     endif
83
84     let stopline = max([0, line('.') - s:maxoff])
85
86     " Return if cursor is in a comment.
87     exe 'if' s:skip_search '| return [0, 0] | endif'
88
89     let positions = []
90     for p in s:paren_pairs
91         call add(positions, searchpairpos(
92            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s:skip_special_chars, stopline))
93     endfor
94
95     " Remove empty matches and return the type with the closest match
96     call filter(positions, 'v:val[0]')
97     call sort(positions, 's:pair_sort')
98
99     return get(positions, -1, [0, 0])
100 endfunction
101
102 " Find the start of a multi-line statement
103 function! s:find_start_of_multiline_statement(lnum)
104     let lnum = a:lnum
105     while lnum > 0
106         if getline(lnum - 1) =~ '\\$'
107             let lnum = prevnonblank(lnum - 1)
108         else
109             let [paren_lnum, _] = s:find_opening_paren(lnum)
110             if paren_lnum < 1
111                 return lnum
112             else
113                 let lnum = paren_lnum
114             endif
115         endif
116     endwhile
117 endfunction
118
119 " Find the block starter that matches the current line
120 function! s:find_start_of_block(lnum, types)
121     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
122
123     let lnum = a:lnum
124     let last_indent = indent(lnum) + 1
125     while lnum > 0 && last_indent > 0
126         if indent(lnum) < last_indent
127             if getline(lnum) =~# re
128                 return lnum
129             endif
130             let last_indent = indent(lnum)
131         endif
132         let lnum = prevnonblank(lnum - 1)
133     endwhile
134     return 0
135 endfunction
136
137 " Line up with open parenthesis/bracket/brace.
138 function! s:indent_like_opening_paren(lnum)
139     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
140     if paren_lnum <= 0
141         return -2
142     endif
143     let text = getline(paren_lnum)
144     let base = indent(paren_lnum)
145
146     let nothing_after_opening_paren = text =~ '\%'.(paren_col + 1).'c\s*$'
147     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
148
149     if nothing_after_opening_paren
150         if starts_with_closing_paren
151             let res = base
152         else
153             let res = base + s:sw()
154         endif
155     else
156         " Indent to match position of opening paren.
157         let res = paren_col
158     endif
159
160     " If this line is the continuation of a control statement
161     " indent further to distinguish the continuation line
162     " from the next logical line.
163     if text =~# s:control_statement && res == base + s:sw()
164         return base + s:sw() * 2
165     else
166         return res
167     endif
168 endfunction
169
170 " Match indent of first block of this type.
171 function! s:indent_like_block(lnum)
172     let text = getline(a:lnum)
173
174     for [line_re, blocks] in items(s:block_rules)
175         if text !~# line_re
176             continue
177         endif
178
179         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
180         if lnum > 0
181             return indent(lnum)
182         else
183             return -1
184         endif
185     endfor
186
187     return -2
188 endfunction
189
190 function! s:indent_like_previous_line(lnum)
191     let lnum = prevnonblank(a:lnum - 1)
192
193     " No previous line, keep current indent.
194     if lnum < 1
195       return -1
196     endif
197
198     let text = getline(lnum)
199     let start = s:find_start_of_multiline_statement(lnum)
200     let base = indent(start)
201     let current = indent(a:lnum)
202
203     " Jump to last character in previous line.
204     call cursor(lnum, len(text))
205     let ignore_last_char = eval(s:skip_special_chars)
206
207     " Search for final colon that is not inside a string or comment.
208     while search(':\s*\%(#.*\)\?$', 'bcW', lnum)
209       if eval(s:skip_special_chars)
210         normal! h
211       else
212         return base + s:sw()
213       endif
214     endwhile
215
216     if text =~ '\\$' && !ignore_last_char
217         " If this line is the continuation of a control statement
218         " indent further to distinguish the continuation line
219         " from the next logical line.
220         if getline(start) =~# s:control_statement
221             return base + s:sw() * 2
222         endif
223
224         " Nest (other) explicit continuations only one level deeper.
225         return base + s:sw()
226     endif
227
228     " If the previous statement was a stop-execution statement or a pass
229     if getline(start) =~# s:stop_statement
230         " Remove one level of indentation if the user hasn't already dedented
231         if indent(a:lnum) > base - s:sw()
232             return base - s:sw()
233         endif
234         " Otherwise, trust the user
235         return -1
236     endif
237
238     " If this line is dedented and the number of indent spaces is valid
239     " (multiple of the indentation size), trust the user
240     let dedent_size = current - base
241     if dedent_size < 0 && current % s:sw() == 0
242         return -1
243     endif
244
245     " In all other cases, line up with the start of the previous statement.
246     return base
247 endfunction
248
249 " Is the syntax at lnum (and optionally cnum) a python string?
250 function! s:is_python_string(lnum, ...)
251     let line = getline(a:lnum)
252     let linelen = len(line)
253     if linelen < 1
254       let linelen = 1
255     endif
256     let cols = a:0 ? type(a:1) != type([]) ? [a:1] : a:1 : range(1, linelen)
257     for cnum in cols
258         if match(map(synstack(a:lnum, cnum),
259                     \ 'synIDattr(v:val,"name")'), 'python\S*String') == -1
260             return 0
261         end
262     endfor
263     return 1
264 endfunction
265
266 function! GetPythonPEPIndent(lnum)
267
268     " First line has indent 0
269     if a:lnum == 1
270         return 0
271     endif
272
273     " Multilinestrings: continous, docstring or starting.
274     if s:is_python_string(a:lnum)
275         if s:is_python_string(a:lnum-1)
276             " Previous line is (completely) a string.
277             return s:indent_like_previous_line(a:lnum)
278         endif
279
280         if match(getline(a:lnum-1), '^\s*\%("""\|''''''\)') != -1
281             " docstring.
282             return s:indent_like_previous_line(a:lnum)
283         endif
284
285         if s:is_python_string(a:lnum-1, len(getline(a:lnum-1)))
286             " String started in previous line.
287             return 0
288         endif
289     endif
290
291     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
292     let indent = s:indent_like_opening_paren(a:lnum)
293     if indent >= -1
294         return indent
295     endif
296
297     " Blocks: Match indent of first block of this type.
298     let indent = s:indent_like_block(a:lnum)
299     if indent >= -1
300         return indent
301     endif
302
303     return s:indent_like_previous_line(a:lnum)
304 endfunction