+" Return list of headers and their levels.
+"
+function! s:GetHeaderList()
+ let l:bufnr = bufnr('%')
+ let l:fenced_block = 0
+ let l:front_matter = 0
+ let l:header_list = []
+ let l:vim_markdown_frontmatter = get(g:, 'vim_markdown_frontmatter', 0)
+ let l:fence_str = ''
+ for i in range(1, line('$'))
+ let l:lineraw = getline(i)
+ let l:l1 = getline(i+1)
+ let l:line = substitute(l:lineraw, '#', "\\\#", 'g')
+ " exclude lines in fenced code blocks
+ if l:line =~# '\v^[[:space:]>]*(`{3,}|\~{3,})\s*(\w+)?\s*$'
+ if l:fenced_block == 0
+ let l:fenced_block = 1
+ let l:fence_str = matchstr(l:line, '\v(`{3,}|\~{3,})')
+ elseif l:fenced_block == 1 && matchstr(l:line, '\v(`{3,}|\~{3,})') ==# l:fence_str
+ let l:fenced_block = 0
+ let l:fence_str = ''
+ endif
+ " exclude lines in frontmatters
+ elseif l:vim_markdown_frontmatter == 1
+ if l:front_matter == 1
+ if l:line ==# '---'
+ let l:front_matter = 0
+ endif
+ elseif i == 1
+ if l:line ==# '---'
+ let l:front_matter = 1
+ endif
+ endif
+ endif
+ " match line against header regex
+ if join(getline(i, i + 1), "\n") =~# s:headersRegexp && l:line =~# '^\S'
+ let l:is_header = 1
+ else
+ let l:is_header = 0
+ endif
+ if l:is_header ==# 1 && l:fenced_block ==# 0 && l:front_matter ==# 0
+ " remove hashes from atx headers
+ if match(l:line, '^#') > -1
+ let l:line = substitute(l:line, '\v^#*[ ]*', '', '')
+ let l:line = substitute(l:line, '\v[ ]*#*$', '', '')
+ endif
+ " append line to list
+ let l:level = s:GetHeaderLevel(i)
+ let l:item = {'level': l:level, 'text': l:line, 'lnum': i, 'bufnr': bufnr}
+ let l:header_list = l:header_list + [l:item]
+ endif
+ endfor
+ return l:header_list
+endfunction
+