Vim and markdown frontmatter

2019-03-07

Built-in markdown in vim (from Tim Pope — vim hero) has no support for pandoc frontmatter:

Like the picture shows: As is built in markdown in Vim

So, let’s fix it!

Highlight fronmatter

Create a file ~/.vim/after/syntax/markdown.vim:

unlet b:current_syntax
syntax include @Yaml syntax/yaml.vim
syntax region yamlFrontmatter start=/\%^---$/ end=/^---$/ keepend contains=@Yaml

Now reopen your markdown file to see:

highlight frontmatter markdown in Vim

Fix folding

Create a file ~/.vim/after/ftplugin/markdown.vim:

function! MarkdownFold()
  let line = getline(v:lnum)

  " Regular headers
  let depth = match(line, '\(^#\+\)\@<=\( .*$\)\@=')
  if depth > 0
    return ">" . depth
  endif

  " Setext style headings
  let prevline = getline(v:lnum - 1)
  let nextline = getline(v:lnum + 1)
  if (line =~ '^.\+$') && (nextline =~ '^=\+$') && (prevline =~ '^\s*$')
    return ">1"
  endif

  if (line =~ '^.\+$') && (nextline =~ '^-\+$') && (prevline =~ '^\s*$')
    return ">2"
  endif

  " frontmatter
  if (v:lnum == 1) && (line =~ '^----*$')
	  return ">1"
  endif

  return "="
endfunction

Built-in markdown filetype plugin uses MarkdownFold() function to define folds.

We just copy-pasted it here with some changes:

Reload your markdown file and it should look like:

Fold frontmatter markdown in Vim

But wait! It doesn’t look this fancy! I see dashes in frontmatter fold instead of the title…

To fix this you have to setup your foldtext, add to your .vimrc:

" My fancy foldtext
set foldtext=MyFoldText()
fu! MyFoldText()
	let line = getline(v:foldstart)

	" markdown frontmatter -- just take the next line hoping it would be
	" title: Your title
	if line =~ '^----*$'
		let line = getline(v:foldstart+1)
	endif

	let indent = max([indent(v:foldstart)-v:foldlevel, 1])
	let lines = (v:foldend - v:foldstart + 1)
	let strip_line = substitute(line, '^//\|=\+\|["#]\|/\*\|\*/\|{{{\d\=\|title:\s*', '', 'g')
	let strip_line = substitute(strip_line, '^[[:space:]]*\|[[:space:]]*$', '', 'g')
	let text = strpart(strip_line, 0, winwidth(0) - v:foldlevel - indent - 6 - strlen(lines))
	if strlen(strip_line) > strlen(text)
		let text = text.'…'
	endif
	return repeat('▧', v:foldlevel) . repeat(' ', indent) . text .' ('. lines .')'
endfu

Change unicode characters to whatever you prefer if your font has no support for ones in this function.