Usable Ruby folding for Vim
The folding method described below has been released as the Simplefold plugin.
The folding methods you get with a default vim install (even with updated Ruby support) are unsatisfying for a number of reasons:
- fold_syntax and fold_indent create far too many folds (e.g. for syntactical elements like if or while). On top of that, they're nested, so you have to open folds recursively all the time. foldnestmax isn't an option because you never know how many class/module levels you're going to get: a value that works fine for code wrapped in a module would show too many folds in one consisting of bare (private Object) method definitions.
- fold_marker requires too much work to mark all methods and classes
- fold_expr seems a bit restrictive
As happens often with vim, spotting what I dislike is easier than finding right away what I want. I now know I want code folding in Ruby to work as follows:
- no need for manual markers in general, but I want to have the option to use them.
- folds for modules, classes, methods and constant definitions, and nothing else. I specifically want no intra-method folding (on if and such): if the method is large enough to warrant folds, you're screwed anyway.
- no nested folds: I don't want to have to open a class fold to look at the folded methods.
To sum up, this is how I'd like folds to behave:
I can glance through the class without having to open nested folds, and can locate visually constant definitions, methods and attributes very quickly. I actually prefer this to tagexplorer.
Usage
After adding the following snippet to .vimrc, vim will fold on :R (that won't set foldenable though, so you might have to zi too).
The default fold expression will work with Ruby code, but the b:foldsearchexpr buffer-local variable can be set before folding to suit it to other languages.
" FoldSearch-based folding.
" Copyright (C) 2005 Mauricio Fernandez <mfp@acm.org>
" Current version: http://eigenclass.org/hiki.rb?Usable+Ruby+folding+for+Vim
"
" Add this to your .vimrc and fold with :R. The default fold expression will
" work with Ruby scripts; you can specify where folds start with
" let b:foldsearchexpr = 'myexpression'
" e.g.
" let b:foldsearchexpr='\(^\s*\(\(private\|public\|protected\|class\)\s\)\)'
" or so for Java.
" One way to have this buffer-local variable set is
" au Filetype java let b:foldsearchexpr='\(^\s*\(\(private\|public\|protected\|class\)\s\)\)'
"
" It is possible to have comments above a method/class/etc be included in the
" fold, by setting b:foldsearchprefix. All the lines above the detected fold
" matching b:foldsearchprefix will be included in said fold.
" For instance, for Ruby code:
" let b:foldsearchprefix = '\v^\s*(#.*)?$'
" which can be automated with
" au Filetype ruby let b:foldsearchprefix='\v^\s*(#.*)?$'
"
" Changelog:
" 2005-12-12 1.1 use b:foldsearchprefix to prepend comments to a fold.
"{{{ 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
execute 'set foldtext=' . s:sid . 'MyNewFoldText()'
function! <SID>MyNewFoldText()
let linenum = v:foldstart
while linenum <= v:foldend
let line = getline(linenum)
if !exists("b:foldsearchprefix") || match(line, b:foldsearchprefix) == -1
break
else
let linenum = linenum + 1
endif
endwhile
if exists("b:foldsearchprefix") && match(line, b:foldsearchprefix) != -1
" all lines matched the prefix regexp
let line = getline(v:foldstart)
endif
let sub = substitute(line, '/\*\|\*/\|{{{\d\=', '', 'g')
let diff = v:foldend - v:foldstart + 1
return '+ [' . s:Num2S(diff,4) . ']' . sub
endfunction
"{{{~foldsearch adapted from t77: Fold on search result (Fs <pattern>)
"Fs pattern Fold search
"Vimtip put to good use by Ralph Amissah zxy@irc.freenode.net
"Modified by Mauricio Fernandez <mfp@acm.org>
function! Foldsearch(search)
setlocal fdm=manual
let origlineno = line(".")
normal zE
normal G$
let folded = 0 "flag to set when a fold is found
let flags = "w" "allow wrapping in the search
let line1 = 0 "set marker for beginning of fold
if a:search == ""
if exists("b:foldsearchexpr")
let searchre = b:foldsearchexpr
else
"Default value, suitable for Ruby scripts
"\(^\s*\(\(def\|class\|module\)\s\)\)\|^\s*[#%"0-9]\{0,4\}\s*{\({{\|!!\)
let searchre = '\v(^\s*(def|class|module|attr_reader|attr_accessor|alias_method)\s' .
\ '|^\s*\w+attr_(reader|accessor)\s|^\s*[#%"0-9]{0,4}\s*\{(\{\{|!!))' .
\ '|^\s*[A-Z]\w+\s*\='
let b:foldsearchexpr = searchre
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:foldsearchprefix") && (match(prevline, b:foldsearchprefix) != -1)
let line2 = line2 - 1
else
break
endif
endwhile
if (line2 -1 >= line1)
execute ":" . line1 . "," . (line2-1) . "fold"
let folded = 1 "at least one fold has been found
endif
let line1 = line2 "update marker
let flags = "W" "turn off wrapping
endwhile
normal $G
let line2 = line(".")
if (line2 >= line1 && folded == 1)
execute ":". line1 . "," . line2 . "fold"
endif
execute "normal " . origlineno . "G"
endfunction
"{{{~folds Fold Patterns
" Command is executed as ':Fs pattern'"
command! -nargs=? -complete=command Fs call Foldsearch(<q-args>)
command! -nargs=? -complete=command Fold call Foldsearch(<q-args>)
"command! R Fs \(^\s*\(\(def\|class\|module\)\s\)\)\|^\s*[#%"0-9]\{0,4\}\s*{\({{\|!!\)
command! R Fs
Sorry for my ignorance - Josiah Ritchie (2005-11-04 (Fri) 08:01:05)
Is this intended to be added http://vim-ruby.rubyforge.org/ or standalone?
Maybe a better question? Do you assume familiarity with vim-ruby before using what you have here?
mfp 2005-11-04 (Fri) 15:25:44
The described folding mechanism doesn't require vim-ruby; it will work fine without it. Besides, the Foldsearch function was derived from vimtip #77 and is fairly generic: it can be used for non-Ruby stuff too. For instance, when editing markup for this site, I use :Fs ^! (!, !!... correspond to headings in HikiDoc markup).
Keyword(s):[ruby] [vim] [code] [fold] [folding]
References:[Usable Ruby folding for Vim, second update] [Ruby support for Vim]