SimpleFold 0.4.0 and visual comparison of (vim) folding methods
0.4.0b: quick bugfix release fixing issues with non-Ruby files, and defaulting to closing folds after <Leader>f. Also a few words about how to use the :Fold command as a sort of "internal grep" (a tribute to the vimtip this was originally based on).
I've been reworking my vim folding script for Ruby. It stays within the usability subspace I like (optimized vertical space, sensible foldtext, fold on classes, methods, etc.), but now it can also fold optionally on if/while and friends, and also supports folds set with markers. It is now available as a plugin, so installation is as easy as copying it into ~/.vim/plugin/; the default mapping is <Leader>f (i.e. \f if you didn't change mapleader).
Here's a few animations comparing it to the 'syntax' and 'marker' methods.
Syntax vs. SimpleFold
Both images correspond to the same files; I've timed them carefully to show equivalent views
(top-level, class-level, method-level) at a time (you might have to reload the page to make
sure they play synchronously). The first one is fdm=syntax, the second is SimpleFold's

- syntax doesn't fold comments, so the window covers but a small part of the buffer even when all folds are closed
- syntax uses nested folds for nested classes/modules/methods; SimpleFold makes them all top-level so there's no need to open class/module folds to see their methods (fdm=syntax with foldnestmax is not enough)
- syntax folds on lots of things, which is good or bad depending on your preferences
Marker vs. Simplefold

- markers are robust and portable, but need some work and pollute the code
- SimpleFold is almost a superset of fdm=marker because you can specify nestable folds with
# open a fold {{{{
.... stuff
# and now close it }}}} (the markers can be anywhere in the line)
(yes, FOUR braces, so that legacy markers are ignored) and top-level ones with
#{{{ # this line must match ^\s*\{\{\{
code
#}}} # and this one ^\s*\{\{\{
# again, to ignore legacy markers
- SimpleFold optimizes vertical space usage: no markers (by default) cluttering the code. This, and the "sticky comments" (which get folded along with the next method/class/etc) plus the use of foldtext help achieve over 66% vertical space savings in the "class-level" view.
Download
Just copy it to the plugin directory (e.g. ~/.vim/plugin), and use <Leader>f to fold.
You can also use
:Fold UNQUOTED-REGEXP
to create folds showing the lines matching the given regexp, so that everything else is hidden; e.g.
:Fold \vfoo(bar|baz)
will create folds starting at each line matching foo(bar|baz), so that these are the only lines you see in your buffer (it's effectively a sort of internal grep).
This vimscript is fairly tricky; I consider it BETA until I get some feedback, so please report any problems (preferably along with the code that triggers the error).
" Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
" Plugin for simple search-based folding
" Designed for use with Ruby, but can be tailored to other filetypes.
" Version: 0.4.0b 2006-05-12
" Author: Mauricio Fernandez <mfp@acm.org>
" Maintainer: Mauricio Fernandez <mfp@acm.org> http://eigenclass.org
" License: Ruby's license (dual GPL/"Ruby artistic license")
"
" Mappings and commands
" ---------------------
" Defines the :Fold command; use it as
" :Fold \v^function
" You can try it with this very file to see what happens.
" The default mapping to fold the current file using the default fold
" expression (more on this below) is
" map <unique> <silent> <Leader>f <Plug>SimpleFold_Foldsearch
" =========
" i.e. \f unless you changed mapleader. You can copy the above mapping to your
" .vimrc and modify it as desired.
"
" Options
" -------
" By default, secondary, nestable subfolds will be created for the supported
" filetypes (read below to see how this is controlled by the associated fold
" expressions). You can turn that off with:
" let g:SimpleFold_use_subfolds = 0
"
" Fold expressions
" ----------------
" The default fold expression for most filetypes is
" let b:simplefold_expr = '\v^\s*[#%"0-9]{0,4}\s*\{(\{\{|!!)'
" The expressions for the extra marker-based folding phase are:
" let b:simplefold_marker_start = '\v\{\{\{\{'
" let b:simplefold_marker_end = '\v\}\}\}\}'
"
" You can tailor the fold expressions to other filetypes, taking the
" expressions for Ruby as an example:
"
" au Filetype ruby let b:simplefold_expr =
" \'\v(^\s*(def|class|module|attr_reader|attr_accessor|alias_method|' .
" \ 'attr|module_function' . ')\s' .
" \ '\v^\s*(public|private|protected)>' .
" \ '|^\s*\w+attr_(reader|accessor)\s|^\s*[#%"0-9]{0,4}\s*\{\{\{[^{])' .
" \ '|^\s*[A-Z]\w+\s*\=[^=]'
" au Filetype ruby let b:simplefold_nestable_start_expr =
" \ '\v^\s*(def>|if>|unless>|while>.*(<do>)?|' .
" \ 'until>.*(<do>)?|case>|for>|begin>)' .
" \ '|^[^#]*.*<do>\s*(\|.*\|)?'
" au Filetype ruby let b:simplefold_nestable_end_expr =
" \ '\v^\s*end'
"
" Here's the (simpler) setup for Java:
" Java support
" au Filetype java let b:simplefold_expr =
" \ '\(^\s*\(\(private\|public\|protected\|class\)\>\)\)'
if exists("loaded_simplefold")
finish
endif
let loaded_simplefold = 1
let s:save_cpo = &cpo
set cpo&vim
"{{{ set s:sid
map <SID>xx <SID>xx
let s:sid = maparg("<SID>xx")
unmap <SID>xx
let s:sid = substitute(s:sid, 'xx', '', '')
"{{{ FoldText
function! s:Num2S(num, len)
let filler = " "
let text = '' . a:num
return strpart(filler, 1, a:len - strlen(text)) . text
endfunction
function! s:SimpleFold_FoldText()
let linenum = v:foldstart
if match(getline(linenum), b:simplefold_marker_start) != -1
let line = getline(linenum)
else
while linenum <= v:foldend
let line = getline(linenum)
if !exists("b:simplefold_prefix") || match(line, b:simplefold_prefix) == -1
break
else
let linenum = linenum + 1
endif
endwhile
if exists("b:simplefold_prefix") && match(line, b:simplefold_prefix) != -1
" all lines matched the prefix regexp
let line = getline(v:foldstart)
endif
endif
let sub = substitute(line, '/\*\|\*/\|{{{\d\=', '', 'g')
let diff = v:foldend - v:foldstart + 1
return '+' . v:folddashes . '[' . s:Num2S(diff,3) . ']' . sub
endfunction
"{{{~ Foldsearch adapted from t77: Fold on search result
function! s:Foldsearch(search)
call s:SimpleFold_SetupBuffer()
" set manual
setlocal fdm=manual
let origlineno = line(".")
normal zE
normal G$
" set the foldtext
execute 'setlocal foldtext=' . s:sid . 'SimpleFold_FoldText()'
let folded = 0 "flag to set when a fold is found
let line1 = 0 "set marker for beginning of fold
let flags = "w" "allow wrapping
let first_code_line = 0
if a:search == ""
if exists("b:simplefold_expr")
let searchre = b:simplefold_expr
else
let searchre = '\v^\s*[#%"0-9]{0,4}\s*\{(\{\{|!!)'
endif
else
let searchre = a:search
endif
while search(searchre, flags) > 0
let line2 = line(".")
while line2 - 1 >= line1 && line2 - 1 > 0 "sanity check
let prevline = getline(line2 - 1)
if exists("b:simplefold_prefix") && (match(prevline, b:simplefold_prefix) != -1)
let line2 = line2 - 1
else
break
endif
endwhile
if (line2 - 1 >= line1)
execute ":" . line1 . "," . (line2-1) . "fold"
"echo "fold " . line1 . " - " . (line2 - 1)
if g:SimpleFold_use_subfolds
call s:FoldNestableBlocks(first_code_line + 1, line2 - 2, "", "")
endif
let folded = 1 "at least one fold has been found
endif
let line1 = line2 "update marker
let first_code_line = line2 + 1
let flags = "W" "turn off wrapping
endwhile
let line2 = line("$")
if (line2 >= line1 && folded == 1)
execute ":". line1 . "," . line2 . "fold"
execute "normal " . line1 . "G"
" try to find the last top-level fold so that we get the correct range
" for nested subblocks
if search(searchre, "W") > 0
let line1 = line(".")
endif
let line1 = line1 + 1
"echo "last call: " line1 . " - " . line2
if g:SimpleFold_use_subfolds
call s:FoldNestableBlocks(line1, line2, "", "")
endif
endif
call s:FoldNestableBlocks(1, line("$"), b:simplefold_marker_start,
\ b:simplefold_marker_end)
normal zM
execute "normal " . origlineno . "G"
endfunction
function! s:FoldNestableBlocks(start, end, start_expr, end_expr)
call s:SimpleFold_SetupBuffer()
if a:end - a:start < 1
return 0
endif
if a:start_expr == ""
if exists("b:simplefold_nestable_start_expr")
let start_expr = b:simplefold_nestable_start_expr
else
return
endif
else
let start_expr = a:start_expr
endif
if a:end_expr == ""
if exists("b:simplefold_nestable_end_expr")
let end_expr = b:simplefold_nestable_end_expr
else
return
endif
else
let end_expr = a:end_expr
endif
"echo "nested " . a:start . " <-> " . a:end
let origlineno = line(".")
execute "normal " . (a:start - 1). "G"
normal $
normal zR
" allow wrapping if a:start was 1 (i.e. we moved to line("$"))
let flags = (a:start == 1) ? "w" : "W"
let done_up_to = a:start
"echo "searching for " . start_expr . " from " . line(".")
while search(start_expr, flags) > 0
let flags = "W"
let first_line = line(".")
"echo "MATCH " . start_expr . " " . first_line
if first_line >= a:end || first_line < done_up_to
break
endif
if searchpair(start_expr, "", end_expr, "W") > 0
let last_line = line(".")
let done_up_to = last_line
if last_line <= a:end
"echo "nested fold (" . a:start . " - " . a:end . ") " .
" \ first_line . " - " . last_line
execute ":" . first_line . "," . last_line . "fold"
if last_line - first_line >= 2 && last_line <= a:end
call s:FoldNestableBlocks(first_line + 1, last_line - 1,
\ start_expr, end_expr)
endif
endif
endif
endwhile
execute "normal " . origlineno . "G"
"echo "RET " . a:start . " - " . a:end " -> " . origlineno
endfunction
function! s:SimpleFold_SetupBuffer()
if !exists("b:simplefold_expr")
let b:simplefold_expr = '\v^\s*[#%"0-9]{0,4}\s*\{(\{\{|!!)'
endif
if !exists("b:simplefold_marker_start")
let b:simplefold_marker_start = '\v\{\{\{\{'
endif
if !exists("b:simplefold_marker_end")
let b:simplefold_marker_end = '\v\}\}\}\}'
endif
endfunction
"{{{~fold commands
if !exists(":Fold")
command -nargs=1 Fold :call s:Foldsearch(<q-args>)
endif
"{{{ mappings and default options
if !hasmapto("<Plug>SimpleFold_Foldsearch")
map <unique> <silent> <Leader>f <Plug>SimpleFold_Foldsearch
endif
noremap <unique> <script> <Plug>SimpleFold_Foldsearch <SID>FoldSearch
noremap <SID>FoldSearch :call <SID>Foldsearch("")<cr>
let g:SimpleFold_use_subfolds = 1
"{{{ Fold expressions for different filetypes
" default expression
aug SimpleFold
au!
au BufEnter * call s:SimpleFold_SetupBuffer()
" Ruby support
au Filetype ruby let b:simplefold_expr =
\'\v(^\s*(def|class|module|attr_reader|attr_accessor|alias_method|' .
\ 'attr|module_function' . ')\s' .
\ '|\v^\s*(public|private|protected)>' .
\ '|^\s*\w+attr_(reader|accessor)\s|^\s*[#%"0-9]{0,4}\s*\{\{\{[^{])' .
\ '|^\s*[A-Z]\w+\s*\=[^=]|^__END__$'
au Filetype ruby let b:simplefold_nestable_start_expr =
\ '\v^\s*(def>|if>|unless>|while>.*(<do>)?|' .
\ 'until>.*(<do>)?|case>|for>|begin>)' .
\ '|^[^#]*.*<do>\s*(\|.*\|)?'
au Filetype ruby let b:simplefold_nestable_end_expr =
\ '\v^\s*end'
au Filetype ruby let b:simplefold_prefix='\v^\s*(#.*)?$'
" Java support
au Filetype java let b:simplefold_expr =
\ '\(^\s*\(\(private\|public\|protected\|class\)\s\)\)'
aug END
let &cpo = s:save_cpo
Bug in call to s:FoldNestableBlocks - Scott Guelich (2006-05-26 (Fri) 20:30:18)
Right now if b:simplefold_expr doesn't match anything in the file, then none of the nestable folds happen either and nothing gets folded. It looks like it's due to a bug in the call to s:FoldNestableBlocks that happens at the end of s:Foldsearch (line 162).
The call currently reads:
call s:FoldNestableBlocks(1, line("$"), b:simplefold_marker_start,
\ b:simplefold_marker_end)
when it should probably be:
call s:FoldNestableBlocks(1, line("$"), b:simplefold_nestable_start_expr,
\ b:simplefold_nestable_end_expr)
Scott 2006-05-26 (Fri) 20:34:27
Oops, too quick on the tab key... meant to also add a big *THANKS*! It's great that this solution is so customizable... and it definitely makes folding more useful with Ruby.
mfp 2006-05-29 (Mon) 05:50:22
Thanks for the bug report! Actually, you could call it a misfeature: I thought that nestable folds should only happen when there are top-level folds, but what you describe makes probably more sense. I've spent most of my hacktime on rcov lately, but I also made some changes to SimpleFold (simplified filetype-based settings and cleaned it up a little). I have an idea to make SimpleFold faster and more "interactive" (so that folds update automatically and you don't need to <Leader>f all the time), but it'll take a while.
Support for PHP - Dan (2006-05-13 (Sat) 03:05:52)
After a little experimentation, here's what I've got so far for PHP.
au Filetype php let b:simplefold_expr =
\ '\v^\s*(class|function|const|public|private|define)>' .
\ '|^\s*[#%"0-9]{0,4}\s*\{\{\{[^{]'
au Filetype php let b:simplefold_nestable_start_expr =
\ '\v^\s*(if|for(each)?|while|switch)\s*\(.*\)\_s*\{'
au Filetype php let b:simplefold_nestable_end_expr =
\ '\v^\s*\}'
au Filetype php let b:simplefold_prefix =
\ '\v^\s*((#|//).*)?$' .
\ '|\v^\s*(/\*\_.*\*/)?$' .
\ '|^\s*$'
It works alright for files that contain classes, but doesn't work on files that just contain PHP and HTML. Adding
'\v^\s*(if|for(each)?|while|switch)\s*\(.*\)\_s*\{'
to the alternatives in b:simplefold_expr makes it fold those files too, but since the top-level folding doesn't use any type of end_expr to find the closing curly braces, the folds are all off. I'd be interested to see what improvements others can make to these rules.
Dan 2006-05-13 (Sat) 03:15:26
Oops, that last line in b:simplefold_prefix is redundant. It could be better defined like this:
au Filetype php let b:simplefold_prefix = \ '\v^\s*((/\*\_.*\*/)|((#|//).*))?$'
mfp 2006-05-13 (Sat) 13:37:21
Thank you! This is getting into the next release.
mfp 2006-05-13 (Sat) 13:41:40
Almost forgot... could I have your full name to credit you properly? Dan(iel Berger)?
Dan 2006-05-13 (Sat) 23:30:44
Ah, sure. And nope, I'm not that Dan :) I'm Dan McCormack.
Note that I only tested these PHP rules on my own code (and thus, on my own coding style). They may very well miss some types of syntax that are allowed by the parser but I don't happen to use in my code. But it's a start, and anyone else can feel free to expand on them as necessary.
No Title - Dan (2006-05-13 (Sat) 00:21:00)
Maybe this is more of a general vim issue, but do we really have to type \f each time we want to 'detect' new folds, such as after defining a new method? I like your folding approach a lot, but having to type \f all the time does a lot to outweigh the advantages. I've been using fdm=marker for a year and was very happy with the way it immediately reflects changes in the file. Is there some way to configure vim to do the same with SimpleFold?
mfp 2006-05-13 (Sat) 13:36:14
I have an idea... stay tunned :)
Folding all borked on vim 64 - kaspar (2006-05-12 (Fri) 07:25:53)
I just tried out that script on vim64 and all I got was vim complaining that b:simplefold_marker_start didn't exist. Should that work on 6.4 ?
mfp 2006-05-12 (Fri) 08:15:13
OK, just tested it on 6.2 and 6.4, and reproduced the bug. It happens when ((filetype is off OR you're editing a file whose filetype is not ruby nor java) AND you didn't specify the file in the cmdline); e.g.
vim foo.txt
works (so it'll fold on markers when you do <Leader>f --- there's no other expression for non-{Ruby-Java) files) but
vim and then... :e foo.txt
won't.
For the time being, the workarounds are:
- use <Leader>f only with ruby files :)
- open the file from the cmdline: vim whatever
I'm fixing this in a few hours when I get home.
mfp 2006-05-12 (Fri) 14:14:36
Should be fixed by now in 0.4.0b.
Justin Dossey 2006-05-16 (Tue) 12:37:48
I'm using this heavily, and there are indeed some minor bugs. 1. it's slow :) 2. When you move a folded method with a key sequence like dd10kp, the method is unfolded under the cursor; 3. When you start a new method above an older one, the older one's end is highlighted, and then when you type in the end for the new method, the older one is expanded.
I really appreciate the work you've done with ruby vim folding. Thank you!
Bruno Michel 2006-05-30 (Mardi) 17:09:14
Thanks for this very usefull plugin.
I have a minor bug report: line 251, the let definition can overwrite a previous declaration of g:SimpleFold_use_subfolds (in .vimrc for example). The patch: if !exists("g:SimpleFold_use_subfolds")
let g:SimpleFold_use_subfolds = 1
endif
- 226 http://www.google.com/url?sa=D&q=http://eigenclass.org/hiki.rb?simplefold+plugin+0.4.0
- 148 http://vimmate.rubyforge.org/html/files/README.html
- 41 http://anarchaia.org
- 41 http://www.ruby-forum.com/topic/65227
- 20 http://www.artima.com/forums/flat.jsp?forum=123&thread=159874
- 18 http://planetruby.0x42.net
- 15 http://www.anarchaia.org
- 12 http://anarchaia.org/archive/2006/05/11.html
- 8 http://www.artima.com/forums/flat.jsp?forum=123&thread=181572
- 4 http://www.artima.com/buzz/community.jsp?forum=123
Keyword(s):[blog] [ruby] [frontpage] [vim] [folding] [syntax] [marker] [manual] [simplefold] [release] [snippet]
References:[Ruby support for Vim] [Usable Ruby folding for Vim] [VimInAnger competition, how about Project + simplefold + taglist + rcov + bufferlist + xmpfilter?]