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