1- k " Python-mode folding function2
21" Notice that folding is based on single line so complex regular expressions
32" that take previous line into consideration are not fit for the job.
43
@@ -47,25 +46,44 @@ endfunction "}}}
4746
4847fun ! pymode#folding#expr (lnum) " {{{
4948
49+ return pymode#folding#foldcase (a: lnum )[' foldlevel' ]
50+
51+ endfunction " }}}
52+
53+ fun ! pymode#folding#foldcase (lnum) " {{{
54+ " Return a dictionary with a brief description of the foldcase and the
55+ " evaluated foldlevel: {'foldcase': 'case description', 'foldlevel': 1}.
56+
57+ let l: foldcase = ' general'
58+ let l: foldlevel = 0
59+
5060 let line = getline (a: lnum )
5161 let indent = indent (a: lnum )
5262 let prev_line = getline (a: lnum - 1 )
5363 let next_line = getline (a: lnum + 1 )
5464
5565 " Decorators {{{
5666 if line = ~ s: decorator_regex
57- return " >" .(indent / &shiftwidth + 1 )
67+ let l: foldcase = ' decorator declaration'
68+ let l: foldlevel = ' >' .(indent / &shiftwidth + 1 )
69+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
5870 endif " }}}
5971
6072 " Definition {{{
6173 if line = ~ s: def_regex
74+
75+ " TODO: obscure case.
6276 " If indent of this line is greater or equal than line below
6377 " and previous non blank line does not end with : (that is, is not a
6478 " definition)
6579 " Keep the same indentation
66- if indent (a: lnum ) >= indent (a: lnum+ 1 ) && getline (prevnonblank (a: lnum )) !~ ' :\s*$'
67- return ' ='
68- endif
80+ " xxx " if indent(a:lnum) >= indent(a:lnum+1)
81+ " xxx " \ && getline(prevnonblank(a:lnum)) !~ ':\s*$'
82+ " xxx " let l:foldcase = 'definition'
83+ " xxx " let l:foldlevel = '='
84+ " xxx " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel}
85+ " xxx " endif
86+
6987 " Check if last decorator is before the last def
7088 let decorated = 0
7189 let lnum = a: lnum - 1
@@ -79,14 +97,13 @@ fun! pymode#folding#expr(lnum) "{{{
7997 let lnum -= 1
8098 endwhile
8199 if decorated
82- return ' ='
100+ let l: foldcase = ' decorated function declaration'
101+ let l: foldlevel = ' ='
83102 else
84- " The line below may improve folding.
85- return " >" .(indent / &shiftwidth + 1 )
86- " This was the previous rule. It grouped classes definitions
87- " together (undesired).
88- " return indent / &shiftwidth + 1
103+ let l: foldcase = ' function declaration'
104+ let l: foldlevel = ' >' .(indent / &shiftwidth + 1 )
89105 endif
106+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
90107 endif " }}}
91108
92109 " Docstrings {{{
@@ -98,60 +115,105 @@ fun! pymode#folding#expr(lnum) "{{{
98115 " Notice that an effect of this is that other docstring matches will not
99116 " be one liners.
100117 if line = ~ s: docstring_line_regex
101- return " ="
118+ let l: foldcase = ' one-liner docstring'
119+ let l: foldlevel = ' ='
120+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
102121 endif
103-
104122 if line = ~ s: docstring_begin_regex
105123 if s: Is_opening_folding (a: lnum )
106- return " >" .(indent / &shiftwidth + 1 )
124+ let l: foldcase = ' open multiline docstring'
125+ let l: foldlevel = ' a1'
107126 endif
127+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
108128 endif
109129 if line = ~ s: docstring_end_regex
110130 if ! s: Is_opening_folding (a: lnum )
111- return " <" .(indent / &shiftwidth + 1 )
131+ let l: foldcase = ' close multiline docstring'
132+ let l: foldlevel = ' s1'
112133 endif
134+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
113135 endif " }}}
114136
115137 " Blocks. {{{
138+ let line_block_start = s: BlockStart (a: lnum )
139+ let line_block_end = s: BlockEnd (a: lnum )
140+ let prev_line_block_start = s: BlockStart (a: lnum - 1 )
116141 let save_cursor = getcurpos ()
117142 if line !~ s: blank_regex
118- let line_block_start = s: BlockStart (a: lnum )
119- let prev_line_block_start = s: BlockStart (a: lnum - 1 )
120143 call setpos (' .' , save_cursor)
121- if line_block_start == prev_line_block_start || a: lnum - line_block_start == 1
122- return ' ='
144+ if line_block_start == prev_line_block_start
145+ \ || a: lnum - line_block_start == 1
146+ let l: foldcase = ' non blank line; first line of block or part of it'
147+ let l: foldlevel = ' ='
123148 elseif indent < indent (prevnonblank (a: lnum - 1 ))
124- return indent (line_block_start) / &shiftwidth + 1
125- else
149+ if indent == 0
150+ let l: foldcase = ' non blank line; zero indent'
151+ let l: foldlevel = 0
152+ else
153+ let l: foldcase = ' non blank line; non zero indent'
154+ let l: foldlevel = indent (line_block_start) / &shiftwidth + 1
155+ endif
126156 endif
157+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
127158 endif
128159 " endif " }}}
129160
130161 " Blank Line {{{
162+ " Comments: cases of blank lines:
163+ " 1. After non blank line: gets folded with previous line.
164+ " 1. Just after a block; in this case it gets folded with the block.
165+ " 1. Between docstrings and imports.
166+ " 1. Inside docstrings.
167+ " 2. Inside functions/methods.
168+ " 3. Between functions/methods.
131169 if line = ~ s: blank_regex
132- if prev_line = ~ s: blank_regex
133- if indent (a: lnum + 1 ) == 0 && next_line !~ s: blank_regex && next_line !~ s: docstring_general_regex
134- if s: Is_opening_folding (a: lnum )
135- return " ="
136- else
137- return 0
138- endif
139- endif
140- return -1
141- else
142- return ' ='
170+ if prev_line !~ s: blank_regex
171+ let l: foldcase = ' blank line after non blank line'
172+ let l: foldlevel = ' ='
173+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
174+ elseif a: lnum > line_block_start && a: lnum < line_block_end
175+ let l: foldcase = ' blank line inside block'
176+ let l: foldlevel = ' ='
177+ return {' foldcase' : l: foldcase , ' foldlevel' : l: foldlevel }
143178 endif
179+ " if prev_line =~ s:blank_regex
180+ " if indent(a:lnum + 1) == 0 && next_line !~ s:blank_regex && next_line !~ s:docstring_general_regex
181+ " if s:Is_opening_folding(a:lnum)
182+ " let l:foldcase = 'case 1'
183+ " let l:foldlevel = '='
184+ " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel}
185+ " else
186+ " let l:foldcase = 'case 2'
187+ " let l:foldlevel = 0
188+ " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel}
189+ " endif
190+ " endif
191+ " let l:foldcase = 'case 3'
192+ " let l:foldlevel = -1
193+ " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel}
194+ " else
195+ " let l:foldcase = 'case 4'
196+ " let l:foldlevel = '='
197+ " return {'foldcase': l:foldcase, 'foldlevel': l:foldlevel}
198+ " endif
144199 endif " }}}
145200
146- return ' = '
201+ return { ' foldcase ' : l: foldcase , ' foldlevel ' : l: foldlevel }
147202
148203endfunction " }}}
149204
150- fun ! s: BlockStart (line_number ) " {{{
205+ fun ! s: BlockStart (lnum ) " {{{
151206 " Returns the definition statement which encloses the current line.
152207
208+ let line = getline (a: lnum )
209+ if line !~ s: blank_regex
210+ let l: inferred_indent = indent (a: lnum )
211+ else
212+ let l: inferred_indent = prevnonblank (a: lnum )
213+ endif
214+
153215 " Note: Make sure to reset cursor position after using this function.
154- call cursor (a: line_number , 0 )
216+ call cursor (a: lnum , 0 )
155217
156218 " In case the end of the block is indented to a higher level than the def
157219 " statement plus one shiftwidth, we need to find the indent level at the
@@ -162,7 +224,9 @@ fun! s:BlockStart(line_number) "{{{
162224 " W: don't Wrap around the end of the file
163225 let previous_definition = searchpos (s: def_regex , ' bnW' )
164226 if previous_definition != [0 , 0 ]
165- while previous_definition != [0 , 0 ] && indent (previous_definition[0 ]) >= indent (a: line_number )
227+ " Lines that are blank have zero indent.
228+ while previous_definition != [0 , 0 ]
229+ \ && indent (previous_definition[0 ]) >= l: inferred_indent
166230 let previous_definition = searchpos (s: def_regex , ' bnW' )
167231 call cursor (previous_definition[0 ] - 1 , 0 )
168232 endwhile
@@ -179,13 +243,15 @@ fun! s:BlockStart(line_number) "{{{
179243
180244 " Now find the class/def one shiftwidth lower than the start of the
181245 " aforementioned indent block.
182- if next_stmt_at_def_indent && next_stmt_at_def_indent < a: line_number
246+ if next_stmt_at_def_indent && ( next_stmt_at_def_indent < a: lnum )
183247 let max_indent = max ([indent (next_stmt_at_def_indent) - &shiftwidth , 0 ])
184248 else
185- let max_indent = max ([indent (prevnonblank (a: line_number )) - &shiftwidth , 0 ])
249+ let max_indent = max ([indent (prevnonblank (a: lnum )) - &shiftwidth , 0 ])
186250 endif
187251
188- return searchpos (' \v^\s{,' .max_indent.' }(def |class )\w' , ' bcnW' )[0 ]
252+ let result = searchpos (' \v^\s{,' .max_indent.' }(def |class )\w' , ' bcnW' )[0 ]
253+
254+ return result
189255
190256endfunction " }}}
191257function ! Blockstart (x )
@@ -206,56 +272,55 @@ function! Blockend(lnum)
206272endfunction
207273
208274function ! s: Is_opening_folding (lnum) " {{{
209- " Helper function to see if docstring is opening or closing
275+ " Helper function to see if multi line docstring is opening or closing.
210276
211- " Cache the result so the loop runs only once per change
277+ " Cache the result so the loop runs only once per change.
212278 if get (b: , ' fold_changenr' , -1 ) == changenr ()
213- return b: fold_cache [a: lnum ] " If odd then it is an opening
279+ return b: fold_cache [a: lnum - 1 ] " If odd then it is an opening
214280 else
215281 let b: fold_changenr = changenr ()
216282 let b: fold_cache = []
217283 endif
218284
219- let number_of_folding = 0 " To be analized if odd/even to inform if it is opening or closing.
220- let has_open_docstring = 0 " To inform is already has an open docstring.
221- let extra_docstrings = 0 " To help skipping ''' and " " " which are not docstrings
285+ " To be analized if odd/even to inform if it is opening or closing.
286+ let fold_odd_even = 0
287+ " To inform is already has an open docstring.
288+ let has_open_docstring = 0
289+ " To help skipping ''' and """ which are not docstrings.
290+ let extra_docstrings = 0
222291
223292 " The idea of this part of the function is to identify real docstrings and
224293 " not just triple quotes (that could be a regular string).
225- "
294+
226295 " Iterater over all lines from the start until current line (inclusive)
227296 for i in range (1 , line (' $' ))
228- call add (b: fold_cache , number_of_folding % 2 )
229297
230298 let i_line = getline (i )
231299
232- if i_line = ~ s: docstring_line_regex
233- continue
234- endif
235-
236300 if i_line = ~ s: docstring_begin_regex && ! has_open_docstring
237301 " This causes the loop to continue if there is a triple quote which
238302 " is not a docstring.
239303 if extra_docstrings > 0
240304 let extra_docstrings = extra_docstrings - 1
241- continue
242305 else
243306 let has_open_docstring = 1
244- let number_of_folding = number_of_folding + 1
307+ let fold_odd_even = fold_odd_even + 1
245308 endif
246309 " If it is an end doc and has an open docstring.
247310 elseif i_line = ~ s: docstring_end_regex && has_open_docstring
248311 let has_open_docstring = 0
249- let number_of_folding = number_of_folding + 1
312+ let fold_odd_even = fold_odd_even + 1
250313
251314 elseif i_line = ~ s: docstring_general_regex
252315 let extra_docstrings = extra_docstrings + 1
253316 endif
254- endfor
255317
256- call add (b: fold_cache , number_of_folding % 2 )
318+ call add (b: fold_cache , fold_odd_even % 2 )
319+
320+ endfor
257321
258322 return b: fold_cache [a: lnum ]
323+
259324endfunction " }}}
260325
261326" vim: fdm = marker:fdl = 0
0 commit comments