]> git.madduck.net Git - etc/vim.git/blob - ftplugin/markdown.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Add basic usage in table of contents
[etc/vim.git] / ftplugin / markdown.vim
1 "TODO print messages when on visual mode. I only see VISUAL, not the messages.
2
3 " Function interface phylosophy:
4 "
5 " - functions take arbitrary line numbers as parameters.
6 "    Current cursor line is only a suitable default parameter.
7 "
8 " - only functions that bind directly to user actions:
9 "
10 "    - print error messages.
11 "       All intermediate functions limit themselves return `0` to indicate an error.
12 "
13 "    - move the cursor. All other functions do not move the cursor.
14 "
15 " This is how you should view headers for the header mappings:
16 "
17 "   |BUFFER
18 "   |
19 "   |Outside any header
20 "   |
21 " a-+# a
22 "   |
23 "   |Inside a
24 "   |
25 " a-+
26 " b-+## b
27 "   |
28 "   |inside b
29 "   |
30 " b-+
31 " c-+### c
32 "   |
33 "   |Inside c
34 "   |
35 " c-+
36 " d-|# d
37 "   |
38 "   |Inside d
39 "   |
40 " d-+
41 " e-|e
42 "   |====
43 "   |
44 "   |Inside e
45 "   |
46 " e-+
47
48 " For each level, contains the regexp that matches at that level only.
49 "
50 let s:levelRegexpDict = {
51     \ 1: '\v^(#[^#]@=|.+\n\=+$)',
52     \ 2: '\v^(##[^#]@=|.+\n-+$)',
53     \ 3: '\v^###[^#]@=',
54     \ 4: '\v^####[^#]@=',
55     \ 5: '\v^#####[^#]@=',
56     \ 6: '\v^######[^#]@='
57 \ }
58
59 " Maches any header level of any type.
60 "
61 " This could be deduced from `s:levelRegexpDict`, but it is more
62 " efficient to have a single regexp for this.
63 "
64 let s:headersRegexp = '\v^(#|.+\n(\=+|-+)$)'
65
66 " Returns the line number of the first header before `line`, called the
67 " current header.
68 "
69 " If there is no current header, return `0`.
70 "
71 " @param a:1 The line to look the header of. Default value: `getpos('.')`.
72 "
73 function! s:GetHeaderLineNum(...)
74     if a:0 == 0
75         let l:l = line('.')
76     else
77         let l:l = a:1
78     endif
79     while(l:l > 0)
80         if join(getline(l:l, l:l + 1), "\n") =~ s:headersRegexp
81             return l:l
82         endif
83         let l:l -= 1
84     endwhile
85     return 0
86 endfunction
87
88 " -  if inside a header goes to it.
89 "    Return its line number.
90 "
91 " -  if on top level outside any headers,
92 "    print a warning
93 "    Return `0`.
94 "
95 function! s:MoveToCurHeader()
96     let l:lineNum = s:GetHeaderLineNum()
97     if l:lineNum != 0
98         call cursor(l:lineNum, 1)
99     else
100         echo 'outside any header'
101         "normal! gg
102     endif
103     return l:lineNum
104 endfunction
105
106 " Move cursor to next header of any level.
107 "
108 " If there are no more headers, print a warning.
109 "
110 function! s:MoveToNextHeader()
111     if search(s:headersRegexp, 'W') == 0
112         "normal! G
113         echo 'no next header'
114     endif
115 endfunction
116
117 " Move cursor to previous header (before current) of any level.
118 "
119 " If it does not exist, print a warning.
120 "
121 function! s:MoveToPreviousHeader()
122     let l:curHeaderLineNumber = s:GetHeaderLineNum()
123     let l:noPreviousHeader = 0
124     if l:curHeaderLineNumber <= 1
125         let l:noPreviousHeader = 1
126     else
127         let l:previousHeaderLineNumber = s:GetHeaderLineNum(l:curHeaderLineNumber - 1)
128         if l:previousHeaderLineNumber == 0
129             let l:noPreviousHeader = 1
130         else
131             call cursor(l:previousHeaderLineNumber, 1)
132         endif
133     endif
134     if l:noPreviousHeader
135         echo 'no previous header'
136     endif
137 endfunction
138
139 " - if line is inside a header, return the header level (h1 -> 1, h2 -> 2, etc.).
140 "
141 " - if line is at top level outside any headers, return `0`.
142 "
143 function! s:GetHeaderLevel(...)
144     if a:0 == 0
145         let l:line = line('.')
146     else
147         let l:line = a:1
148     endif
149     let l:linenum = s:GetHeaderLineNum(l:line)
150     if l:linenum != 0
151         return s:GetLevelOfHeaderAtLine(l:linenum)
152     else
153         return 0
154     endif
155 endfunction
156
157 " Returns the level of the header at the given line.
158 "
159 " If there is no header at the given line, returns `0`.
160 "
161 function! s:GetLevelOfHeaderAtLine(linenum)
162     let l:lines = join(getline(a:linenum, a:linenum + 1), "\n")
163     for l:key in keys(s:levelRegexpDict)
164         if l:lines =~ get(s:levelRegexpDict, l:key)
165             return l:key
166         endif
167     endfor
168     return 0
169 endfunction
170
171 " Move cursor to parent header of the current header.
172 "
173 " If it does not exit, print a warning and do nothing.
174 "
175 function! s:MoveToParentHeader()
176     let l:linenum = s:GetParentHeaderLineNumber()
177     if l:linenum != 0
178         call cursor(l:linenum, 1)
179     else
180         echo 'no parent header'
181     endif
182 endfunction
183
184 " Return the line number of the parent header of line `line`.
185 "
186 " If it has no parent, return `0`.
187 "
188 function! s:GetParentHeaderLineNumber(...)
189     if a:0 == 0
190         let l:line = line('.')
191     else
192         let l:line = a:1
193     endif
194     let l:level = s:GetHeaderLevel(l:line)
195     if l:level > 1
196         let l:linenum = s:GetPreviousHeaderLineNumberAtLevel(l:level - 1, l:line)
197         return l:linenum
198     endif
199     return 0
200 endfunction
201
202 " Return the line number of the previous header of given level.
203 " in relation to line `a:1`. If not given, `a:1 = getline()`
204 "
205 " `a:1` line is included, and this may return the current header.
206 "
207 " If none return 0.
208 "
209 function! s:GetNextHeaderLineNumberAtLevel(level, ...)
210     if a:0 < 1
211         let l:line = line('.')
212     else
213         let l:line = a:1
214     endif
215     let l:l = l:line
216     while(l:l <= line('$'))
217         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
218             return l:l
219         endif
220         let l:l += 1
221     endwhile
222     return 0
223 endfunction
224
225 " Return the line number of the previous header of given level.
226 " in relation to line `a:1`. If not given, `a:1 = getline()`
227 "
228 " `a:1` line is included, and this may return the current header.
229 "
230 " If none return 0.
231 "
232 function! s:GetPreviousHeaderLineNumberAtLevel(level, ...)
233     if a:0 == 0
234         let l:line = line('.')
235     else
236         let l:line = a:1
237     endif
238     let l:l = l:line
239     while(l:l > 0)
240         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
241             return l:l
242         endif
243         let l:l -= 1
244     endwhile
245     return 0
246 endfunction
247
248 " Move cursor to next sibling header.
249 "
250 " If there is no next siblings, print a warning and don't move.
251 "
252 function! s:MoveToNextSiblingHeader()
253     let l:curHeaderLineNumber = s:GetHeaderLineNum()
254     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
255     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
256     let l:nextHeaderSameLevelLineNumber = s:GetNextHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber + 1)
257     let l:noNextSibling = 0
258     if l:nextHeaderSameLevelLineNumber == 0
259         let l:noNextSibling = 1
260     else
261         let l:nextHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:nextHeaderSameLevelLineNumber)
262         if l:curHeaderParentLineNumber == l:nextHeaderSameLevelParentLineNumber
263             call cursor(l:nextHeaderSameLevelLineNumber, 1)
264         else
265             let l:noNextSibling = 1
266         endif
267     endif
268     if l:noNextSibling
269         echo 'no next sibling header'
270     endif
271 endfunction
272
273 " Move cursor to previous sibling header.
274 "
275 " If there is no previous siblings, print a warning and do nothing.
276 "
277 function! s:MoveToPreviousSiblingHeader()
278     let l:curHeaderLineNumber = s:GetHeaderLineNum()
279     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
280     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
281     let l:previousHeaderSameLevelLineNumber = s:GetPreviousHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber - 1)
282     let l:noPreviousSibling = 0
283     if l:previousHeaderSameLevelLineNumber == 0
284         let l:noPreviousSibling = 1
285     else
286         let l:previousHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:previousHeaderSameLevelLineNumber)
287         if l:curHeaderParentLineNumber == l:previousHeaderSameLevelParentLineNumber
288             call cursor(l:previousHeaderSameLevelLineNumber, 1)
289         else
290             let l:noPreviousSibling = 1
291         endif
292     endif
293     if l:noPreviousSibling
294         echo 'no previous sibling header'
295     endif
296 endfunction
297
298 function! s:Toc(...)
299     if a:0 > 0
300         let l:window_type = a:1
301     else
302         let l:window_type = 'vertical'
303     endif
304
305
306     let l:bufnr = bufnr('%')
307     let l:cursor_line = line('.')
308     let l:cursor_header = 0
309     let l:fenced_block = 0
310     let l:front_matter = 0
311     let l:header_list = []
312     let l:header_max_len = 0
313     let l:vim_markdown_toc_autofit = get(g:, "vim_markdown_toc_autofit", 0)
314     let l:vim_markdown_frontmatter = get(g:, "vim_markdown_frontmatter", 0)
315     for i in range(1, line('$'))
316         let l:lineraw = getline(i)
317         let l:l1 = getline(i+1)
318         let l:line = substitute(l:lineraw, "#", "\\\#", "g")
319         if l:line =~ '````*' || l:line =~ '\~\~\~\~*'
320             if l:fenced_block == 0
321                 let l:fenced_block = 1
322             elseif l:fenced_block == 1
323                 let l:fenced_block = 0
324             endif
325         elseif l:vim_markdown_frontmatter == 1
326             if l:front_matter == 1
327                 if l:line == '---'
328                     let l:front_matter = 0
329                 endif
330             elseif i == 1
331                 if l:line == '---'
332                     let l:front_matter = 1
333                 endif
334             endif
335         endif
336         if l:line =~ '^#\+' || (l:l1 =~ '^=\+\s*$' || l:l1 =~ '^-\+\s*$') && l:line =~ '^\S'
337             let l:is_header = 1
338         else
339             let l:is_header = 0
340         endif
341         if l:is_header == 1 && l:fenced_block == 0 && l:front_matter == 0
342             " append line to location list
343             let l:item = {'lnum': i, 'text': l:line, 'valid': 1, 'bufnr': l:bufnr, 'col': 1}
344             let l:header_list = l:header_list + [l:item]
345             " set header number of the cursor position
346             if l:cursor_header == 0
347                 if i == l:cursor_line
348                     let l:cursor_header = len(l:header_list)
349                 elseif i > l:cursor_line
350                     let l:cursor_header = len(l:header_list) - 1
351                 endif
352             endif
353             " keep track of the longest header size (heading level + title)
354             let l:total_len = stridx(l:line, ' ') + strdisplaywidth(l:line)
355             if l:total_len > l:header_max_len
356                 let l:header_max_len = l:total_len
357             endif
358         endif
359     endfor
360     call setloclist(0, l:header_list)
361     if len(l:header_list) == 0
362         echom "Toc: No headers."
363         return
364     endif
365
366     if l:window_type ==# 'horizontal'
367         lopen
368     elseif l:window_type ==# 'vertical'
369         vertical lopen
370         " auto-fit toc window when possible to shrink it
371         if (&columns/2) > l:header_max_len && l:vim_markdown_toc_autofit == 1
372             execute 'vertical resize ' . (l:header_max_len + 1)
373         else
374             execute 'vertical resize ' . (&columns/2)
375         endif
376     elseif l:window_type ==# 'tab'
377         tab lopen
378     else
379         lopen
380     endif
381     setlocal modifiable
382     for i in range(1, line('$'))
383         " this is the location-list data for the current item
384         let d = getloclist(0)[i-1]
385         " atx headers
386         if match(d.text, "^#") > -1
387             let l:level = len(matchstr(d.text, '#*', 'g'))-1
388             let d.text = substitute(d.text, '\v^#*[ ]*', '', '')
389             let d.text = substitute(d.text, '\v[ ]*#*$', '', '')
390         " setex headers
391         else
392             let l:next_line = getbufline(d.bufnr, d.lnum+1)
393             if match(l:next_line, "=") > -1
394                 let l:level = 0
395             elseif match(l:next_line, "-") > -1
396                 let l:level = 1
397             endif
398         endif
399         call setline(i, repeat('  ', l:level). d.text)
400     endfor
401     setlocal nomodified
402     setlocal nomodifiable
403     execute 'normal! ' . l:cursor_header . 'G'
404 endfunction
405
406 " Convert Setex headers in range `line1 .. line2` to Atx.
407 "
408 " Return the number of conversions.
409 "
410 function! s:SetexToAtx(line1, line2)
411     let l:originalNumLines = line('$')
412     execute 'silent! ' . a:line1 . ',' . a:line2 . 'substitute/\v(.*\S.*)\n\=+$/# \1/'
413     execute 'silent! ' . a:line1 . ',' . a:line2 . 'substitute/\v(.*\S.*)\n-+$/## \1/'
414     return l:originalNumLines - line('$')
415 endfunction
416
417 " If `a:1` is 0, decrease the level of all headers in range `line1 .. line2`.
418 "
419 " Otherwise, increase the level. `a:1` defaults to `0`.
420 "
421 function! s:HeaderDecrease(line1, line2, ...)
422     if a:0 > 0
423         let l:increase = a:1
424     else
425         let l:increase = 0
426     endif
427     if l:increase
428         let l:forbiddenLevel = 6
429         let l:replaceLevels = [5, 1]
430         let l:levelDelta = 1
431     else
432         let l:forbiddenLevel = 1
433         let l:replaceLevels = [2, 6]
434         let l:levelDelta = -1
435     endif
436     for l:line in range(a:line1, a:line2)
437         if join(getline(l:line, l:line + 1), "\n") =~ s:levelRegexpDict[l:forbiddenLevel]
438             echomsg 'There is an h' . l:forbiddenLevel . ' at line ' . l:line . '. Aborting.'
439             return
440         endif
441     endfor
442     let l:numSubstitutions = s:SetexToAtx(a:line1, a:line2)
443     let l:flags = (&gdefault ? '' : 'g')
444     for l:level in range(replaceLevels[0], replaceLevels[1], -l:levelDelta)
445         execute 'silent! ' . a:line1 . ',' . (a:line2 - l:numSubstitutions) . 'substitute/' . s:levelRegexpDict[l:level] . '/' . repeat('#', l:level + l:levelDelta) . '/' . l:flags
446     endfor
447 endfunction
448
449 " Format table under cursor.
450 "
451 " Depends on Tabularize.
452 "
453 function! s:TableFormat()
454     let l:pos = getpos('.')
455     normal! {
456     " Search instead of `normal! j` because of the table at beginning of file edge case.
457     call search('|')
458     normal! j
459     " Remove everything that is not a pipe, colon or hyphen next to a colon othewise
460     " well formated tables would grow because of addition of 2 spaces on the separator
461     " line by Tabularize /|.
462     let l:flags = (&gdefault ? '' : 'g')
463     execute 's/\(:\@<!-:\@!\|[^|:-]\)//e' . l:flags
464     execute 's/--/-/e' . l:flags
465     Tabularize /|
466     " Move colons for alignment to left or right side of the cell.
467     execute 's/:\( \+\)|/\1:|/e' . l:flags
468     execute 's/|\( \+\):/|:\1/e' . l:flags
469     execute 's/ /-/' . l:flags
470     call setpos('.', l:pos)
471 endfunction
472
473 " Wrapper to do move commands in visual mode.
474 "
475 function! s:VisMove(f)
476     norm! gv
477     call function(a:f)()
478 endfunction
479
480 " Map in both normal and visual modes.
481 "
482 function! s:MapNormVis(rhs,lhs)
483     execute 'nn <buffer><silent> ' . a:rhs . ' :call ' . a:lhs . '()<cr>'
484     execute 'vn <buffer><silent> ' . a:rhs . ' <esc>:call <sid>VisMove(''' . a:lhs . ''')<cr>'
485 endfunction
486
487 " Parameters:
488 "
489 " - step +1 for right, -1 for left
490 "
491 " TODO: multiple lines.
492 "
493 function! s:FindCornerOfSyntax(lnum, col, step)
494     let l:col = a:col
495     let l:syn = synIDattr(synID(a:lnum, l:col, 1), 'name')
496     while synIDattr(synID(a:lnum, l:col, 1), 'name') ==# l:syn
497         let l:col += a:step
498     endwhile
499     return l:col - a:step
500 endfunction
501
502 " Return the next position of the given syntax name,
503 " inclusive on the given position.
504 "
505 " TODO: multiple lines
506 "
507 function! s:FindNextSyntax(lnum, col, name)
508     let l:col = a:col
509     let l:step = 1
510     while synIDattr(synID(a:lnum, l:col, 1), 'name') !=# a:name
511         let l:col += l:step
512     endwhile
513     return [a:lnum, l:col]
514 endfunction
515
516 function! s:FindCornersOfSyntax(lnum, col)
517     return [<sid>FindLeftOfSyntax(a:lnum, a:col), <sid>FindRightOfSyntax(a:lnum, a:col)]
518 endfunction
519
520 function! s:FindRightOfSyntax(lnum, col)
521     return <sid>FindCornerOfSyntax(a:lnum, a:col, 1)
522 endfunction
523
524 function! s:FindLeftOfSyntax(lnum, col)
525     return <sid>FindCornerOfSyntax(a:lnum, a:col, -1)
526 endfunction
527
528 " Returns:
529 "
530 " - a string with the the URL for the link under the cursor
531 " - an empty string if the cursor is not on a link
532 "
533 " TODO
534 "
535 " - multiline support
536 " - give an error if the separator does is not on a link
537 "
538 function! s:Markdown_GetUrlForPosition(lnum, col)
539     let l:lnum = a:lnum
540     let l:col = a:col
541     let l:syn = synIDattr(synID(l:lnum, l:col, 1), 'name')
542
543     if l:syn ==# 'mkdInlineURL' || l:syn ==# 'mkdURL' || l:syn ==# 'mkdLinkDefTarget'
544         " Do nothing.
545     elseif l:syn ==# 'mkdLink'
546         let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
547         let l:syn = 'mkdURL'
548     elseif l:syn ==# 'mkdDelimiter'
549         let l:line = getline(l:lnum)
550         let l:char = l:line[col - 1]
551         if l:char ==# '<'
552             let l:col += 1
553         elseif l:char ==# '>' || l:char ==# ')'
554             let l:col -= 1
555         elseif l:char ==# '[' || l:char ==# ']' || l:char ==# '('
556             let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
557         else
558             return ''
559         endif
560     else
561         return ''
562     endif
563
564     let [l:left, l:right] = <sid>FindCornersOfSyntax(l:lnum, l:col)
565     return getline(l:lnum)[l:left - 1 : l:right - 1]
566 endfunction
567
568 " Front end for GetUrlForPosition.
569 "
570 function! s:OpenUrlUnderCursor()
571     let l:url = s:Markdown_GetUrlForPosition(line('.'), col('.'))
572     if l:url != ''
573         call s:VersionAwareNetrwBrowseX(l:url)
574     else
575         echomsg 'The cursor is not on a link.'
576     endif
577 endfunction
578
579 " We need a definition guard because we invoke 'edit' which will reload this
580 " script while this function is running. We must not replace it.
581 if !exists('*s:EditUrlUnderCursor')
582     function s:EditUrlUnderCursor()
583         let l:url = s:Markdown_GetUrlForPosition(line('.'), col('.'))
584         if l:url != ''
585             if get(g:, 'vim_markdown_autowrite', 0)
586                 write
587             endif
588             let l:anchor = ''
589             if get(g:, 'vim_markdown_follow_anchor', 0)
590                 let l:parts = split(l:url, '#', 1)
591                 if len(l:parts) == 2
592                     let [l:url, l:anchor] = parts
593                     let l:anchorexpr = get(g:, 'vim_markdown_anchorexpr', '')
594                     if l:anchorexpr != ''
595                         let l:anchor = eval(substitute(
596                             \ l:anchorexpr, 'v:anchor',
597                             \ escape('"'.l:anchor.'"', '"'), ''))
598                     endif
599                 endif
600             endif
601             if l:url != ''
602                 let l:ext = ''
603                 if get(g:, 'vim_markdown_no_extensions_in_markdown', 0)
604                     " use another file extension if preferred
605                     if exists('g:vim_markdown_auto_extension_ext')
606                         let l:ext = '.'.g:vim_markdown_auto_extension_ext
607                     else
608                         let l:ext = '.md'
609                     endif
610                 endif
611                 let l:url = fnameescape(fnamemodify(expand('%:h').'/'.l:url.l:ext, ':.'))
612                 let l:editmethod = ''
613                 " determine how to open the linked file (split, tab, etc)
614                 if exists('g:vim_markdown_edit_url_in')
615                   if g:vim_markdown_edit_url_in == 'tab'
616                     let l:editmethod = 'tabnew'
617                   elseif g:vim_markdown_edit_url_in == 'vsplit'
618                     let l:editmethod = 'vsp'
619                   elseif g:vim_markdown_edit_url_in == 'hsplit'
620                     let l:editmethod = 'sp'
621                   else
622                     let l:editmethod = 'edit'
623                   endif
624                 else
625                   " default to current buffer
626                   let l:editmethod = 'edit'
627                 endif
628                 execute l:editmethod l:url
629             endif
630             if l:anchor != ''
631                 silent! execute '/'.l:anchor
632             endif
633         else
634             echomsg 'The cursor is not on a link.'
635         endif
636     endfunction
637 endif
638
639 function! s:VersionAwareNetrwBrowseX(url)
640     if has('patch-7.4.567')
641         call netrw#BrowseX(a:url, 0)
642     else
643         call netrw#NetrwBrowseX(a:url, 0)
644     endif
645 endf
646
647 function! s:MapNotHasmapto(lhs, rhs)
648     if !hasmapto('<Plug>' . a:rhs)
649         execute 'nmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
650         execute 'vmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
651     endif
652 endfunction
653
654 call <sid>MapNormVis('<Plug>Markdown_MoveToNextHeader', '<sid>MoveToNextHeader')
655 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousHeader', '<sid>MoveToPreviousHeader')
656 call <sid>MapNormVis('<Plug>Markdown_MoveToNextSiblingHeader', '<sid>MoveToNextSiblingHeader')
657 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousSiblingHeader', '<sid>MoveToPreviousSiblingHeader')
658 call <sid>MapNormVis('<Plug>Markdown_MoveToParentHeader', '<sid>MoveToParentHeader')
659 call <sid>MapNormVis('<Plug>Markdown_MoveToCurHeader', '<sid>MoveToCurHeader')
660 nnoremap <Plug>Markdown_OpenUrlUnderCursor :call <sid>OpenUrlUnderCursor()<cr>
661 nnoremap <Plug>Markdown_EditUrlUnderCursor :call <sid>EditUrlUnderCursor()<cr>
662
663 if !get(g:, 'vim_markdown_no_default_key_mappings', 0)
664     call <sid>MapNotHasmapto(']]', 'Markdown_MoveToNextHeader')
665     call <sid>MapNotHasmapto('[[', 'Markdown_MoveToPreviousHeader')
666     call <sid>MapNotHasmapto('][', 'Markdown_MoveToNextSiblingHeader')
667     call <sid>MapNotHasmapto('[]', 'Markdown_MoveToPreviousSiblingHeader')
668     call <sid>MapNotHasmapto(']u', 'Markdown_MoveToParentHeader')
669     call <sid>MapNotHasmapto(']c', 'Markdown_MoveToCurHeader')
670     call <sid>MapNotHasmapto('gx', 'Markdown_OpenUrlUnderCursor')
671     call <sid>MapNotHasmapto('ge', 'Markdown_EditUrlUnderCursor')
672 endif
673
674 command! -buffer -range=% HeaderDecrease call s:HeaderDecrease(<line1>, <line2>)
675 command! -buffer -range=% HeaderIncrease call s:HeaderDecrease(<line1>, <line2>, 1)
676 command! -buffer -range=% SetexToAtx call s:SetexToAtx(<line1>, <line2>)
677 command! -buffer TableFormat call s:TableFormat()
678 command! -buffer Toc call s:Toc()
679 command! -buffer Toch call s:Toc('horizontal')
680 command! -buffer Tocv call s:Toc('vertical')
681 command! -buffer Toct call s:Toc('tab')
682
683 " Heavily based on vim-notes - http://peterodding.com/code/vim/notes/
684 if exists('g:vim_markdown_fenced_languages')
685     let s:filetype_dict = {}
686     for s:filetype in g:vim_markdown_fenced_languages
687         let key = matchstr(s:filetype, "[^=]*")
688         let val = matchstr(s:filetype, "[^=]*$")
689         let s:filetype_dict[key] = val
690     endfor
691 else
692     let s:filetype_dict = {
693         \ 'c++': 'cpp',
694         \ 'viml': 'vim',
695         \ 'bash': 'sh',
696         \ 'ini': 'dosini'
697     \ }
698 endif
699
700 function! s:MarkdownHighlightSources(force)
701     " Syntax highlight source code embedded in notes.
702     " Look for code blocks in the current file
703     let filetypes = {}
704     for line in getline(1, '$')
705         let ft = matchstr(line, '```\s*\zs[0-9A-Za-z_+-]*')
706         if !empty(ft) && ft !~ '^\d*$' | let filetypes[ft] = 1 | endif
707     endfor
708     if !exists('b:mkd_known_filetypes')
709         let b:mkd_known_filetypes = {}
710     endif
711     if !exists('b:mkd_included_filetypes')
712         " set syntax file name included
713         let b:mkd_included_filetypes = {}
714     endif
715     if !a:force && (b:mkd_known_filetypes == filetypes || empty(filetypes))
716         return
717     endif
718
719     " Now we're ready to actually highlight the code blocks.
720     let startgroup = 'mkdCodeStart'
721     let endgroup = 'mkdCodeEnd'
722     for ft in keys(filetypes)
723         if a:force || !has_key(b:mkd_known_filetypes, ft)
724             if has_key(s:filetype_dict, ft)
725                 let filetype = s:filetype_dict[ft]
726             else
727                 let filetype = ft
728             endif
729             let group = 'mkdSnippet' . toupper(substitute(filetype, "[+-]", "_", "g"))
730             if !has_key(b:mkd_included_filetypes, filetype)
731                 let include = s:SyntaxInclude(filetype)
732                 let b:mkd_included_filetypes[filetype] = 1
733             else
734                 let include = '@' . toupper(filetype)
735             endif
736             let command = 'syntax region %s matchgroup=%s start="^\s*```\s*%s$" matchgroup=%s end="\s*```$" keepend contains=%s%s'
737             execute printf(command, group, startgroup, ft, endgroup, include, has('conceal') && get(g:, 'vim_markdown_conceal', 1) ? ' concealends' : '')
738             execute printf('syntax cluster mkdNonListItem add=%s', group)
739
740             let b:mkd_known_filetypes[ft] = 1
741         endif
742     endfor
743 endfunction
744
745 function! s:SyntaxInclude(filetype)
746     " Include the syntax highlighting of another {filetype}.
747     let grouplistname = '@' . toupper(a:filetype)
748     " Unset the name of the current syntax while including the other syntax
749     " because some syntax scripts do nothing when "b:current_syntax" is set
750     if exists('b:current_syntax')
751         let syntax_save = b:current_syntax
752         unlet b:current_syntax
753     endif
754     try
755         execute 'syntax include' grouplistname 'syntax/' . a:filetype . '.vim'
756         execute 'syntax include' grouplistname 'after/syntax/' . a:filetype . '.vim'
757     catch /E484/
758         " Ignore missing scripts
759     endtry
760     " Restore the name of the current syntax
761     if exists('syntax_save')
762         let b:current_syntax = syntax_save
763     elseif exists('b:current_syntax')
764         unlet b:current_syntax
765     endif
766     return grouplistname
767 endfunction
768
769
770 function! s:MarkdownRefreshSyntax(force)
771     if &filetype == 'markdown' && line('$') > 1
772         call s:MarkdownHighlightSources(a:force)
773     endif
774 endfunction
775
776 function! s:MarkdownClearSyntaxVariables()
777     if &filetype == 'markdown'
778         unlet! b:mkd_included_filetypes
779     endif
780 endfunction
781
782 augroup Mkd
783     autocmd! * <buffer>
784     autocmd BufWinEnter <buffer> call s:MarkdownRefreshSyntax(1)
785     autocmd BufUnload <buffer> call s:MarkdownClearSyntaxVariables()
786     autocmd BufWritePost <buffer> call s:MarkdownRefreshSyntax(0)
787     autocmd InsertEnter,InsertLeave <buffer> call s:MarkdownRefreshSyntax(0)
788     autocmd CursorHold,CursorHoldI <buffer> call s:MarkdownRefreshSyntax(0)
789 augroup END