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

set shiftwidth,tabstop and softtabstop to 4
[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 = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
36            \ '=~? "string\\|comment"'
37
38 " compatibility with vim patch 7.3.629: 'sw' can be set to -1 to follow 'ts'
39 if exists('*shiftwidth')
40     function! s:sw()
41         return shiftwidth()
42     endfunction
43 else
44     function! s:sw()
45         return &sw
46     endfunction
47 endif
48
49 function! s:pair_sort(x, y)
50     if a:x[0] == a:y[0]
51         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
52     else
53         return a:x[0] > a:y[0] ? 1 : -1
54     endif
55 endfunction
56
57 " Find backwards the closest open parenthesis/bracket/brace.
58 function! s:find_opening_paren(...)
59     " optional arguments: line and column (defaults to 1) to search around
60     if a:0 > 0
61         let view = winsaveview()
62         call cursor(a:1, a:0 > 1 ? a:2 : 1)
63         let ret = s:find_opening_paren()
64         call winrestview(view)
65         return ret
66     endif
67
68     let stopline = max([0, line('.') - s:maxoff])
69
70     " Return if cursor is in a comment or string
71     exe 'if' s:skip '| return [0, 0] | endif'
72
73     let positions = []
74     for p in s:paren_pairs
75         call add(positions, searchpairpos(
76            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s: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
177     " No previous line, keep current indent.
178     if lnum < 1
179       return -1
180     endif
181
182     let text = getline(lnum)
183     let start = s:find_start_of_multiline_statement(lnum)
184     let base = indent(start)
185     let current = indent(a:lnum)
186
187     " Jump to last character in previous line.
188     call cursor(lnum, len(text))
189     let ignore_last_char = eval(s:skip)
190
191     " Search for final colon that is not inside a string or comment.
192     while search(':\s*\%(#.*\)\?$', 'bcW', lnum)
193       if eval(s:skip)
194         normal! h
195       else
196         return base + s:sw()
197       endif
198     endwhile
199
200     if text =~ '\\$' && !ignore_last_char
201         " If this line is the continuation of a control statement
202         " indent further to distinguish the continuation line
203         " from the next logical line.
204         if getline(start) =~# s:control_statement
205             return base + s:sw() * 2
206         endif
207
208         " Nest (other) explicit continuations only one level deeper.
209         return base + s:sw()
210     endif
211
212     " If the previous statement was a stop-execution statement or a pass
213     if getline(start) =~# s:stop_statement
214         " Remove one level of indentation if the user hasn't already dedented
215         if indent(a:lnum) > base - s:sw()
216             return base - s:sw()
217         endif
218         " Otherwise, trust the user
219         return -1
220     endif
221
222     " If this line is dedented and the number of indent spaces is valid
223     " (multiple of the indentation size), trust the user
224     let dedent_size = current - base
225     if dedent_size < 0 && current % s:sw() == 0
226         return -1
227     endif
228
229     " In all other cases, line up with the start of the previous statement.
230     return base
231 endfunction
232
233 function! GetPythonPEPIndent(lnum)
234
235     " First line has indent 0
236     if a:lnum == 1
237         return 0
238     endif
239
240     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
241     let indent = s:indent_like_opening_paren(a:lnum)
242     if indent >= -1
243         return indent
244     endif
245
246     " Blocks: Match indent of first block of this type.
247     let indent = s:indent_like_block(a:lnum)
248     if indent >= -1
249         return indent
250     endif
251
252     return s:indent_like_previous_line(a:lnum)
253 endfunction