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