26

I recently realized that my vimrc is now more than 400 lines long (which IMO is too much I'll try to reduce that) and to make it easier to navigate, read and edit it I decided to investigate the concept of folding in vim (which I wasn't familiar with).

  • I tried to set the folding method to indent but I didn't like the result (It was too messy mostly because a great part of my vimrc isn't really indented).
  • I also tried to set foldmethod to expr and syntax but I wasn't able to fold anything properly.
  • Here using diff as folding method doesn't seem relevant. (Or if it is I didn't understand how to use it)
  • So for now I'm using the marker method which doesn't totally satisfy me because of the "{{{ and "}}} markers which I found "noisy" in the file.

So I'd like to know if there are best practices or common guidelines about properly folding a vimrc.

Note 1: As we all know SO isn't a forum and isn't made to collect personal opinions and that's not what I'm looking for: of course I guess some people has their preferences but I'd like to know why using markers (for example) improves the readability more than using indent.

Note 2: Also my main goal is to make my vimrc as clear as possible so if other best practices exists to create a nice vimrc I'm curious about it.

Edit 1: I should have precised that my vimrc is already subdivided in sections (and sometimes subsection) the main ones being

  • general options
  • plugins (containing a subsection for each plugin and its configuration)
  • mappings
  • navigation (also containing subsection)
  • color
  • etc...

And it's this structure which made me thought of folding: I feel that being able to output only the section I'm interested in at a certain point is something pretty convenient.

Edit 2: Answer mentioning subdivisions of the vimrc in several files are valid, but as a personal preference I'd rather use folding because I think it is easier to maintain only one file in the git repo containing my dotfiles. That is only a personal preference and I'm aware that is it possible to also use this approach but I'd prefer to use folding.

1
  • 1
    I think using the "{{{ is the most 'vim like' way of doing things, the solarized plugin uses this and although it might be noisy it gives you the most standard way of setting manual folds
    – icc97
    Commented Jun 16, 2018 at 23:17

8 Answers 8

25

I have the following modelines at the bottom of my vimrc which I copied from godlygeek, the author of tabular:

"" vim:fdm=expr:fdl=0
"" vim:fde=getline(v\:lnum)=~'^""'?'>'.(matchend(getline(v\:lnum),'""*')-2)\:'='

This will make any line starting with 2+ "'s a fold. The more "'s the deeper the fold. This allows you to sub divide sections if you need.

Update: As noted in a comment below the second modeline now causes an error in Vim. Here's a note from the patch that introduced the change:

Patch 8.1.1366

Problem: Using expressions in a modeline is unsafe.

Solution: Disallow using expressions in a modeline, unless the 'modelineexpr' option is set.

And, no, you can't set 'modelineexpr' from the modeline! You could try setting 'fde' as above in an autocmd (e.g. autocmd Filetype vim ...). I (B Layer) didn't test this but I did test it manually. You need to escape the "s.

set fde=getline(v\:lnum)=~'^\"\"'?'>'.(matchend(getline(v\:lnum),'\"\"*')-2)\:'='
4
  • I can't test it right now but that seems to be an ideal solution to me! Thanks for sharing!
    – statox
    Commented Jul 3, 2015 at 17:15
  • Some explain:getline(v:lnum) returns the string of the line given by the line number (v:lnum); =~ means regular expression match; '^""' means all lines start with two "s; matchend(getline(v:lnum),'""*')-2 counts the extra number of ", that means """ will fold with level 1, """" will fold with level 2 and so on; getline(v:lnum)=~'^""' returns true or false depending on the v:lnum line start with two " or not; if true, the fde is set to >extra number of "(start level which is sat by the number after < at this line) or '=' (use previous line's level), the meaning can be found at fold-expr
    – van abel
    Commented Aug 27, 2017 at 7:24
  • 2
    after recent vim update 8.1.1517), I am getting ""Error detected while processing modelines" with this configuration.
    – apollo
    Commented Jun 12, 2019 at 13:33
  • Some of you may have figured this out since it's been a year but I updated the answer with information about the breakage.
    – B Layer
    Commented Aug 29, 2020 at 19:01
13

It is a good idea to first define your own categories in your .vimrc (like a list with sublists and subsublists) and add all your plugins/settings/functions to the respective categories. Combined with customized folding this can work great:

Example

The example above shows possible categories that I find helpful to structure my .vimrc. It uses the following customized fold settings:

""""""""""""""""""""""""
"  THIS IS A CATEGORY  "
""""""""""""""""""""""""
"" Autofolding .vimrc
" see http://vimcasts.org/episodes/writing-a-custom-fold-expression/
""" defines a foldlevel for each line of code
function! VimFolds(lnum)
  let s:thisline = getline(a:lnum)
  if match(s:thisline, '^"" ') >= 0
    return '>2'
  endif
  if match(s:thisline, '^""" ') >= 0
    return '>3'
  endif
  let s:two_following_lines = 0
  if line(a:lnum) + 2 <= line('$')
    let s:line_1_after = getline(a:lnum+1)
    let s:line_2_after = getline(a:lnum+2)
    let s:two_following_lines = 1
  endif
  if !s:two_following_lines
      return '='
    endif
  else
    if (match(s:thisline, '^"""""') >= 0) &&
       \ (match(s:line_1_after, '^"  ') >= 0) &&
       \ (match(s:line_2_after, '^""""') >= 0)
      return '>1'
    else
      return '='
    endif
  endif
endfunction

""" defines a foldtext
function! VimFoldText()
  " handle special case of normal comment first
  let s:info = '('.string(v:foldend-v:foldstart).' l)'
  if v:foldlevel == 1
    let s:line = ' ◇ '.getline(v:foldstart+1)[3:-2]
  elseif v:foldlevel == 2
    let s:line = '   ●  '.getline(v:foldstart)[3:]
  elseif v:foldlevel == 3
    let s:line = '     ▪ '.getline(v:foldstart)[4:]
  endif
  if strwidth(s:line) > 80 - len(s:info) - 3
    return s:line[:79-len(s:info)-3+len(s:line)-strwidth(s:line)].'...'.s:info
  else
    return s:line.repeat(' ', 80 - strwidth(s:line) - len(s:info)).s:info
  endif
endfunction

""" set foldsettings automatically for vim files
augroup fold_vimrc
  autocmd!
  autocmd FileType vim 
                   \ setlocal foldmethod=expr |
                   \ setlocal foldexpr=VimFolds(v:lnum) |
                   \ setlocal foldtext=VimFoldText() |
     "              \ set foldcolumn=2 foldminlines=2
augroup END

To define your own categories and subcategories use the following syntax:

""""""""""""""
"  Category  "
""""""""""""""
"" Subcategory
""" Subsubcategory
" Just a comment, gets ignored no matter where

The top level category can be created really easy if you use vim-snippets (e.g. with UltiSnips): Just expand the box or bbox snippet provided by vim-snippets (write box or bbox and press the expand trigger).

To toggle folds open and closed even faster by pressing space twice:

let mapleader = "\<space>"
" Toggle folds
nnoremap <silent> <leader><Space> @=(foldlevel('.')?'za':"\<Space>")<CR>
vnoremap <leader><space> zf

That way you have an well structured .vimrc which can be navigated easily.

2
  • +1 for the nice animated gif :) Just curious, what do you used to display the typed keys?
    – mMontu
    Commented Feb 22, 2016 at 11:25
  • 1
    @mMontu: I used screenkey to display the keys and gtk-recordmydesktop to record it (both in Debian repos). With 5fps a 45 seconds clips is less than a MiB. Then converted it online to a gif (that is where the distortions came in, before the picture quality was perfect).
    – cbaumhardt
    Commented Feb 22, 2016 at 12:48
10

I use my primary vimrc as a link to several other categorized files, sourcing each as it goes along, e.g. Vim options in one file, plugin settings in another.

"--- Vim Options
source ~/.vim/config/vim_options.vim

"--- Here Be Functions!
" (need to be sourced before stuff that uses 'em)
runtime! functions/*.vim

"--- Key Mapping
source ~/.vim/config/key_mapping.vim

"--- Folding
source ~/.vim/config/folding.vim

"--- Autocmds
source ~/.vim/config/autocmds.vim

"--- We Are Plugged In!
source ~/.vim/config/plugins.vim

" vim: ft=vim fdm=marker

As a more direct answer to OP question, I do use the marker method, but off to the right side with spacing, and around more categories than individual, for the most part. I do each plugin separately though.

2
  • I forgot to precise that in my question: I'm not a a fan of "separating" the vimrc in different files because (IMO) it increase the complexity and make it more difficult to maintain. About the folding what do you mean by "off to the right side with spacing"?
    – statox
    Commented Jul 3, 2015 at 13:54
  • I mean " {{{ with as many spaces in there as your textwidth so the markers are near the right edges. I do also have a personalized FoldText function in folding.vim file. I prefer separate files so that my git repo only one specific type of mod per commit.
    – Cometsong
    Commented Jul 3, 2015 at 15:39
9

You could say that "best practice" is preeminently a matter of opinion, :) but there are two approaches that (1) make obvious sense, and (2) can be applied to all config files, not only Vim's: folding by logical sections and subsections (or even deeper, if you feel brave), and splitting your config into several smaller files and :source-ing them.

I personally prefer folding because it makes things easier to access, while still giving me some hierarchy to pick up from. Folding functions and autocmds at innermost levels is also a good idea, since these make "natural" logical units. marker folding makes most sense for all this, because logical hierarchies are not necessarily reflected into indent levels, or into syntax highlighting. I also increase foldcolumn, which gives me a visual hint of where I am:

# vim: filetype=vim foldmethod=marker foldlevel=0 foldcolumn=3

On a side note, this foldtext function (a modification of a similar function by Drew Neil, IIRC) makes more sense to me than the default:

function! MyFoldText()
    let line = getline(v:foldstart)

    let nucolwidth = &foldcolumn + &number * &numberwidth
    let windowwidth = winwidth(0) - nucolwidth - 3
    let foldedlinecount = v:foldend - v:foldstart

    " expand tabs into spaces
    let chunks = split(line, "\t", 1)
    let line = join(map(chunks[:-2], 'v:val . repeat(" ", &tabstop - strwidth(v:val) % &tabstop)'), '') . chunks[-1]

    let line = strpart(line, 0, windowwidth - 2 - len(foldedlinecount))
    let fillcharcount = windowwidth - len(line) - len(foldedlinecount) - 1
    return line . '...' . repeat(' ', fillcharcount) . foldedlinecount . ' '
endfunction
set foldtext=MyFoldText()

With the other approach, split files, the main problems are finding things, and switching from one file to another. A very nice way to address both is to use a plugin such as CtrlSF, CtrlP, or similar. But you're probably already using one of those anyway.

3
  • So you go with marker. Indeed customizing foldcolumn is a nice thing to do, I'll see which value fits best on my need. Also I share your view on the split files but I didn't know CtrlSF I'll take a look at it even if I'm pretty happy with CtrlP.
    – statox
    Commented Jul 3, 2015 at 14:30
  • Also could you explain how to use the custom folding method please? I tried to set fdm to foldtext and MyFoldText() but it seems like it's not the right way to use it.
    – statox
    Commented Jul 3, 2015 at 14:39
  • @statox CtrlSF works best with ag or ack, which are essentially specialized versions of grep. foldtext is not a custom folding method, but a function to change the way folded text looks. The last line in my snippet shows how it's used: set foldtext=MyFoldText().
    – lcd047
    Commented Jul 3, 2015 at 14:54
2

Basic best practises:

  • Divide into sections:

    • Plugins
    • Settings
    • Rebinds
  • Comment each section/rebind

  • (backup your .vimrc or _vimrc on Github)

Just my personal preference. Maybe not so much help though.

4
  • I personally don't use folding, and you shuld not need to. Just organise your vimrc and it should all be good. Commented Jul 3, 2015 at 13:48
  • My vimrc is already organized in section (general options, plugins, mappings, navigation, color, etc...). The fact to be able to fold a section (or subsection) is actually good to focus on what you're editing/ looking for.
    – statox
    Commented Jul 3, 2015 at 13:51
  • Okay. Sorry for bad answer. Commented Jul 3, 2015 at 14:18
  • That's not a bad answer and I'm also guilty for not giving detailled enough question, no need to be sorry ;-)
    – statox
    Commented Jul 3, 2015 at 14:26
2

Inspired by @PeterRincker's answer, I crafted the following to use ATX style headers. Add it to the end of your .vimrc

"# Folding

" Fold with ATX style headers - "# is H1, "## is H2, and so on
" vim:fdm=expr:fdl=0
" vim:fde=getline(v\:lnum)=~'^"#'?'>'.(matchend(getline(v\:lnum),'"#*')-1)\:'='
2

Expanding the idea of @Peter Rincker and @go2null. If you do not want to set the folding options in Vim modeline. You can use the following autocmd to set the folding method and folding expression.

augroup vim_folding
    autocmd!
    autocmd FileType vim set foldmethod=expr foldlevel=0
    " note that double quote in foldexpr has to be escaped with backslash
    autocmd FileType vim set foldexpr=getline(v:lnum)=~'^\"#'?'>'.(matchend(getline(v:lnum),'\"#*')-1):'='
augroup END

I made small modifications to make the original answer to make it work as regular vim command (no need to escape colon, but double quote needs to be escaped).

If you do not like the long foldexpr string, we can define a function for that:

function! VimFolds(lnum)
    let s:cur_line = getline(a:lnum)

    if s:cur_line =~ '^"#'
        return '>' . (matchend(s:cur_line, '"#*')-1)
    else
        return '='
    endif

endfunction

Then replace the autocmd line about foldexpr to

autocmd FileType vim set foldexpr=VimFolds(v:lnum)
1

If you have large functions like me, you can use this to fold your functions:

fun! MyFoldLevel(linenum)
    if ! exists('w:nextline')
        let w:nextline = 0
        let w:insideafun = 0
    endif

    if w:nextline == 1
        let w:nextline = 0
        let w:insideafun = 0
    endif

    let l:line = getline(a:linenum)

    if l:line =~# '^[[:space:]]*fun'
        let w:insideafun = 1
        return '>1'
    elseif l:line =~# '^[[:space:]]*endf'
        let w:nextline = 1
        return '<1'
    endif

    if w:insideafun == 1
        return 1
    else
        return 0
    endif
endfun

And add this modeline to your vimrc:

" vim:fde=MyFoldLevel(v\:lnum):fdm=expr:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.