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