ref: a209ae15974071706783e535d2ddde126e198dd8
vim/ftplugin/python/folding.vim
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
" Fold routines for python code, version 3.2 " Source: http://www.vim.org/scripts/script.php?script_id=2527 " Last Change: 2009 Feb 25 " Author: Jurjen Bos " Bug fixes and helpful comments: Grissiom, David Froger, Andrew McNabb " Principles: " - a def/class starts a fold " a line with indent less than the previous def/class ends a fold " empty lines and comment lines are linked to the previous fold " comment lines outside a def/class are never folded " other lines outside a def/class are folded together as a group " for algorithm, see bottom of script " - optionally, you can get empty lines between folds, see (***) " - another option is to ignore non-python files see (**) " - you can also modify the def/class check, " allowing for multiline def and class definitions see (*) " Note for vim 7 users: " Vim 6 line numbers always take 8 columns, while vim 7 has a numberwidth variable " you can change the 8 below to &numberwidth if you have vim 7, " this is only really useful when you plan to use more than 8 columns (i.e. never) " Note for masochists trying to read this: " I wanted to keep the functions short, so I replaced occurences of " if condition " statement " by " if condition | statement " wherever I found that useful " (*) " class definitions are supposed to ontain a colon on the same line. " function definitions are *not* required to have a colon, to allow for multiline defs. " I you disagree, use instead of the pattern '^\s*\(class\s.*:\|def\s\)' " to enforce : for defs: '^\s*\(class\|def\)\s.*:' " you'll have to do this in two places. let s:defpat = '^\s*\(@\|class\s.*:\|def\s\)' " (**) Ignore non-python files " Commented out because some python files are not recognized by Vim if &filetype != 'python' finish endif setlocal foldmethod=expr setlocal foldexpr=GetPythonFold(v:lnum) setlocal foldtext=PythonFoldText() function! PythonFoldText() " ignore decorators let fs = v:foldstart while getline(fs) =~ '^\s*@' | let fs = nextnonblank(fs + 1) endwhile let line = getline(fs) let nucolwidth = &fdc + &number * &numberwidth let windowwidth = winwidth(0) - nucolwidth - 3 let foldedlinecount = v:foldend - v:foldstart " expand tabs into spaces let onetab = strpart(' ', 0, &tabstop) let line = substitute(line, '\t', onetab, 'g') let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount)) let fillcharcount = windowwidth - len(line) - len(foldedlinecount) return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' ' endfunction function! PythonFoldTextDocstrings() " ignore decorators let fs = v:foldstart while getline(fs) =~ '^\s*@' | let fs = nextnonblank(fs + 1) endwhile " add docstrings let line = getline(fs) if getline(fs + 1) =~ '^\s*"""' " Not sure which wrapping symbols I like yet. " «»≺≻⸮⸫ let line = line . " «" . getline(fs + 1) . "»" let line = substitute(line, '\s*"""', '', 'g') let line = substitute(line, '"""', '', 'g') endif let nucolwidth = &fdc + &number * &numberwidth let windowwidth = winwidth(0) - nucolwidth - 3 let foldedlinecount = v:foldend - v:foldstart " expand tabs into spaces let onetab = strpart(' ', 0, &tabstop) let line = substitute(line, '\t', onetab, 'g') let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount)) let fillcharcount = windowwidth - len(line) - len(foldedlinecount) return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' ' endfunction function! GetBlockIndent(lnum) " Auxiliary function; determines the indent level of the surrounding def/class " "global" lines are level 0, first def &shiftwidth, and so on " scan backwards for class/def that is shallower or equal let ind = 100 let p = a:lnum+1 while indent(p) >= 0 let p = p - 1 " skip empty and comment lines if getline(p) =~ '^$\|^\s*#' | continue " zero-level regular line elseif indent(p) == 0 | return 0 " skip deeper or equal lines elseif indent(p) >= ind || getline(p) =~ '^$\|^\s*#' | continue " indent is strictly less at this point: check for def/class elseif getline(p) =~ s:defpat && getline(p) !~ '^\s*@' " level is one more than this def/class return indent(p) + &shiftwidth endif " shallower line that is neither class nor def: continue search at new level let ind = indent(p) endwhile "beginning of file return 0 endfunction " Clever debug code, use as: call PrintIfCount(n,"Line: ".a:lnum.", value: ".x) let s:counter=0 function! PrintIfCount(n,t) "Print text the nth time this function is called let s:counter = s:counter+1 if s:counter==a:n | echo a:t endif endfunction function! GetPythonFold(lnum) " Determine folding level in Python source (see "higher foldlevel theory" below) let line = getline(a:lnum) let ind = indent(a:lnum) " Case D***: class and def start a fold " If previous line is @, it is not the first if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@' " let's see if this range of 0 or more @'s end in a class/def let n = a:lnum while getline(n) =~ '^\s*@' | let n = nextnonblank(n + 1) endwhile " yes, we have a match: this is the first of a real def/class with decorators if getline(n) =~ s:defpat return ">".(ind/&shiftwidth+1) endif " Case E***: empty lines fold with previous " (***) change '=' to -1 if you want empty lines/comment out of a fold elseif line == '' && getline(a:lnum-1) == '' | return '-1' elseif line == '' && getline(a:lnum-1) != '' | return '=' endif " now we need the indent from previous let p = prevnonblank(a:lnum-1) while p>0 && getline(p) =~ '^\s*#' | let p = prevnonblank(p-1) endwhile let pind = indent(p) " If previous was definition: count as one level deeper if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@' let pind = pind + &shiftwidth " if begin of file: take zero elseif p==0 | let pind = 0 endif " Case S*=* and C*=*: indent equal if ind>0 && ind==pind | return '=' " Case S*>* and C*>*: indent increase elseif ind>pind | return '=' " All cases with 0 indent elseif ind==0 " Case C*=0*: separate global code blocks if pind==0 && line =~ '^#' | return 0 " Case S*<0* and S*=0*: global code elseif line !~'^#' " Case S*<0*: new global statement if/while/for/try/with if 0<pind && line!~'^else\s*:\|^except.*:\|^elif.*:\|^finally\s*:' | return '>1' " Case S*=0*, after level 0 comment elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' | return '>1' " Case S*=0*, other, stay 1 else | return '=' endif endif " Case C*<0= and C*<0<: compute next indent let n = nextnonblank(a:lnum+1) while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) endwhile " Case C*<0=: split definitions if indent(n)==0 | return 0 " Case C*<0<: shallow comment else | return -1 end endif " now we really need to compute the actual fold indent " do the hard computation let blockindent = GetBlockIndent(a:lnum) " Case SG<* and CG<*: global code, level 1 if blockindent==0 | return 1 endif " now we need the indent from next let n = nextnonblank(a:lnum+1) while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) endwhile let nind = indent(n) " Case CR<= and CR<> "if line !~ '^\s*#' | call PrintIfCount(4,"Line: ".a:lnum.", blockindent: ".blockindent.", n: ".n.", nind: ".nind.", p: ".p.", pind: ".pind) endif if line =~ '^\s*#' && ind>=nind | return -1 " Case CR<<: return next indent elseif line =~ '^\s*#' | return nind / &shiftwidth " Case SR<*: return actual indent else | return blockindent / &shiftwidth endif endfunction " higher foldlevel theory " There are five kinds of statements: S (code), D (def/class), E (empty), C (comment) " Note that a decorator statement (beginning with @) counts as definition, " but that of a sequence of @,@,@,def only the first one counts " This means that a definiion only counts if not preceded by a decorator " There are two kinds of folds: R (regular), G (global statements) " There are five indent situations with respect to the previous non-emtpy non-comment line: " > (indent), < (dedent), = (same); < and = combine with 0 (indent is zero) " Note: if the previous line is class/def, its indent is interpreted as one higher " There are three indent situations with respect to the next (non-E non-C) line: " > (dedent), < (indent), = (same) " Situations (in order of the script): " stat fold prev next " SDEC RG ><=00 ><= " D * * * begin fold level if previous is not @: '>'.ind/&sw+1 " E * * * keep with previous: '=' " S * = * stays the same: '=' " C * = * combine with previous: '=' " S * > * stays the same: '=' " C * > * combine with previous: '=' " C * =0 * separate blocks: 0 " S * <0 * becomes new level 1: >1 (except except/else: 1) " S * =0 * stays 1: '=' (after level 0 comment: '>1') " C * <0 = split definitions: 0 " C * <0 < shallow comment: -1 " C * <0 > [never occurs] " S G < * global, not the first: 1 " C G < * indent isn't 0: 1 " C R < = foldlevel as computed for next line: -1 " C R < > foldlevel as computed for next line: -1 " S R < * compute foldlevel the hard way: use function " C R < < foldlevel as computed for this line: use function |