blob: d5f4862363cec50854092ea959d28e5380a9593b [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)
23 return searchpairpos('[[({]', '', '[])}]', a:flags,
Bram Moolenaare8008642022-08-19 17:15:35 +010024 \ {-> synstack('.', col('.'))
Bram Moolenaardd60c362023-02-27 15:49:53 +000025 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\|String\)$'}) >= 0},
Bram Moolenaarfd999452022-08-24 18:30:14 +010026 \ [0, a:fromlnum - s:maxoff]->max(), g:python_indent.searchpair_timeout)
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010027endfunction
28
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010029" See if the specified line is already user-dedented from the expected value.
30function s:Dedented(lnum, expected)
31 return indent(a:lnum) <= a:expected - shiftwidth()
32endfunction
33
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010034" Some other filetypes which embed Python have slightly different indent
35" rules (e.g. bitbake). Those filetypes can pass an extra funcref to this
36" function which is evaluated below.
37function python#GetIndent(lnum, ...)
38 let ExtraFunc = a:0 > 0 ? a:1 : 0
39
40 " If this line is explicitly joined: If the previous line was also joined,
41 " line it up with that one, otherwise add two 'shiftwidth'
42 if getline(a:lnum - 1) =~ '\\$'
43 if a:lnum > 1 && getline(a:lnum - 2) =~ '\\$'
44 return indent(a:lnum - 1)
45 endif
Bram Moolenaarfd999452022-08-24 18:30:14 +010046 return indent(a:lnum - 1) + get(g:, 'pyindent_continue', g:python_indent.continue)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010047 endif
48
49 " If the start of the line is in a string don't change the indent.
50 if has('syntax_items')
51 \ && synIDattr(synID(a:lnum, 1, 1), "name") =~ "String$"
52 return -1
53 endif
54
55 " Search backwards for the previous non-empty line.
56 let plnum = prevnonblank(v:lnum - 1)
57
58 if plnum == 0
59 " This is the first non-empty line, use zero indent.
60 return 0
61 endif
62
Bram Moolenaarfd999452022-08-24 18:30:14 +010063 if g:python_indent.disable_parentheses_indenting == 1
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010064 let plindent = indent(plnum)
65 let plnumstart = plnum
66 else
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010067 " Indent inside parens.
68 " Align with the open paren unless it is at the end of the line.
69 " E.g.
70 " open_paren_not_at_EOL(100,
71 " (200,
72 " 300),
73 " 400)
74 " open_paren_at_EOL(
75 " 100, 200, 300, 400)
76 call cursor(a:lnum, 1)
77 let [parlnum, parcol] = s:SearchBracket(a:lnum, 'nbW')
Bram Moolenaarfd999452022-08-24 18:30:14 +010078 if parlnum > 0
79 if parcol != col([parlnum, '$']) - 1
80 return parcol
81 elseif getline(a:lnum) =~ '^\s*[])}]' && !g:python_indent.closed_paren_align_last_line
82 return indent(parlnum)
83 endif
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010084 endif
85
86 call cursor(plnum, 1)
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010087
88 " If the previous line is inside parenthesis, use the indent of the starting
89 " line.
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +010090 let [parlnum, _] = s:SearchBracket(plnum, 'nbW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +010091 if parlnum > 0
92 if a:0 > 0 && ExtraFunc(parlnum)
93 " We may have found the opening brace of a bitbake Python task, e.g. 'python do_task {'
94 " If so, ignore it here - it will be handled later.
95 let parlnum = 0
96 let plindent = indent(plnum)
97 let plnumstart = plnum
98 else
99 let plindent = indent(parlnum)
100 let plnumstart = parlnum
101 endif
102 else
103 let plindent = indent(plnum)
104 let plnumstart = plnum
105 endif
106
107 " When inside parenthesis: If at the first line below the parenthesis add
108 " two 'shiftwidth', otherwise same as previous line.
109 " i = (a
110 " + b
111 " + c)
112 call cursor(a:lnum, 1)
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +0100113 let [p, _] = s:SearchBracket(a:lnum, 'bW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100114 if p > 0
115 if a:0 > 0 && ExtraFunc(p)
116 " Currently only used by bitbake
117 " Handle first non-empty line inside a bitbake Python task
118 if p == plnum
119 return shiftwidth()
120 endif
121
122 " Handle the user actually trying to close a bitbake Python task
123 let line = getline(a:lnum)
124 if line =~ '^\s*}'
125 return -2
126 endif
127
128 " Otherwise ignore the brace
129 let p = 0
130 else
131 if p == plnum
132 " When the start is inside parenthesis, only indent one 'shiftwidth'.
Bram Moolenaar48c3f4e2022-08-08 15:42:38 +0100133 let [pp, _] = s:SearchBracket(a:lnum, 'bW')
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100134 if pp > 0
Bram Moolenaarfd999452022-08-24 18:30:14 +0100135 return indent(plnum)
136 \ + get(g:, 'pyindent_nested_paren', g:python_indent.nested_paren)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100137 endif
Bram Moolenaarfd999452022-08-24 18:30:14 +0100138 return indent(plnum)
139 \ + get(g:, 'pyindent_open_paren', g:python_indent.open_paren)->eval()
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100140 endif
141 if plnumstart == p
142 return indent(plnum)
143 endif
144 return plindent
145 endif
146 endif
147 endif
148
149
150 " Get the line and remove a trailing comment.
151 " Use syntax highlighting attributes when possible.
152 let pline = getline(plnum)
153 let pline_len = strlen(pline)
154 if has('syntax_items')
155 " If the last character in the line is a comment, do a binary search for
156 " the start of the comment. synID() is slow, a linear search would take
157 " too long on a long line.
Bram Moolenaare8008642022-08-19 17:15:35 +0100158 if synstack(plnum, pline_len)
Bram Moolenaardd60c362023-02-27 15:49:53 +0000159 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100160 let min = 1
161 let max = pline_len
162 while min < max
163 let col = (min + max) / 2
Bram Moolenaare8008642022-08-19 17:15:35 +0100164 if synstack(plnum, col)
Bram Moolenaardd60c362023-02-27 15:49:53 +0000165 \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
Bram Moolenaarb529cfb2022-07-25 15:42:07 +0100166 let max = col
167 else
168 let min = col + 1
169 endif
170 endwhile
171 let pline = strpart(pline, 0, min - 1)
172 endif
173 else
174 let col = 0
175 while col < pline_len
176 if pline[col] == '#'
177 let pline = strpart(pline, 0, col)
178 break
179 endif
180 let col = col + 1
181 endwhile
182 endif
183
184 " If the previous line ended with a colon, indent this line
185 if pline =~ ':\s*$'
186 return plindent + shiftwidth()
187 endif
188
189 " If the previous line was a stop-execution statement...
190 if getline(plnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>'
191 " See if the user has already dedented
192 if s:Dedented(a:lnum, indent(plnum))
193 " If so, trust the user
194 return -1
195 endif
196 " If not, recommend one dedent
197 return indent(plnum) - shiftwidth()
198 endif
199
200 " If the current line begins with a keyword that lines up with "try"
201 if getline(a:lnum) =~ '^\s*\(except\|finally\)\>'
202 let lnum = a:lnum - 1
203 while lnum >= 1
204 if getline(lnum) =~ '^\s*\(try\|except\)\>'
205 let ind = indent(lnum)
206 if ind >= indent(a:lnum)
207 return -1 " indent is already less than this
208 endif
209 return ind " line up with previous try or except
210 endif
211 let lnum = lnum - 1
212 endwhile
213 return -1 " no matching "try"!
214 endif
215
216 " If the current line begins with a header keyword, dedent
217 if getline(a:lnum) =~ '^\s*\(elif\|else\)\>'
218
219 " Unless the previous line was a one-liner
220 if getline(plnumstart) =~ '^\s*\(for\|if\|elif\|try\)\>'
221 return plindent
222 endif
223
224 " Or the user has already dedented
225 if s:Dedented(a:lnum, plindent)
226 return -1
227 endif
228
229 return plindent - shiftwidth()
230 endif
231
232 " When after a () construct we probably want to go back to the start line.
233 " a = (b
234 " + c)
235 " here
236 if parlnum > 0
237 " ...unless the user has already dedented
238 if s:Dedented(a:lnum, plindent)
239 return -1
240 else
241 return plindent
242 endif
243 endif
244
245 return -1
246endfunction
247
248let &cpo = s:keepcpo
249unlet s:keepcpo