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