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 )
0 commit comments