]> 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 #35 from tgs/master
[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 " compatibility with vim patch 7.3.629: 'sw' can be set to -1 to follow 'ts'
54 if exists('*shiftwidth')
55     function! s:sw()
56         return shiftwidth()
57     endfunction
58 else
59     function! s:sw()
60         return &sw
61     endfunction
62 endif
63
64 function! s:pair_sort(x, y)
65     if a:x[0] == a:y[0]
66         return a:x[1] == a:y[1] ? 0 : a:x[1] > a:y[1] ? 1 : -1
67     else
68         return a:x[0] > a:y[0] ? 1 : -1
69     endif
70 endfunction
71
72 " Find backwards the closest open parenthesis/bracket/brace.
73 function! s:find_opening_paren(...)
74     " optional arguments: line and column (defaults to 1) to search around
75     if a:0 > 0
76         let view = winsaveview()
77         call cursor(a:1, a:0 > 1 ? a:2 : 1)
78         let ret = s:find_opening_paren()
79         call winrestview(view)
80         return ret
81     endif
82
83     let stopline = max([0, line('.') - s:maxoff])
84
85     " Return if cursor is in a comment or string
86     exe 'if' s:skip_search '| return [0, 0] | endif'
87
88     let positions = []
89     for p in s:paren_pairs
90         call add(positions, searchpairpos(
91            \ '\V'.p[0], '', '\V'.p[1], 'bnW', s:skip_special_chars, stopline))
92     endfor
93
94     " Remove empty matches and return the type with the closest match
95     call filter(positions, 'v:val[0]')
96     call sort(positions, 's:pair_sort')
97
98     return get(positions, -1, [0, 0])
99 endfunction
100
101 " Find the start of a multi-line statement
102 function! s:find_start_of_multiline_statement(lnum)
103     let lnum = a:lnum
104     while lnum > 0
105         if getline(lnum - 1) =~ '\\$'
106             let lnum = prevnonblank(lnum - 1)
107         else
108             let [paren_lnum, _] = s:find_opening_paren(lnum)
109             if paren_lnum < 1
110                 return lnum
111             else
112                 let lnum = paren_lnum
113             endif
114         endif
115     endwhile
116 endfunction
117
118 " Find the block starter that matches the current line
119 function! s:find_start_of_block(lnum, types)
120     let re = '\V\^\s\*\('.join(a:types, '\|').'\)\>'
121
122     let lnum = a:lnum
123     let last_indent = indent(lnum) + 1
124     while lnum > 0 && last_indent > 0
125         if indent(lnum) < last_indent
126             if getline(lnum) =~# re
127                 return lnum
128             endif
129             let last_indent = indent(lnum)
130         endif
131         let lnum = prevnonblank(lnum - 1)
132     endwhile
133     return 0
134 endfunction
135
136 " Line up with open parenthesis/bracket/brace.
137 function! s:indent_like_opening_paren(lnum)
138     let [paren_lnum, paren_col] = s:find_opening_paren(a:lnum)
139     if paren_lnum <= 0
140         return -2
141     endif
142     let text = getline(paren_lnum)
143     let base = indent(paren_lnum)
144
145     let nothing_after_opening_paren = text =~ '\%'.(paren_col + 1).'c\s*$'
146     let starts_with_closing_paren = getline(a:lnum) =~ '^\s*[])}]'
147
148     if nothing_after_opening_paren
149         if starts_with_closing_paren
150             let res = base
151         else
152             let res = base + s:sw()
153         endif
154     else
155         " Indent to match position of opening paren.
156         let res = paren_col
157     endif
158
159     " If this line is the continuation of a control statement
160     " indent further to distinguish the continuation line
161     " from the next logical line.
162     if text =~# s:control_statement && res == base + s:sw()
163         return base + s:sw() * 2
164     else
165         return res
166     endif
167 endfunction
168
169 " Match indent of first block of this type.
170 function! s:indent_like_block(lnum)
171     let text = getline(a:lnum)
172
173     for [line_re, blocks] in items(s:block_rules)
174         if text !~# line_re
175             continue
176         endif
177
178         let lnum = s:find_start_of_block(a:lnum - 1, blocks)
179         if lnum > 0
180             return indent(lnum)
181         else
182             return -1
183         endif
184     endfor
185
186     return -2
187 endfunction
188
189 function! s:indent_like_previous_line(lnum)
190     let lnum = prevnonblank(a:lnum - 1)
191
192     " No previous line, keep current indent.
193     if lnum < 1
194       return -1
195     endif
196
197     let text = getline(lnum)
198     let start = s:find_start_of_multiline_statement(lnum)
199     let base = indent(start)
200     let current = indent(a:lnum)
201
202     " Jump to last character in previous line.
203     call cursor(lnum, len(text))
204     let ignore_last_char = eval(s:skip_special_chars)
205
206     " Search for final colon that is not inside a string or comment.
207     while search(':\s*\%(#.*\)\?$', 'bcW', lnum)
208       if eval(s:skip_special_chars)
209         normal! h
210       else
211         return base + s:sw()
212       endif
213     endwhile
214
215     if text =~ '\\$' && !ignore_last_char
216         " If this line is the continuation of a control statement
217         " indent further to distinguish the continuation line
218         " from the next logical line.
219         if getline(start) =~# s:control_statement
220             return base + s:sw() * 2
221         endif
222
223         " Nest (other) explicit continuations only one level deeper.
224         return base + s:sw()
225     endif
226
227     " If the previous statement was a stop-execution statement or a pass
228     if getline(start) =~# s:stop_statement
229         " Remove one level of indentation if the user hasn't already dedented
230         if indent(a:lnum) > base - s:sw()
231             return base - s:sw()
232         endif
233         " Otherwise, trust the user
234         return -1
235     endif
236
237     " If this line is dedented and the number of indent spaces is valid
238     " (multiple of the indentation size), trust the user
239     let dedent_size = current - base
240     if dedent_size < 0 && current % s:sw() == 0
241         return -1
242     endif
243
244     " In all other cases, line up with the start of the previous statement.
245     return base
246 endfunction
247
248 function! GetPythonPEPIndent(lnum)
249
250     " First line has indent 0
251     if a:lnum == 1
252         return 0
253     endif
254
255     " Parens: If we can find an open parenthesis/bracket/brace, line up with it.
256     let indent = s:indent_like_opening_paren(a:lnum)
257     if indent >= -1
258         return indent
259     endif
260
261     " Blocks: Match indent of first block of this type.
262     let indent = s:indent_like_block(a:lnum)
263     if indent >= -1
264         return indent
265     endif
266
267     return s:indent_like_previous_line(a:lnum)
268 endfunction