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

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