11" Python-mode folding functions
2-
32" Notice that folding is based on single line so complex regular expressions
43" that take previous line into consideration are not fit for the job.
54
@@ -8,13 +7,13 @@ let s:def_regex = g:pymode_folding_regex
87let s: blank_regex = ' ^\s*$'
98" Spyder, a very popular IDE for python has a template which includes
109" '@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*$'
1414" This one is needed for the while loop to count for opening and closing
1515" docstrings.
16- let s: doc_general_regex = ' \%("""\|'''''' \)'
17- let s: doc_line_regex = ' ^\s*[uUrR]\=\("""\|'''''' \).\+\1\s*$'
16+ let s: docstring_general_regex = ' \%("""\|'''''' \)'
1817let s: symbol = matchstr (&fillchars , ' fold:\zs.' ) " handles multibyte characters
1918if s: symbol == ' '
2019 let s: symbol = ' '
@@ -24,10 +23,10 @@ endif
2423
2524fun ! pymode#folding#text () " {{{
2625 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
2827 let fs = nextnonblank (fs + 1 )
2928 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
3130 let fs = nextnonblank (fs + 1 )
3231 endif
3332 let line = getline (fs )
@@ -83,7 +82,11 @@ fun! pymode#folding#expr(lnum) "{{{
8382 if decorated
8483 return ' ='
8584 else
85+ " The line below may improve folding.
8686 return " >" .(indent / &shiftwidth + 1 )
87+ " This was the previous rule. It grouped classes definitions
88+ " together (undesired).
89+ " return indent / &shiftwidth + 1
8790 endif
8891 endif " }}}
8992
@@ -95,80 +98,43 @@ fun! pymode#folding#expr(lnum) "{{{
9598
9699 " Notice that an effect of this is that other docstring matches will not
97100 " be one liners.
98- if line = ~ s: doc_line_regex
101+ if line = ~ s: docstring_line_regex
99102 return " ="
100103 endif
101104
102- if line = ~ s: doc_begin_regex
103- " echom 'just entering'
105+ if line = ~ s: docstring_begin_regex
104106 if s: Is_opening_folding (a: lnum )
105- " echom 'entering at line ' . a:lnum
106107 return " >" .(indent / &shiftwidth + 1 )
107108 endif
108109 endif
109- if line = ~ s: doc_end_regex
110+ if line = ~ s: docstring_end_regex
110111 if ! s: Is_opening_folding (a: lnum )
111- " echom 'leaving at line ' . a:lnum
112112 return " <" .(indent / &shiftwidth + 1 )
113113 endif
114114 endif " }}}
115115
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 " }}}
162130
163131 " Blank Line {{{
164132 if line = ~ s: blank_regex
165133 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
167135 if s: Is_opening_folding (a: lnum )
168- " echom a:lnum
169136 return " ="
170137 else
171- " echom "not " . a:lnum
172138 return 0
173139 endif
174140 endif
@@ -182,15 +148,25 @@ fun! pymode#folding#expr(lnum) "{{{
182148
183149endfunction " }}}
184150
185- fun ! s: BlockStart (lnum) " {{{
151+ fun ! s: BlockStart (line_number) " {{{
152+ " Returns the definition statement which encloses the current line.
153+
186154 " Note: Make sure to reset cursor position after using this function.
187- call cursor (a: lnum , 0 )
155+ call cursor (a: line_number , 0 )
188156
189157 " In case the end of the block is indented to a higher level than the def
190158 " statement plus one shiftwidth, we need to find the indent level at the
191159 " 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 ]
193168 if last_def
169+ call cursor (last_def, 0 )
194170 let last_def_indent = indent (last_def)
195171 call cursor (last_def, 0 )
196172 let next_stmt_at_def_indent = searchpos (' \v^\s{' .last_def_indent.' }[^[:space:]#]' , ' nW' )[0 ]
@@ -200,19 +176,33 @@ fun! s:BlockStart(lnum) "{{{
200176
201177 " Now find the class/def one shiftwidth lower than the start of the
202178 " 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
204180 let max_indent = max ([indent (next_stmt_at_def_indent) - &shiftwidth , 0 ])
205181 else
206- let max_indent = max ([indent (prevnonblank (a: lnum )) - &shiftwidth , 0 ])
182+ let max_indent = max ([indent (prevnonblank (a: line_number )) - &shiftwidth , 0 ])
207183 endif
184+
185+ " " Debug:
186+
208187 return searchpos (' \v^\s{,' .max_indent.' }(def |class )\w' , ' bcnW' )[0 ]
188+
209189endfunction " }}}
190+ function ! Blockstart (x )
191+ let save_cursor = getcurpos ()
192+ return s: BlockStart (a: x )
193+ call setpos (' .' , save_cursor)
194+ endfunction
210195
211196fun ! s: BlockEnd (lnum) " {{{
212197 " Note: Make sure to reset cursor position after using this function.
213198 call cursor (a: lnum , 0 )
214199 return searchpos (' \v^\s{,' .indent (' .' ).' }\S' , ' nW' )[0 ] - 1
215200endfunction " }}}
201+ function ! Blockend (lnum)
202+ let save_cursor = getcurpos ()
203+ return s: BlockEnd (a: lnum )
204+ call setpos (' .' , save_cursor)
205+ endfunction
216206
217207function ! s: Is_opening_folding (lnum) " {{{
218208 " Helper function to see if docstring is opening or closing
@@ -238,13 +228,11 @@ function! s:Is_opening_folding(lnum) "{{{
238228
239229 let i_line = getline (i )
240230
241- if i_line = ~ s: doc_line_regex
242- " echom "case 00 on line " . i
231+ if i_line = ~ s: docstring_line_regex
243232 continue
244233 endif
245234
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
248236 " This causes the loop to continue if there is a triple quote which
249237 " is not a docstring.
250238 if extra_docstrings > 0
@@ -255,15 +243,13 @@ function! s:Is_opening_folding(lnum) "{{{
255243 let number_of_folding = number_of_folding + 1
256244 endif
257245 " 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
260247 let has_open_docstring = 0
261248 let number_of_folding = number_of_folding + 1
262249
263- elseif i_line = ~ s: doc_general_regex
264- " echom "extra docstrings on line " . i
250+ elseif i_line = ~ s: docstring_general_regex
265251 let extra_docstrings = extra_docstrings + 1
266- endif
252+ endif
267253 endfor
268254
269255 call add (b: fold_cache , number_of_folding % 2 )
0 commit comments