eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Usable Ruby folding for Vim

update.png 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:


vim-ruby-folding.png

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.


vim-ruby-folding-1.1.vim

" 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).

Last modified:2005/12/12 09:35:17
Keyword(s):[ruby] [vim] [code] [fold] [folding]
References:[Usable Ruby folding for Vim, second update] [Ruby support for Vim]