1
1
" Python-mode folding functions
2
-
3
2
" Notice that folding is based on single line so complex regular expressions
4
3
" that take previous line into consideration are not fit for the job.
5
4
@@ -8,13 +7,13 @@ let s:def_regex = g:pymode_folding_regex
8
7
let s: blank_regex= ' ^\s*$'
9
8
" Spyder, a very popular IDE for python has a template which includes
10
9
" '@author:' ; thus the regex below.
11
- let s: decorator_regex= ' ^\s*@\(author:\)\@!'
12
- let s: doc_begin_regex= ' ^\s*[uUrR]\=\%("""\|'''''' \)'
13
- let s: doc_end_regex= ' \%("""\|'''''' \)\s*$'
10
+ let s: decorator_regex= ' ^\s*@\(author:\)\@!'
11
+ let s: docstring_line_regex= ' ^\s*[uUrR]\=\("""\|'''''' \).\+\1\s*$'
12
+ let s: docstring_begin_regex= ' ^\s*[uUrR]\=\%("""\|'''''' \).*\S'
13
+ let s: docstring_end_regex= ' \%("""\|'''''' \)\s*$'
14
14
" This one is needed for the while loop to count for opening and closing
15
15
" docstrings.
16
- let s: doc_general_regex= ' \%("""\|'''''' \)'
17
- let s: doc_line_regex= ' ^\s*[uUrR]\=\("""\|'''''' \).\+\1\s*$'
16
+ let s: docstring_general_regex= ' \%("""\|'''''' \)'
18
17
let s: symbol= matchstr (&fillchars ,' fold:\zs.' )" handles multibyte characters
19
18
if s: symbol== ' '
20
19
let s: symbol= ' '
@@ -24,10 +23,10 @@ endif
24
23
25
24
fun ! pymode#folding#text ()" {{{
26
25
let fs = v: foldstart
27
- while getline (fs )!~ s: def_regex&& getline (fs )!~ s: doc_begin_regex
26
+ while getline (fs )!~ s: def_regex&& getline (fs )!~ s: docstring_begin_regex
28
27
let fs = nextnonblank (fs + 1 )
29
28
endwhile
30
- if getline (fs )= ~s: doc_end_regex && getline (fs )= ~s: doc_begin_regex
29
+ if getline (fs )= ~s: docstring_end_regex && getline (fs )= ~s: docstring_begin_regex
31
30
let fs = nextnonblank (fs + 1 )
32
31
endif
33
32
let line = getline (fs )
@@ -83,7 +82,11 @@ fun! pymode#folding#expr(lnum) "{{{
83
82
if decorated
84
83
return ' ='
85
84
else
85
+ " The line below may improve folding.
86
86
return " >" .(indent / &shiftwidth + 1 )
87
+ " This was the previous rule. It grouped classes definitions
88
+ " together (undesired).
89
+ " return indent / &shiftwidth + 1
87
90
endif
88
91
endif " }}}
89
92
@@ -95,80 +98,43 @@ fun! pymode#folding#expr(lnum) "{{{
95
98
96
99
" Notice that an effect of this is that other docstring matches will not
97
100
" be one liners.
98
- if line = ~s: doc_line_regex
101
+ if line = ~s: docstring_line_regex
99
102
return " ="
100
103
endif
101
104
102
- if line = ~s: doc_begin_regex
103
- " echom 'just entering'
105
+ if line = ~s: docstring_begin_regex
104
106
if s: Is_opening_folding (a: lnum )
105
- " echom 'entering at line ' . a:lnum
106
107
return " >" .(indent / &shiftwidth + 1 )
107
108
endif
108
109
endif
109
- if line = ~s: doc_end_regex
110
+ if line = ~s: docstring_end_regex
110
111
if ! s: Is_opening_folding (a: lnum )
111
- " echom 'leaving at line ' . a:lnum
112
112
return " <" .(indent / &shiftwidth + 1 )
113
113
endif
114
114
endif " }}}
115
115
116
- " Nested Definitions {{{
117
- " Handle nested defs but only for files shorter than
118
- " g:pymode_folding_nest_limit lines due to performance concerns
119
- if line (' $' ) < g: pymode_folding_nest_limit&& indent (prevnonblank (a: lnum ))
120
- let curpos= getpos (' .' )
121
- try
122
- let last_block= s: BlockStart (a: lnum )
123
- let last_block_indent= indent (last_block)
124
-
125
- " Check if last class/def is not indented and therefore can't be
126
- " nested.
127
- if last_block_indent
128
- call cursor (a: lnum ,0 )
129
- let next_def= searchpos (s: def_regex ,' nW' )[0 ]
130
- let next_def_indent= next_def ?indent (next_def) :-1
131
- let last_block_end= s: BlockEnd (last_block)
132
-
133
- " If the next def has greater indent than the previous def, it
134
- " is nested one level deeper and will have its own fold. If
135
- " the class/def containing the current line is on the first
136
- " line it can't be nested, and if this block ends on the last
137
- " line, it contains no trailing code that should not be
138
- " folded. Finally, if the next non-blank line after the end of
139
- " the previous def is less indented than the previous def, it
140
- " is not part of the same fold as that def. Otherwise, we know
141
- " the current line is at the end of a nested def.
142
- if next_def_indent<= last_block_indent&& last_block > 1 && last_block_end < line (' $' )
143
- \ && indent (nextnonblank (last_block_end))>= last_block_indent
144
-
145
- " Include up to one blank line in the fold
146
- if getline (last_block_end)= ~s: blank_regex
147
- let fold_end= min ([prevnonblank (last_block_end- 1 ), last_block_end])+ 1
148
- else
149
- let fold_end= last_block_end
150
- endif
151
- if a: lnum== fold_end
152
- return ' s1'
153
- else
154
- return ' ='
155
- endif
156
- endif
157
- endif
158
- finally
159
- call setpos (' .' , curpos)
160
- endtry
161
- endif " }}}
116
+ " Blocks. {{{
117
+ let save_cursor= getcurpos ()
118
+ if line !~ s: blank_regex
119
+ let line_block_start= s: BlockStart (a: lnum )
120
+ let prev_line_block_start= s: BlockStart (a: lnum- 1 )
121
+ call setpos (' .' , save_cursor)
122
+ if line_block_start== prev_line_block_start|| a: lnum- line_block_start== 1
123
+ return ' ='
124
+ elseif indent < indent (prevnonblank (a: lnum- 1 ))
125
+ return indent (line_block_start) / &shiftwidth + 1
126
+ else
127
+ endif
128
+ endif
129
+ " endif " }}}
162
130
163
131
" Blank Line {{{
164
132
if line = ~s: blank_regex
165
133
if prev_line= ~s: blank_regex
166
- if indent (a: lnum+ 1 )== 0 && next_line!~ s: blank_regex&& next_line!~ s: doc_general_regex
134
+ if indent (a: lnum+ 1 )== 0 && next_line!~ s: blank_regex&& next_line!~ s: docstring_general_regex
167
135
if s: Is_opening_folding (a: lnum )
168
- " echom a:lnum
169
136
return " ="
170
137
else
171
- " echom "not " . a:lnum
172
138
return 0
173
139
endif
174
140
endif
@@ -182,15 +148,25 @@ fun! pymode#folding#expr(lnum) "{{{
182
148
183
149
endfunction " }}}
184
150
185
- fun ! s: BlockStart (lnum)" {{{
151
+ fun ! s: BlockStart (line_number)" {{{
152
+ " Returns the definition statement which encloses the current line.
153
+
186
154
" Note: Make sure to reset cursor position after using this function.
187
- call cursor (a: lnum ,0 )
155
+ call cursor (a: line_number ,0 )
188
156
189
157
" In case the end of the block is indented to a higher level than the def
190
158
" statement plus one shiftwidth, we need to find the indent level at the
191
159
" bottom of that if/for/try/while/etc. block.
192
- let last_def= searchpos (s: def_regex ,' bcnW' )[0 ]
160
+ let previous_definition= searchpos (s: def_regex ,' bcnW' )
161
+ if previous_definition!= [0 ,0 ]
162
+ while previous_definition!= [0 ,0 ]&& indent (previous_definition[0 ])>= indent (a: line_number )
163
+ let previous_definition= searchpos (s: def_regex ,' bncW' )
164
+ call cursor (previous_definition[0 ]- 1 ,0 )
165
+ endwhile
166
+ endif
167
+ let last_def= previous_definition[0 ]
193
168
if last_def
169
+ call cursor (last_def,0 )
194
170
let last_def_indent= indent (last_def)
195
171
call cursor (last_def,0 )
196
172
let next_stmt_at_def_indent= searchpos (' \v^\s{' .last_def_indent.' }[^[:space:]#]' ,' nW' )[0 ]
@@ -200,19 +176,33 @@ fun! s:BlockStart(lnum) "{{{
200
176
201
177
" Now find the class/def one shiftwidth lower than the start of the
202
178
" aforementioned indent block.
203
- if next_stmt_at_def_indent&& next_stmt_at_def_indent < a: lnum
179
+ if next_stmt_at_def_indent&& next_stmt_at_def_indent < a: line_number
204
180
let max_indent= max ([indent (next_stmt_at_def_indent)- &shiftwidth ,0 ])
205
181
else
206
- let max_indent= max ([indent (prevnonblank (a: lnum ))- &shiftwidth ,0 ])
182
+ let max_indent= max ([indent (prevnonblank (a: line_number ))- &shiftwidth ,0 ])
207
183
endif
184
+
185
+ " " Debug:
186
+
208
187
return searchpos (' \v^\s{,' .max_indent.' }(def |class )\w' ,' bcnW' )[0 ]
188
+
209
189
endfunction " }}}
190
+ function ! Blockstart (x )
191
+ let save_cursor= getcurpos ()
192
+ return s: BlockStart (a: x )
193
+ call setpos (' .' , save_cursor)
194
+ endfunction
210
195
211
196
fun ! s: BlockEnd (lnum)" {{{
212
197
" Note: Make sure to reset cursor position after using this function.
213
198
call cursor (a: lnum ,0 )
214
199
return searchpos (' \v^\s{,' .indent (' .' ).' }\S' ,' nW' )[0 ]- 1
215
200
endfunction " }}}
201
+ function ! Blockend (lnum)
202
+ let save_cursor= getcurpos ()
203
+ return s: BlockEnd (a: lnum )
204
+ call setpos (' .' , save_cursor)
205
+ endfunction
216
206
217
207
function ! s: Is_opening_folding (lnum)" {{{
218
208
" Helper function to see if docstring is opening or closing
@@ -238,13 +228,11 @@ function! s:Is_opening_folding(lnum) "{{{
238
228
239
229
let i_line= getline (i )
240
230
241
- if i_line= ~s: doc_line_regex
242
- " echom "case 00 on line " . i
231
+ if i_line= ~s: docstring_line_regex
243
232
continue
244
233
endif
245
234
246
- if i_line= ~s: doc_begin_regex&& ! has_open_docstring
247
- " echom "case 01 on line " . i
235
+ if i_line= ~s: docstring_begin_regex&& ! has_open_docstring
248
236
" This causes the loop to continue if there is a triple quote which
249
237
" is not a docstring.
250
238
if extra_docstrings > 0
@@ -255,15 +243,13 @@ function! s:Is_opening_folding(lnum) "{{{
255
243
let number_of_folding= number_of_folding+ 1
256
244
endif
257
245
" If it is an end doc and has an open docstring.
258
- elseif i_line= ~s: doc_end_regex&& has_open_docstring
259
- " echom "case 02 on line " . i
246
+ elseif i_line= ~s: docstring_end_regex&& has_open_docstring
260
247
let has_open_docstring= 0
261
248
let number_of_folding= number_of_folding+ 1
262
249
263
- elseif i_line= ~s: doc_general_regex
264
- " echom "extra docstrings on line " . i
250
+ elseif i_line= ~s: docstring_general_regex
265
251
let extra_docstrings= extra_docstrings+ 1
266
- endif
252
+ endif
267
253
endfor
268
254
269
255
call add (b: fold_cache , number_of_folding% 2 )