12

Is there a way that I can setup vim to automatically fold ruby source files, but only fold at the method level regardless of the level that they are defined?

So it will fold when I have:

class MyClass
  def method
    ...
  end
end

but also when I have:

module FirstModule
  module SecondModule
    class MyClass
      def method
        ...
      end
    end
  end
end

I've experimented with foldmethod=syntax and various fold levels but it doesn't take into account the depth where the method is defined.

Also I don't want nothing inside the method to get folded (if blocks, each blocks, etc).

I think foldmethod=expr would be my best bet, but I haven't manage to figure out how fold expressions work, and the help in vim hasn't been very enlightening.

adivasile
  • 2,377
  • 3
  • 24
  • 32

5 Answers5

11

Your hunch about using the expr method, I believe, was correct!

You can use the syntax structure of the file to jury-rig your own syntax-style folding. The following in my .vimrc produced expected behavior:

function! RubyMethodFold(line)
  let line_is_method_or_end = synIDattr(synID(a:line,1,0), 'name') == 'rubyMethodBlock'
  let line_is_def = getline(a:line) =~ '\s*def '
  return line_is_method_or_end || line_is_def
endfunction

set foldexpr=RubyMethodFold(v:lnum)

Some caveats:

I'm not sure if the final argument to synID should be 0 or 1. It's the argument that determines whether you get the syntax information for the topmost transparent or non-transparent element at the provided location. When there's no transparent element, the argument has no effect. In the trivial example I tried, it didn't cause any issues, but it might.

It's also worth noting that the line_is_def regex is probably too lenient. It might be better to return -1 in this situation, so a line matching the regex is only folded when it's right next to the folded method block. A more restrictive regex could also work.

If you're feeling squirrely, you could expand on this and return separate foldlevels for rubyClass and rubyModule elements as well.

If you decide to go down that route, there are some useful custom functions in my .vimrc for introspecting into syntax elements and hilighting. They're handiest when mapped to a keybinding for quick use like so:

nnoremap g<C-h> :echo GetSynInfo()<CR>

Please do let me know if this works out for you! It was fun to figure out. Also, for what it's worth, while :help 'foldexpr' is light on details, :help 'fold-expr' is much more helpful, and even has some examples at the top.

Max Cantor
  • 8,229
  • 7
  • 45
  • 59
  • 2
    this is a great answer.this looks like it's the right solution, or at least a great starting point to get where I want to.I will test to see how it works later today and accept your answer. Also your vimrc looks like a great learning resource, especially with all the functions you defined. Thanks. – adivasile Oct 04 '11 at 10:34
  • I'd be interested to know how this worked out for you, by the way! – Max Cantor Oct 14 '11 at 21:18
  • Well, first of all, synIDattr never returned rubyMethodBlock for a line.I only got rubyBlock. So I changed it to rubyBlock, but the folds were weird. If folded from a def statement to the first line in the method that had an if statement. I'm not really familiar with the syntax instrospection commands. Will do some research later today, and then see if I manage to crack this. – adivasile Oct 18 '11 at 12:11
  • Keep in mind that synIDattr and synID apply to characters as opposed to rows, so on the same row you might have a rubyMethodBlock char followed by a rubyBlock char. I wonder if perhaps we have very different version of the syntax file? Mine has Doug Kearns listed as the maintainer with the VCS Id "ruby.vim,v 1.152 2008/06/29 04:33:41 tpope Exp ". – Max Cantor Oct 18 '11 at 17:06
  • I have the same version as you do. I've checked the file, and there's no definition of a rubyMethodBlock and also, I've checked through the code with your GetSynInfo function, and it never returned rubyMethodBlock – adivasile Oct 19 '11 at 12:09
  • Terribly sorry. I am a moron. I deftly forgot that I compiled vim from source on my machine, so I was looking at the *wrong* files. The last change on the file I'm actually using is marked as "2009 Dec 2". It's the default file for Vim 7.3 (2010 Aug 15). I think many versions of Ubuntu still use 7.2, which does not have `rubyMethodBlock` in its ruby.vim syntax file. Unfortunately I don't see a quick way to make my solution work for the 7.2 file! Maybe I can take another look tomorrow... sorry for leading you on a goose chase! – Max Cantor Oct 20 '11 at 02:06
  • 1
    It's absolutely no problem. It wasn't at all a goose chase, you did point me in a good direction and I've learned a ton about how syntax works in vim and also about folding. I think i'll upgrade my vim files, or just tweak your solution to fit my needs. – adivasile Oct 20 '11 at 07:54
  • Yeah, if you grab Vim 7.3's version of the file and put it in ~/.vim/syntax, that should be enough. Incidentally if you are on Ubuntu, you can compile Vim from source like so: `sudo aptitude install build-essential mercurial && mkdir ~/src && cd ~/src && hg clone https://vim.googlecode.com/hg/ vim && cd vim && make configure && make install` Feel free to poke me if you have other questions (my e-mail's on my website linked in my profile) – Max Cantor Oct 20 '11 at 15:22
4

You may have better luck with this, especially if you already have syntax highlighting:

This would go into your ~/.vimrc

"" Enable folding based on syntax rules
set foldmethod=syntax

"" Adjust the highlighting
highlight Folded guibg=grey guifg=blue

"" Map folding to Spacebar
nnoremap  za

This is also a great VIMcast on folding: http://vimcasts.org/episodes/how-to-fold/

keyvan
  • 2,947
  • 1
  • 18
  • 13
4

Made a change to Max Cantor's solution, mine works perfectly, it will fold def methods (including the end keyword) and documentations (=begin =end) blocks regardless of where it is (in a class, or indented)

function! RubyMethodFold(line)
  let stack = synstack(a:line, (match(getline(a:line), '^\s*\zs'))+1)

  for synid in stack
    if GetSynString(GetSynDict(synid)) ==? "rubyMethodBlock" || GetSynString(GetSynDict(synid)) ==? "rubyDefine" || GetSynString(GetSynDict(synid)) ==? "rubyDocumentation"
      return 1
    endif
  endfor

  return 0
endfunction

set foldexpr=RubyMethodFold(v:lnum)

set foldmethod=expr

Thanks to Max Cantor for his helper methods to print out syntax info.

Let me explain this line by line:

The most important line would be the second line where it gets the stacks of verious level of syntax element in a line. So if you have a line like def methodname with whitespace or tabs before it, it will always get the stack at the first non blank character. The problem with Max Cantor's solution is that his code will only print out the lowest syntax element found at the first column of the line, so it will always return something random, elements like "rubyContant" which we don't care about.

So by using match(getline(a:line), '^\s*\zs') we can move our cursor to the column of the first non blank character so we can get a more accurate picture of the stacks. +1 because match is zero-indexed.

And then the rest is similar to the GetSynInfo method where we loop through the stack and match the elements we want, and return 1 so they are all on the same fold level and 0 for the rest.

And that's it, a working folding expr function that will only fold ruby define methods :)

Enjoy and have a nice day!

Pencilcheck
  • 2,664
  • 3
  • 25
  • 14
  • 1
    Your answer looks promising, but unfortunately when I tried it nothing happened. It seems the solution misses these functions to be defined: `GetSynString` and `GetSynDict`. Would you please care to provide them? Thanks –  Nov 23 '14 at 14:17
2

I made vim-ruby-fold plugin for simple method folding in ruby. I hope it helps people.

1

You might be able to get away with a solution which folds inside a method as well, by using the zO command. zO will recursively open a fold, so if you use it instead of zo, it will be as if nothing inside the method was folded. You could also remap zo to zO if you are feeling squirrely.

Max Cantor
  • 8,229
  • 7
  • 45
  • 59
  • that would indeed be helpful, but the main problem still remains: having the methods automatically folded. – adivasile Oct 03 '11 at 09:10