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

Merge pull request #39 from blueyed/fix-indent
[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:          CC0
7 "
8 " vim-python-pep8-indent - A nicer Python indentation style for vim.
9 " Written in 2004 by David Bustos <bustos@caltech.edu>
10 " Maintained from 2004-2005 by Eric Mc Sween <em@tomcom.de>
11 " Maintained from 2013 by Hynek Schlawack <hs@ox.cx>
12 "
13 " To the extent possible under law, the author(s) have dedicated all copyright
14 " and related and neighboring rights to this software to the public domain
15 " worldwide. This software is distributed without any warranty.
16 " You should have received a copy of the CC0 Public Domain Dedication along
17 " with this software. If not, see
18 " <http://creativecommons.org/publicdomain/zero/1.0/>.
19
20 " Only load this indent file when no other was loaded.
21 if exists("b:did_indent")
22     finish
23 endif
24 let b:did_indent = 1
25
26 setlocal expandtab
27 setlocal nolisp
28 setlocal autoindent
29 setlocal indentexpr=GetPythonPEPIndent(v:lnum)
30 setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except
31 setlocal tabstop=4
32 setlocal softtabstop=4
33 setlocal shiftwidth=4
34
35 let s:maxoff = 50
36 let s:block_rules = {
37             \ '^\s*elif\>': ['if', 'elif'],
38             \ '^\s*else\>': ['if', 'elif', 'for', 'try', 'except'],
39             \ '^\s*except\>': ['try', 'except'],
40             \ '^\s*finally\>': ['try', 'except', 'else']
41             \ }
42 let s:paren_pairs = ['()', '{}', '[]']
43 let s:control_statement = '^\s*\(if\|while\|with\|for\|except\)\>'
44 let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
45
46 " Skip strings and comments
47 let s:skip_special_chars = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
48             \ '=~? "string\\|comment"'
49
50 let s:skip_search = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
51             \ '=~? "comment"'
52
53 " Use 'shiftwidth()' instead of '&sw'.
54 " (Since Vim patch 7.3.629, 'shiftwidth' can be set to 0 to follow 'tabstop').
55 if exists('*shiftwidth')
56     function! s:sw()
57         return shiftwidth()
58     endfunction
59 else
60     function! s:sw()
61         return &sw
62     endfunction
63 endif
64
65 function! s:pair_sort(x, y)
66     if a:x[0] == a:y[0]
67         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
68     else
69         return a:x[0] > a:y[0] ? 1 : -1
70     endif
71 endfunction
72
73 " Find backwards the closest open parenthesis/bracket/brace.
74 function! s:find_opening_paren(...)
75     " optional arguments: line and column (defaults to 1) to search around
76     if a:0 > 0
77         let view = winsaveview()
78         call cursor(a:1, a:0 > 1 ? a:2 : 1)
79         let ret = s:find_opening_paren()
80         call winrestview(view)
81         return ret
82     endif
83
84     let stopline = max([0, line('.') - s:maxoff])
85
86     " Return if cursor is in a comment or string
87     exe 'if' s:skip_search '| return [0, 0] | endif'
88
89     let positions = []
90     for p in s:paren_pairs
91         call add(positions, searchpairpos(
92            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s:skip_special_chars, stopline))
93     endfor
94
95     " Remove empty matches and return the type with the closest match
96     call filter(positions, 'v:val[0]')
97     call sort(positions, 's:pair_sort')
98
99     return get(positions, -1, [0, 0])
100 endfunction
101
102 " Find the start of a multi-line statement
103 function! s:find_start_of_multiline_statement(lnum)
104     let lnum = a:lnum
105     while lnum > 0
106         if getline(lnum - 1) =~ '\\$'
107             let lnum = prevnonblank(lnum - 1)
108         else
109             let [paren_lnum, _] = s:find_opening_paren(lnum)
110             if paren_lnum < 1
111                 return lnum
112             else
113                 let lnum = paren_lnum
114             endif
115         endif
116     endwhile
117 endfunction
118
119 " Find the block starter that matches the current line
120 function! s:find_start_of_block(lnum, types)
121     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
122
123     let lnum = a:lnum
124     let last_indent = indent(lnum) + 1
125     while lnum > 0 && last_indent > 0
126         if indent(lnum) < last_indent
127             if getline(lnum) =~# re
128                 return lnum
129             endif
130             let last_indent = indent(lnum)
131         endif
132         let lnum = prevnonblank(lnum - 1)
133     endwhile
134     return 0
135 endfunction
136
137 " Line up with open parenthesis/bracket/brace.
138 function! s:indent_like_opening_paren(lnum)
139     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
140     if paren_lnum <= 0
141         return -2
142     endif
143     let text = getline(paren_lnum)
144     let base = indent(paren_lnum)
145
146     let nothing_after_opening_paren = text =~ '\%'.(paren_col + 1).'c\s*$'
147     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
148
149     if nothing_after_opening_paren
150         if starts_with_closing_paren
151             let res = base
152         else
153             let res = base + s:sw()
154         endif
155     else
156         " Indent to match position of opening paren.
157         let res = paren_col
158     endif
159
160     " If this line is the continuation of a control statement
161     " indent further to distinguish the continuation line
162     " from the next logical line.
163     if text =~# s:control_statement && res == base + s:sw()
164         return base + s:sw() * 2
165     else
166         return res
167     endif
168 endfunction
169
170 " Match indent of first block of this type.
171 function! s:indent_like_block(lnum)
172     let text = getline(a:lnum)
173
174     for [line_re, blocks] in items(s:block_rules)
175         if text !~# line_re
176             continue
177         endif
178
179         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
180         if lnum > 0
181             return indent(lnum)
182         else
183             return -1
184         endif
185     endfor
186
187     return -2
188 endfunction
189
190 function! s:indent_like_previous_line(lnum)
191     let lnum = prevnonblank(a:lnum - 1)
192
193     " No previous line, keep current indent.
194     if lnum < 1
195       return -1
196     endif
197
198     let text = getline(lnum)
199     let start = s:find_start_of_multiline_statement(lnum)
200     let base = indent(start)
201     let current = indent(a:lnum)
202
203     " Jump to last character in previous line.
204     call cursor(lnum, len(text))
205     let ignore_last_char = eval(s:skip_special_chars)
206
207     " Search for final colon that is not inside a string or comment.
208     while search(':\s*\%(#.*\)\?$', 'bcW', lnum)
209       if eval(s:skip_special_chars)
210         normal! h
211       else
212         return base + s:sw()
213       endif
214     endwhile
215
216     if text =~ '\\$' && !ignore_last_char
217         " If this line is the continuation of a control statement
218         " indent further to distinguish the continuation line
219         " from the next logical line.
220         if getline(start) =~# s:control_statement
221             return base + s:sw() * 2
222         endif
223
224         " Nest (other) explicit continuations only one level deeper.
225         return base + s:sw()
226     endif
227
228     " If the previous statement was a stop-execution statement or a pass
229     if getline(start) =~# s:stop_statement
230         " Remove one level of indentation if the user hasn't already dedented
231         if indent(a:lnum) > base - s:sw()
232             return base - s:sw()
233         endif
234         " Otherwise, trust the user
235         return -1
236     endif
237
238     " If this line is dedented and the number of indent spaces is valid
239     " (multiple of the indentation size), trust the user
240     let dedent_size = current - base
241     if dedent_size < 0 && current % s:sw() == 0
242         return -1
243     endif
244
245     " In all other cases, line up with the start of the previous statement.
246     return base
247 endfunction
248
249 function! GetPythonPEPIndent(lnum)
250
251     " First line has indent 0
252     if a:lnum == 1
253         return 0
254     endif
255
256     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
257     let indent = s:indent_like_opening_paren(a:lnum)
258     if indent >= -1
259         return indent
260     endif
261
262     " Blocks: Match indent of first block of this type.
263     let indent = s:indent_like_block(a:lnum)
264     if indent >= -1
265         return indent
266     endif
267
268     return s:indent_like_previous_line(a:lnum)
269 endfunction