Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic ruby folding in vim

Tags:

vim

ruby

folding

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.

like image 1000
adivasile Avatar asked Sep 29 '11 09:09

adivasile


3 Answers

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.

like image 173
Max Cantor Avatar answered Nov 16 '22 05:11

Max Cantor


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/

like image 4
keyvan Avatar answered Nov 16 '22 04:11

keyvan


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!

like image 4
Pencilcheck Avatar answered Nov 16 '22 04:11

Pencilcheck