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

d86eabbcbb667f1e88a26a71177cb2ec17070afa
[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
183     " Jump to last character in previous line.
184     call cursor(lnum, len(text))
185     let ignore_last_char = eval(s:skip)
186
187     " Search for final colon that is not inside a string or comment.
188     while search(':\s*\%(#.*\)\?$', 'bcW', lnum)
189       if eval(s:skip)
190         normal! h
191       else
192         return base + s:sw()
193       endif
194     endwhile
195
196     if text =~ '\\$' && !ignore_last_char
197         " If this line is the continuation of a control statement
198         " indent further to distinguish the continuation line
199         " from the next logical line.
200         if getline(start) =~# s:control_statement
201             return base + s:sw() * 2
202         endif
203
204         " Nest (other) explicit continuations only one level deeper.
205         return base + s:sw()
206     endif
207
208     " If the previous statement was a stop-execution statement or a pass
209     if getline(start) =~# s:stop_statement
210         " Remove one level of indentation if the user hasn't already dedented
211         if indent(a:lnum) > base - s:sw()
212             return base - s:sw()
213         endif
214         " Otherwise, trust the user
215         return -1
216     endif
217
218     " In all other cases, line up with the start of the previous statement.
219     return base
220 endfunction
221
222 function! GetPythonPEPIndent(lnum)
223
224     " First line has indent 0
225     if a:lnum == 1
226         return 0
227     endif
228
229     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
230     let indent = s:indent_like_opening_paren(a:lnum)
231     if indent >= -1
232         return indent
233     endif
234
235     " Blocks: Match indent of first block of this type.
236     let indent = s:indent_like_block(a:lnum)
237     if indent >= -1
238         return indent
239     endif
240
241     return s:indent_like_previous_line(a:lnum)
242 endfunction