Text 5 May Folding Fun With Vim and Markdown

Here’s a fun little bit of vimscript that I came up with (after a great deal of experimentation and delving into Vim’s documentation for it’s regular expressions (which kick the crap out of Emacs’, if I can say)) to give you syntax folding for your markdown documents. Just add this to ~/.vim/after/syntax/markdown.vim (creating that file if it doesn’t exist):

vimscript:
  syntax region markdownFold start="^\z(#\+\) " end="\(^#\(\z1#*\)\@!#*[^#]\)\@=" transparent fold

Then put an autocmd somewhere in your .vimrc to the effect of

vimscript:
 autocmd FileType markdown set foldmethod=syntax

Once you’ve done this, you can enjoy having your markdown files folded by their headers.

How Does That Hideous Regex Work?

So how does this work? The syntax line defines a “region” for vim’s syntax highlighter named “markdownFold” (although you could call it anything you like). As the keywords on the end indicate, the region will be transparent (since we don’t want to actually see the region) and delimit folds.

The guts of the line are the start and end arguments, which indicate where the region starts and ends, obviously enough. The region will start at the beginning of a line (^) immediately followed by one or more octothorpes (#\+). We also capture the octothorpes we see for use in the end pattern. While normally vim uses \( and \) to indicate capturing groups, to be able to use the matched patterns across the start and end patterns we use \z( to capture (and \z1 to use the backreference, as we shall see (try :h /\z( in vim for more information)).

Now that we have the start of the region to fold, how do we find the end? First, let’s consider where the fold should end. We want a heading of a given level to contain all headings of a smaller level (e.g. so a h1 heading (# foo #) will contain an h2 heading (## bar ##) and to stop the line above a header of the same or higher level. To see how the (highly non-regular) regexp above does this, let’s break it down and examine it piece-by-piece:

  \(              # Start the group
    ^#            # Match a line begining with an octothorpe
    \(            # 
      \z1         # The number of octothorpes that began this group,
      #*          #  followed by zero or more octothorpes...
    \)\@!         # ...should *not* match here (\(foo\)\@! is like
                  #  (?!foo) in perl)
    #*            # and then any number of octothorpes
    [^#]          # and then some non-octothorpe
  \)              #
  \@=             # and make this match zero-width (\(foo\)\@= is like
                  #  perl's (?=foo) , so the region ends on the line
                  #  above this one.

Fairly confusing, but hopefully you get the idea. For more details on vim’s regexps, take a gander at :help pattern-overview.


Design crafted by Prashanth Kamalakanthan. Powered by Tumblr.