1- " Fold routines for python code, version 2.5
1+ " Fold routines for python code, version 3.0.1
22" Source: http://www.vim.org/scripts/script.php?script_id=2527
3- " Last Change: 2009 Feb 6
3+ " Last Change: 2009 Feb 9
44" Author: Jurjen Bos
55" Bug fixes and helpful comments: Grissiom, David Froger, Andrew McNabb
66
77" Principles:
88" - a def/class starts a fold
99" a line with indent less than the previous def/class ends a fold
10- " empty lines are linked to the previous fold
11- " - optionally, you can get empty lines between folds, see (***)
12- " - another option is to ignore non-python files see (**)
13- " - you can also modify the def/class check, allowing for multiline def and class definitions
10+ " empty lines and comment lines are linked to the previous fold
1411" comment lines outside a def/class are never folded
1512" other lines outside a def/class are folded together as a group
16- " Note:
13+ " for algorithm, see bottom of script
14+
15+ " - optionally, you can get empty lines between folds, see (***)
16+ " - another option is to ignore non-python files see (**)
17+ " - you can also modify the def/class check,
18+ " allowing for multiline def and class definitions see (*)
19+
20+ " Note for vim 7 users:
1721" Vim 6 line numbers always take 8 columns, while vim 7 has a numberwidth variable
1822" you can change the 8 below to &numberwidth if you have vim 7,
1923" this is only really useful when you plan to use more than 8 columns (i.e. never)
20- " Note 2:
24+
25+ " Note for masochists trying to read this:
26+ " I wanted to keep the functions short, so I replaced occurences of
27+ " if condition
28+ " statement
29+ " by
30+ " if condition | statement
31+ " wherever I found that useful
32+
33+ " (*)
2134" class definitions are supposed to ontain a colon on the same line.
2235" function definitions are *not* required to have a colon, to allow for multiline defs.
2336" I you disagree, use instead of the pattern '^\s*\(class\s.*:\|def\s\)'
2437" to enforce : for defs: '^\s*\(class\|def\)\s.*:'
25- " to allow multiline class definitions: '^\s*\(class\|def\)\s'
2638" you'll have to do this in two places.
39+ let s: defpat = ' ^\s*\(class\s.*:\|def\s\)'
2740
2841" (**) Ignore non-python files
2942" Commented out because some python files are not recognized by Vim
@@ -39,9 +52,10 @@ function! PythonFoldText()
3952 let line = getline (v: foldstart )
4053 let nnum = nextnonblank (v: foldstart + 1 )
4154 let nextline = getline (nnum)
42- " get the document string.
55+ " get the document string: next line is ''' or """
4356 if nextline = ~ " ^\\ s\\ +[\" ']\\ {3}\\ s*$"
4457 let line = line . " " . matchstr (getline (nextnonblank (nnum + 1 )), ' ^\s*\zs.*\ze$' )
58+ " next line starts with qoutes, and has text
4559 elseif nextline = ~ " ^\\ s\\ +[\" ']\\ {1,3}"
4660 let line = line ." " .matchstr (nextline, " ^\\ s\\ +[\" ']\\ {1,3}\\ zs.\\ {-}\\ ze['\" ]\\ {0,3}$" )
4761 elseif nextline = ~ ' ^\s\+pass\s*$'
@@ -52,8 +66,7 @@ function! PythonFoldText()
5266 let size = 1 + v: foldend - v: foldstart
5367 " compute expansion string
5468 let spcs = ' ................'
55- while strlen (spcs) < w
56- let spcs = spcs . spcs
69+ while strlen (spcs) < w | let spcs = spcs . spcs
5770 endwhile
5871 " expand tabs (mail me if you have tabstop>10)
5972 let onetab = strpart (' ' , 0 , &tabstop )
@@ -69,16 +82,16 @@ function! GetBlockIndent(lnum)
6982 let p = a: lnum
7083 while indent (p ) >= 0
7184 let p = p - 1
72- " skip deeper lines, comments and empty lines
73- if indent (p ) >= ind || getline (p ) = ~ ' ^$\|^\s*#'
74- continue
85+ " skip empty and comment lines
86+ if getline (p ) = ~ ' ^$\|^\s*#' | continue
87+ " zero-level regular line
88+ elseif indent (p ) == 0 | return 0
89+ " skip deeper or equal lines
90+ elseif indent (p ) >= ind || getline (p ) = ~ ' ^$\|^\s*#' | continue
7591 " indent is strictly less at this point: check for def/class
76- elseif getline (p ) = ~ ' ^\s*\(class\s.*:\|def\s\) '
92+ elseif getline (p ) = ~ s: defpat
7793 " level is one more than this def/class
7894 return indent (p ) + &shiftwidth
79- " zero-level regular line
80- elseif indent (p ) == 0
81- return 0
8295 endif
8396 " shallower line that is neither class nor def: continue search at new level
8497 let ind = indent (p )
@@ -87,46 +100,116 @@ function! GetBlockIndent(lnum)
87100 return 0
88101endfunction
89102
103+ " Clever debug code, use as: call PrintIfCount(n,"Line: ".a:lnum.", value: ".x)
104+ let s: counter= 0
105+ function ! PrintIfCount (n ,t )
106+ " Print text the nth time this function is called
107+ let s: counter = s: counter+ 1
108+ if s: counter== a: n | echo a: t
109+ endif
110+ endfunction
111+
90112function ! GetPythonFold (lnum)
91- " Determine folding level in Python source
113+ " Determine folding level in Python source (see "higher foldlevel theory" below)
92114 let line = getline (a: lnum )
93115 let ind = indent (a: lnum )
94- " class and def start a fold
95- if line = ~ ' ^\s*\(class\s.*:\|def\s\)'
96- return " >" . (ind / &shiftwidth + 1 )
97- " (***) uncomment the next two lines if you want empty lines/comment out of a fold
98- " elseif line=~'^$\|^\s*#'
99- " return -1
100- " some speed optimizations for common cases: same level if:
101- " - indent positive and non-decreasing without def/class
102- " (don't change this def/class pattern even if you change the others!)
103- elseif ind>0 && ind>= indent (a: lnum- 1 ) && getline (a: lnum- 1 )!~ ' ^$\|^\s*\(def\|class\)\s'
104- return ' ='
105- " - empty lines before non-global lines
106- elseif line == ' ' && getline (a: lnum+ 1 ) !~ ' ^[^ \t#]'
107- return ' ='
108- " - global code
109- elseif line = ~ ' ^[^ \t#]'
110- return 1
116+ " Case D***: class and def start a fold
117+ if line = ~ s: defpat | return " >" . (ind / &shiftwidth + 1 )
118+ " Case E***: empty lines fold with previous
119+ " (***) change '=' to -1 if you want empty lines/comment out of a fold
120+ elseif line == ' ' | return ' ='
111121 endif
112-
113- " figure out the surrounding class/def block
122+ " now we need the indent from previous
123+ let p = prevnonblank (a: lnum- 1 )
124+ while p >0 && getline (p ) = ~ ' ^\s*#' | let p = prevnonblank (p - 1 )
125+ endwhile
126+ let pind = indent (p )
127+ " If previous was definition: count as one level deeper
128+ if getline (p ) = ~ s: defpat
129+ let pind = pind + &shiftwidth
130+ " if begin of file: take zero
131+ elseif p == 0 | let pind = 0
132+ endif
133+ " Case S*=* and C*=*: indent equal
134+ if ind>0 && ind== pind | return ' ='
135+ " Case S*>* and C*>*: indent increase
136+ elseif ind>pind | return ' ='
137+ " All cases with 0 indent
138+ elseif ind== 0
139+ " Case C*=0*: separate blocks
140+ if pind== 0 && line = ~ ' ^\s*#' | return 0
141+ " Case S*<0* and S*=0*
142+ elseif line !~ ' ^\s*#'
143+ " Case S*<0*: New global comment: start fold
144+ if 0 <pind | return '> 1 '
145+ " Case S*=0*, after level 0 comment
146+ elseif 0 == pind && getline (prevnonblank (a: lnum- 1 )) = ~ ' ^\s*#'
147+ return ' >1'
148+ " Case S*=0*, other, stay 1
149+ else | return ' ='
150+ endif
151+ endif
152+ " Case C*<0= and C*<0<: compute next indent
153+ let n = nextnonblank (a: lnum+ 1 )
154+ while n >0 && getline (n ) = ~' ^\s*#' | let n = nextnonblank (n + 1 )
155+ endwhile
156+ " Case C*<0=: split definitions
157+ if indent (n )== 0 | return 0
158+ " Case C*<0<: shallow comment
159+ else | return -1
160+ end
161+ endif
162+ " now we really need to compute the actual fold indent
163+ " do the hard computation
114164 let blockindent = GetBlockIndent (a: lnum )
115- " global code follows: end all blocks
116- if blockindent>0 && getline (a: lnum+ 1 ) = ~ ' ^[^ \t#]'
117- return ' <1'
118- " global code, with indented comments, form a block
119- elseif blockindent== 0 && line !~ ' ^#'
120- return 1
121- " regular line: deep line or non-comment line
122- elseif ind>= blockindent || line !~ ' ^\s*#'
123- return blockindent / &shiftwidth
165+ " Case SG<* and CG<*: global code, level 1
166+ if blockindent== 0 | return 1
124167 endif
125- " shallow comment: level is determined by next line
126- " search for next non-comment nonblank line
127- let n = nextnonblank (a: lnum + 1 )
128- while n >0 && getline (n ) = ~ ' ^\s*#\|^$'
129- let n = nextnonblank (n + 1 )
168+ " now we need the indent from next
169+ let n = nextnonblank (a: lnum+ 1 )
170+ while n >0 && getline (n ) = ~' ^\s*#' | let n = nextnonblank (n + 1 )
130171 endwhile
131- return GetBlockIndent (n ) / &shiftwidth
172+ let nind = indent (n )
173+ " Case CR<= and CR<>
174+ call PrintIfCount (60 , ' Line: ' .a: lnum .' , indent: ' .ind.' , n: ' .n .' , nind: ' .nind)
175+ if line = ~ ' ^\s*#' && ind>= nind | return -1
176+ " Case CR<<: return next indent
177+ elseif line = ~ ' ^\s*#' | return nind / &shiftwidth
178+ " Case SR<*: return actual indent
179+ else | return blockindent / &shiftwidth
180+ endif
132181endfunction
182+
183+ " higher foldlevel theory
184+ " There are four kinds of statements: S (code), D (def/class), E (empty), C (comment)
185+
186+ " There are two kinds of folds: R (regular), G (global statements)
187+
188+ " There are five indent situations with respect to the previous non-emtpy non-comment line:
189+ " > (indent), < (dedent), = (same); < and = combine with 0 (indent is zero)
190+ " Note: if the previous line is class/def, its indent is interpreted as one higher
191+
192+ " There are three indent situations with respect to the next (non-E non-C) line:
193+ " > (dedent), < (indent), = (same)
194+
195+ " Situations (in order of frequency):
196+ " stat fold prev next
197+ " SDEC RG ><=00 ><=
198+ " D * * * begin fold level: '>'.ind/&sw+1
199+ " E * * * keep with previous: '='
200+ " S * = * stays the same: '='
201+ " C * = * combine with previous: '='
202+ " S * > * stays the same: '='
203+ " C * > * combine with previous: '='
204+ " C * =0 * separate blocks: 0
205+ " S * <0 * wordt nieuwe 1: >1
206+ " S * =0 * stays 1: '=' (after level 0 comment: '>1')
207+ " C * <0 = split definitions: 0
208+ " C * <0 < shallow comment: -1
209+ " C * <0 > [never occurs]
210+ " S G < * global, not the first: 1
211+ " C G < * indent isn't 0: 1
212+ " C R < = foldlevel as computed for next line: -1
213+ " C R < > foldlevel as computed for next line: -1
214+ " S R < * compute foldlevel the hard way: use function
215+ " C R < < foldlevel as computed for this line: use function
0 commit comments