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

7b6580ab69f8c618259c6201d4502f5ff6781802
[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, 0, [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             return base
136         else
137             return base + s:sw()
138         endif
139     else
140         " Indent to match position of opening paren.
141         return paren_col
142     endif
143 endfunction
144
145 " Match indent of first block of this type.
146 function! s:indent_like_block(lnum)
147     let text = getline(a:lnum)
148
149     for [line_re, blocks] in items(s:block_rules)
150         if text !~# line_re
151             continue
152         endif
153
154         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
155         if lnum > 0
156             return indent(lnum)
157         else
158             return -1
159         endif
160     endfor
161
162     return -2
163 endfunction
164
165 function! s:indent_like_previous_line(lnum)
166     let lnum = prevnonblank(a:lnum - 1)
167     let text = getline(lnum)
168     let start = s:find_start_of_multiline_statement(lnum)
169     let base = indent(start)
170
171     " Remove string literals.
172     let text = substitute(text, s:string_literal, '', 'g')
173
174     " If the previous line ended with a colon and is not a comment, indent
175     " relative to statement start.
176     if text =~ '^[^#]*:\s*\(#.*\)\?$'
177         return base + s:sw()
178     endif
179
180
181     if text =~ '\\$'
182         " If this line is the continuation of a control statement
183         " indent further to distinguish the continuation line
184         " from the next logical line.
185         if getline(start) =~# s:control_statement
186             return base + s:sw() * 2
187         endif
188
189         " Nest (other) explicit continuations only one level deeper.
190         return base + s:sw()
191     endif
192
193     " If the previous statement was a stop-execution statement or a pass
194     if getline(start) =~# s:stop_statement
195         " Remove one level of indentation if the user hasn't already dedented
196         if indent(a:lnum) > base - s:sw()
197             return base - s:sw()
198         endif
199         " Otherwise, trust the user
200         return -1
201     endif
202
203     " In all other cases, line up with the start of the previous statement.
204     return base
205 endfunction
206
207 function! GetPythonPEPIndent(lnum)
208
209     " First line has indent 0
210     if a:lnum == 1
211         return 0
212     endif
213
214     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
215     let indent = s:indent_like_opening_paren(a:lnum)
216     if indent >= -1
217         return indent
218     endif
219
220     " Blocks: Match indent of first block of this type.
221     let indent = s:indent_like_block(a:lnum)
222     if indent >= -1
223         return indent
224     endif
225
226     return s:indent_like_previous_line(a:lnum)
227 endfunction