Search code examples
cvimfolding

Compact C Folding in Vim


I'm trying to make a simple Vim script that would create very compact top-level folds for c files. Ideally, if it was run on this code:

static void funca(...)
{
   ... 
}

/* Example comment */
static void funcb(...)
{
   ... 
}

Then it would create folds which would look like this when closed:

+-- x Lines: static void funca(...)----------------------
+-- x Lines: static void funcb(...)----------------------

So basically it would be like foldmethod=syntax with foldlevel=1, except that each fold would start one line further up, and would extend further down to include all following blank lines.

I know how to make one of these folds (assuming foldmethod=manual):

/^{<cr>kVnn?^$<cr>zf

But I'm not sure how to put it into a function. This is my effort:

function Cfold()
  set foldmethod=manual  " Manual folds
  ggzE                   " Delete all folds
  while (/^{<cr>)        " Somehow loop through each match
     kVnn?^$<cr>zf       " This would work fine except for the last function
  endwhile
endfunction
map <Leader>f  :call Cfold()<cr>

But it isn't valid, I'm not entirely sure how functions work. Also, it won't work for the last function in the file, since it won't find '^{' again. If someone could help me get this working, and somehow add a case for the last function in a file, I would be extremely grateful.

Thanks in advance :)


Solution

  • You can create folds programmatically using the foldexpr and foldtext. Try this, though you may have to tweak CFoldLevel so it doesn't swallow non-function parts of the code:

    function! CFoldLevel(lnum)
      let line = getline(a:lnum)
      if line =~ '^/\*'
        return '>1' " A new fold of level 1 starts here.
      else
        return '1' " This line has a foldlevel of 1.
      endif
    endfunction
    
    function! CFoldText()
      " Look through all of the folded text for the function signature.
      let signature = ''
      let i = v:foldstart
      while signature == '' && i < v:foldend
        let line = getline(i)
        if line =~ '\w\+(.*)$'
          let signature = line
        endif 
        let i = i + 1
      endwhile
    
      " Return what the fold should show when folded.
      return '+-- ' . (v:foldend - v:foldstart) . ' Lines: ' . signature . ' '
    endfunction
    
    function! CFold()               
      set foldenable
      set foldlevel=0   
      set foldmethod=expr
      set foldexpr=CFoldLevel(v:lnum)
      set foldtext=CFoldText()
      set foldnestmax=1
    endfunction
    

    See :help 'foldexpr' for more details.