headers.vim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. " vim: set fdm=marker et ts=4 sw=4 sts=4:
  2. " functions for header navigation and information retrieval.
  3. function! markdown#headers#CheckValidHeader(lnum) abort "{{{1
  4. if a:lnum != 0
  5. if exists('g:vim_pandoc_syntax_exists')
  6. let synId = synIDattr(synID(a:lnum, 1, 1), 'name')
  7. if synId !~# '\v^(pandoc|pdc)' || synId ==# 'pandocDelimitedCodeBlock'
  8. return 0
  9. endif
  10. endif
  11. if match(getline(a:lnum), '^#') >= 0 || match(getline(a:lnum+1), '^[-=]') >= 0
  12. return 1
  13. endif
  14. endif
  15. return 0
  16. endfunction
  17. function! markdown#headers#NextHeader(...) abort "{{{1
  18. let origin_pos = getpos('.')
  19. if a:0 > 0
  20. let search_from = [0, a:1, 1, 0]
  21. else
  22. let search_from = getpos('.')
  23. endif
  24. call cursor(search_from[1], 2)
  25. let h_lnum = search('\(^.*\n[-=]\{2\}\|^#\)','nW')
  26. if h_lnum != 0 && markdown#headers#CheckValidHeader(h_lnum) != 1
  27. let h_lnum = markdown#headers#NextHeader(h_lnum)
  28. endif
  29. if h_lnum == 0
  30. if match(getline('.'), '^#') >= 0 || match(getline(line('.')+1), '^[-=]') >= 0
  31. let h_lnum = line('.')
  32. endif
  33. endif
  34. call cursor(origin_pos[1], origin_pos[2])
  35. return h_lnum
  36. endfunction
  37. function! markdown#headers#PrevHeader(...) abort "{{{1
  38. let origin_pos = getpos('.')
  39. if a:0 > 0
  40. let search_from = [0, a:1, 1, 0]
  41. else
  42. let search_from = origin_pos
  43. endif
  44. call cursor(search_from[1], 1)
  45. let h_lnum = search('\(^.*\n[-=]\{2\}\|^#\)', 'bnW')
  46. if h_lnum != 0 && markdown#headers#CheckValidHeader(h_lnum) != 1
  47. let h_lnum = markdown#headers#PrevHeader(h_lnum)
  48. " we might go back into the YAML frontmatter, we must recheck if we
  49. " are fine
  50. if markdown#headers#CheckValidHeader(h_lnum) != 1
  51. let h_lnum = 0
  52. endif
  53. endif
  54. if h_lnum == 0
  55. if match(getline('.'), '^#') >= 0 || match(getline(line('.')+1), '^[-=]') >= 0
  56. let h_lnum = line('.')
  57. endif
  58. endif
  59. call cursor(origin_pos[1], origin_pos[2])
  60. return h_lnum
  61. endfunction
  62. function! markdown#headers#ForwardHeader(count) abort "{{{1
  63. let lnum = line('.')
  64. for i in range(a:count)
  65. let lnum = markdown#headers#NextHeader(lnum)
  66. endfor
  67. return lnum
  68. endfunction
  69. function! markdown#headers#BackwardHeader(count) abort "{{{1
  70. let lnum = line('.')
  71. for i in range(a:count)
  72. let lnum = markdown#headers#PrevHeader(lnum)
  73. endfor
  74. return lnum
  75. endfunction
  76. function! markdown#headers#CurrentHeader(...) abort "{{{1
  77. if a:0 > 0
  78. let search_from = [0, a:1, 1, 0]
  79. else
  80. let search_from = getpos('.')
  81. endif
  82. " same as PrevHeader(), except don't search if we are already at a header
  83. if match(getline(search_from[1]), '^#') < 0 && match(getline(search_from[1]+1), '^[-=]') < 0
  84. return markdown#headers#PrevHeader(search_from[1])
  85. else
  86. return search_from[1]
  87. endif
  88. endfunction
  89. function! markdown#headers#CurrentHeaderParent(...) abort "{{{1
  90. let origin_pos = getpos('.')
  91. if a:0 > 0
  92. let search_from = [0, a:1, 1, 0]
  93. else
  94. let search_from = origin_pos
  95. endif
  96. let ch_lnum = markdown#headers#CurrentHeader(search_from[1])
  97. call cursor(ch_lnum, 1)
  98. let l = getline('.')
  99. if match(l, '^#') > -1
  100. let parent_level = len(matchstr(l, '#*')) - 1
  101. elseif match(getline(line('.')+1), '^-') > -1
  102. let parent_level = 1
  103. else
  104. let parent_level = 0
  105. endif
  106. " don't go further than level 1 headers
  107. if parent_level > 0
  108. if parent_level == 1
  109. let setext_regex = "^.*\\n="
  110. else
  111. let setext_regex = "^.*\\n[-=]"
  112. endif
  113. let arrival_lnum = search('\('.setext_regex.'\|^#\{1,'.parent_level.'}\s\)', 'bnW')
  114. if markdown#headers#CheckValidHeader(arrival_lnum) != 1
  115. let arrival_lnum = search('\('.setext_regex.'\|^#\{1,'.parent_level.'}\s\)', 'bnW')
  116. if markdown#headers#CheckValidHeader(arrival_lnum) != 1
  117. let arrival_lnum = 0
  118. endif
  119. endif
  120. else
  121. let arrival_lnum = 0
  122. endif
  123. call cursor(origin_pos[1], origin_pos[2])
  124. return arrival_lnum
  125. endfunction
  126. function! markdown#headers#CurrentHeaderAncestral(...) abort "{{{1
  127. let origin_pos = getpos('.')
  128. if a:0 > 0
  129. let search_from = [0, a:1, 1, 0]
  130. else
  131. let search_from = origin_pos
  132. endif
  133. let p_lnum = markdown#headers#CurrentHeaderParent(search_from[1])
  134. " we don't have a parent, so we are an ancestral
  135. " or we are not under a header
  136. if p_lnum == 0
  137. return markdown#headers#CurrentHeader(search_from[1])
  138. endif
  139. while p_lnum != 0
  140. call cursor(p_lnum, 1)
  141. let a_lnum = markdown#headers#CurrentHeaderParent()
  142. if a_lnum != 0
  143. let p_lnum = a_lnum
  144. else
  145. call cursor(origin_pos[1], origin_pos[2])
  146. return p_lnum
  147. endif
  148. endwhile
  149. call cursor(origin_pos[1], origin_pos[2])
  150. endfunction
  151. function! markdown#headers#CurrentHeaderAncestors(...) abort "{{{1
  152. let origin_pos = getpos('.')
  153. if a:0 > 0
  154. let search_from = [0, a:1, 1, 0]
  155. else
  156. let search_from = origin_pos
  157. endif
  158. let h_genealogy = []
  159. let head = markdown#headers#CurrentHeader(search_from[1])
  160. if head == 0
  161. return []
  162. else
  163. call add(h_genealogy, head)
  164. endif
  165. let p_lnum = markdown#headers#CurrentHeaderParent(search_from[1])
  166. " we don't have a parent, so we are an ancestral
  167. if p_lnum == 0
  168. return h_genealogy
  169. endif
  170. while p_lnum != 0
  171. call cursor(p_lnum, 1)
  172. call add(h_genealogy, p_lnum)
  173. let a_lnum = markdown#headers#CurrentHeaderParent()
  174. if a_lnum != 0
  175. let p_lnum = a_lnum
  176. else
  177. break
  178. endif
  179. endwhile
  180. call cursor(origin_pos[1], origin_pos[2])
  181. return h_genealogy
  182. endfunction
  183. function! markdown#headers#SiblingHeader(direction, ...) abort "{{{1
  184. let origin_pos = getpos('.')
  185. if a:0 > 0
  186. let search_from = [1, a:1, 1, 0]
  187. else
  188. let search_from = origin_pos
  189. endif
  190. call cursor(search_from[1], search_from[2])
  191. let parent_lnum = markdown#headers#CurrentHeaderParent()
  192. let ch_lnum = markdown#headers#CurrentHeader()
  193. if a:direction ==# 'b'
  194. call cursor(ch_lnum, 1)
  195. endif
  196. let l = getline(ch_lnum)
  197. if match(l, '^#') > -1
  198. let header_level = len(matchstr(l, '#*'))
  199. elseif match(l, '^-') > -1
  200. let header_level = 2
  201. else
  202. let header_level = 1
  203. endif
  204. if header_level == 1
  205. let arrival_lnum = search('\(^.*\n=\|^#\s\)', a:direction.'nW')
  206. elseif header_level == 2
  207. let arrival_lnum = search('\(^.*\n-\|^##\s\)', a:direction.'nW')
  208. else
  209. let arrival_lnum = search('^#\{'.header_level.'}', a:direction.'nW')
  210. endif
  211. " we might have overshot, check if the parent is still correct
  212. let arrival_parent_lnum = markdown#headers#CurrentHeaderParent(arrival_lnum)
  213. if arrival_parent_lnum != parent_lnum
  214. let arrival_lnum = 0
  215. endif
  216. call cursor(origin_pos[1], origin_pos[2])
  217. return arrival_lnum
  218. endfunction
  219. function! markdown#headers#NextSiblingHeader(...) abort "{{{1
  220. if a:0 > 0
  221. let search_from = a:1
  222. else
  223. let search_from = line('.')
  224. endif
  225. return markdown#headers#SiblingHeader('', search_from)
  226. endfunction
  227. function! markdown#headers#PrevSiblingHeader(...) abort "{{{1
  228. if a:0 > 0
  229. let search_from = a:1
  230. else
  231. let search_from = line('.')
  232. endif
  233. return markdown#headers#SiblingHeader('b', search_from)
  234. endfunction
  235. function! markdown#headers#FirstChild(...) abort "{{{1
  236. if a:0 > 0
  237. let search_from = [1, a:1, 1, 0]
  238. else
  239. let search_from = getpos('.')
  240. endif
  241. let ch_lnum = markdown#headers#CurrentHeader(search_from[1])
  242. let l = getline(ch_lnum)
  243. if match(l, '^#') > -1
  244. let children_level = len(matchstr(l, '#*')) + 1
  245. elseif match(getline(line('.')+1), '^-') > -1
  246. let children_level = 3
  247. else
  248. let children_level = 2
  249. endif
  250. call cursor(search_from[1], search_from[2])
  251. let next_lnum = markdown#headers#NextHeader()
  252. if children_level == 2
  253. let arrival_lnum = search('\(^.*\n-\|^##\s\)', 'nW')
  254. else
  255. let arrival_lnum = search('^#\{'.children_level.'}', 'nW')
  256. endif
  257. if arrival_lnum != next_lnum
  258. let arrival_lnum = 0
  259. endif
  260. return arrival_lnum
  261. endfunction
  262. function! markdown#headers#LastChild(...) abort "{{{1
  263. let origin_pos = getpos('.')
  264. if a:0 > 0
  265. let search_from = [1, a:1, 1, 0]
  266. else
  267. let search_from = origin_pos
  268. endif
  269. call cursor(search_from[1], search_from[2])
  270. let fc_lnum = markdown#headers#FirstChild()
  271. if fc_lnum != 0
  272. call cursor(fc_lnum, 1)
  273. let n_lnum = markdown#headers#NextSiblingHeader()
  274. if n_lnum != 0
  275. while n_lnum
  276. call cursor(n_lnum, 1)
  277. let a_lnum = markdown#headers#NextSiblingHeader()
  278. if a_lnum != 0
  279. let n_lnum = a_lnum
  280. else
  281. break
  282. endif
  283. endwhile
  284. else
  285. let n_lnum = fc_lnum
  286. endif
  287. else
  288. let n_lnum = 0
  289. endif
  290. call cursor(origin_pos[1], origin_pos[2])
  291. return n_lnum
  292. endfunction
  293. function! markdown#headers#NthChild(count, ...) abort "{{{1
  294. let origin_pos = getpos('.')
  295. if a:0 > 0
  296. let search_from = [1, a:1, 1, 0]
  297. else
  298. let search_from = origin_pos
  299. endif
  300. let fc_lnum = markdown#headers#FirstChild(search_from[1])
  301. call cursor(fc_lnum, 1)
  302. if a:count > 1
  303. for child in range(a:count-1)
  304. let arrival_lnum = markdown#headers#NextSiblingHeader()
  305. if arrival_lnum == 0
  306. break
  307. endif
  308. call cursor(arrival_lnum, 1)
  309. endfor
  310. else
  311. let arrival_lnum = fc_lnum
  312. endif
  313. call cursor(origin_pos[1], origin_pos[2])
  314. return arrival_lnum
  315. endfunction
  316. function! markdown#headers#ID(...) abort "{{{1
  317. let origin_pos = getpos('.')
  318. if a:0 > 0
  319. let search_from = [1, a:1, 1, 0]
  320. else
  321. let search_from = origin_pos
  322. endif
  323. let cheader_lnum = markdown#headers#CurrentHeader(search_from[1])
  324. let cheader = getline(cheader_lnum)
  325. call cursor(origin_pos[1], origin_pos[2])
  326. return markdown#headers#GetAutomaticID(cheader)
  327. endfunction
  328. " GetAutomaticID(header)
  329. " see http://johnmacfarlane.net/pandoc/README.html#extension-auto_identifiers
  330. function! markdown#headers#GetAutomaticID(header) abort " {{{1
  331. let header_metadata = matchstr(a:header, '{.*}')
  332. if header_metadata !=# ''
  333. let header_id = matchstr(header_metadata, '#[[:alnum:]-]*')[1:]
  334. endif
  335. if !exists('header_id') || header_id ==# ''
  336. let text = substitute(a:header, '\[\(.\{-}\)\]\[.*\]', '\1', '') " remove links
  337. let text = substitute(text, '\s{.*}', '', '') " remove attributes
  338. let text = substitute(text, '[!"#\$%\&''()\*+,/:;<=>?@\[\\\]\^`{|}\~]', '', 'g') " remove formatting and punctuation, except -_. (hyphen, underscore, period)
  339. let text = substitute(text, '.\{-}[[:alpha:]\u20AC-\uFFFF]\@=', '', '') " remove everything before the first letter
  340. let text = substitute(text, '\s', '-', 'g') " replace spaces with dashes
  341. let text = tolower(text) " turn lowercase
  342. if match(text, "[[:alpha:]\u20AC-\uFFFF]") > -1
  343. let header_id = text
  344. else
  345. let header_id = 'section'
  346. endif
  347. endif
  348. return header_id
  349. endfunction
  350. " GetAllIDs()
  351. " get all the header indentifiers and it's position, both specified and automatic generated
  352. function! markdown#headers#GetAllIDs() abort " {{{1
  353. let header_pos = {}
  354. " update the location list
  355. call pandoc#toc#Update()
  356. let headers = getloclist(0)
  357. for header in headers
  358. let header_pos[markdown#headers#GetAutomaticID(header.text)] = header.lnum
  359. endfor
  360. return header_pos
  361. endfunction