blob: cf01198d73c82853b0707d7aeab9a14efe370b92 [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 Moolenaarfd999452022-08-24 18:30:14 +01006" need to inspect some old g:pyindent_* variables to be backward compatible
7let g:python_indent = extend(get(g:, 'python_indent', {}), #{
8 \ closed_paren_align_last_line: v:true,
9 \ open_paren: get(g:, 'pyindent_open_paren', 'shiftwidth() * 2'),
10 \ nested_paren: get(g:, 'pyindent_nested_paren', 'shiftwidth()'),
11 \ continue: get(g:, 'pyindent_continue', 'shiftwidth() * 2'),
12 "\ searchpair() can be slow, limit the time to 150 msec or what is put in
13 "\ g:python_indent.searchpair_timeout
14 \ searchpair_timeout: get(g:, 'pyindent_searchpair_timeout', 150),
15 "\ Identing inside parentheses can be very slow, regardless of the searchpair()
16 "\ timeout, so let the user disable this feature if he doesn't need it
17 \ disable_parentheses_indenting: get(g:, 'pyindent_disable_parentheses_indenting', v:false),
18 \ }, 'keep')
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010019
20let s:maxoff = 50 " maximum number of lines to look backwards for ()
21
22function s:SearchBracket(fromlnum, flags)
Aliaksei Budavei4e3df442025-04-13 21:00:42 +030023 " VIM_INDENT_TEST_TRACE_START
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010024 return searchpairpos('[[({]', '', '[])}]', a:flags,
Bram Moolenaare8008642022-08-19 17:15:35 +010025 \ {-> synstack('.', col('.'))
Bram Moolenaardd60c362023-02-27 15:49:53 +000026 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\|String\)$'}) >= 0},
Bram Moolenaarfd999452022-08-24 18:30:14 +010027 \ [0, a:fromlnum - s:maxoff]->max(), g:python_indent.searchpair_timeout)
Aliaksei Budavei4e3df442025-04-13 21:00:42 +030028 " VIM_INDENT_TEST_TRACE_END python#s:SearchBracket
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010029endfunction
30
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010031" See if the specified line is already user-dedented from the expected value.
32function s:Dedented(lnum, expected)
33 return indent(a:lnum) <= a:expected - shiftwidth()
34endfunction
35
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010036" Some other filetypes which embed Python have slightly different indent
37" rules (e.g. bitbake). Those filetypes can pass an extra funcref to this
38" function which is evaluated below.
39function python#GetIndent(lnum, ...)
40 let ExtraFunc = a:0 > 0 ? a:1 : 0
41
42 " If this line is explicitly joined: If the previous line was also joined,
43 " line it up with that one, otherwise add two 'shiftwidth'
44 if getline(a:lnum - 1) =~ '\\$'
45 if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$'
46 return indent(a:lnum - 1)
47 endif
Bram Moolenaarfd999452022-08-24 18:30:14 +010048 return indent(a:lnum - 1) + get(g:, 'pyindent_continue', g:python_indent.continue)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010049 endif
50
51 " If the start of the line is in a string don't change the indent.
52 if has('syntax_items')
53 \ && synIDattr(synID(a:lnum, 1, 1), "name") =~ "String$"
54 return -1
55 endif
56
57 " Search backwards for the previous non-empty line.
58 let plnum = prevnonblank(v:lnum - 1)
59
60 if plnum == 0
61 " This is the first non-empty line, use zero indent.
62 return 0
63 endif
64
Bram Moolenaarfd999452022-08-24 18:30:14 +010065 if g:python_indent.disable_parentheses_indenting == 1
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010066 let plindent = indent(plnum)
67 let plnumstart = plnum
68 else
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010069 " Indent inside parens.
70 " Align with the open paren unless it is at the end of the line.
71 " E.g.
72 " open_paren_not_at_EOL(100,
73 " (200,
74 " 300),
75 " 400)
76 " open_paren_at_EOL(
77 " 100, 200, 300, 400)
78 call cursor(a:lnum, 1)
79 let [parlnum, parcol] = s:SearchBracket(a:lnum, 'nbW')
Bram Moolenaarfd999452022-08-24 18:30:14 +010080 if parlnum > 0
81 if parcol != col([parlnum, '$']) - 1
82 return parcol
83 elseif getline(a:lnum) =~ '^\s*[])}]' && !g:python_indent.closed_paren_align_last_line
84 return indent(parlnum)
85 endif
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010086 endif
87
88 call cursor(plnum, 1)
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010089
90 " If the previous line is inside parenthesis, use the indent of the starting
91 " line.
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010092 let [parlnum, _] = s:SearchBracket(plnum, 'nbW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010093 if parlnum > 0
94 if a:0 > 0 && ExtraFunc(parlnum)
95 " We may have found the opening brace of a bitbake Python task, e.g. 'python do_task {'
96 " If so, ignore it here - it will be handled later.
97 let parlnum = 0
98 let plindent = indent(plnum)
99 let plnumstart = plnum
100 else
101 let plindent = indent(parlnum)
102 let plnumstart = parlnum
103 endif
104 else
105 let plindent = indent(plnum)
106 let plnumstart = plnum
107 endif
108
109 " When inside parenthesis: If at the first line below the parenthesis add
110 " two 'shiftwidth', otherwise same as previous line.
111 " i = (a
112 " + b
113 " + c)
114 call cursor(a:lnum, 1)
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +0100115 let [p, _] = s:SearchBracket(a:lnum, 'bW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100116 if p > 0
117 if a:0 > 0 && ExtraFunc(p)
118 " Currently only used by bitbake
119 " Handle first non-empty line inside a bitbake Python task
120 if p == plnum
121 return shiftwidth()
122 endif
123
124 " Handle the user actually trying to close a bitbake Python task
125 let line = getline(a:lnum)
126 if line =~ '^\s*}'
127 return -2
128 endif
129
130 " Otherwise ignore the brace
131 let p = 0
132 else
133 if p == plnum
134 " When the start is inside parenthesis, only indent one 'shiftwidth'.
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +0100135 let [pp, _] = s:SearchBracket(a:lnum, 'bW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100136 if pp > 0
Bram Moolenaarfd999452022-08-24 18:30:14 +0100137 return indent(plnum)
138 \ + get(g:, 'pyindent_nested_paren', g:python_indent.nested_paren)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100139 endif
Bram Moolenaarfd999452022-08-24 18:30:14 +0100140 return indent(plnum)
141 \ + get(g:, 'pyindent_open_paren', g:python_indent.open_paren)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100142 endif
143 if plnumstart == p
144 return indent(plnum)
145 endif
146 return plindent
147 endif
148 endif
149 endif
150
151
152 " Get the line and remove a trailing comment.
153 " Use syntax highlighting attributes when possible.
154 let pline = getline(plnum)
155 let pline_len = strlen(pline)
156 if has('syntax_items')
157 " If the last character in the line is a comment, do a binary search for
158 " the start of the comment. synID() is slow, a linear search would take
159 " too long on a long line.
Bram Moolenaare8008642022-08-19 17:15:35 +0100160 if synstack(plnum, pline_len)
Bram Moolenaardd60c362023-02-27 15:49:53 +0000161 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100162 let min = 1
163 let max = pline_len
164 while min < max
165 let col = (min + max) / 2
Bram Moolenaare8008642022-08-19 17:15:35 +0100166 if synstack(plnum, col)
Bram Moolenaardd60c362023-02-27 15:49:53 +0000167 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100168 let max = col
169 else
170 let min = col + 1
171 endif
172 endwhile
173 let pline = strpart(pline, 0, min - 1)
174 endif
175 else
176 let col = 0
177 while col < pline_len
178 if pline[col] == '#'
179 let pline = strpart(pline, 0, col)
180 break
181 endif
182 let col = col + 1
183 endwhile
184 endif
185
186 " If the previous line ended with a colon, indent this line
187 if pline =~ ':\s*$'
188 return plindent + shiftwidth()
189 endif
190
191 " If the previous line was a stop-execution statement...
192 if getline(plnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>'
193 " See if the user has already dedented
194 if s:Dedented(a:lnum, indent(plnum))
195 " If so, trust the user
196 return -1
197 endif
198 " If not, recommend one dedent
199 return indent(plnum) - shiftwidth()
200 endif
201
202 " If the current line begins with a keyword that lines up with "try"
203 if getline(a:lnum) =~ '^\s*\(except\|finally\)\>'
204 let lnum = a:lnum - 1
205 while lnum >= 1
206 if getline(lnum) =~ '^\s*\(try\|except\)\>'
207 let ind = indent(lnum)
208 if ind >= indent(a:lnum)
209 return -1 " indent is already less than this
210 endif
211 return ind " line up with previous try or except
212 endif
213 let lnum = lnum - 1
214 endwhile
215 return -1 " no matching "try"!
216 endif
217
218 " If the current line begins with a header keyword, dedent
219 if getline(a:lnum) =~ '^\s*\(elif\|else\)\>'
220
221 " Unless the previous line was a one-liner
222 if getline(plnumstart) =~ '^\s*\(for\|if\|elif\|try\)\>'
223 return plindent
224 endif
225
226 " Or the user has already dedented
227 if s:Dedented(a:lnum, plindent)
228 return -1
229 endif
230
231 return plindent - shiftwidth()
232 endif
233
234 " When after a () construct we probably want to go back to the start line.
235 " a = (b
236 " + c)
237 " here
238 if parlnum > 0
239 " ...unless the user has already dedented
240 if s:Dedented(a:lnum, plindent)
241 return -1
242 else
243 return plindent
244 endif
245 endif
246
247 return -1
248endfunction
249
250let &cpo = s:keepcpo
251unlet s:keepcpo