blob: 7e7bca6fb6f2fe45b8666d16d4bf4a12f027de64 [file] [log] [blame]
Bram Moolenaarb529cfb2022-07-25 15:42:07 +01001" Support for Python indenting, see runtime/indent/python.vim
2
3let s:keepcpo= &cpo
4set cpo&vim
5
6" See if the specified line is already user-dedented from the expected value.
7function s:Dedented(lnum, expected)
8 return indent(a:lnum) <= a:expected - shiftwidth()
9endfunction
10
11let s:maxoff = 50 " maximum number of lines to look backwards for ()
12
13" Some other filetypes which embed Python have slightly different indent
14" rules (e.g. bitbake). Those filetypes can pass an extra funcref to this
15" function which is evaluated below.
16function python#GetIndent(lnum, ...)
17 let ExtraFunc = a:0 > 0 ? a:1 : 0
18
19 " If this line is explicitly joined: If the previous line was also joined,
20 " line it up with that one, otherwise add two 'shiftwidth'
21 if getline(a:lnum - 1) =~ '\\$'
22 if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$'
23 return indent(a:lnum - 1)
24 endif
25 return indent(a:lnum - 1) + (exists("g:pyindent_continue") ? eval(g:pyindent_continue) : (shiftwidth() * 2))
26 endif
27
28 " If the start of the line is in a string don't change the indent.
29 if has('syntax_items')
30 \ && synIDattr(synID(a:lnum, 1, 1), "name") =~ "String$"
31 return -1
32 endif
33
34 " Search backwards for the previous non-empty line.
35 let plnum = prevnonblank(v:lnum - 1)
36
37 if plnum == 0
38 " This is the first non-empty line, use zero indent.
39 return 0
40 endif
41
42 call cursor(plnum, 1)
43
44 " Identing inside parentheses can be very slow, regardless of the searchpair()
45 " timeout, so let the user disable this feature if he doesn't need it
46 let disable_parentheses_indenting = get(g:, "pyindent_disable_parentheses_indenting", 0)
47
48 if disable_parentheses_indenting == 1
49 let plindent = indent(plnum)
50 let plnumstart = plnum
51 else
52 " searchpair() can be slow sometimes, limit the time to 150 msec or what is
53 " put in g:pyindent_searchpair_timeout
54 let searchpair_stopline = 0
55 let searchpair_timeout = get(g:, 'pyindent_searchpair_timeout', 150)
56
57 " If the previous line is inside parenthesis, use the indent of the starting
58 " line.
59 " Trick: use the non-existing "dummy" variable to break out of the loop when
60 " going too far back.
61 let parlnum = searchpair('(\|{\|\[', '', ')\|}\|\]', 'nbW',
62 \ "line('.') < " . (plnum - s:maxoff) . " ? dummy :"
63 \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')"
64 \ . " =~ '\\(Comment\\|Todo\\|String\\)$'",
65 \ searchpair_stopline, searchpair_timeout)
66 if parlnum > 0
67 if a:0 > 0 && ExtraFunc(parlnum)
68 " We may have found the opening brace of a bitbake Python task, e.g. 'python do_task {'
69 " If so, ignore it here - it will be handled later.
70 let parlnum = 0
71 let plindent = indent(plnum)
72 let plnumstart = plnum
73 else
74 let plindent = indent(parlnum)
75 let plnumstart = parlnum
76 endif
77 else
78 let plindent = indent(plnum)
79 let plnumstart = plnum
80 endif
81
82 " When inside parenthesis: If at the first line below the parenthesis add
83 " two 'shiftwidth', otherwise same as previous line.
84 " i = (a
85 " + b
86 " + c)
87 call cursor(a:lnum, 1)
88 let p = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW',
89 \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :"
90 \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')"
91 \ . " =~ '\\(Comment\\|Todo\\|String\\)$'",
92 \ searchpair_stopline, searchpair_timeout)
93 if p > 0
94 if a:0 > 0 && ExtraFunc(p)
95 " Currently only used by bitbake
96 " Handle first non-empty line inside a bitbake Python task
97 if p == plnum
98 return shiftwidth()
99 endif
100
101 " Handle the user actually trying to close a bitbake Python task
102 let line = getline(a:lnum)
103 if line =~ '^\s*}'
104 return -2
105 endif
106
107 " Otherwise ignore the brace
108 let p = 0
109 else
110 if p == plnum
111 " When the start is inside parenthesis, only indent one 'shiftwidth'.
112 let pp = searchpair('(\|{\|\[', '', ')\|}\|\]', 'bW',
113 \ "line('.') < " . (a:lnum - s:maxoff) . " ? dummy :"
114 \ . " synIDattr(synID(line('.'), col('.'), 1), 'name')"
115 \ . " =~ '\\(Comment\\|Todo\\|String\\)$'",
116 \ searchpair_stopline, searchpair_timeout)
117 if pp > 0
118 return indent(plnum) + (exists("g:pyindent_nested_paren") ? eval(g:pyindent_nested_paren) : shiftwidth())
119 endif
120 return indent(plnum) + (exists("g:pyindent_open_paren") ? eval(g:pyindent_open_paren) : (shiftwidth() * 2))
121 endif
122 if plnumstart == p
123 return indent(plnum)
124 endif
125 return plindent
126 endif
127 endif
128 endif
129
130
131 " Get the line and remove a trailing comment.
132 " Use syntax highlighting attributes when possible.
133 let pline = getline(plnum)
134 let pline_len = strlen(pline)
135 if has('syntax_items')
136 " If the last character in the line is a comment, do a binary search for
137 " the start of the comment. synID() is slow, a linear search would take
138 " too long on a long line.
139 if synIDattr(synID(plnum, pline_len, 1), "name") =~ "\\(Comment\\|Todo\\)$"
140 let min = 1
141 let max = pline_len
142 while min < max
143 let col = (min + max) / 2
144 if synIDattr(synID(plnum, col, 1), "name") =~ "\\(Comment\\|Todo\\)$"
145 let max = col
146 else
147 let min = col + 1
148 endif
149 endwhile
150 let pline = strpart(pline, 0, min - 1)
151 endif
152 else
153 let col = 0
154 while col < pline_len
155 if pline[col] == '#'
156 let pline = strpart(pline, 0, col)
157 break
158 endif
159 let col = col + 1
160 endwhile
161 endif
162
163 " If the previous line ended with a colon, indent this line
164 if pline =~ ':\s*$'
165 return plindent + shiftwidth()
166 endif
167
168 " If the previous line was a stop-execution statement...
169 if getline(plnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>'
170 " See if the user has already dedented
171 if s:Dedented(a:lnum, indent(plnum))
172 " If so, trust the user
173 return -1
174 endif
175 " If not, recommend one dedent
176 return indent(plnum) - shiftwidth()
177 endif
178
179 " If the current line begins with a keyword that lines up with "try"
180 if getline(a:lnum) =~ '^\s*\(except\|finally\)\>'
181 let lnum = a:lnum - 1
182 while lnum >= 1
183 if getline(lnum) =~ '^\s*\(try\|except\)\>'
184 let ind = indent(lnum)
185 if ind >= indent(a:lnum)
186 return -1 " indent is already less than this
187 endif
188 return ind " line up with previous try or except
189 endif
190 let lnum = lnum - 1
191 endwhile
192 return -1 " no matching "try"!
193 endif
194
195 " If the current line begins with a header keyword, dedent
196 if getline(a:lnum) =~ '^\s*\(elif\|else\)\>'
197
198 " Unless the previous line was a one-liner
199 if getline(plnumstart) =~ '^\s*\(for\|if\|elif\|try\)\>'
200 return plindent
201 endif
202
203 " Or the user has already dedented
204 if s:Dedented(a:lnum, plindent)
205 return -1
206 endif
207
208 return plindent - shiftwidth()
209 endif
210
211 " When after a () construct we probably want to go back to the start line.
212 " a = (b
213 " + c)
214 " here
215 if parlnum > 0
216 " ...unless the user has already dedented
217 if s:Dedented(a:lnum, plindent)
218 return -1
219 else
220 return plindent
221 endif
222 endif
223
224 return -1
225endfunction
226
227let &cpo = s:keepcpo
228unlet s:keepcpo