" vim: set fdm=marker et ts=4 sw=4 sts=4: " functions for header navigation and information retrieval. function! markdown#headers#CheckValidHeader(lnum) abort "{{{1 if a:lnum != 0 if exists('g:vim_pandoc_syntax_exists') let synId = synIDattr(synID(a:lnum, 1, 1), 'name') if synId !~# '\v^(pandoc|pdc)' || synId ==# 'pandocDelimitedCodeBlock' return 0 endif endif if match(getline(a:lnum), '^#') >= 0 || match(getline(a:lnum+1), '^[-=]') >= 0 return 1 endif endif return 0 endfunction function! markdown#headers#NextHeader(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = getpos('.') endif call cursor(search_from[1], 2) let h_lnum = search('\(^.*\n[-=]\{2\}\|^#\)','nW') if h_lnum != 0 && markdown#headers#CheckValidHeader(h_lnum) != 1 let h_lnum = markdown#headers#NextHeader(h_lnum) endif if h_lnum == 0 if match(getline('.'), '^#') >= 0 || match(getline(line('.')+1), '^[-=]') >= 0 let h_lnum = line('.') endif endif call cursor(origin_pos[1], origin_pos[2]) return h_lnum endfunction function! markdown#headers#PrevHeader(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = origin_pos endif call cursor(search_from[1], 1) let h_lnum = search('\(^.*\n[-=]\{2\}\|^#\)', 'bnW') if h_lnum != 0 && markdown#headers#CheckValidHeader(h_lnum) != 1 let h_lnum = markdown#headers#PrevHeader(h_lnum) " we might go back into the YAML frontmatter, we must recheck if we " are fine if markdown#headers#CheckValidHeader(h_lnum) != 1 let h_lnum = 0 endif endif if h_lnum == 0 if match(getline('.'), '^#') >= 0 || match(getline(line('.')+1), '^[-=]') >= 0 let h_lnum = line('.') endif endif call cursor(origin_pos[1], origin_pos[2]) return h_lnum endfunction function! markdown#headers#ForwardHeader(count) abort "{{{1 let lnum = line('.') for i in range(a:count) let lnum = markdown#headers#NextHeader(lnum) endfor return lnum endfunction function! markdown#headers#BackwardHeader(count) abort "{{{1 let lnum = line('.') for i in range(a:count) let lnum = markdown#headers#PrevHeader(lnum) endfor return lnum endfunction function! markdown#headers#CurrentHeader(...) abort "{{{1 if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = getpos('.') endif " same as PrevHeader(), except don't search if we are already at a header if match(getline(search_from[1]), '^#') < 0 && match(getline(search_from[1]+1), '^[-=]') < 0 return markdown#headers#PrevHeader(search_from[1]) else return search_from[1] endif endfunction function! markdown#headers#CurrentHeaderParent(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = origin_pos endif let ch_lnum = markdown#headers#CurrentHeader(search_from[1]) call cursor(ch_lnum, 1) let l = getline('.') if match(l, '^#') > -1 let parent_level = len(matchstr(l, '#*')) - 1 elseif match(getline(line('.')+1), '^-') > -1 let parent_level = 1 else let parent_level = 0 endif " don't go further than level 1 headers if parent_level > 0 if parent_level == 1 let setext_regex = "^.*\\n=" else let setext_regex = "^.*\\n[-=]" endif let arrival_lnum = search('\('.setext_regex.'\|^#\{1,'.parent_level.'}\s\)', 'bnW') if markdown#headers#CheckValidHeader(arrival_lnum) != 1 let arrival_lnum = search('\('.setext_regex.'\|^#\{1,'.parent_level.'}\s\)', 'bnW') if markdown#headers#CheckValidHeader(arrival_lnum) != 1 let arrival_lnum = 0 endif endif else let arrival_lnum = 0 endif call cursor(origin_pos[1], origin_pos[2]) return arrival_lnum endfunction function! markdown#headers#CurrentHeaderAncestral(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = origin_pos endif let p_lnum = markdown#headers#CurrentHeaderParent(search_from[1]) " we don't have a parent, so we are an ancestral " or we are not under a header if p_lnum == 0 return markdown#headers#CurrentHeader(search_from[1]) endif while p_lnum != 0 call cursor(p_lnum, 1) let a_lnum = markdown#headers#CurrentHeaderParent() if a_lnum != 0 let p_lnum = a_lnum else call cursor(origin_pos[1], origin_pos[2]) return p_lnum endif endwhile call cursor(origin_pos[1], origin_pos[2]) endfunction function! markdown#headers#CurrentHeaderAncestors(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [0, a:1, 1, 0] else let search_from = origin_pos endif let h_genealogy = [] let head = markdown#headers#CurrentHeader(search_from[1]) if head == 0 return [] else call add(h_genealogy, head) endif let p_lnum = markdown#headers#CurrentHeaderParent(search_from[1]) " we don't have a parent, so we are an ancestral if p_lnum == 0 return h_genealogy endif while p_lnum != 0 call cursor(p_lnum, 1) call add(h_genealogy, p_lnum) let a_lnum = markdown#headers#CurrentHeaderParent() if a_lnum != 0 let p_lnum = a_lnum else break endif endwhile call cursor(origin_pos[1], origin_pos[2]) return h_genealogy endfunction function! markdown#headers#SiblingHeader(direction, ...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [1, a:1, 1, 0] else let search_from = origin_pos endif call cursor(search_from[1], search_from[2]) let parent_lnum = markdown#headers#CurrentHeaderParent() let ch_lnum = markdown#headers#CurrentHeader() if a:direction ==# 'b' call cursor(ch_lnum, 1) endif let l = getline(ch_lnum) if match(l, '^#') > -1 let header_level = len(matchstr(l, '#*')) elseif match(l, '^-') > -1 let header_level = 2 else let header_level = 1 endif if header_level == 1 let arrival_lnum = search('\(^.*\n=\|^#\s\)', a:direction.'nW') elseif header_level == 2 let arrival_lnum = search('\(^.*\n-\|^##\s\)', a:direction.'nW') else let arrival_lnum = search('^#\{'.header_level.'}', a:direction.'nW') endif " we might have overshot, check if the parent is still correct let arrival_parent_lnum = markdown#headers#CurrentHeaderParent(arrival_lnum) if arrival_parent_lnum != parent_lnum let arrival_lnum = 0 endif call cursor(origin_pos[1], origin_pos[2]) return arrival_lnum endfunction function! markdown#headers#NextSiblingHeader(...) abort "{{{1 if a:0 > 0 let search_from = a:1 else let search_from = line('.') endif return markdown#headers#SiblingHeader('', search_from) endfunction function! markdown#headers#PrevSiblingHeader(...) abort "{{{1 if a:0 > 0 let search_from = a:1 else let search_from = line('.') endif return markdown#headers#SiblingHeader('b', search_from) endfunction function! markdown#headers#FirstChild(...) abort "{{{1 if a:0 > 0 let search_from = [1, a:1, 1, 0] else let search_from = getpos('.') endif let ch_lnum = markdown#headers#CurrentHeader(search_from[1]) let l = getline(ch_lnum) if match(l, '^#') > -1 let children_level = len(matchstr(l, '#*')) + 1 elseif match(getline(line('.')+1), '^-') > -1 let children_level = 3 else let children_level = 2 endif call cursor(search_from[1], search_from[2]) let next_lnum = markdown#headers#NextHeader() if children_level == 2 let arrival_lnum = search('\(^.*\n-\|^##\s\)', 'nW') else let arrival_lnum = search('^#\{'.children_level.'}', 'nW') endif if arrival_lnum != next_lnum let arrival_lnum = 0 endif return arrival_lnum endfunction function! markdown#headers#LastChild(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [1, a:1, 1, 0] else let search_from = origin_pos endif call cursor(search_from[1], search_from[2]) let fc_lnum = markdown#headers#FirstChild() if fc_lnum != 0 call cursor(fc_lnum, 1) let n_lnum = markdown#headers#NextSiblingHeader() if n_lnum != 0 while n_lnum call cursor(n_lnum, 1) let a_lnum = markdown#headers#NextSiblingHeader() if a_lnum != 0 let n_lnum = a_lnum else break endif endwhile else let n_lnum = fc_lnum endif else let n_lnum = 0 endif call cursor(origin_pos[1], origin_pos[2]) return n_lnum endfunction function! markdown#headers#NthChild(count, ...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [1, a:1, 1, 0] else let search_from = origin_pos endif let fc_lnum = markdown#headers#FirstChild(search_from[1]) call cursor(fc_lnum, 1) if a:count > 1 for child in range(a:count-1) let arrival_lnum = markdown#headers#NextSiblingHeader() if arrival_lnum == 0 break endif call cursor(arrival_lnum, 1) endfor else let arrival_lnum = fc_lnum endif call cursor(origin_pos[1], origin_pos[2]) return arrival_lnum endfunction function! markdown#headers#ID(...) abort "{{{1 let origin_pos = getpos('.') if a:0 > 0 let search_from = [1, a:1, 1, 0] else let search_from = origin_pos endif let cheader_lnum = markdown#headers#CurrentHeader(search_from[1]) let cheader = getline(cheader_lnum) call cursor(origin_pos[1], origin_pos[2]) return markdown#headers#GetAutomaticID(cheader) endfunction " GetAutomaticID(header) " see http://johnmacfarlane.net/pandoc/README.html#extension-auto_identifiers function! markdown#headers#GetAutomaticID(header) abort " {{{1 let header_metadata = matchstr(a:header, '{.*}') if header_metadata !=# '' let header_id = matchstr(header_metadata, '#[[:alnum:]-]*')[1:] endif if !exists('header_id') || header_id ==# '' let text = substitute(a:header, '\[\(.\{-}\)\]\[.*\]', '\1', '') " remove links let text = substitute(text, '\s{.*}', '', '') " remove attributes let text = substitute(text, '[!"#\$%\&''()\*+,/:;<=>?@\[\\\]\^`{|}\~]', '', 'g') " remove formatting and punctuation, except -_. (hyphen, underscore, period) let text = substitute(text, '.\{-}[[:alpha:]\u20AC-\uFFFF]\@=', '', '') " remove everything before the first letter let text = substitute(text, '\s', '-', 'g') " replace spaces with dashes let text = tolower(text) " turn lowercase if match(text, "[[:alpha:]\u20AC-\uFFFF]") > -1 let header_id = text else let header_id = 'section' endif endif return header_id endfunction " GetAllIDs() " get all the header indentifiers and it's position, both specified and automatic generated function! markdown#headers#GetAllIDs() abort " {{{1 let header_pos = {} " update the location list call pandoc#toc#Update() let headers = getloclist(0) for header in headers let header_pos[markdown#headers#GetAutomaticID(header.text)] = header.lnum endfor return header_pos endfunction